Skip to Content

This article is the second in a series of three tutorials that cover the following topics:

  1. JPA2 in SAP NetWeaver
  2. Building server-side database-backed OData services in SAP NetWeaver with JPA2
  3. Building server-side database-backed OData services in SAP NetWeaver with JPA1

Introduction

REST services have become the de-facto standard for inter-service communication in web applications over the last years. The OData protocol simplifies the provision and consumption of RESTful APIs in a standard way and can for example be used to feed SAPUI5 front-end applications with business data from a server-side backend, typically from a database.

SAPUI5 integrates nicely with OData services as the entities provided by OData services can be bound to UI elements so that the SAPUI5 framework takes care of implementing CRUD-like operations and the front end developer does not need to add any boilerplate code for consuming the business data services. On the server side, in most scenarios it is sufficient to map business entities (e.g. tables in a relational DBMS) to OData entities and expose these as OData entities (effectively REST endpoints). For Java-based back end applications, data will typically be stored in relational databases and mapped to JPA entities in the application code.

In this tutorial, we will demonstrate how to expose JPA entities as OData entities in OData web services for consumption for example by SAPUI5 front end applications. In the case of JPA2, this can be done with minimal boilerplate code as we will show.

Prerequisites

We are assuming that you are developing JEE applications for SAP NetWeaver AS and have taken the necessary steps to be able to deploy JPA2 applications (for example by following our tutorial). This implies that you are using SAP NetWeaver AS Java 7.5 SP03 or newer!

We will implement the OData services with the Apache Olingo framework, specifically version 2.0.8 of Apache Olingo. Note that SAP NetWeaver 7.5 is a JEE5 application server, which means that it does not provide a JAX-RS implementation. Because of this, we must provide our own JAX-RS implementation. We decided to use Apache CXF, but you can use different implementations such as RESTEasy if you wish. In our scenario, the following artifacts are required:

  • commons-codec-1.6.jar
  • cxf-api-2.7.6.jar
  • cxf-rt-bindings-xml-2.7.6.jar
  • cxf-rt-core-2.7.6.jar
  • cxf-rt-frontend-jaxrs-2.7.6.jar
  • cxf-rt-transports-http-2.7.6.jar
  • geronimo-javamail_1.4_spec-1.7.1.jar
  • gson-2.4.jar
  • javax.ws.rs-api-2.0-m10.jar
  • jaxb-impl-2.1.13.jar
  • olingo-odata2-annotation-processor-api-2.0.8.jar
  • olingo-odata2-annotation-processor-core-2.0.8.jar
  • olingo-odata2-api-2.0.8.jar
  • olingo-odata2-api-annotation-2.0.8.jar
  • olingo-odata2-core-2.0.8.jar
  • olingo-odata2-jpa-processor-api-2.0.8.jar
  • olingo-odata2-jpa-processor-core-2.0.8.jar
  • stax2-api-3.1.1.jar
  • woodstox-core-asl-4.2.0.jar
  • wsdl4j-1.6.3.jar
  • xmlschema-core-2.0.3.jar

Pitfall:

We strongly advise not to use OData v4 in conjunction with SAPUI5, as the SAPUI5 support for OData v4 is lackluster and leads to a number of problems in practice. OData v2 however is fully supported by SAPUI5.

Bundle all artifacts in an external library “olingo/lib” DC and expose both api and archives as public parts of the DC.

Setting up Apache Olingo

Create a JPA2 JEE application with an EAR, EJB and WEB DC and make all of them depend on the “olingo/lib” DC.

Pitfall:

This will result in the Olingo artifacts being packaged into your EAR (and all further OData service EARs that you build). However, there is no other way to deploy Olingo-based JEE applications as Apache Olingo has a dependency on JPA2. Due to classloading mechanisms in SAP NetWeaver these can only be resolved correctly in a classloading chain that includes your application code, if the Olingo artifacts are bundled inside your EAR.

In your WEB-DC, edit the web.xml file to include the following:

<filter>
	<filter-name>ODataClassloaderFilter</filter-name>
	<filter-class>com.yourpackage.web.ODataFilter</filter-class>
</filter>
<filter-mapping>
	<filter-name>ODataClassloaderFilter</filter-name>
	<url-pattern>/Data.svc/*</url-pattern>
	<servlet-name>DataServlet</servlet-name>
	<dispatcher>REQUEST</dispatcher>
</filter-mapping>
<servlet>
	<servlet-name>DataServlet</servlet-name>
	<servlet-class>org.apache.cxf.jaxrs.servlet.CXFNonSpringJaxrsServlet</servlet-class>
	<init-param>
		<param-name>javax.ws.rs.Application</param-name>
		<param-value>org.apache.olingo.odata2.core.rest.app.ODataApplication</param-value>
	</init-param>
	<init-param>
		<param-name>org.apache.olingo.odata2.service.factory</param-name>
		<param-value>com.yourpackage.service.ODataServiceFactory</param-value>
	</init-param>
	<load-on-startup>3</load-on-startup>
</servlet>
<servlet-mapping>
	<servlet-name>DataServlet</servlet-name>
	<url-pattern>/Data.svc/*</url-pattern>
</servlet-mapping>

There are two things we do here:

  • We introduce an ODataClassloaderFilter that filters all requests to our OData endpoint. This is, again, necessary to avoid classloading issues at runtime. The filter itself just sets the ODataServiceFactory again, which we will look at shortly:
    public class ODataFilter extends Filter {
    
    	@Override
    	public void init(FilterConfig filterConfig) throws ServletException {
    	}
    
    	@Override
    	public void destroy() {
    	}
    
    	@Override
    	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
    			throws IOException, ServletException {
    
    		request.setAttribute(ODataServiceFactory.FACTORY_CLASSLOADER_LABEL,
    				ODataServiceFactory.class.getClassLoader());
    
    		chain.doFilter(request, response);
    	}
    }
  • In the ODataServiceFactory, we initialize the Olingo framework with JPA related objects such as an EntityManager.
    public class ODataServiceFactory extends ODataJPAServiceFactory {
    
    	private static final String PERSISTENCE_UNIT_NAME = "pu-name";
    	
    	private static final Location LOGGER = Location.getLocation(ODataServiceFactory.class);
    
    	private static final String USER_TRANSACTION_JNDI_NAME = "UserTransaction";
    
    	private EntityManagerService entityManagerService;
    
    	public ProcessODataServiceFactory() {
    		entityManagerService = ServiceLocator.getServiceInstance(EntityManagerService.class, EarDc.ODATA_EAR,
    				EjbDc.ODATA_EJB);
    	}
    
    	protected EntityManager getEntityManager() {
    		return entityManagerService.getProcessEntityManager();
    	}
    
    	protected String getPersistenceUnitName() {
    		return PERSISTENCE_UNIT_NAME;
    	}
    	
    	@Override
    	public ODataJPAContext initializeODataJPAContext() throws ODataJPARuntimeException {
    
    		InitialContext initialContext;
    		try {
    			initialContext = new InitialContext();
    			ODataJPAContext oDataJPAContext = getODataJPAContext();
    			oDataJPAContext.setEntityManager(getEntityManager());
    			oDataJPAContext.setPersistenceUnitName(getPersistenceUnitName());
    			oDataJPAContext.setContainerManaged(true);
    			oDataJPAContext.getODataContext().setDebugMode(true);
    
    			final UserTransaction userTransaction = (UserTransaction) initialContext.lookup(USER_TRANSACTION_JNDI_NAME);
    			setODataJPATransaction(new ODataJPATransaction() {
    				@Override
    				public void rollback() {
    					try {
    						userTransaction.rollback();
    					} catch (IllegalStateException | SecurityException | SystemException e) {
    						LOGGER.errorT("Problem with rollback: " + ExceptionUtils.getStackTrace(e));
    						throw new OdataJpaException("Error during rollback.");
    					}
    				}
    
    				@Override
    				public boolean isActive() {
    					try {
    						return userTransaction.getStatus() == Status.STATUS_ACTIVE;
    					} catch (SystemException e) {
    						LOGGER.errorT("Problem with isActive: " + ExceptionUtils.getStackTrace(e));
    						return false;
    					}
    				}
    
    				@Override
    				public void commit() {
    					try {
    						userTransaction.commit();
    					} catch (SecurityException | IllegalStateException | RollbackException | HeuristicMixedException
    							| HeuristicRollbackException | SystemException e) {
    						LOGGER.errorT("Problem with commit: " + ExceptionUtils.getStackTrace(e));
    						throw new OdataJpaException("Error during commit.");
    					}
    				}
    
    				@Override
    				public void begin() {
    					try {
    						userTransaction.begin();
    					} catch (NotSupportedException | SystemException e) {
    						LOGGER.errorT("Problem with begin: " + ExceptionUtils.getStackTrace(e));
    						throw new OdataJpaException("Error beginning transaction.");
    					}
    				}
    			});
    			return oDataJPAContext;
    		} catch (NamingException e) {
    			throw ODataJPARuntimeException.throwException(ODataJPARuntimeException.ENTITY_MANAGER_NOT_INITIALIZED, e);
    		}
    
    	}
    
    	@SuppressWarnings("unchecked")
    	@Override
    	public <T extends ODataCallback> T getCallback(Class<T> callbackInterface) {
    		if (callbackInterface.isAssignableFrom(ODataErrorCallback.class)) {
    			return (T) new CustomErrorCallback();
    		}
    		T callback = super.getCallback(callbackInterface);
    		return callback;
    	}
    }

     

In this example, we assume that you have some mechanism to inject an EntityManager from your EJB DC into ODataServiceFactory class that is located in your WEB DC (we used the ServiceLocator pattern in this example). Then, we feed the ODataJPAContext with our EntityManager and our PersistenceUnit and set the transaction management to container-managed. Finally, Olingo allows us to hook into transaction methods with callbacks to handle possible errors.

Implementing the persistence

Next, we obviously need to set up the PersistenceUnit in our EJB DC. Create a JPA2 persistence.xml file in your EJB DC and include at least one persistence-unit with the name “pu-name” that we used above. Note that with the PersistenceUnit mechanism you can deploy multiple OData endpoints in a single JEE project by including multiple Filters and ServiceFactories that point to different PersistenceUnits as long as you manually list your JPA entities in each PersistenceUnit.

Finally, implement your JPA entities in the EJB DC and ensure that the class names match the ones in your persistence.xml if you chose to manually list entity classes. Apache Olingo will take care of all the rest: It will generate the OData web service that will be available when you deploy your application under the URL /context/Data.svc/$metadata.

Furthermore, Olingo will generate an OData entity for each of your JPA entities and list these in the OData metadata.

That’s it, you are now ready to consume your OData web services, for example in a front end SAPUI 5 application!

Conclusion

In this tutorial, we have demonstrated how to expose business data from a relational database mapped to JPA entities in OData web services using the Apache Olingo framework. Looking back, we only had to implement 2 classes with a combined 150 lines of code.

If you are experiencing problems, need assistance, or would like to see a minimal sample project, do not hesitate to contact us via SAP Community or by commenting on this article.

Note that Apache Olingo uses the JPA2 metamodel to extract the information about the entities and automatically generate the OData entities. For this solution with minimal boilerplate code to work, it is crucial to use a JPA2-capable SAP NetWeaver because otherwise the JPA-processor of Olingo would not work (as it requires the JPA2 metamodel) and we would have to manually map all JPA entities to OData entities and implement our own OData service processors, which we will demonstrate in the final article of this series.

 

To report this post you need to login first.

Be the first to leave a comment

You must be Logged on to comment or reply to a post.

Leave a Reply