Thursday, August 26, 2010

RESTful Web Services with Restlet Framework


RESTful Web Services with Restlet Framework

The acronym REST stands for Representational State Transfer, this basically means that each unique URL is a representation of some object. REST defines a set of architectural principles by which you can design Web services that focus on a system's resources, including how resource states are addressed and transferred over HTTP by a wide range of clients written in different languages.
Implementation of a REST Web service follows four basic design principles:
  • Use HTTP methods explicitly (GET, POST, PUT, DELETE).
  • Be stateless.
  • Expose directory structure-like URIs.
  • Transfer XML, JavaScript Object Notation (JSON), or both.
REST design principle establishes a one-to-one mapping between create, read, update, and delete (CRUD) operations and HTTP methods. According to this mapping:
  • To create a resource on the server, use POST.
  • To retrieve a resource, use GET.
  • To change the state of a resource or to update it, use PUT.
  • To remove or delete a resource, use DELETE.
The following code snippets show the implementation of RESTful Web Services using restlet framework 1.1.9, spring, xstream, jdbc.

UserResource class handles the incoming requests.

package service;

import org.apache.log4j.Logger;
import org.restlet.Context;
import org.restlet.data.MediaType;
import org.restlet.data.Request;
import org.restlet.data.Response;
import org.restlet.resource.Representation;
import org.restlet.resource.Resource;
import org.restlet.resource.ResourceException;
import org.restlet.resource.StringRepresentation;
import org.restlet.resource.Variant;
import org.springframework.context.ApplicationContext;

import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.xml.DomDriver;

import data.beans.UserInfo;
import data.bo.UserInfoBO;
import data.util.ApplicationContextUtil;

/**
 * Resource which handle userInfo resource requests.
 */
public class UserResource extends Resource {

  private static Logger log = Logger.getLogger(UserResource.class);

  @Override
  public boolean allowPost() {
    return true;
  }

  @Override
  public boolean allowDelete() {
    return true;
  }

  public UserResource(Context context, Request request, Response response) {
    super(context, request, response);
    // This representation has only one type of representation.
    getVariants().add(new Variant(MediaType.APPLICATION_XML));
  }

  /**
   * Handle a GET request and returns xml representation of user.
   */
  @Override
  public Representation represent(Variant variant) throws ResourceException {

    String username = (String) getRequest().getAttributes().get("username");
    if (username != null) {

      ApplicationContext ctx = ApplicationContextUtil.getApplicationContextInstance();
      UserInfoBO userInfoDAO = (UserInfoBO) ctx.getBean("userInfoBO");
      UserInfo userInfo = userInfoDAO.getUserInfo(username);
      if (userInfo == null)
        return new StringRepresentation("UserInfo with username : " + username + " doesn't exists.");

      XStream xstream = new XStream(new DomDriver());
      xstream.alias("userInfo", UserInfo.class);
      Representation result = new StringRepresentation(xstream.toXML(userInfo));
      return result;
    }

    return new StringRepresentation("Invalid Request.");
  }

  /**
   * Handle a POST request.
   */
  @Override
  public void acceptRepresentation(Representation entity) {
    log.info("acceptRepresentation -- ");

    String ext = (String) getRequest().getAttributes().get("resType");

    try {
      if (ext.equalsIgnoreCase("xml")) {
        String xmlString = entity.getText();
        log.info("input data : " + xmlString);

        XStream xstream = new XStream(new DomDriver());
        xstream.alias("userInfo", UserInfo.class);
        UserInfo userInfo = (UserInfo) xstream.fromXML(xmlString);
        log.info("userInfo : " + userInfo);

        ApplicationContext ctx = ApplicationContextUtil.getApplicationContextInstance();
        UserInfoBO userInfoDAO = (UserInfoBO) ctx.getBean("userInfoBO");
        UserInfo dbUserInfo = userInfoDAO.getUserInfo(userInfo.getUserName());
        if (dbUserInfo != null) {
          getResponse().setEntity(new StringRepresentation("User already exists.\n" + xstream.toXML(dbUserInfo)));
          return;
        }

        userInfo = userInfoDAO.createUserInfo(userInfo);
        Representation rep = new StringRepresentation(xstream.toXML(userInfo));
        getResponse().setEntity(rep);
      } else {
        getResponse().setEntity(new StringRepresentation("Invalid Request."));
      }
    } catch (Exception e) {
      e.printStackTrace();
      getResponse().setEntity(new StringRepresentation("Bad Request."));
    }
  }

  /**
   * Handle DELETE requests.
   */
  @Override
  public void removeRepresentations() throws ResourceException {
    String username = (String) getRequest().getAttributes().get("username");
    if (username != null) {

      ApplicationContext ctx = ApplicationContextUtil.getApplicationContextInstance();
      UserInfoBO userInfoDAO = (UserInfoBO) ctx.getBean("userInfoBO");
      UserInfo userInfo = userInfoDAO.getUserInfo(username);
      if (userInfo == null) {
        getResponse().setEntity(new StringRepresentation("UserInfo with username : " + username + " doesn't exists."));
        return;
      }

      XStream xstream = new XStream(new DomDriver());
      xstream.alias("userInfo", UserInfo.class);
      userInfoDAO.deleteUserInfo(userInfo);
      getResponse().setEntity(
          new StringRepresentation("UserInfo with username : " + username + " is deleted.\n"
              + xstream.toXML((userInfo))));
    } else {
      getResponse().setEntity(new StringRepresentation("Bad Request."));
    }
  }
}

In RESTful Web Services, URL will be mapped to Resource classes.
The following ApplicationRouter class maps the URLs to the UserResource class.
** We can also run the Router class to handle the request without deploying in server.

package service;

import org.restlet.Application;
import org.restlet.Component;
import org.restlet.Restlet;
import org.restlet.Router;
import org.restlet.data.Protocol;

public class ApplicationRouter extends Application {

  /**
   * Creates a root Restlet that will receive all incoming calls.
   */
  @Override
  public synchronized Restlet createRoot() {
    // Create a router Restlet that defines routes.
    Router router = new Router(getContext());

    router.attach("/user/user.{resType}", UserResource.class);
    router.attach("/user/{username}/user.{resType}", UserResource.class);

    return router;
  }

  public static void main(String[] args) {
    try {
      // Create a new Component.
      Component component = new Component();

      // Add a new HTTP server listening on port 8182.
      component.getServers().add(Protocol.HTTP, 9100);

      // Attach the sample application.
      component.getDefaultHost().attach(new ApplicationRouter());

      // Start the component.
      component.start();

    } catch (Exception e) {
      // Something is wrong.
      e.printStackTrace();
    }
  }
}

UserInfoBO class wraps the database calls to DAO.

package data.bo;

import java.util.List;

import data.beans.UserInfo;
import data.dao.UserInfoDAO;

public class UserInfoBO {

  private UserInfoDAO userInfoDAO;

  public UserInfoDAO getUserInfoDAO() {
    return userInfoDAO;
  }

  public void setUserInfoDAO(UserInfoDAO userInfoDAO) {
    this.userInfoDAO = userInfoDAO;
  }

  public UserInfo getUserInfo(String userName) {
    List<UserInfo> userInfo = userInfoDAO.select(userName);

    if (userInfo != null && userInfo.size() > 0)
      return userInfo.get(0);

    return null;
  }

  public void deleteUserInfo(UserInfo userInfo) {
    userInfoDAO.delete(userInfo);
  }

  public UserInfo createUserInfo(UserInfo userInfo) {
    userInfoDAO.create(userInfo);
    return getUserInfo(userInfo.getUserName());
  }
}

UserInfoDAO.java

package data.dao;

import java.util.List;

import data.beans.UserInfo;

public interface UserInfoDAO {
  void create(UserInfo userInfo);
  List<UserInfo> select(String userId);
  void delete(UserInfo userInfo);
}

UserInfoDAOImpl.java – handle the database operations with USERS_INFO table.

package data.dao;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;

import javax.sql.DataSource;

import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;

import data.beans.UserInfo;

public class UserInfoDAOImpl implements UserInfoDAO {

  private DataSource dataSource;

  public void setDataSource(DataSource ds) {
    dataSource = ds;
  }

  public DataSource getDataSource() {
    return dataSource;
  }

  public void create(UserInfo userInfo) {

    JdbcTemplate insert = new JdbcTemplate(dataSource);
    insert.update(
        "INSERT INTO USERS_INFO(USER_ID, USERNAME, STATE, COUNTRY, IS_ACTIVE) VALUES(USER_ID_SEQ.NEXTVAL, ?,?,?,?)",
        new Object[] { userInfo.getUserName(), userInfo.getState(), userInfo.getCountry(), userInfo.getIsActive() });

  }

  public List<UserInfo> select(String userName) {
    JdbcTemplate insert = new JdbcTemplate(dataSource);
    return insert.query("SELECT USER_ID, USERNAME, STATE, COUNTRY, IS_ACTIVE FROM USERS_INFO WHERE USERNAME = ?",
        new Object[] { userName }, new RowMapper() {
          public Object mapRow(ResultSet rs, int arg1) throws SQLException {
            UserInfo userInfo = new UserInfo();
            userInfo.setUserId(rs.getLong(1));
            userInfo.setUserName(rs.getString(2));
            userInfo.setState(rs.getString(3));
            userInfo.setCountry(rs.getString(4));
            userInfo.setIsActive(rs.getString(5));
            return userInfo;
          }
        });
  }

  public void delete(UserInfo userInfo) {
    JdbcTemplate delete = new JdbcTemplate(dataSource);
    delete.update("DELETE FROM USERS_INFO WHERE USER_ID = ?", new Object[] { userInfo.getUserId() });
  }
}

ApplicationContextUtil.java provides the access to spring configuration and provide the ApplicationContext reference.

package data.util;

import java.io.IOException;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class ApplicationContextUtil {
  private static final String CONTEXT_CONFIG = "applicationContext.xml";

  private static ApplicationContext applicationContext = null;

  private ApplicationContextUtil() {
  }

  public static ApplicationContext getApplicationContextInstance() {

    if (applicationContext == null) {
      synchronized (ApplicationContextUtil.class) {
        if (applicationContext == null) {
          try {
            applicationContext = createApplicationContext();
          } catch (IOException io) {
            io.printStackTrace();
          }
        }
      }
    }
    return applicationContext;
  }

  private static ApplicationContext createApplicationContext() throws IOException {
    return new ClassPathXmlApplicationContext(CONTEXT_CONFIG);
  }
}

Configuration for spring and jdbc integration.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
      "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>

      <bean id="dataSource"
            class="org.springframework.jdbc.datasource.DriverManagerDataSource"
            destroy-method="close">
            <property name="driverClassName" value="oracle.jdbc.driver.OracleDriver" />
            <property name="url"
                  value="jdbc:oracle:thin:@sbkj2kdbd02.wsjdev.dowjones.net:1521:COMMERCE" />
            <property name="username" value="provision" />
            <property name="password" value="provision123" />
      </bean>

      <bean id="userInfoDAO" class="data.dao.UserInfoDAOImpl">
            <property name="dataSource" ref="dataSource" />
      </bean>

      <bean id="userInfoBO" class="data.bo.UserInfoBO">
            <property name="userInfoDAO" ref="userInfoDAO" />
      </bean>
</beans>

If you want deploy it in server, please use the following configuration in 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">
      <display-name>user.service2</display-name>

      <!-- Application class name -->
      <context-param>
            <param-name>org.restlet.application</param-name>
            <param-value>service.ApplicationRouter</param-value>
      </context-param>

      <!-- Restlet adapter -->
      <servlet>
            <servlet-name>RestletServlet</servlet-name>
            <servlet-class>org.restlet.ext.servlet.ServerServlet</servlet-class>
      </servlet>

      <!-- Catch all requests -->
      <servlet-mapping>
            <servlet-name>RestletServlet</servlet-name>
            <url-pattern>/*</url-pattern>
      </servlet-mapping>
</web-app>

Database Objects
USERS_INFO Table:

CREATE TABLE USERS_INFO
(USER_ID NUMBER,
USERNAME VARCHAR2(100) unique,
STATE VARCHAR2(100),
COUNTRY VARCHAR2(100),
IS_ACTIVE CHAR(1))

USER_ID_SEQ Sequence:
CREATE SEQUENCE USER_ID_SEQ
INCREMENT BY 1
START WITH 1
NOMAXVALUE;

TestRequests.java to test the UserResource.java

package test;

import java.io.BufferedReader;
import java.io.InputStreamReader;

import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.methods.DeleteMethod;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.methods.StringRequestEntity;

public class TestRequests {
  public static void postMethod() {
    System.out.println("in post method");
    HttpClient client = new HttpClient();
    PostMethod method = new PostMethod("http://localhost:9100/user/user.xml");

    BufferedReader br = null;
    try {
      String xmlString = "<userInfo><userName>testuser</userName><state>NJ</state><country>USA</country><isActive>Y</isActive></userInfo>";
      method.setRequestEntity(new StringRequestEntity(xmlString, "application/xml", null));

      int returnCode = client.executeMethod(method);
      System.out.println("Return Code : " + returnCode);

      if (returnCode == HttpStatus.SC_NOT_IMPLEMENTED) {
        System.err.println("The Post method is not implemented by this URI");
      } else {
        br = new BufferedReader(new InputStreamReader(method.getResponseBodyAsStream()));
        String readLine;
        while (((readLine = br.readLine()) != null)) {
          System.out.println(readLine);
        }
      }
    } catch (Exception e) {
      System.err.println(e);
    } finally {
      method.releaseConnection();
      if (br != null)
        try {
          br.close();
        } catch (Exception fe) {
        }
    }
  }

  public static void getMethod() {
    System.out.println("in get method");
    HttpClient client = new HttpClient();
    GetMethod method = new GetMethod("http://localhost:9100/user/testuser/user.xml");
    BufferedReader br = null;
    try {
      int returnCode = client.executeMethod(method);
      System.out.println("Return Code : " + returnCode);

      if (returnCode == HttpStatus.SC_NOT_IMPLEMENTED) {
        System.err.println("The Post method is not implemented by this URI");
      } else {
        br = new BufferedReader(new InputStreamReader(method.getResponseBodyAsStream()));
        String readLine;
        while (((readLine = br.readLine()) != null)) {
          System.out.println(readLine);
        }
      }
    } catch (Exception e) {
    } finally {
      method.releaseConnection();
      if (br != null)
        try {
          br.close();
        } catch (Exception fe) {
        }
    }
  }

  public static void deleteMethod() {
    System.out.println("in delete method");
    HttpClient client = new HttpClient();
    DeleteMethod method = new DeleteMethod("http://localhost:9100/user/testuser/user.xml");
    BufferedReader br = null;
    try {
      int returnCode = client.executeMethod(method);
      System.out.println("Return Code : " + returnCode);

      if (returnCode == HttpStatus.SC_NOT_IMPLEMENTED) {
        System.err.println("The Post method is not implemented by this URI");
      } else {
        br = new BufferedReader(new InputStreamReader(method.getResponseBodyAsStream()));
        String readLine;
        while (((readLine = br.readLine()) != null)) {
          System.out.println(readLine);
        }
      }
    } catch (Exception e) {
    } finally {
      method.releaseConnection();
      if (br != null)
        try {
          br.close();
        } catch (Exception fe) {
        }
    }
  }

  public static void main(String[] args) {
    try {
      postMethod();
      //deleteMethod();
      //getMethod();
    } catch (Exception e) {
    }
  }
}

Required Jar files:

antlr.jar
cglib.jar
com.noelios.restlet-1.1.9.jar
com.noelios.restlet.ext.httpclient-1.1.9.jar
com.noelios.restlet.ext.servlet-1.1.9.jar
com.noelios.restlet.ext.simple-1.1.9.jar
commons-codec-1.4.jar
commons-collections-3.2.1.jar
commons-httpclient-3.1.jar
commons-logging-1.1.1.jar
dom4j.jar
jboss-j2ee.jar
log4j-1.2.15.jar
ojdbc14.jar
org.restlet-1.1.9.jar
org.restlet.ext.json-1.1.9.jar
org.simpleframework-3.1.3.jar
servlet-api.jar
spring.jar
xstream-1.3.1.jar

No comments:

Post a Comment