Overview
This tutorial documents the steps to expose an ofbiz service using REST. The ofbiz ping service is exposed. The ping service returns a copy on the input message to the response. If the input message is null, ping returns PONG.
Apache wink was chosen as the REST implementation purely on the basis that it is an apache project.
Assumptions
This tutorial assumes you have followed the ofbiz developer tutorial: http://cwiki.apache.org/confluence/x/cQFk
Step 1 - Create a new component
ant create-component Component name: restcomponent Component resource name: RestComponent Webapp name: restcomponent Base permission: RESTCOMPONENT
Step 2 - Grab apache wink
Download apache wink. I used http://www.apache.org/dyn/closer.cgi/incubator/wink/1.0-incubating/apache-wink-1.0-incubating.zip
Unzip and copy lib/* and dist/* to your ofbiz component restcomponent/lib
Step 3 - Create java files and update web.xml
The steps followed were taken from the wink user (i.e. developer guide): http://incubator.apache.org/wink/1.0/Apache_Wink_User_Guide.pdf
In the folder restcomponent/src/restcomponent, create:
PingApplication.java
PingResource.java
package restcomponent; import java.util.HashSet; import java.util.Set; import javax.ws.rs.core.Application; public class PingApplication extends Application { @Override public Set<Class<?>> getClasses() { Set<Class<?>> classes = new HashSet<Class<?>>(); classes.add(PingResource.class); return classes; } }
package restcomponent; import java.util.Map; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.core.Context; import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.Response; import javolution.util.FastMap; import org.ofbiz.base.util.Debug; import org.ofbiz.base.util.UtilMisc; import org.ofbiz.entity.DelegatorFactory; import org.ofbiz.entity.GenericDelegator; import org.ofbiz.service.GenericDispatcher; import org.ofbiz.service.GenericServiceException; import org.ofbiz.service.LocalDispatcher; import org.ofbiz.service.ServiceUtil; @Path("/ping") public class PingResource { @GET @Produces("text/plain") @Path("{message}") public Response sayHello(@PathParam("message") String message) { GenericDelegator delegator = (GenericDelegator) DelegatorFactory.getDelegator("default"); LocalDispatcher dispatcher = GenericDispatcher.getLocalDispatcher("default",delegator); Map<String, String> paramMap = UtilMisc.toMap( "message", message ); Map<String, Object> result = FastMap.newInstance(); try { result = dispatcher.runSync("ping", paramMap); } catch (GenericServiceException e1) { Debug.logError(e1, PingResource.class.getName()); return Response.serverError().entity(e1.toString()).build(); } if (ServiceUtil.isSuccess(result)) { return Response.ok("RESPONSE: *** " + result.get("message") + " ***").type("text/plain").build(); } if (ServiceUtil.isError(result) || ServiceUtil.isFailure(result)) { return Response.serverError().entity(ServiceUtil.getErrorMessage(result)).build(); } // shouldn't ever get here ... should we? throw new RuntimeException("Invalid "); } }
Update restcomponent/webapp/restcomponent/WEB-INF/web.xml:
<?xml version="1.0" encoding="UTF-8"?> <web-app> <display-name>Open For Business - RestComponent Component</display-name> <description>RestComponent Component of the Open For Business Project</description> <servlet> <servlet-name>restServlet</servlet-name> <servlet-class>org.apache.wink.server.internal.servlet.RestServlet</servlet-class> <init-param> <param-name>javax.ws.rs.Application</param-name> <param-value>restcomponent.PingApplication</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>restServlet</servlet-name> <url-pattern>/*</url-pattern> </servlet-mapping> </web-app>
Step 4 - compile and run
compile: ./ant
run: ./ant run
Test using a web browser:
http://localhost:8080/restcomponent/ping/abc
Step 5 - turn on authentication in the service
Turn on auth="true" in framework/common/servicedef/services_test.xml
<service name="ping" engine="java" export="true" require-new-transaction="true" location="org.ofbiz.common.CommonServices" invoke="ping" auth="true">
Now change PingResource to take http headers with the login.username and login.password and pass the values to the service.
package restcomponent; import java.util.Map; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.core.Context; import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.Response; import javolution.util.FastMap; import org.ofbiz.base.util.Debug; import org.ofbiz.base.util.UtilMisc; import org.ofbiz.entity.DelegatorFactory; import org.ofbiz.entity.GenericDelegator; import org.ofbiz.service.GenericDispatcher; import org.ofbiz.service.GenericServiceException; import org.ofbiz.service.LocalDispatcher; import org.ofbiz.service.ServiceUtil; @Path("/ping") public class PingResource { @Context HttpHeaders headers; @GET @Produces("text/plain") @Path("{message}") public Response sayHello(@PathParam("message") String message) { String username = null; String password = null; try { username = headers.getRequestHeader("login.username").get(0); password = headers.getRequestHeader("login.password").get(0); } catch (NullPointerException e) { return Response.serverError().entity("Problem reading http header(s): login.username or login.password").build(); } if (username == null || password == null) { return Response.serverError().entity("Problem reading http header(s): login.username or login.password").build(); } GenericDelegator delegator = (GenericDelegator) DelegatorFactory.getDelegator("default"); LocalDispatcher dispatcher = GenericDispatcher.getLocalDispatcher("default",delegator); Map<String, String> paramMap = UtilMisc.toMap( "message", message, "login.username", username, "login.password", password ); Map<String, Object> result = FastMap.newInstance(); try { result = dispatcher.runSync("ping", paramMap); } catch (GenericServiceException e1) { Debug.logError(e1, PingResource.class.getName()); return Response.serverError().entity(e1.toString()).build(); } if (ServiceUtil.isSuccess(result)) { return Response.ok("RESPONSE: *** " + result.get("message") + " ***").type("text/plain").build(); } if (ServiceUtil.isError(result) || ServiceUtil.isFailure(result)) { return Response.serverError().entity(ServiceUtil.getErrorMessage(result)).build(); } // shouldn't ever get here ... should we? throw new RuntimeException("Invalid "); } }
Now trying accessing with web browser:
Access to the specified resource () has been forbidden.
Step 6 - add http headers for authentication
If using firefox, install RESTClient
https://addons.mozilla.org/en-US/firefox/addon/9780
In firefox, open the RESTClient addon, and set:
Method = GET
URL = http://localhost:8080/restcomponent/ping/abc
Request Headers:
login.username = admin
login.password = ofbiz
Click send - you should see the ping response.
Example files
Attached to this page is a backup of my restcomponent.tgz
Next Steps
I am currently working on making REST configurable in the service definition file (similar to export="true"). This could remove the need to manually code any REST services.