Spring Surf Development #1 - Creating a REST API with Spring WebScripts

cancel
Showing results for 
Search instead for 
Did you mean: 

Spring Surf Development #1 - Creating a REST API with Spring WebScripts

erikwinlof
Active Member
0 7 2,890
.


.


A couple weeks ago I implemented 2 web applications and a REST API for a  new open sourced BPM / BPMN 2.0 engine named Activiti. Feel free to visit http://www.activiti.org where you may download the distribution (Apache licensed) and play around with it. The actual BPM engine is just a simple jar file having the benefit of therefore being deployable in virtually any JAVA environment. The whole architecture for the REST API and web applications was built using the Spring Surf framework.


.


Spring Surf is the name of a collection of script centric frameworks used to create web applications (Spring Surf) and REST APIs (Spring WebScripts). The project page can be found at http://www.springsource.org/extensions/se-surf while documentation and downloads can be found at http://www.springsurf.org/.


.


I will write a series of blog posts introducing the Spring Surf  framework by writing an example application for a 'Safari' company. The application will be built using Maven and will contain of 4 modules:


.






  • safari-root - The root module containing documentation and from which the project is built.


  • safari-core - The core business logic packaged into a .jar file.


  • safari-rest - The Safari JSON REST API packaged as a deployable .war-file.


  • safari-shop - The Safari Web Shop packaged as a deployable .war file.





.


There will be 4 blog posts and they will be organized as below:


.






  1. Setting up the Maven project and creating the Safari REST API using Spring WebScript.


  2. Securing the REST API.


  3. Setting up the Safari Web Shop using Spring Surf.


  4. Securing the Safari Web Shop and connecting it with the REST API.





.



In this first post we will: install Maven, create a maven project with the root, core & rest modules, add  Spring WebScripts' dependencies, install Tomcat and the tomcat-maven-plugin, create a service tier for the 'Safari' company and create the Safari REST API. Now lets go ahead and do it.



Note that if you want the complete project you can download it from here: Safari - Spring WebScripts REST API sample and if you want to take a closer look at the Activiti source code to learn more about Spring WebScripts after this blog post you can do that here http://svn.codehaus.org/activiti/activiti/trunk/.



.

Install maven & setup the project structure



.



Visit http://maven.apache.org/download.html and download the latest Maven 2 release. When downloaded unzip it and make sure to set the following environment variables:

M2_HOME=<path-to-the-expanded-folder>

MAVEN_OPTS=-Xmx512m


Also make sure you set your PATH variable to include '${M2_HOME}/bin'.


.


Create a directory named 'Safari' for your project and change into that directory with your terminal.


Then type the following to create the safari-root module.


mvn archetype:create \

    -DgroupId=com.safari \

    -DartifactId=safari-root \

    -DarchetypeArtifactId=maven-archetype-site-simple


You have now created a directory named 'safari-root' that contains a pom.xml file which is where you instruct Maven on how to compile, package and deploy its modules. The safari-root module will not contain any source code but it may contain user documentation since we created it using the maven-archetype-site-simple archetype. To read further about Maven and archetypes please visit http://maven.apache.org/archetype/index.html.


Now lets go ahead and create the business logic module by typing:


.


mvn archetype:create \

   -DgroupId=com.safari \

   -DartifactId=safari-core \

   -DpackageName=com.safari.core \

   -DarchetypeGroupId=org.apache.maven.archetypes


...and a web module for the rest api by typing (note the archetype used is maven-archetype-webapp)....


.


mvn archetype:create \

   -DarchetypeGroupId=org.apache.maven.archetypes \

   -DarchetypeArtifactId=maven-archetype-webapp \

   -DgroupId=com.safari \

   -DartifactId=safari-rest


You have now created 2 new folders (safari-core & safari-rest) that shall be located next to the safari-root folder.


To make the new modules child modules to safari-root enter the following xml snippet in safari-root/pom.xml after the <properties> element:


<modules>

  <module>../safari-core</module>

  <module>../safari-rest</module>

</modules>


You also need to add the following xml snippet in your pom.xml files (located under safari-core & safari-rest) after their <url> elements:


<parent>

   <groupId>com.safari</groupId>

   <artifactId>safari-root</artifactId>

   <version>1.0-SNAPSHOT</version>

</parent>


Now lets try and build the whole project by typing:


mvn -f safari-root/pom.xml clean install


Since we have added safari-core & safari-rest as sub modules to safari-root they will also be built when building safari-root. And since we instructed maven to 'install' the modules they will end up in your local repository which by default is located in the '.m2' directory inside the current users home directory. Feel free to browse around in the repository and note that we have already created a .jar file (repository/com/safari/safari-core/1.0-SNAPSHOT/safari-core-1.0-SNAPSHOT.jar) & and a .war file (repository/com/safari/safari-rest/1.0-SNAPSHOT/safari-rest-1.0-SNAPSHOT.war). The '1.0-SNAPSHOT' is set by Maven automatically since we haven't set the version ourselves and to reflect that we are just building the files for development rather than for a release.


.






Adding the Spring WebScripts runtime



.


So far we only have an empty project structure so lets go ahead and add the Spring WebScripts runtime to our project. First add the maven repository where the Spring WebScript releases are located by pasting the following snippet into safari-root/pom.xml after the <modules> element:


<repositories>

   <repository>

      <id>spring-extensions-milestone</id>

      <name>Spring Extensions Milestone Repository</name>

      <url>http://extensions.springframework.org/milestone</url>

   </repository>

</repositories>


Since the safari-rest module is dependent upon Spring WebScripts (which  is dependent upon Spring) we need to instruct maven to download  those dependencies. We also need to instruct maven to include the safari-core.jar file in the safari-rest.war file so we can use the business logic from the REST API. We do that by adding the following xml-snippet to safari-rest/pom.xml inside the <dependencies> element:


<!-- Make the Safari Core API available to our the safari REST API -->

<dependency>

   <groupId>com.safari</groupId>

   <artifactId>safari-core</artifactId>

   <version>1.0-SNAPSHOT</version>

</dependency>

<!-- Use servlet.jar for compiling but not for packaging since it will be provided by the server -->

<dependency>

   <groupId>javax.servlet</groupId>

   <artifactId>servlet-api</artifactId>

   <version>2.5</version>

   <scope>provided</scope>

</dependency>

<!-- Include the Spring Core Framework-->

<dependency>

   <groupId>org.springframework</groupId>

   <artifactId>spring-core</artifactId>

   <version>3.0.1.RELEASE</version>

</dependency>

<!-- Include the Spring WebScripts runtime -->

<dependency>

   <groupId>org.springframework.extensions.surf</groupId>

   <artifactId>spring-webscripts</artifactId>

   <version>1.0.0.M3</version>

</dependency>

<!-- Include the Spring WebScript API so we can browse and list our webscripts on the server -->

<dependency>

   <groupId>org.springframework.extensions.surf</groupId>

   <artifactId>spring-webscripts-api</artifactId>

   <version>1.0.0.M3</version>

</dependency>


To ensure Spring and Spring WebScripts are initialised we need to configure the safari-rest/src/main/webapp/WEB-INF/web.xml file to look like below. First we tell the Spring framework to start up and use the config file (WEB-INF/web-application-context.xml) and then we instruct the WebScriptServlet to handle all URL requests starting with '/service/*' which means that all REST API calls will start with 'http://localhost:8080/activiti-rest/service/'. We also set the session timeout so we don't need to worry about server having different default settings.


<!DOCTYPE web-app PUBLIC

'-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN'

'http://java.sun.com/dtd/web-app_2_3.dtd' >

<web-app>

   <display-name>Safari REST API</display-name>



   <!-- Spring Application Context location -->

   <context-param>

      <param-name>contextConfigLocation</param-name>

      <param-value>/WEB-INF/web-application-context.xml</param-value>

      <description>Spring config file location</description>

   </context-param>



   <!-- Spring Context Loader listener -->

   <listener>

      <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>

   </listener>



   <!-- Spring WebScripts -->

   <servlet>

      <servlet-name>WebScriptServlet</servlet-name>

      <servlet-class>org.springframework.extensions.webscripts.servlet.WebScriptServlet</servlet-class>

   </servlet>



   <!-- The WebScript Servlet -->

   <servlet-mapping>

      <servlet-name>WebScriptServlet</servlet-name>

      <url-pattern>/service/*</url-pattern>

   </servlet-mapping>



   <!-- Session configuration -->

   <session-config>

      <session-timeout>30</session-timeout>

   </session-config>

</web-app>


Of course we also need to create the web-application-context.xml file inside the safari-rest/src/main/webapp/WEB-INF directory. Make sure it looks like the following so that the Spring WebScript runtime will get the framework configuration files it needs.


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

<beans xmlns='http://www.springframework.org/schema/beans'

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

       xmlns:context='http://www.springframework.org/schema/context'

       xsi:schemaLocation='

           http://www.springframework.org/schema/beans

           http://www.springframework.org/schema/beans/spring-beans-2.5.xsd

           http://www.springframework.org/schema/context

           http://www.springframework.org/schema/context/spring-context-2.5.xsd'>

   <!-- Import Spring WebScripts Framework config files-->

   <import resource='classpath*:org/springframework/extensions/webscripts/*-context.xml' />

</beans>


Now try and rebuild your project once again by running:


mvn -f safari-root/pom.xml clean install


If you have done everything correctly you should get a successful build. To  confirm that the Spring WebScripts are properly setup we need to deploy  it to a server. So lets go ahead and install Tomcat so we can deploy the safari-rest module to it...


.


Install and setup tomcat



.

Download the latest Tomcat 6 release from http://tomcat.apache.org/. To make things easy for us we will let maven handle the deployment to the Tomcat server by using the tomcat-maven-plugin. The tomcat-maven-plugin will use the Tomcat manager application to deploy our rest api, but when it does it expects


certain users and roles to be setup inside Tomcat. To add the required users and roles configure


${TOMCAT_HOME}/conf/tomcat-users.xml to look like below:


<?xml version='1.0' encoding='utf-8'?>

<tomcat-users>

   <role rolename='tomcat'/>

   <role rolename='manager'/>

   <user username='tomcat' password='tomcat' roles='tomcat,manager'/>

   <user username='admin' password='' roles='tomcat,manager'/>

</tomcat-users>


Now you may start your tomcat using ${TOMCAT_HOME}/bin/startup.sh (or startup.bat if your on windows).


To install the tomcat-maven-plugin you need to add the following xml snippet to safari-root/pom.xml after the <repositories> element:


<build>

   <plugins>

      <plugin>

      <groupId>org.codehaus.mojo</groupId>

      <artifactId>tomcat-maven-plugin</artifactId>

      <configuration>

         <url>http://localhost:8080/manager</url>

      </configuration>

      </plugin>

   </plugins>

</build>


Once this is done you're ready to deploy the safari-rest module by invoking:


mvn -f safari-root/pom.xml clean install org.codehaus.mojo:tomcat-maven-plugin:1.0-beta-1:deploy


If you have done everything correctly you can now point your browser to http://localhost:8080/safari-rest/service/index which will show the 'dashboard' for the Spring Webscripts environment. As you know we haven't created any webscripts of our own but the pages that you are accessing are actually webscripts that return html (instead of JSON) and gives the user the possibility to browse, reload and test webscripts. If you for example click the 'Browse by Web Script URI' link you will see all the URL's that are possible to invoke. Once you have configured webscripts of your own they will also show up in this list.


.


It is also useful to pay attention to the 'Refresh Webscripts' button, if you make changes in your webscripts and copy them over to a web application deployed as an exploded directory you may reload the Spring WebScript runtime by clicking this button, so the changes are picked up without you needing to restart the server.


.


Add some business logic



.


Before we write our webscripts we will create a simple service class and POJO for the core api so we can list the trips that the Safari company offers. First create a directory named 'travel' in safari-core/src/main/java/com/safari/core and then create Trip.java file like below in the safari-core/src/main/java/com/safari/core/travel directory.


package com.safari.core.travel;



public class Trip {



private int id;

private String name;



public Trip (int id, String name) {

    super();

    this.id = id;

    this.name = name;

}



public int getId(){ return id; }

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



public String getName() { return name; }

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



}




Then go ahead and create a TravelService.java like below in the same directory.


package com.safari.core.travel;



import java.util.ArrayList;

import java.util.List;



public class TravelService {



   public List<Trip> getTrips() {

      List<Trip> trips = new ArrayList<Trip>();

      trips.add(new Trip(1, 'Masai Mara Adventurer'));

      trips.add(new Trip(2, 'Serengeti Explorer'));

      trips.add(new Trip(3, 'Kruger Wildlife'));

      return trips;

   }



}






Finally make sure we make Spring manage the service class by adding the following xml snippet in


safari-rest/src/main/webapp/WEB-INF/web-application-context.xml after the <import> element:


<!-- Safari Services -->

<bean id='travelService' class='com.safari.core.travel.TravelService'/>


Now if you try to compile this you will probably get an error complaining about the use of generics requiring a Java source level of 5 or higher. To change the compilation source level in maven add the following xml snippet in safari-root/pom.xml inside the <plugins> element (which is inside the <build> element).


<plugin>

   <artifactId>maven-compiler-plugin</artifactId>

   <configuration>

      <source>1.5</source>

      <target>1.5</target>

   </configuration>

</plugin>


Lets try to compile this so we know we are on top of things by invoking the following command:


mvn -f safari-root/pom.xml clean install


If the compilation worked for you, you are ready for the actual REST api development.


.


.


Writing our first webscript (our first REST API service)



.


First let's explain really quickly what a webscript actually is. A webscript can be described as a set of files that together form a component that receives a request, reacts to it by invoking some logic (using Java, JavaScript, Groovy) and afterwards returns a response in a suitable format (JSON, HTML, XML, Atom or whatever format that is suitable). In other words each REST API call will be implemented as a separate webscript.


A webscript MAY consist of the following files:


.






  • trips.get.desc.xml - The webscript descriptor in xml format, telling us which URL:s that shall be handled by this webscript


  • trips.get.js - The logic written in JavaScript (note the .js-suffix) that loads the trips and prepares it for the response template


  • trips.get.json.ftl - The response template that written as Freemarker template (note the '.ftl' suffix) returning JSON data (note the '.json')


  • trips.get.config.xml - An xml configuration file


  • trips.get.properties - The default i18n messages ready for use by the JavaScript logic and the response templates


  • trips.get.sv_SE.properties - Language & Locale specific i18n messages.





As you can see all the files have similar file names, this is because Spring WebScripts uses naming convention instead of configuration to indicate which files that shall 'grouped' together and become a webscript component. The '.get' part of the file names indicate that this webscript shall be invoked only if the URL was called using the GET HTTP method. In other words if somebody would POST an html address form towards a URL that is handled by a webscript that webscript would be named something like address.post.desc.xml.


It is common practice to use the following pattern when implementing a REST API:


.






  • GET - used for retreiving data


  • POST - Used for creating new data


  • PUT - Used for updating existing data


  • DELETE - Used for deleting data





In our case we will choose a slightly different path than the one described as above when implementing our first webscript. To make the example really simple we will neither use the .get.properties or the config.xml files and we will use Java instead of JavaScript for the logic. Our webscript will then consist of the following 3 files:


.






  • trips.get.desc.xml


  • TripsGet.java


  • trips.get.json.ftl





First we create the trips.get.desc.xml descriptor as described below in the


safari-rest/src/main/resources/webscripts/com/safari/travel directory (Note that you will need to create the directories). The descriptor will get picked up by the webscripts runtime and the webscript will be registered to handle GET HTTP Requests against the '/travel/trips' url. In other words to invoke this webscript you would need to type http://localhost:8080/safari-rest/service/travel/trips in your web browser's address bar.


<webscript>

  <shortname>Trips</shortname>

  <description>Lists all trips</description>

  <url>/travel/trips</url>

  <format default='json'>argument</format>

</webscript>


Then we implement the logic by creating a TripsGet.java as below in the safari-rest/src/main/java/com/safari/rest/api/travel directory (again you will need to create the directories, all the way form the 'main' directory).


package com.safari.rest.api.travel;



import com.safari.rest.api.SafariWebScript;

import org.springframework.extensions.webscripts.Cache;

import org.springframework.extensions.webscripts.Status;

import org.springframework.extensions.webscripts.WebScriptRequest;



import java.util.HashMap;

import java.util.Map;



public class TripsGet extends SafariWebScript

{

   @Override

   protected Map<String, Object> executeImpl(WebScriptRequest req, Status status, Cache cache)

   {

      // Create the template model and fill it with trips

      Map<String, Object> model = new HashMap<String, Object>();

      model.put('trips', getTravelService().getTrips());

      return model;

   }

}


The executeImpl method serves as an entry point for Java backed webscript so this is where the logic shall be defined. Looking at the code you can see that it creates a Map named model, this is where the logic places all the object that the response template shall render. Since this webscript is a webscript responding to a GET HTTP request it doesn't perform any updates against the core api, but if it would, this would be the place to do it.


What you can see however is that it uses a method called getTravelService() and that it extends another class named SafariWebScript.


.


SafariWebscript is our base webscript class suitable for helper methods and easy access to services and other resources. Therefore, make sure you also create SafariWebScript.java as below, in the safari-rest/src/main/java/com/safari/rest/api directory. As you can see this class extends the DeclarativeWebScript class which is required for Java backed webscripts.


package com.safari.rest.api;



import com.safari.core.travel.TravelService;

import org.springframework.extensions.webscripts.*;



public class SafariWebScript extends DeclarativeWebScript {



   protected TravelService travelService;



   public void setTravelService(TravelService travelService) {

      this.travelService = travelService;

   }



   public TravelService getTravelService() {

      return this.travelService;

   }



}


Now we will render the response in JSON format using Freemarker. To do that create trips.get.json.ftl in the same directory as the descriptor file (safari-rest/src/main/resources/webscripts/com/safari/travel).


<#escape x as jsonUtils.encodeJSONString(x)>

[

   <#list trips as trip>

   {

      'id': ${trip.id},

      'name': '${trip.name}'

   }<#if trip_has_next>,</#if>

   </#list>

]

</#escape>


The <#escape> tag is a Freemarker helper utility for making sure the json attribute values are escaped.


The <#list> tag makes the Freemarker iterate over the trips so each one can be rendered.


The <#if> tag uses the Freemarker '_has_next' built in to decided if a ',' shall be rendered depending on if its the last trip or not. To read more about Freemarker visit http://freemarker.sourceforge.net/


Now we only have one last thing to do before we can deploy the REST API to the server.


.


When writing webscripts using JavaScript for the logic we would have been ready by now, but since we are writing Java backed webscripts in which the Java classes actually are Spring beans, we need to define the Java classes/beans in our Spring configuration file. Add the following xml snippets in the bottom of safari-rest/src/main/webapp/WEB-INF/web-application-context.xml


<!-- Safari WebScript base helper class -->

<bean id='safariWebscript'

      class='com.safari.rest.api.SafariWebScript'

      parent='webscript'>

   <property name='travelService' ref='travelService'/>

</bean>



<!-- Safari WebScripts / REST API calls -->

<bean id='webscript.com.safari.travel.trips.get'

      class='com.safari.rest.api.travel.TripsGet'

      parent='safariWebscript'>

</bean>


Note the bean id for the TripsGet class. It follows a naming convention used to let the framework understand that this Java bean is part of the 'trips.get'-webscript component. First it must start with 'webscript.', the next part shall be the package name ('com.safari.travel') followed by a dot ('.') and the webscript name ('trips') followed by a dot ('.') and the http method ('get'). Note also that this where we instruct Spring to always insert the TravelService into our webscript base helper class. In case you're wondering where the parent bean 'webscript' is defined you can find it in one of the many Spring WebScript framework config files that you imported a couple of rows up in this file, namely spring-webscripts-application-context.xml.


.


Now you are ready to redeploy your REST API which now contains your  first REST API services. Do that by invoking the following command (note the 'redeploy' instead of the 'deploy' in the end):


mvn -f safari-root/pom.xml clean install org.codehaus.mojo:tomcat-maven-plugin:1.0-beta-1:redeploy


You are now ready to invoke your first webscript by pointing your browser to http://localhost:8080/safari-rest/service/travel/trips where you should get the available trips as a result. Note that your browser might open the result in a text editor or external program.


.




Hopefully you have found this post interesting and can see that Spring WebScripts  are an exciting development alternative for rapidly implementing REST  API's. The next part, about securing the REST API, will be posted in roughly 6 weeks.


.
7 Comments