Expose JPA Entity as Odata Service with Odata4j
The aim of this blog is to expose a new Odata Service based on a JPA entity and run the service as an Hana Cloud Java Application.
It must be considered merely an exercise, since normally the odata services are provided by backend systems.
In this blog i’ll use the JPAProducer API provided by odata4j library.
For the excercise we’ll use the “persistence-with-ejb” sample, delivered with Hana Cloud Platform SDK. I’ve used version 2.x
First of all we need to get odata4j. I prefered to get the latest source version. Here the commands to build and install via maven.
cd ~/apps/libs
git clone https://code.google.com/p/odata4j/
cd odata4j
mvn clean compile install
At thispoint odata4j is installed in local maven repository.
Next step is to configure maven dependencies in eclipse project:
Odata4j can produce odata services using jersey or cxf.
We’re going to use jersey.
Add the following dependencies in <workspace>/persistence-with-ejb/pom.xml
<dependency>
<groupId>org.odata4j</groupId>
<artifactId>odata4j-core</artifactId>
<version>0.8.0-SNAPSHOT</version>
<type>pom</type>
</dependency>
<dependency>
<groupId>org.odata4j</groupId>
<artifactId>odata4j-jersey</artifactId>
<version>0.8.0-SNAPSHOT</version>
</dependency>
Then copy the odata4j-dist-0.8.0-SNAPSHOT.jar file located in local maven repository (org/odata4j/odata4j-dist/0.8.0-SNAPSHOT/) in WebContent/WEB-INF/lib directory of the eclipse project.
NB: If you’re using HANA as database you have also to copy the jar file com.sap.core.persistence.osgi.hdb.platform_x.x.x.rt2.jar that is located in HCP_SDK/repository/plugins/ into WebContent/WEB-INF/lib.
Next we need to to create the Odata Producer.
I’ll use the standard org.odata4j.producer.jpa.JPAProducer. Here the code for the factory:
package com.sap.cloud.sample.odata;
import java.util.Properties;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
import org.odata4j.producer.ODataProducer;
import org.odata4j.producer.ODataProducerFactory;
import org.odata4j.producer.jpa.JPAProducer;
public class PersonProducerFactory implements ODataProducerFactory {
private String persistenceUnitName = "persistence-with-ejb";
private String namespace = "Example";
private int maxResults = 50;
@Override
public ODataProducer create(final Properties arg0) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory(
persistenceUnitName);
JPAProducer producer = new JPAProducer(
emf,
namespace,
maxResults);
return producer;
}
}
Next we need to create the real odata service that uses our producer.
This means we need to configure a specific odata4j servlet and configure it.
Add the the specific servlet definitions at the end web-app sertion in web.xml file.
<!-- Odata4j producer -->
<!-- Servlet 1: Expose the OData service endpoint -->
<servlet>
<servlet-name>OData</servlet-name>
<servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>javax.ws.rs.Application</param-name>
<param-value>org.odata4j.jersey.producer.resources.ODataApplication</param-value>
</init-param>
<init-param>
<param-name>odata4j.producerfactory</param-name>
<param-value>com.sap.cloud.sample.odata.ExampleProducerFactory</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>OData</servlet-name>
<url-pattern>/odata/example.svc/*</url-pattern>
</servlet-mapping>
<!-- Servlet 2: Enable crossdomain access for browser clients -->
<servlet>
<servlet-name>CrossDomain</servlet-name>
<servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>javax.ws.rs.Application</param-name>
<param-value>org.odata4j.producer.resources.RootApplication</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>CrossDomain</servlet-name>
<url-pattern>/odata/*</url-pattern>
</servlet-mapping>
Next we can re-deploy and run the application in hanatrial.ondemand.com.
NB: remember to configure the target database in persistence.xml if you’re using HANA Database: com.sap.persistence.platform.database.HDBPlatform
If everything is fine, we can reach the Odata Service here:
https://persistenceejb2p079297trial.hanatrial.ondemand.com/persistence-with-ejb/odata/example.svc/
Here the metadata:
Since we’re using jersey, also the WADL is available:
References
Hi Francesco,
great blog, thanks for sharing.
I am currently investigating options for exposing OData services on the Cloud, and I found out another OData library
A usage scenario is described here
I would highly appreciate any opinion on this library and how it compares to the one you described in the blog (easier/more powerful/...).
I am also creating a SDN thread for this subject, any feedback is welcome.
Thanks a lot
Regards
Hi Francesco,
I started originally with the well known PersonsList GettingStarted application tutorial also with the odata4j approach like you did with your very nice blog.
Later also the ESPM application scenarios, with a more complex JPA model serving a WebShop and Retailer application (see http://scn.sap.com/docs/DOC-46869) were developed and based initially on odata4j.
Nevertheless it turned out that our ESPM team had several issues to get the more complex ESPM JPA model exposed as OData service when using odata4j.
For that reason we based our SAP HCP example applications (PersonsList and ESPM) where JPA and OData connectivity is used, technically on the Olingo OData libraries.
At least our developer experience was better when using Olingo OData with JPA and also we support this new open source project by own committers.
Would be nice if you could also have a look at the Olingo approach (see e.g. todays updated PersonsList Application) and let us know about your further experience.
Best regards,
Jens
Hi Jens
thank you for your comment.
i've seen the olingo project, very interesting.
It also seems that the project is more active than odata4j. For sure i'll use this for next odata implementation.
I've used odata4j for a very stupid reason, it was the first hint in google search. Probably beacuse it's an older project.
Which kind of issues had you using odata4j?
Best Regards
Francesco
Hi Francesco,
To answer your question regarding what kind of issues we faced with OData4j
These could be overcome in Olingo. As mentioned by Jens you can refer to ESPM application scenarios with a more detailed implementation with JPA, Olingo for OData connectivity.
Best Regards
Stephen
Hello,
To make it run on Java Web Tomcat 8 Server, I had to include in web_inf\lib
core4j-0..jar, jersey_bundle-1.19.1.jar joda_time-1.19.jar odata4j-core odata4j-cxf and odata4j-jersey.
However I get an exception on hana cloud platfrom :
2016 07 29 15:32:09#+00#ERROR#org.apache.catalina.core.ContainerBase.[Catalina].[localhost].[/ejb_jpa]##anonymous#http-bio-8041-exec-8#na#i022455sapdev#ejbjpa#web#i022455sapdev#StandardWrapper.Throwablejava.lang.RuntimeException: javax.naming.NameNotFoundException: Name [CDIExtension] is not bound in this Context. Unable to find [CDIExtension]. at com.sun.jersey.server.impl.cdi.CDIExtension.getInitializedExtension(CDIExtension.java:183) at com.sun.jersey.server.impl.cdi.CDIComponentProviderFactory.(CDIComponentProviderFactory.java:95) at com.sun.jersey.server.impl.cdi.CDIComponentProviderFactoryInitializer.initialize(CDIComponentProviderFactoryInitializer.java:76) at com.sun.jersey.spi.container.servlet.WebComponent.configure(WebComponent.java:572) at com.sun.jersey.spi.container.servlet.ServletContainer$InternalWebComponent.configure(ServletContainer.java:332) at com.sun.jersey.spi.container.servlet.WebComponent.load(WebComponent.java:604) at com.sun.jersey.spi.container.servlet.WebComponent.init(WebComponent.java:207) at com.sun.jersey.spi.container.servlet.ServletContainer.init(ServletContainer.java:394) at com.sun.jersey.spi.container.servlet.ServletContainer.init(ServletContainer.java:577) at javax.servlet.GenericServlet.init(GenericServlet.java:244) at org.apache.catalina.core.StandardWrapper.initServlet(StandardWrapper.java:1282) at org.apache.catalina.core.StandardWrapper.loadServlet(StandardWrapper.java:1195) at org.apache.catalina.core.StandardWrapper.allocate(StandardWrapper.java:866) at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:134) at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:122) at org.eclipse.virgo.web.enterprise.security.valve.OpenEjbSecurityInitializationValve.invoke(OpenEjbSecurityInitializationValve.java:44) at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:505) at com.sap.core.jpaas.security.auth.service.lib.AbstractAuthenticator.invoke(AbstractAuthenticator.java:168) at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:169) at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:956) at com.sap.core.tenant.valve.TenantValidationValve.invokeNextValve(TenantValidationValve.java:168) at com.sap.core.tenant.valve.TenantValidationValve.invoke(TenantValidationValve.java:94) at com.sap.js.statistics.tomcat.valve.RequestTracingValve.invoke(RequestTracingValve.java:38) at com.sap.core.js.monitoring.tomcat.valve.RequestTracingValve.invoke(RequestTracingValve.java:27) at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:103) at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:116) at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:436) at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1078) at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:625) at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:318) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615) at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) at java.lang.Thread.run(Thread.java:812) Caused by: javax.naming.NameNotFoundException: Name [CDIExtension] is not bound in this Context. Unable to find [CDIExtension]. at org.apache.naming.NamingContext.lookup(NamingContext.java:819) at org.apache.naming.NamingContext.lookup(NamingContext.java:167) at com.sun.jersey.server.impl.cdi.CDIExtension.getInitializedExtension(CDIExtension.java:181) ... 33 common frames omitted Any Idea ?
Hi Experts.,
I want to achieve deep entity creation using JPA. Anyone please guide me on the same?
-Mahi
Unless you are asking for clarification/correction of some part of the Document, please create a new Discussion marked as a Question. The Comments section of a Blog (or Document) is not the right vehicle for asking questions as the results are not easily searchable. Once your issue is solved, a Discussion with the solution (and marked with Correct Answer) makes the results visible to others experiencing a similar problem. If a blog or document is related, put in a link. Read the Getting Started documents (link at the top right) including the Rules of Engagement.
NOTE: Getting the link is easy enough for both the author and Blog. Simply MouseOver the item, Right Click, and select Copy Shortcut. Paste it into your Discussion. You can also click on the url after pasting. Click on the A to expand the options and select T (on the right) to Auto-Title the url.
Thanks, Mike (Moderator)
SAP Technology RIG