Skip to Content
Author's profile photo Former Member

Exposing a REST API from #SAPNWCloud

As part of my work in bouvet, I’ve started experimenting with the new SAP Netweaver Cloud (aka. Neo and JPaaS) and boy is there much exciting stuff to share.

SAP NetWeaver Cloud has a free developer license, that is currently limited to a 90 days trial. Get access by following the instructions here. Removing the 90 day trial limitation is definitively something SAP wants, and it is being worked on (ref https://twitter.com/#!/schmerdy/status/202783043075846145). The @SAPMentors will do our utmost to make sure SAP delivers on this promise.

Update 25.05: Added information on how to support cross-origin resource sharing .

Update 22.10: Project is now shared on Github https://github.com/elsewhat/nwcloud-rest_example

Overview

The goal of this blog is to show how you can expose data through a REST API from an application hosted on the SAP NetWeaver Cloud.

Going into this experiment, I had the following goals:

  1. Data must be persisted in SAP NetWeaver Cloud
  2. REST API must support the operations: read all objects, read single object, create single object and update single object
  3. No coding for xml and json marshalling and unmarshalling
  4. Extensible to more complex objects
  5. To be consumed by a sapui5 client (next blog?)
    (the sapui5 client can subsequently be consumed in SAP NetWeaver Cloud Portal! ref @ohad_yassin)

One of the main benefits of  basing the SAP NetWeaver Cloud on the JVM and Java, is that there is a huge selection of mature and production ready libraries and frameworks. You are truly standing on the shoulders of giants that have small java-coding hands.

I had already decided to use JPA as the persistency layer, and SAP has a great tutorial in place on how to use it with SAP NW Cloud.

For the REST framework, I quickly found out that the Java Community Process had produced a standard extension to java named JAX-RS: Java API for RESTful Web Services. The reference implementation of this, called Jersey, is well suited to be include in a SAP NetWeaver Cloud application.

For the data behind the REST service, I choose a simple FeedEntry object that represents an update in a social feed.

It basically consists of a sender and a text. Simple, yet useful in several contexts.

Demo

The REST API is publicly available on #SAPNWCloud , so you can test it right now!

For testing REST APIs it’s common to use the command line tool curl. It is part of most linux and unix distributions and If you are on windows or amiga you can download it from here .

Here are a few examples you can try out yourself.

Read all objects call:

curl -k -i -X GET -H 'Content-Type: application/json' https://feedstream1p013234trial.netweaver.ondemand.com/feed_stream/api/feed/
HTTP/1.1 200 OK
Content-Type: application/json
Transfer-Encoding: chunked
Date: Fri, 25 May 2012 08:01:42 GMT
Server: SAP
Set-Cookie: cookie_f=370167306.26911.0000; path=/
{"feedEntry":[{"feedText":"First post with the REST API","id":"1","isComment":"f
alse","senderEmail":"dagfinn.parnas@bouvet.no","senderName":"Dagfinn Parnas"},{"
feedText":"UPDATE:Bouvet experiment with #sapnwcloud seems to work well","id":"2
","isComment":"false","senderEmail":"dagfinn.parnas@gmail.com","senderName":"Dag
finn Parnas"}]}

Create new feed:

curl -k -i -X POST -H 'Content-Type: application/json' -d '{"senderName":"Dagfinn Parnas",
"feedText":"Bouvet experiment with #sapnwcloud seems to work well","isComment":false,"senderEmail":"dagfinn.parnas@gmail.com"}'
 https://feedstream1p013234trial.netweaver.ondemand.com/feed_stream/api/feed

Update existing feed:

curl -k -i -X POST -H 'Content-Type: application/json' -d '{"feedText":"UPDATE:Bouvet experiment with #sapnwcloud seems to work well"}'
https://feedstream1p013234trial.netweaver.ondemand.com/feed_stream/api/feed/2

How to

Show us the code!

Step 1

Complete the tutorial “Adding Persistence to a Neo Application Using JPA”

After doing this, you should in eclipse have two projects:

  1. A project representing your JPA data model
    (named JPAModel)
  2. A web project which demonstrates reading and updating the data persisted in your JAP data model
    (name Hello World)

Both of these eclipse projects will be used. The JPA project will represent our data model FeedEntry and the web project will become our REST API

Step 2

Download the java library files from Jersey project.

  • Go to http://jersey.java.net
  • Select download
  • Download from the link that says: “A zip of Jersey containing the Jersey jars, core dependencies (it does not provide dependencies for third party jars beyond those for JSON support) “
  • Unzip it to a local temp folder

(it would be much better to set up this dependency to SAP NW Cloud through Maven. I’ll leave that as an exercise and a potential future blog)

Step 3

Next step is to copy the jersey libraries to the web project.

Copy all jersey library files (.jars) from the lib subfolder in step 2 to your web project under the path WebContent/WEB-INF/lib

Here is my web project structure after the copy operation.

(please note that in the screenshot I have a few extra .jar files. This doesn’t matter to this blog and is because I started out with the sapui5 getting-started web project)

files_jersey2.PNG

Step 4

Next step is to setup jersey in the web project. Our goal is that all urls matching http://<server>/<web_app_context_root>/api/* are processed using jersey.

We do this setup in the Deplyoment descriptor of the web project. You can either access it from the Deployment Descriptor object in the project structure or directly to the web.xml file it represents. The web.xml file is located under WebContent/WEB-INF/web.xml.

This is the contents you need to have

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5">
  <display-name>feed_stream</display-name>
  <servlet>
    <servlet-name>Jersey REST Service</servlet-name>
    <servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
    <init-param>
      <param-name>com.sun.jersey.config.property.packages</param-name>
      <param-value>no.bouvet.sap.neo.rest</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>Jersey REST Service</servlet-name>
    <url-pattern>/api/*</url-pattern>
  </servlet-mapping>
</web-app>

Please note that you may have multiple other lines in the web.xml file, for example for a HelloWorldServlet. You do not need to delete them.

How does this work? Let me explain the three simple steps:

  1. We define a servlet which is instansiated through the class com.sun.jersey.spi.container.servlet.ServletContainer.
    This class is included in the java libraries we copied in the previous step
  2. This servlet has a parameter which is jersey specific. This parameter must have the value of the java package name where jersey should look for REST based services. It must match the package name we use in the FeedResource class later
  3. We map the Jersey servlet to all incoming urls with pattern /api/*

Step 5

We now have configured jersey for our web application, and are ready to implement our REST-based service.

Before we look at the actual service implementation, we’ll extend our data model to the fields we need.

This step is done on the JPAModel project (and not the web project).

Right-click JPA Content and select Open Diagram

/wp-content/uploads/2012/05/open_digram_103469.png

In the diagram, add new fields for the attributes you’d like.

I’ve renamed both the JPAModel project to FeedModel and the entity to FeedEntry. Note that you need to refer to the project name (or more correctly, the persistency model name) in the REST service class later.

Here are is my model at the end.

/wp-content/uploads/2012/05/jpafields_105013.png

My JPA model has the same query defined  as the JPA tutorial (name AllFeedEntries)

Step 6

The JPA Model we saw in previous step is represented in a Java class.

We must update this class, so that Jersey understands that this is a REST data object which should be marshalled and unmarshalled based on requests.

In order to do this, we need only to add the notation @XmlRootElement to the before the java class declaration.

In the JPA Model project, open the java class under src\org.persistence\FeedEntry.java

(you name may be slightly different).

For completeness sake, I’ve included the whole FeedEntry.java class here. Note that I’ve only added the @XmlRootElement here, the rest of the class is generated based on the JPA modelling you did in step 5.

package org.persistence;
import javax.persistence.*;
import javax.xml.bind.annotation.XmlRootElement;
import java.util.Date;
@XmlRootElement
@Entity
@Table(name = "T_FEEDENTRY")
@NamedQuery(name = "AllFeedEntries", query = "select f from FeedEntry f")
public class FeedEntry {
          @Id
          @GeneratedValue
          private long id;
          @Basic
          private String senderName;
          @Basic
          private String feedText;
          @Basic
          private String senderEmail;
          @Basic
          private boolean isComment;
          @Basic
          private String parent;
          @Temporal(TemporalType.TIMESTAMP)
          @Basic
          private Date timeCreated;
          public long getId() {
                    return id;
          }
          public void setId(long id) {
                    this.id = id;
          }
          public void setSenderName(String param) {
                    this.senderName = param;
          }
          public String getSenderName() {
                    return senderName;
          }
          public void setFeedText(String param) {
                    this.feedText = param;
          }
          public String getFeedText() {
                    return feedText;
          }
          public void setSenderEmail(String param) {
                    this.senderEmail = param;
          }
          public String getSenderEmail() {
                    return senderEmail;
          }
          public void setIsComment(boolean param) {
                    this.isComment = param;
          }
          public boolean isIsComment() {
                    return isComment;
          }
          public void setParent(String param) {
                    this.parent = param;
          }
          public String getParent() {
                    return parent;
          }
          public void setTimeCreated(Date param) {
                    this.timeCreated = param;
          }
          public Date getTimeCreated() {
                    return timeCreated;
          }
}

Step 7

All we are left with now is to implement the actual REST service class. As mentioned in the introduction, we want it to support the operations: read all objects, read single object, create single object and update single object. The operations should support both JSON and XML as input and output based on the preferences of the client. The object we are building the REST service on top of is the FeedEntry from step 6.

The code is very simple due to jersey’s clever usage of annotations. This means that we can have the service as plain old java object (aka POJO) and therefore do not have to think about the traditional complexities of Java EE.

package no.bouvet.sap.neo.rest;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
import javax.sql.DataSource;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
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.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
import org.eclipse.persistence.config.PersistenceUnitProperties;
import org.persistence.FeedEntry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
 * Bouvet Experiment: REST API for the FeedEntry class
 *
 * Uses annotations from JAX-RS and JAXB
 * Is called from the Jersey servlet com.sun.jersey.spi.container.servlet.ServletContainer
 *
 * This class is processed if url is http(s)://<server>/<appl_context_root>/<jersey_root>/feed/*
 *
 * @author dagfinn.parnas@bouvet.no
 */
@Path("/feed")
public class FeedResource {
          final Logger logger = LoggerFactory.getLogger(FeedResource.class);
          //Ask jersey to populate this parameter for one of the REST methods
          @Context
          UriInfo uriInfo;
          //attributes used for reading/writing to JPA persistence
          private static DataSource ds;
          private static EntityManagerFactory emf;
          /**
           * Constructor needs to have no parameters.
           * It will initialize the datasource we are using (JPA)
           *
           */
          public FeedResource(){
                    try {
                              initPersistencyLayer();
                    }catch (Exception e) {
                              //TODO: Handle better
                              logger.error("Failed to initialize persistency layer", e);
                    }
          }
          /**
           * Main method that returns all feeds in the persistency layer.
           * It can produce the content in either JSON or XML (based on client preferences).
           * Jersey handles the marshalling automatically.
           *
           * Curl example (return all feeds in json format):
           * $ curl  -i -H "Accept: application/json"
           * http://localhost:8080/feed_stream/api/feed/
           */
          @GET
          @Produces( { MediaType.APPLICATION_JSON ,  MediaType.APPLICATION_XML})
          public List<FeedEntry> getAllFeedEntries() {
                    //Get all feed entries from persistency layer
                    EntityManager em = emf.createEntityManager();
                    List<FeedEntry> resultList = em.createNamedQuery("AllFeedEntries",
                                        FeedEntry.class).getResultList();
                    //Logging
                    String message = (resultList==null)? "getAllFeedEntries returning null": "getAllFeedEntries returning " + resultList.size() + " entries";
                    logger.info(message);
                    return resultList;
          }
          /**
           * Method that returns all feeds in the persistency layer.
           * Can be used for testing in the browser, as it request
           * the media type we expose in this method
           */
          @GET
          @Produces( { MediaType.TEXT_XML })
          public List<FeedEntry> getAllFeedEntriesForHTML() {
                    EntityManager em = emf.createEntityManager();
                    List<FeedEntry> resultList = em.createNamedQuery("AllFeedEntries",
                                        FeedEntry.class).getResultList();
                    //Logging
                    String message = (resultList==null)? "getAllFeedEntries returning null": "getAllFeedEntries returning " + resultList.size() + " entries";
                    logger.info(message);
                    return resultList;
          }
          /**
           * Return a single feed entry based on ID
           * Will be called if request has syntax /feed/<feed id>
           * It can produce the content in either JSON or XML (based on client preferences)
           *
           * Curl example (return feed with id 2)
           * $ curl  -i -H "Accept: application/json"
           * http://localhost:8080/feed_stream/api/feed/2
           */
          @GET
          @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
          @Path("{feedid}/")
    public FeedEntry getSingleFeed(@PathParam("feedid") String strFeedId) {
                    EntityManager em = emf.createEntityManager();
                    //Logging
                    logger.error("getSingleFeed with id:"+ strFeedId + " called");
                    try {
                              long feedId = Long.parseLong(strFeedId);
                              FeedEntry feedEntry = em.find(FeedEntry.class, feedId);
                              return feedEntry;
                    }catch (NumberFormatException e1) {
                              // TODO: Input parameter is not a long and therefore not a valid primary key
                              logger.warn("getSingleFeed for " + strFeedId + " is not a valid key", e1);
                    }catch (IllegalArgumentException e2){
                              //Invalid type of parameter . Should not happen normally
                              logger.warn("getSingleFeed for " + strFeedId + " gave exception", e2);
                    }
                    return null;
    }
          /**
           * POST a new object and store it in the persistency layer
           * Must be called with the HTTP POST method
           * and accepts input in both JSON and XML format.
           *
           * Curl example (creates new feed):
           * $ curl -i -X POST -H 'Content-Type: application/json'
           * -d '{"senderName":"Jane Doe","feedText":"test","isComment":false,"senderEmail":"dagfinn.parnas@bouvet.no"}'
           * http://localhost:8080/feed_stream/api/feed/
           *
           * @param feedEntry
           * @return
           */
          @POST
          @Consumes( { MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
          public Response createSingleFeed(FeedEntry feedEntry) {
                    //The feedEntry is automatically populated based on the input. Yeah!
                    logger.info("Creating new feed ");
                    //persist the entry
                    EntityManager em = emf.createEntityManager();
                    em.getTransaction().begin();
                    em.persist(feedEntry);
                    em.getTransaction().commit();
                    //The HTTP response should include the URL to the newly generated new entry.
                    //Probably exist a better way of doing this, but it works
                    try {
                              URI createdURI = new URI(uriInfo.getAbsolutePath()+""+ feedEntry.getId());
                              return Response.created(createdURI).build();
                    } catch (URISyntaxException e) {
                              logger.warn("Unable to create correct URI for newly created feed " + feedEntry, e);
                              //fallback is to include the input path (which will be lacking the id of the new object)
                              return Response.created(uriInfo.getAbsolutePath()).build();
                    }
          }
          /**
           * Update one or more fields of a single feed entry
           *
           * Curl example (updates senderEmail for feed with id 2) :
           * $ curl -i -X POST -H 'Content-Type: application/json'
           * -d '{"senderEmail":"dagfinn.parnas@gmail.com"}' http://localhost:8080/feed_stream/api/feed/2
           */
          @POST
          @Consumes( { MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
          @Path("{feedid}/")
    public Response updateSingleFeed(@PathParam("feedid") String strFeedId, FeedEntry modifiedFeedEntry) {
                    logger.info("updateSingleFeed with id:"+ strFeedId);
                    try {
                              long feedId = Long.parseLong(strFeedId);
                              EntityManager em = emf.createEntityManager();
                              FeedEntry currentFeedEntry = em.find(FeedEntry.class, feedId);
                              if(currentFeedEntry==null){
                                        logger.warn("updateSingleFeed failed as " + strFeedId + " does not exist");
                                        return Response.notModified(strFeedId + " does not exist").build();
                              }
                              //allow the post to only have one or more fields updated
                              if(modifiedFeedEntry.getParent()!=null){
                                        currentFeedEntry.setParent(modifiedFeedEntry.getParent());
                              }
                              if(modifiedFeedEntry.getSenderEmail()!=null){
                                        currentFeedEntry.setSenderEmail(modifiedFeedEntry.getSenderEmail());
                              }
                              if(modifiedFeedEntry.getSenderName()!=null){
                                        currentFeedEntry.setSenderName(modifiedFeedEntry.getSenderName());
                              }
                              if(modifiedFeedEntry.getFeedText()!=null){
                                        currentFeedEntry.setFeedText(modifiedFeedEntry.getFeedText());
                              }
                              if(modifiedFeedEntry.getTimeCreated()!=null){
                                        currentFeedEntry.setTimeCreated(modifiedFeedEntry.getTimeCreated());
                              }
                              //store in persistency store
                              em.getTransaction().begin();
                              em.persist(currentFeedEntry);
                              em.getTransaction().commit();
                              //return an ok response
                              return Response.ok().build();
                    }catch (NumberFormatException e1) {
                              // TODO: Input parameter is not a long and therefore not a valid primary key
                              logger.warn("getSingleFeed for " + strFeedId + " is not a valid key", e1);
                              return Response.serverError().build();
                    }catch (IllegalArgumentException e2){
                              //Invalid type of parameter . Should not happen normally
                              logger.warn("getSingleFeed for " + strFeedId + " gave exception", e2);
                              return Response.serverError().build();
                    }
    }
          /**
           * Initialize the persistency layer (JPA)
           *
           * @throws Exception
           */
          private void initPersistencyLayer() throws Exception  {
                    try {
                              logger.debug("Setting up persistency layer for FeedResource");
                              InitialContext ctx = new InitialContext();
                              ds = (DataSource) ctx.lookup("java:comp/env/jdbc/DefaultDB");
                              Map properties = new HashMap();
                              properties.put(PersistenceUnitProperties.NON_JTA_DATASOURCE, ds);
                              //IMPORTANT! The first parameter must match your JPA Model name in persistence.xml
                              emf = Persistence.createEntityManagerFactory("FeedModel", properties);
                    } catch (NamingException e) {
                              //TODO: Handle exception better
                              logger.error("FATAL: Could not intialize database", e);
                              throw new Exception(e);
                    }
          }
}

That’s all there is to it.

Cross-origin resource sharing

In order for this service to be consumed through javascript from a html page on a different host, you need to setup cross-orign resource sharing. Basically, this means to add a few http headers to all api calls.

Jersey allows you to define filters that processed are called for all methods.

You need to create a new class and refer to this class in the deployment descriptor web.xml file as a property to the Jersey servlet.

Filter class

package no.bouvet.sap.neo.rest.filter;
import com.sun.jersey.spi.container.ContainerRequest;
import com.sun.jersey.spi.container.ContainerResponse;
import com.sun.jersey.spi.container.ContainerResponseFilter;
/**
 * Filter for adding Access-Control-Allow-Origin headers for all API methods
 *
 * Is registered in the properties to Jersey in web.xml
 *
 * @author dagfinn.parnas
 *
 */
public class ResponseCorsFilter implements ContainerResponseFilter {
    @Override
    public ContainerResponse filter(ContainerRequest request, ContainerResponse response) {
              response.getHttpHeaders().add("Access-Control-Allow-Origin", "*");
              response.getHttpHeaders().add("Access-Control-Allow-Methods", "GET, POST, OPTIONS, PUT, DELETE");
              //allow any request headers sent by the client
                    String requestACRHeaders = request.getHeaderValue("Access-Control-Request-Headers");
                    if(requestACRHeaders!=null && !"".equals(requestACRHeaders.trim())){
                                   response.getHttpHeaders().add("Access-Control-Allow-Headers", requestACRHeaders);
                    }
        return response;
    }
}

Web.xml configuration

 <init-param>
      <param-name>com.sun.jersey.spi.container.ContainerResponseFilters</param-name>
      <param-value>no.bouvet.sap.neo.rest.filter.ResponseCorsFilter</param-value>
</init-param> 

Assigned Tags

      18 Comments
      You must be Logged on to comment or reply to a post.
      Author's profile photo Pankaj Kumar
      Pankaj Kumar

      Dagfinn

      Thanks for a comprehensive treatment to this topic. I am sure it would benefit a lot to everyone interested in SAP NetWeaver Cloud. For testing the REST api to your app one can also use the really good "Advanced Rest Client App" extension

      https://chrome.google.com/webstore/detail/hgmloofddffdnphfgcellkdfbfbjeloo

      Author's profile photo Matthias Steiner
      Matthias Steiner

      Thanks for the great post Dagfinn. Couldn't agree more... exposing RESTful services based on JAX-RS is simple as 1-2-3. Looking forward to your future blogs about SAP NetWeaver Cloud.

      Author's profile photo Former Member
      Former Member

      Dagfinn, thank you for this educating post!

      However, I have some troubles running a simple example following the instructions in this post. When I specify the com.sun.jersey.config.property.packages init-param of the Jersey servlet, then when requesting my REST service, I get an internal server error like:

      com.sun.jersey.core.spi.scanning.ScannerException: The URI scheme bundleentry of the URI bundleentry://179.fwk1911776907/WEB-INF/classes/my/jaxrs/demo/Hello.class is not supported. Package scanning deployment is not supported for such URIs. Try using a different deployment mechanism such as explicitly declaring root resource and provider classes using an extension of javax.ws.rs.core.Application com.sun.jersey.core.spi.scanning.PackageNamesScanner.scan(PackageNamesScanner.java:227)

      ...

      It looks like the package scanning in Jersey has some troubles when REST services are deployed in OSGi-based web containers, like the one of SAP NetWeaver Cloud.

      Have you experienced this problem?

      I was able to work it around by explicitly declaring my REST service classes in an extension of javax.ws.rs.core.Application as suggested in the error message.

      Author's profile photo Dagfinn Parnas
      Dagfinn Parnas

      Hi,

      No, I haven't received this error. Are there different ways of structuring the app (osgi vs non-osgi) ?

      Regards

      Dagfinn

      Author's profile photo Former Member
      Former Member

      OSGi and non-OSGi apps are structured a little bit differently. Nevertheless, when you deploy a non-OSGi app, it is converted to a OSGi app by the deploy service so the OSGi-based container of Neo can handle it.

      In this blog post you developed an non-OSGi app, don't you? So, did I. And I just wonder what's the difference...

      Author's profile photo Former Member
      Former Member

      Hi Kaloyan,

      Did you find a solution to your problem? I am facing similar issues on my local LJS.

      Thanks

      Manish

      Author's profile photo Former Member
      Former Member

      Yes, there is a solution. Instead of using the com.sun.jersey.config.property.packages init-param, you should use an alternative way for registering a JAX-RS application in the web.xml.

      You should execute these steps:

      1. Remove the com.sun.jersey.config.property.packages from the web.xml
      2. Register your root resource in an extension of javax.ws.rs.core.Application as described in the Jersey documentation in Example 2.7
      3. Register your extension of javax.ws.rs.core.Application in the web.xml as described in Example 2.12
      Author's profile photo Dagfinn Parnas
      Dagfinn Parnas

      Created a new project and experienced the same problem (so your solution was very helpful).

      The only differences from the project here were:

      1. Added the facet for JAX-RS in the new project

      (included the .jars and updated the web.xml manually in the blog version)

      2. Used jersey version 1.14 on new project and 1.12 on old project

      I expect the change in behavior is because of point 2 above.

      Author's profile photo Former Member
      Former Member

      Hi,

      I get still "The ResourceConfig instance does not contain any root resource classes." error. I changed Jersey version, edited web.xml, created a new class as mentioned in Kaloyans' comment. What's the point?

      Author's profile photo Former Member
      Former Member

      Hi Mr Dagfinn,

      I am Dhia, New intern @SAP Labs France.

      I am currently working on a application for retail market using : Sap Netweaver Cloud, SAPUI5.

      I followed your tutorial : http://scn.sap.com/community/developer-center/cloud-platform/blog/2012/05/25/exposing-a-rest-api-from-sapnwcloud

      and I got a problem when trying to deploy on External Cloud (netwever.ondemand) :

      _____

      XMLHttpRequest cannot load https://backendspacios.netweaver.ondemand.com/ServerSide/retail/customers/.

      Origin https://testregspacios.netweaver.ondemand.com is not allowed by Access-Control-Allow-Origin.

      _____

      Some informations that might be useful about my work:

      I followed the tutorial about adding persistence and I am using JPA (and default data source),

      I have 2 projects (front end SAPUI5) and a Backend (JPA+entity) communicating through a REST Service that handles CRUD Operations.

      Thank you for your time

      Best Regards,

      Dhia

      Author's profile photo Pankaj Kumar
      Pankaj Kumar

      Dhia,

      It seems like CORS is the culprit in your case. See this blog for more info on what is causing this

      http://scn.sap.com/community/developer-center/cloud-platform/blog/2013/02/11/cors-and-sapnwcloud-on-a-whiteboard

      - Pankaj

      Author's profile photo Former Member
      Former Member

      Hi there - I wonder if anyone has managed to get something like this working with HANA Cloud SDK 2.x using injection of the entity manager into a stateless bean that wraps the JPA entity?  I keep getting a null pointer exception when trying to inject the entity bean into the Jersey resource class (even if I make the resource a stateless bean too).

      Cheers,
      Scott

      Author's profile photo Former Member
      Former Member

      I have the same problem like Scott with SDK 2.x. I cannot make injection to work inside jaxrs service, even though it is annotated as ejb with @Stateless. I tried to separate the jaxrs class from the stateless bean and use InitialContext.lookup() but whatever jndi name I tried didn't work.

      Can anyone give an advice how to make injection inside jersey resource class work or how to manually lookup an EJB class from a jersey resource?

      Author's profile photo Former Member
      Former Member

      Hi Ivan,

      I did manage to get this to work.  I'm fairly inexperienced in Java so perhaps this is obvious to others, but it turned out that I just needed to add an "empty" beans.xml file to WEB-INF (see below), after marking the resource class as @ManagedBean.

      After that, @EJB injection was working fine inside my Jersey resource class.

      Cheers,
      Scott

      <?xml version="1.0" encoding="UTF-8"?>

      <beans xmlns="http://java.sun.com/xml/ns/javaee"

         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

         xsi:schemaLocation="

            http://java.sun.com/xml/ns/javaee

            http://java.sun.com/xml/ns/javaee/beans_1_0.xsd">

      </beans>

      Author's profile photo Former Member
      Former Member

      Thanks Scott. I didn't know that the app needs beans.xml and probably this was the problem. However, I managed to do it the hard way by exposing the EJBs with @Local and @EJB(name="ejbname", beanInterface=Intf.class) and then initialContext.lookup("ejbname") works. I'll definitely try what you suggested, because it looks much better.

      ---edit---

      It didn't work for me. The injected field is still null.

      Author's profile photo Former Member
      Former Member

      Hmmm... not sure it's relevant but I don't use web.xml to register the Jersey servlet, but rather implement a Jersey application to register the available resources.  Perhaps this makes a difference as I seem to recall reading somewhere about Jersey 1.x being problematic with EJB injection due to the way it implements its own injection providers (or something like that).

      I'm using Jersey 1.17.1.

      Scott

      ===

      @ApplicationPath("api")

      public class JerseyServletApplication extends Application {

       

          @Override

          public Set<Class<?>> getClasses() {

              Set<Class<?>> s = new HashSet<Class<?>>();

              s.add(OrganisationResource.class);

              s.add(TestResource.class);

              return s;

          }

         

      }

      Author's profile photo Former Member
      Former Member

      I already use the mechanism with the Jersey Application, so it is not that. My version of Jersey is rather old - 1.9, but with higher versions I have problems with odata4j.

      Author's profile photo Former Member
      Former Member

      Have you tried annotating with @ManagedBean and then injecting a stateless bean with @EJB?