10 Replies Latest reply on Jun 8, 2007 4:25 PM by Sanjeev Vijapurapu

    REST applications with Seam and the Restlet framework (no JS

    d1g Newbie

      I have created a proof-of-concept application based on Seam and the Restlet framework. Fortunately Seam has been designed with this kind of integration in mind and the job was not difficult. I thought other users might like to see how easy this is.

      By using Seam & Restlets I can reuse my existing Seam components within a dedicated REST environment and still have access to EJB 3.0 via Seam and the JBoss Embeddable EJB 3.0 container.

      I'm considering using the Restlet framework to build full featured web applications backed by Seam - but of course, those apps need to be RESTful. JSF offers a different approach which is not appropriate to the projects I'm currently working on. I will still use Seam with JSF where relevant (as well as Seam's REST functionality).

      This post is a follow up to my earlier post on embedding Seam (http://www.jboss.com/index.html?module=bb&op=viewtopic&t=92597). Thanks are due to Gavin for pointing me in the right direction.

      If you need to know more about Restlets look at http://www.restlet.org.

      My application allows you to see the name of users by accessing /users/[id] and is similar to example Restlet code. The application uses the Seam MockServletContext to simulate the presence of a Servlet container. Here is how I start Seam and the Restlet framework:

      public class SeamContainer extends Container {
      
       public SeamContainer() throws Exception {
      
       // we need to wrap all this in a call via Seam
       Lifecycle.beginCall();
      
       // retrieve Context from our Container
       Context context = getContext();
      
       // add an HTTP server connector to the Restlet container
       getServers().add(Protocol.HTTP, 8182);
      
       // attach a filter to manage calls via Seam
       SeamCallFilter seamCallFilter = new SeamCallFilter();
       setRoot(seamCallFilter);
      
       // attach a log Filter to the container
       LogFilter logFilter = new LogFilter(context, "org.restlet.seam");
       seamCallFilter.setNext(logFilter);
      
       // create a host router matching calls to the server
       HostRouter host = new HostRouter(context, 8182);
       seamCallFilter.setNext(host);
      
       // create the user router
       Router userRouter = new Router(context);
       host.getScorers().add("/users/[0-9]+", userRouter);
       userRouter.getScorers().add("$", getSeamRestlet("userRestlet"));
      
       // done with Seam for now
       Lifecycle.endCall();
       }
      
       public static void main(String[] args) {
       ServletContext servletContext = new MockServletContext();
       try {
       // start JBoss Seam
       new Initialization(servletContext).init();
       Lifecycle.setServletContext(servletContext);
       // start the container
       new SeamContainer().start();
       // TODO: stop JBoss Seam - when we make this a service
       // TODO: Lifecycle.endApplication(servletContext);
       } catch (Exception e) {
       System.err.println("Can't launch the Web server.\nAn unexpected exception occured:");
       e.printStackTrace(System.err);
       }
       }
      
       public Restlet getSeamRestlet(String name) {
       return (Restlet) Component.getInstance(name, true);
       }
      }
      


      Note that my Restlet is also a Seam component, though I'm not using this to do anything right now. Restlet objects are shared by many calls so we need to be careful when injecting Seam components. Here is the Restlet source:

      @Name("userRestlet")
      @Startup
      @Scope(ScopeType.APPLICATION)
      public class UserRestlet extends Restlet {
      
       private final static Logger log = Logger.getLogger(UserRestlet.class);
      
       public void handleGet(Call call) {
      
       String output;
      
       // get user by id
       String id = call.getBaseRef().getLastSegment();
       log.debug("handleGet() user id: " + id);
      
       // load user with id and create output
       UserService userService = getUserService();
       User user = userService.loadUser(new Long(id));
       if (user != null) {
       log.debug("handleGet() user found");
       output = "User with name: " + user.getName();
       } else {
       log.debug("handleGet() user NOT found");
       output = "User not found";
       }
      
       // all done
       call.setOutput(new StringRepresentation(output, MediaType.TEXT_PLAIN));
       }
      
       public UserService getUserService() {
       return (UserService) Component.getInstance("userService", true);
       }
      }


      The magic bit is a Restlet Filter which I have called SeamCallFilter. This wraps all calls that come into my Restlet application and handles the start and end of a call to Seam. Here is the source:

      public class SeamCallFilter extends Filter {
      
       private final static Logger log = Logger.getLogger(SeamCallFilter.class);
      
       protected void beforeHandle(Call call) {
       log.debug("beforeHandle()");
       Lifecycle.beginCall();
       Lifecycle.setPhaseId(PhaseId.INVOKE_APPLICATION);
       }
      
       protected void afterHandle(Call call) {
       log.debug("afterHandle()");
       Lifecycle.setPhaseId(null);
       Lifecycle.endCall();
       }
      }
      


      You can guess the content of my User entity but it's worth seeing what UserService does:

      @Stateless
      @Name("userService")
      public class UserServiceBean implements UserService, Serializable {
      
       private final static Logger log = Logger.getLogger(UserServiceBean.class);
      
       @PersistenceContext(unitName = "custdb")
       private EntityManager em;
      
       public UserServiceBean() {
       super();
       log.debug("UserServiceBean()");
       }
      
       public long saveUser(User user) {
       log.debug("saveUser()");
       em.persist(user);
       return user.getId();
       }
      
       public User loadUser(long id) {
       log.debug("loadUser()");
       return em.find(User.class, id);
       }
      }


      There are some obvious limitations when using Seam in this manner. Primarily, you can't use the Session and Conversation context (no Servlet API) and you can't use other Servlet / JSF features (validation, model updates, etc).

      It would be helpful if Seam were enhanced to make using non Servlet/JSF applications easier (I needed to include the Servlet API and MyFaces on the classpath to get my application to work). Ideally Servlet/JSF would be pluggable in Seam, as well as the Seam components and contexts that become irrelevant.

      If you want the source please let me know.

      Dig.