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.
This is related: REST Service Implementation
The following steps show the REST implementation on base of Apache Wink.
Apache Wink is retired
As of April 2017, Apache Wink is retired. For new REST implementations, it is not recommended to use it.
This tutorial does not work with OFBIz Release 16.11 and above.
This page will be either updated with an alternative or deprecated in the future.
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 from here: https://wink.apache.org/downloads.html
Unzip and copy lib/* and dist/* to your ofbiz component restcomponent/lib
\* read this https://dzone.com/articles/apache-cxf-vs-apache-axis-vs for a comparison between 2 Apache solutions regarding REST and Spring WS
Step 3 - Create java files and update web.xml
The steps followed were taken from the wink user (i.e. developer) guide: apache-wink-developer-guide.html
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.
1 Comment
Michael Brohl
Apache Juneau has just become a top level project, might be worth a look juneau.apache.org