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: 
matt_steiner
Active Contributor

In part II we  discussed some of the considerations to apply when designing an API and we identified the most suitable approach from an implementation perspective: JAX-RS. Now, it's time to stop talking and start coding...

Creating the basic project structure

In order to make it simple to consume and distribute our application we'll use Maven to take care of some of the heavy lifting in regards to dependency management and build operations. A full introduction into Maven is beyond the focus of this tutorial, so if you're interested in finding out more - please use your search engine of choice. All you need to know (and install) in regards to using Maven in Eclipse for now is described here: Running Sample Applications

So, the first thing we do is to create an simple Maven Project. For this we use the Project Creation Wizard in Eclipse:

  1. Select File > New > Other...
  2. Navigate to the Maven node and select 'Maven Project'
  3. Click on Next
  4. On the first page of the wizard we just need to tick the top checkbox 'Create a simple project (skip archetype selection)' and click on Next
  5. On the second page we need to maintain a few entries like group and artifact ID etc. Please check the screenshot below and adopt/adjust the entries as you see fit. The only important part is the packaging: make sure to select 'war' here. Then click on Finish.

With this empty project structure in place we now have to start setting up the project dependencies and build instructions. Being lazy developers (which is a virtue according to Larry Wall [ref]) we don't want to start from zero, but instead go for the classic copy & paste approach. So, if you have downloaded the SDK (version 1.7 or higher) you will find a samples folder in your SAP NetWeaver Neo SDK. Within this folder there's a pom.xml file. POM is an abbreviation for Project Object Model and in a nutshell it contains all the info required to build your project.

Next we have to merge that sample POM with our own. Sounds more complicated than it really is, because we want to keep almost all of the content from the sample POM, except for the first few lines, which represent the entries we maintained in step 5 above.

To make things a bit more simpler on your end I've attached the result of that operation for you: pom.xml. Make sure to adjust the group and artifact ids again to match what you entered in step 5.

Note: I also added a few standard dependencies as typically required when developing web applications such as pointing to the servlet archive bundled with the SAP NetWeaver Cloud SDK. You may want to keep that as a baseline file for the next projects you'll create (after you've verified that it all works that is!)

:!: Please also make sure to adjust the <nw.cloud.sdk.path> property to reflect your current environment! (I know the Maven purists will now scream out in agony, but to keep things simple it seems to be the best approach for now in order to not overly confuse beginners.)

Now, if all worked out as it should <fingers crossed> saving the file in Eclipse should trigger the Maven2Eclipse plugin to build the project for us - fetching the required libraries from Maven Central.If not, you cal always manually trigger a build by choosing "Run as > Maven install" from the context menu of the project. 

There's one last thing to do and that is to attach our project to the SAP NetWeaver Cloud runtime and making sure the project uses the Servlet API 2.5. For that purpose we go into the project properties by activating the context-menu. Then we navigate to the 'Project Facets' menu and select version 2.5 for the Dynamic Web Module facet. On the right hand side you would need to switch from the 'Details' to the 'Runtime' tab and tick the checkbox for 'SAP NetWeaver Cloud'.

And that's that...

Adding dependencies

Now with the basic structure in place we need to add some dependencies so that Maven can fetch them for us during the build process.

  <dependency>
                              <groupId>org.apache.cxf</groupId>
                              <artifactId>cxf-rt-frontend-jaxws</artifactId>
                              <version>${cxf.version}</version>
  </dependency>
  <dependency>
                              <groupId>org.apache.cxf</groupId>
                              <artifactId>cxf-rt-transports-http</artifactId>
                              <version>${cxf.version}</version>
  </dependency>
  <dependency>
                              <groupId>org.apache.cxf</groupId>
                              <artifactId>cxf-rt-rs-extension-providers</artifactId>
                              <version>${cxf.version}</version>
  </dependency>
  <dependency>
                              <groupId>org.apache.cxf</groupId>
                              <artifactId>cxf-bundle-jaxrs</artifactId>
                              <version>${cxf.version}</version>
  </dependency>

(Note: I only stated the dependencies required for CXF. Please take look at this final POM file to see all the declared dependencies required to run the project.)

Setting up the web.xml

As mentioned in part 2 Apache CXF sits on top of the Servlet API, hence we need to declare a handler servlet within the web.xml:

<?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"
id="WebApp_ID" version="2.5">
<welcome-file-list>
  <welcome-file>index.html</welcome-file>
</welcome-file-list>
<servlet>
  <servlet-name>CXFServlet</servlet-name>
                    <servlet-class>org.apache.cxf.jaxrs.servlet.CXFNonSpringJaxrsServlet</servlet-class>
  <init-param>
                              <param-name>jaxrs.serviceClasses</param-name>
                              <param-value>com.sap.nwcloud.demo.srv.FeedService</param-value>
  </init-param>
  <init-param>
  <param-name>jaxrs.providers</param-name>
                              <param-value>com.sap.nwcloud.demo.utils.cxf.CustomJSONProvider</param-value>
  </init-param>
  <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
  <servlet-name>CXFServlet</servlet-name>
  <url-pattern>/api/v1/*</url-pattern>
</servlet-mapping>
</web-app>

As you can see it's not really a whole lot to specify here, so let's quickly go over it. I registered the CXFNonSpringJaxrsServlet (as we go w/o Spring for now) and mapped it to the url-path 'api/v1/*'. If you read the Web API Design eBook from Apigee I referred to in part II of this blog series you'll recognize that I stick to the conventions they recommend.

The other important thing to notice is that I provided the class path to our service in the jaxrs.serviceClass parameter. This will tell CXF to introspect the corresponding class and search for specific (jaxrs) annotations it can interpret. We'll get to talk about this in a minute...

Sample Use-case

Now every demo app needs a crisp example, right? For some reason it seems that most people familiarizing themselves with SAP NW Cloud and RESTful APIs are all having the same idea: a Feed Reader. I did it a year ago while developing the demo app for my TechEd session and so did a whole lot of other people:

As such, let's stick to that tradition and develop a service that provides a blog Feed as a RESTful Service. And what other feed could be more interesting than our beloved SCN? (Now, let's not argue about the usefulness of converting an RSS feed to JSON. We just want a simple example to be able to focus on the technical aspects, right?)

ℹ Before we dig deeper you may want to see the end-result. If so, you can test-drive the 'app' here:

https://restdemo.netweaver.ondemand.com/simplerest/api/v1/feeds

RESTful Services

As mentioned already we only have service: FeedService. So, let's have a closer look at that file:

package com.sap.nwcloud.demo.srv;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import javax.ws.rs.Consumes;
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 org.apache.cxf.jaxrs.ext.MessageContext;
import com.sap.nwcloud.demo.api.FeedQueryByElements;
import com.sap.nwcloud.demo.dao.DAOFactory;
import com.sap.nwcloud.demo.dao.FeedDAO;
import com.sap.nwcloud.demo.model.Feed;
import com.sap.nwcloud.demo.model.FeedList;
import com.sap.nwcloud.demo.model.ObjectFactory;
@Path("/feeds")
@Produces({ "application/json" })
public class FeedService extends BaseService
{
          @GET
          @Path("/")
          public FeedList getFeeds(@PathParam("") FeedQueryByElements query)
          {
                    FeedList list = new ObjectFactory().createFeedList();
                    Collection<Feed> feeds = new ArrayList<Feed>(1);
                    // dirty hardcoded string 😉
                    final String url = "http://scn.sap.com/community/feeds/blogs";
                    FeedDAO dao = DAOFactory.getInstance().getDAO();
                    Feed feed = dao.findFeedByURL(url);
                    feeds.add(feed);
                    List<Feed> feedList = new ArrayList<Feed>(feeds.size());
                    feedList.addAll(feeds);
                    list.setFeeds(feedList);
                    return list;
          }
}

At the very top of the file I specified two annotations: Path and Produces. The first one specifies the url-path to map this service to, in this case "feeds". With that information CXF knows, which service to invoke if a url is called. The Produces annotation simply specifies the default data format of the data returned in the response (and yes, you can dynamically switch the data format too.)

Let's have a look at the getFeeds() method and its annotations. GET simply specifies the HTTP verb to map the method to. I set the Path to "/" to simply use the default one provided on class level.

Now, the beauty of using JAX-RS is that it's unobtrusive. Our class is just a plain POJO (Plain Ordinary Java Object) with a few JAX-RS annotations. This makes the implementation quite flexible and if I should ever want/need to switch to another JAX-RS implementation it should be fairly easy. It also helps with unit tests as all the services can be consumed outside of the web container as well.

In fact, that's another note-worthy aspect of this implementation: both the request object FeedQueryByElements and the response FeedList are domain model objects. Consequently I can call this method from any other class within my service layer as well and I do not need to provide a dedicated facade. All the marshalling/unmarshalling to JSON is taken care of by the underlying framework. Now, isn't that nice? (We'll talk about that very topic and the benefits it brings when developing flexible APIs in part IV of this series.)

Architecture considerations

Before we call it a day let's talk about two more aspects of this sample app. For one, I still believe the good old way of applying clean separation of concerns (SoC) is appropriate! As you see by the code above, I delegated the whole data fetching to a dedicated DAO (Data Access Object). There are multiple reasons why this makes sense:

  • the business logic data should not need to care where the data comes from, but should only need to cope with business logic.
  • it makes the app more flexible as I can change the data source on the fly, without having to change the service (notice the factory pattern for constructing the DAO?) In a productive app you may want to cache or persist the feed so you don't have to call the feed provider all the time. In that case you may have multiple implementations of the DAO: one that calls the feed provider and one that queries a database or cache. By using the same interface it's just a matter of dynamically calling the appropriate implementation.
  • it also helps in unit tests and in cases where the data-source may not be accessible. All you need to do is to create a stub implementation that mimics the behavior of a real DAO by returning hard-coded data.

Last, but not least let's talk a bit about the domain model objects. Fortunately the days are gone, where we were forced to use dedicated DTOs (Data Transfer Objects) to send data over the wire. There still are valid uses cases for designing dedicated DTOs, but the fact that we can use our domain model objects as-is is certainly nice. However, our objects still need to be serializable so that they can be be converted to/from JSON (or XML etc.) and so a few considerations apply.

Take the Feed class for example:

@XmlRootElement(name = "feed")
@XmlAccessorType(XmlAccessType.FIELD)
public class Feed extends BaseObject implements Serializable
{
  private static final long serialVersionUID = 1L;
  private String title = null;
  //...
}

As you can see we work with annotations again. The XMLRootElement annotation indicates that a FeedObject can be the root object of a hierarchy. The XMLAccessorType annotation tells JAXB (Java Architecture for XML Binding) whether to use the attributes directly or use getter/setter methods during the de-/serialization process. And... we of course mark our class as being serializable and provide a serialVersionUID.

Wrap-up

I sure don't want to provide all the listings for the various objects used in this sample, but instead opted for providing it on Github. You can either clone it from here, or download the zip and manually merge it into the project structure we created at the beginning of this tutorial.

Hope to see you back for part IV, where we'll step it up a notch and port the whole thing to Spring and dig into more advanced features of JAX-RS in general and CFX in particular. Cheers...

PS: Special thanks to vedran.lerenc and kaloyan.raev for helping me sort out some issues with Maven.

3 Comments