Technology Blogs by SAP
Learn how to extend and personalize SAP applications. Follow the SAP technology blog for insights into SAP BTP, ABAP, SAP Analytics Cloud, SAP HANA, and more.
cancel
Showing results for 
Search instead for 
Did you mean: 
In this blog, we will see an example of Olingo OData v2.0 service that consumes HANA DB service on Cloud foundry. This example makes use of Spring cloud connectors together with Spring Cloud HANA Service Connector for connecting to HANA Service in Cloud foundry.

We are going to require below tools/frameworks :

  • Eclipse with JDK 1.8 for development.

  • Spring Boot 1.5.2 for Spring web application.

  • Spring Cloud Connector 1.2.3.RELEASE for connecting to cloud foundry services.

  • Olingo OData V2.0.8-sap-05 for OData service.

  • eclipse link 2.6.4 for JPA.

  • HANA service connector 1.0.4.RELEASE for connecting to HANA service.

  • Maven 3.0.5 or higher for application build.

  • CloudFoundry CommandLineInterface(CLI) for application deployment to Cloud foundry.


Lets start with pom.xml defining all the required dependencies. Complete code can be downloaded from here
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>ODataSpring</groupId>
<artifactId>ODataSpring</artifactId>
<version>0.1</version>
<packaging>war</packaging>
<properties>
<eclipselink.version>2.6.4</eclipselink.version>
<spring.cloud>1.2.3.RELEASE</spring.cloud>
<!-- Using SAP internal verison for Olingo. This could be replaced with
Olingo global version . -->
<olingo.v2.version>2.0.8-sap-05</olingo.v2.version>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<maven.compiler.showDeprecation>true</maven.compiler.showDeprecation>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.2.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-frontend-jaxrs</artifactId>
<version>3.2.0</version>
<exclusions>
<exclusion>
<artifactId>geronimo-javamail_1.4_spec</artifactId>
<groupId>org.apache.geronimo.specs</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.olingo</groupId>
<artifactId>olingo-odata2-jpa-processor-api</artifactId>
<version>${olingo.v2.version}</version>
<exclusions>
<exclusion>
<groupId>org.eclipse.persistence</groupId>
<artifactId>javax.persistence</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.olingo</groupId>
<artifactId>olingo-odata2-jpa-processor-core</artifactId>
<version>${olingo.v2.version}</version>
</dependency>
<dependency>
<groupId>org.apache.olingo</groupId>
<artifactId>olingo-odata2-jpa-processor-ref</artifactId>
<version>${olingo.v2.version}</version>
<scope>provided</scope>
<exclusions>
<exclusion>
<artifactId>slf4j-api</artifactId>
<groupId>org.slf4j</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.olingo</groupId>
<artifactId>olingo-odata2-core</artifactId>
<version>${olingo.v2.version}</version>
</dependency>
<dependency>
<groupId>org.apache.olingo</groupId>
<artifactId>olingo-odata2-api-annotation</artifactId>
<version>${olingo.v2.version}</version>
</dependency>
<dependency>
<groupId>org.apache.olingo</groupId>
<artifactId>
olingo-odata2-annotation-processor-ref
</artifactId>
<version>${olingo.v2.version}</version>
<scope>provided</scope>
<exclusions>
<exclusion>
<artifactId>slf4j-log4j12</artifactId>
<groupId>org.slf4j</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.olingo</groupId>
<artifactId>
olingo-odata2-annotation-processor-core
</artifactId>
<version>${olingo.v2.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
</exclusions>
</dependency>

<!-- Spring JPA. Since, we are using eclipse link JPA. We exclude default
hibernate JPA. -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
<exclusions>
<exclusion>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.eclipse.persistence</groupId>
<artifactId>eclipselink</artifactId>
<version>${eclipselink.version}</version>
</dependency>
<dependency>
<groupId>com.jayway.jsonpath</groupId>
<artifactId>json-path</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<artifactId>slf4j-api</artifactId>
<groupId>org.slf4j</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<!-- <version>2.9.9</version> -->
</dependency>

<!-- spring cloud service connector for CF -->
<!-- For deployment in cloud foundry -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-spring-service-connector</artifactId>
<!-- <version>${spring.cloud}</version> -->
</dependency>
<!-- Cloud foundry connector -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-cloudfoundry-connector</artifactId>
<!-- <version>${spring.cloud}</version> -->
</dependency>
<!-- HANA service connector for connecting to HANA Service in SAP CF. -->
<dependency>
<groupId>com.sap.hana.cloud</groupId>
<artifactId>spring-cloud-cloudfoundry-hana-service-connector</artifactId>
<version>1.0.4.RELEASE</version>
</dependency>
</dependencies>
<build>
<sourceDirectory>src</sourceDirectory>
<resources>
<resource>
<directory>src</directory>
<excludes>
<exclude>**/*.java</exclude>
</excludes>
</resource>
</resources>
<plugins>
<plugin>
<artifactId>maven-war-plugin</artifactId>
<version>2.6</version>
<configuration>
<warSourceDirectory>WebContent</warSourceDirectory>
<failOnMissingWebXml>false</failOnMissingWebXml>
</configuration>
</plugin>
<plugin>
<groupId>de.empulse.eclipselink</groupId>
<artifactId>staticweave-maven-plugin</artifactId>
<version>1.0.0</version>
<executions>
<execution>
<phase>process-classes</phase>
<goals>
<goal>weave</goal>
</goals>
<configuration>
<persistenceXMLLocation>META-INF/persistence.xml</persistenceXMLLocation>
<logLevel>FINE</logLevel>
</configuration>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>org.eclipse.persistence</groupId>
<artifactId>org.eclipse.persistence.jpa</artifactId>
<version>2.6.0</version>
</dependency>
</dependencies>
</plugin>
</plugins>
<pluginManagement>
<plugins>
<!--This plugin's configuration is used to store Eclipse m2e settings
only. It has no influence on the Maven build itself. -->
<plugin>
<groupId>org.eclipse.m2e</groupId>
<artifactId>lifecycle-mapping</artifactId>
<version>1.0.0</version>
<configuration>
<lifecycleMappingMetadata>
<pluginExecutions>
<pluginExecution>
<pluginExecutionFilter>
<groupId>
de.empulse.eclipselink
</groupId>
<artifactId>
staticweave-maven-plugin
</artifactId>
<versionRange>
[1.0.0,)
</versionRange>
<goals>
<goal>weave</goal>
</goals>
</pluginExecutionFilter>
<action>
<ignore></ignore>
</action>
</pluginExecution>
</pluginExecutions>
</lifecycleMappingMetadata>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>

The dependencies in pom are:

  • Olingo OData SAP internal version 2.0.8-sap-05. Non SAP version should also be fine.

  • Spring boot 1.5.2 for Web application development. It takes care of adding the Spring core libraries.

  • Eclipse link persistence V2.6.4 for JPA.

  • Spring cloud service and Spring cloud foundry connectors which provide the required APIs for accessing CloudFoundry services in our code.

  • Spring cloud foundry HANA service connector for connecting to HANA service in SAP CF. This is mandatory since, Cloud foundry by default doesn't support APIs for HANA service.


Notice that we also have enabled static weaving to prevent any LoadTime weaving exception when the application is booted.

Before proceeding further it is recommended to run 'mvn package' to make sure all the required libraries are downloaded and added to the class path. This will package our application into .war file.

The first Java class we create will be a simple JPA entity that maps to DB table Employee:
@Entity
public class Employee implements Serializable {

@Id
private String id;
private String name;
private String description;
private static final long serialVersionUID = 1L;

public Employee() {
super();
}

public String getId() {
return this.id;
}

public void setId(String id) {
this.id = id;
}

public String getName() {
return this.name;
}

public void setName(String name) {
this.name = name;
}

public String getDescription() {
return this.description;
}

public void setDescription(String description) {
this.description = description;
}

@Override
public String toString() {
return "Employee [id=" + id + ", name=" + name + ", description=" + description + "]";
}

}

Next we will see persistence.xml:
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.1"
xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd">
<persistence-unit name="ODataSpring">
<class>com.sap.cf.odata.spring.model.Employee</class>
<properties>
<property name="eclipselink.ddl-generation" value="create-tables" />
<!-- Disable weaving to prevent load time weaver exception. Instead use
static weaving as mentioned in pom.xml -->
<property name="eclipselink.weaving" value="false" />
</properties>
</persistence-unit>
</persistence>

In this file, we disabled weaving, since we already have enabled static weaving in pom.xml and this further prevents any load time weaving by Spring framework.

Now we create required Java classes. The package names and the imports are omitted for the sake of simplicity.
/**
* Main class for this application. extends {@link SpringBootServletInitializer}
* it making a web application for war deployment.
*
* @author i324363
*
*/

@SpringBootApplication
public class AppController extends SpringBootServletInitializer {

// Main method gives control to Spring by invoking run on Spring Application.
// This enables Spring to Bootstrap the application
public static void main(String[] args) {
SpringApplication.run(AppController.class, args);

}

// This method adds Configuration class for Spring Application Context builder
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
return builder.sources(AppController.class, CloudConfig.class);
}
}

The main method delegates the application to SpringApplication by invoking the run method. The SpringApplication will then bootstrap our application.

Inside configure method we inform Spring Boot about our configuration class "CloudConfig" by invoking SpringApplicationBuilder's sources method.

Next we create Configuration class that defines the beans required by our application.
@Configuration
@Profile("cloud")
@ComponentScan(basePackages = "com.sap.cf")
public class CloudConfig extends AbstractCloudConfig {

private static final String HANA_SVC = "hana-schema-svc";

private static final Logger LOG = LoggerFactory.getLogger(CloudConfig.class);

/**
* Create dataSource bean from SAP CF
*
* @return dataSource dataSoruce created from HANA Service.
*/
@Bean
public DataSource dataSource() {
DataSource dataSource = null;
try {
dataSource = connectionFactory().dataSource(HANA_SVC);
} catch (CloudException ex) {
LOG.error(" ", ex);
}
return dataSource;
}

/**
* Create Eclipselink EMF from the dataSource bean. JPAvendor and datasource
* will be set here. rest will be taken from persistence.xml
*
* @return EntityManagerFactory
*/
@Bean
public EntityManagerFactory entityManagerFactory() {
LocalContainerEntityManagerFactoryBean springEMF = new LocalContainerEntityManagerFactoryBean();
springEMF.setJpaVendorAdapter(new EclipseLinkJpaVendorAdapter());
springEMF.setDataSource(dataSource());
springEMF.afterPropertiesSet();
return springEMF.getObject();

}

/**
* Registers OData servlet bean with Spring Application context to handle
* ODataRequests.
*
* @return
*/
@Bean
public ServletRegistrationBean odataServlet() {

ServletRegistrationBean odataServRegstration = new ServletRegistrationBean(new CXFNonSpringJaxrsServlet(),
"/odata.svc/*");
Map<String, String> initParameters = new HashMap<>();
initParameters.put("javax.ws.rs.Application", "org.apache.olingo.odata2.core.rest.app.ODataApplication");
initParameters.put("org.apache.olingo.odata2.service.factory",
"com.sap.cf.odata.spring.context.JPAServiceFactory");
odataServRegstration.setInitParameters(initParameters);

return odataServRegstration;

}

}

In this class, we first create bean of following types in the application context :

  • datasource - is created from the HANA service created in cloud foundry. For this we use the  methods provided by the super class - AbstractCloudConfig. We could have omitted passing the name of the service to dataSource(), since it is the only data source service our application is bound to.

  • entityManagerFactory - we use the datasouce bean to create EMF instance. Notice that we use eclipse link for jpa instead of Spring default hibernate for the EMF.

  • odataServlet - Servlet required for OData is created with the required init parameters. This is analogous to creating the servlet in the web application deployment descriptor - web.xml using servlet XML tags.


Next we create the JPAServiceFactory that extends ODataJPAServiceFactory.
public class JPAServiceFactory extends ODataJPAServiceFactory {

private static final String PERSISTENT_UNIT = "ODataSpring";
private static final String EMF = "entityManagerFactory";

private static final Logger LOG = LoggerFactory.getLogger(JPAServiceFactory.class);

@Override
public ODataJPAContext initializeODataJPAContext() throws ODataJPARuntimeException {
ODataJPAContext oDataJPACtx = getODataJPAContext();
EntityManagerFactory emf = (EntityManagerFactory) SpringContextUtil.getBean(EMF);
LOG.debug("EMF in JPAservicefactory " + emf);
oDataJPACtx.setEntityManagerFactory(emf);
oDataJPACtx.setPersistenceUnitName(PERSISTENT_UNIT);
oDataJPACtx.setDefaultNaming(true);
this.setDetailErrors(true);
return oDataJPACtx;
}

}

Since, JPAServiceFactory is not managed by Spring we cannot auto wire emf bean into JPAServiceFactory. In order to  fetch emf we use a custom class SpringContextUtil. We will see that next.
@Component
public class SpringContextUtil implements ApplicationContextAware {

private static ApplicationContext applicationContext;

public SpringContextUtil() {

}

public static Object getBean(String beanName) throws BeansException {
return applicationContext.getBean(beanName);
}

@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
SpringContextUtil.applicationContext = applicationContext;
}

}

Few points about this class :

  • extends ApplicationContextAware of Spring, so that it is notified of the SpringApplicationContext (where the required beans are stored). This is the simplest way to expose Spring application context beans to classes that are not Spring managed.

  • Provides a static getBean method to fetch beans from the Spring Application Context. This method should only be used by classes where bean injection is not possible.


Important: We also need to copy njdbc.jar  into WEB-INF/lib folder for the required HANA JDBC Driver class. As of now there is no maven dependency to add njdbc.jar.

Now lets see manifest.yml file for the deployment instructions to CLI:
---
applications:
- name: odatav2-spring
path: target/ODataSpring-0.1.war
services:
- hana-schema-svc
env:
SPRING_PROFILES_DEFAULT: cloud

This file should be placed in the root directory of our application. It defines path to our application war, the HANA Service instance and the default profile as 'cloud'.

Now lets build the application and before we run "cf push" from CLI, we should also have created a HANA Schema Service with the name 'hana-schema-svc' in the Cloud foundry service market place. Make sure to select 'schema' from the service plan below:



Finally execute cf push from the directory which has manifest.yml and hopefully if everything is fine the application should be deployed and the OData url should be accessible.



This concludes our sample CloudFoundry OData Service based on Spring 🙂

 

Further references and useful links :-

 

 
10 Comments