Spring MVC portlet with liferay


The Spring framework contains many different modules for application development, including Spring MVC. The Spring reference guide shows us the Spring MVC architecture:

Here, the “Front Controller” is Spring’s DispatcherServlet, and the “Controller” is a plain old Java object (POJO) with methods that can act as handlers mapped to URLs. The upshot is instead of having code tied to the HTTP protocol like traditional servlets, you can have a handler method that can be tested as any other Java class.
For example,
@Controller
public class HelloWorldController {
    @RequestMapping("/helloWorld")
    public String helloWorld(Model model) {
        model.addAttribute("message", "Hello World!");
        return "helloWorld";
    }
}
The annotation @Controller registers the HelloWorldController class as a controller, and the @RequestMapping(“/helloworld”) annotation tells the framework that the URL “/helloworld” will be handled by the helloWorld method, which simply adds the message “Hello World” to the model attributes using the name “message.”
The “View Template” is typically a JSP (Java Server Page). Spring MVC provides view resolvers that allow developers to provide a simple string (e.g., “helloWorld”) that will resolve to a JSP called helloWorld.jsp. You can set up this type of view resolver in the Spring context file, which, by default, is located at /WEB-INF/helloWorld-servlet.xml.
It will look something like this:
<?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.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">
       <context:annotation-config />
       <context:component-scan base-package= "com.helloworld.controller"/>
    <bean />
    <bean id="jspViewResolver"
        class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="viewClass"
            value="org.springframework.web.servlet.view.JstlView" />
        <property name="prefix" value="/WEB-INF/jsp/" />
        <property name="suffix" value=".jsp" />
        <property name="order" value="1" />
    </bean>
</beans>
This resolver will take and string returned by the handler method, prefix it with “/WEB-INF/jsp” and add “.jsp” at the end. So, when our controller method returns “helloWorld,” Spring looks for the JSP at /WEB-INF/jsp/helloWorld.jsp and renders content in that JSP.
Spring also allows us to inject other components (such as services) into the Spring-managed beans, providing a powerful framework for wiring up all kinds of useful things.

Hello World Portlet
Let’s say we want to create a new portlet called Hello World. Portlets are structured as web apps with a WEB-INF directory, and in this directory, we will have the web.xml file (just like in a servlet environment) as well as a portlet.xml file. Liferay also has some additional files (liferay-display.xml, liferay-plugin-package.properties, and liferay-portlet.xml).

Add Spring Dispatcher

First, we need to register Spring’s DispatcherPortlet in our portlet.xml. Like their ServletDispatcher, Spring provides a dispatcher for portlets as well. When creating a new Liferay portlet project, the Liferay IDE will use the portlet class com.liferay.util.bridges.mvc.MVCPortlet. We want to change this mapping to use Spring’s dispatcher portlet instead. So, inside portlet.xml:
<portlet>
    <portlet-name>hello-world</portlet-name>
    <display-name>Hello World</display-name>
    <portlet-class>org.springframework.web.portlet.DispatcherPortlet</portlet-class>
    <init-param>
        <name>contextConfigLocation</name>
        <value>/WEB-INF/spring/hello-world-portlet.xml</value>
    </init-param>
</portlet>
We provide the location for the Spring config file (/WEB-INF/spring/hello-world-portlet.xml).
Note: by default, the framework looks for a config file called /WEB-INF/{portlet name with hyphens removed}-portlet.xml, but we can specify another location and/or file name using the init-param contextConfigLocation as shown above. If we hadn’t specified the location, the default location for our portlet named hello-world would be /WEB-INF/helloworld-portlet.xml.
Add Spring Bridge between Portlet Request/Response and Servlet Request/Response
Next, we need to register Spring’s ViewRendererServlet in our new portlet’s web.xml:

<servlet>
    <servlet-name>view-servlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.ViewRendererServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
    <servlet-name>view-servlet</servlet-name>
    <url-pattern>/WEB-INF/servlet/view</url-pattern>
</servlet-mapping>
Now, any portlet request that comes to the URL /WEB-INF/servlet/view is handled by the ViewRendererServlet. This servlet is responsible for converting portlet requests and responses to their servlet counterparts in order to reuse all the view technologies available to Spring MVC.

Create Controller Class

Now we can create our controller in much the same way we have done in the servlet world. There are a couple differences, however. First, the controller needs to be mapped to @RequestMapping(“VIEW”). The value “VIEW” is defined in the ViewRendererServlet, so this is going to be the same for any controller you need to create:
@Controller
@RequestMapping("VIEW") //always map to “VIEW”
public class MyHelloWorldController { //class name can be anything you want.
    //put handler methods in here
}
If you are following along in an IDE like Eclipse, you may see some problems with the annotations. Our portlet doesn’t have the Spring classes it needs in order to know what the annotations @Controller and @RequestMapping are.
Liferay includes the Spring framework, but each portlet has its own classpath dependencies, so we need to add the Spring code to our portlet. Dependencies like this are managed in the liferay-plugin-package.properties file. Add all the Spring modules you need:
name=Hello World
module-group-id=liferay
module-incremental-version=1
tags=
short-description=
change-log=
page-url=http://www.liferay.com
author=Liferay, Inc.
licenses=LGPL
portal-dependency-jars=\
    spring-asm.jar,\
    spring-beans.jar,\
    spring-context-support.jar,\
    spring-context.jar,\
    spring-core.jar,\
    spring-expression.jar,\
    spring-web-portlet.jar,\
    spring-web-servlet.jar,\
    spring-web.jar,\
    jstl-api.jar,\
    jstl-impl.jar
liferay-versions=6.1.1
Now, all the Spring classes are available in our portlet.

Handler Mapping

The handler mappings for Spring MVC portlets are similar to their servlet cousins, but there are some important differences. First, the portlet spec has more than one kind of request/response pairs. Accordingly, there are three @XxxMapping annotations: @ActionMapping, @RenderMapping, and @ResourceMapping.
Adding the appropriate annotation to our handler methods lets us have access to the correct portlet request/response pairs, if needed. As with Spring MVC in the servlet world, the request and response objects can be passed in as method parameters, but this is optional. Each annotation corresponds to a portlet request type:

  • @ActionMapping javax.portlet.ActionRequest and ActionResponse
  • @RenderMapping javax.portlet.RenderRequest and RenderResponse
  • @ResourceMapping javax.portlet.ResourceRequest and ResourceResponse
These objects can be added to the handler method signature, but they are not required.
In our Hello World example, we could use the same method as the servlet version, but we would put the @RenderMapping annotation on the method:

@Controller
@RequestMapping(“VIEW”)
public class HelloWorldController {
    @RenderMapping()
    public String helloWorld(Model model) {
        model.addAttribute("message", "Hello World!");
        return "helloWorld";
    }
}

AJAX and JSON Views

The Resource action allows the portlet to serve up resources without the usual re-rendering of portlets that the portal container requires for other actions. We can use this to support Ajax requests for JSON data.
First, Spring MVC uses Jackson to marshal and unmarshal JSON objects. In the servlet world, annotations are available to handle all the JSON stuff. In the portlet world, however, we need to be a little more specific in our code. Let’s say we want a resource called “Thing” which has two properties: thing1 and thing2. Our Java object looks like this:

public class Thing {
    private String thing1;
    private String thing2;
    //Getters / Setters, etc.
}
In our controller, let’s add a method for getting a “Thing” resource whenever a resource called “thingResource” is requested: 
@ResourceMapping("thingResource")
public View getThing(){
    Thing thing = new Thing();
    thing.setThing1("I am thing 1");
    thing.setThing2("thing 2");
    MappingJacksonJsonView view = new MappingJacksonJsonView();
    view.addStaticAttribute("thingObject", thing);
    return view;
}

There are a couple things to note here. Spring provides an abstraction called View that can be returned directly, as opposed to using a view resolver. In this case, we want to use MappingJacksonJsonView. This view will transform our Java object into JSON. Of course, Liferay provides its own JSON creation utilities, but if you come from the Spring MVC world, Jackson is likely already familiar, and it works very well with the MVC framework.
We just create the view and add our “thing” object as a static attribute called “thingObject.”
Now, we can call our named resource (“thingResource”) using via Ajax by using some JavaScript like this:

AUI().use('liferay-portlet-url', 'aui-base', 'aui-io', function( A ) {
    var portletURL = new Liferay.PortletURL( 'RESOURCE_PHASE' );
    portletURL.setPortletId( "helloworld_WAR_helloworldportlet" );
    portletURL.setResourceId("thingResource");
    portletURL.setCopyCurrentRenderParameters(true);
    A.io.request( portletURL.toString(), {
        dataType: 'json',
        on: {
            success: function(event, id, obj) {
                var instance = this,
                    data = instance.get('responseData'),
                    thing = data['thingObject'];
                log.console('thing1 = ' + thing.thing1 + ' and thing2 = ' + thing.thing2);
            }
        }
    });
});
Liferay allows you to reference your portlet URLs by using a naming convention like {portlet name without hyphens}_WAR_{portlet name without hyphens}portlet. In our case, this means helloworld_WAR_helloworldportlet. We set the resource ID as “thingResource,” which maps to the @ResourceMapping value in our controller. Once the Ajax call returns with the object, you can retrieve the named “thingObject” as described above.

Adding Jackson to Liferay

Unfortunately, Liferay does NOT include the Jackson mapping jars, so we need to add them. You need both the jackson-core-asl and Jackson-mapper-asl jars. These can be found in the Maven repository (http://mvnrepository.com/artifact/org.codehaus.jackson/jackson-core-asl and http://mvnrepository.com/artifact/org.codehaus.jackson/jackson-mapper-asl). Download the version you want and add the jars to the ROOT webapps library by placing them in tomcat/webapps/ROOT/WEB-INF/lib.
Now that the jars are in the ROOT web app, they can be referenced in our Hello World project’s liferay-plugin-package.properties:

portal-dependency-jars=\
    jackson-core-asl-1.4.2.jar,\
    jackson-mapper-asl-1.4.2.jar,\
    spring-asm.jar,\
    spring-beans.jar,\
    spring-context-support.jar,\
    spring-context.jar,\
    spring-core.jar,\
    spring-expression.jar,\
    spring-web-portlet.jar,\
    spring-web-servlet.jar,\
    spring-web.jar,\
    jstl-api.jar,\
     jstl-impl.jar
liferay-versions=6.1.1
And that’s it. Spring MVC and all the goodies the Spring framework offers are available to our new portlet.

Curtsy: Steve Clement



No comments:

Post a Comment