Overview

Introduction

This module provides tight integration between the spring-webmvc-portlet framework and liferay's APIs. The integration aims at simplifying portlet development with liferay both at the controller and the service level.

Usage

Until this artifact is hosted somewhere centrally, you'll need to check it out, along with the spring-liferay-parent (which this module depends upon) and the liferay-parent (which that spring-liferay-parent depends upon) and the common-parent (which liferay-parent depends upon), and install it locally via the standard "mvn install" command. Something like:

$ mvn install
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building spring-liferay-integration 0.1-SNAPSHOT
[INFO] ------------------------------------------------------------------------
...
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 26.902s
[INFO] Finished at: Mon May 21 21:51:04 EST 2012
[INFO] Final Memory: 20M/81M
[INFO] ------------------------------------------------------------------------
Martins-MacBook-Pro:spring-liferay-integration martinlau$

Once that is done, you can then use this module's artifacts as the parent coordinates for your project:

<dependencies>
    <dependency>
        <groupId>ph.hum</groupId>
        <artifactId>spring-liferay-integration</artifactId>
        <version>0.1-SNAPSHOT</version>
    </dependency>
 </dependencies>

Service integration

The simplest mechanism for integration is to leverage the XML liferay namespace from within the spring context configuration(s) to expose liferay's services and objects at runtime. This means that there is no more need to call the numerous BlahLocalServiceUtil.getService() methods either directly in code, or as a bean definition.

For example (taken from sample-portlet), inside the webapp's application context (/WEB-INF/applicationContext.xml if you're using spring's standard names):

<?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:liferay="http://hum.ph/schema/liferay"
       xsi:schemaLocation="http://hum.ph/schema/liferay
                           http://hum.ph/schema/liferay/spring-liferay.xsd
                           http://www.springframework.org/schema/beans
                           http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!-- No more doing this -->
    <!-- <bean class="com.liferay.portal.service.UserLocalServiceUtil" factory-method="getService" lazy-init="true" /> -->
    <!-- <bean class="com.liferay.portal.service.UserGroupLocalServiceUtil" factory-method="getService" lazy-init="true" /> -->
    <!-- <bean class="com.liferay.portal.service.GroupLocalServiceUtil" factory-method="getService" lazy-init="true" /> -->

    <!-- Do this instead -->
    <liferay:services />

</beans>

This will expose all *LocalService classes on the classpath as beans which can then be autowired. If all of these services are overkill for what you want (there are 150 odd of them, but don't worry: they're loaded lazily - so the overhead of the simple XML configuration is much reduced), you can also filter what's exposed:

<liferay:services>
    <liferay:include-filter type="regex" expression=".*User.*Service" />
    <liferay:exclude-filter type="assignable" expression="com.liferay.portal.service.UserLocalService" />
</liferay:services>

The include and exclude filters have the same semantics as spring's <context:component-scan ...> filters.

These services can then be used via standard spring autowiring:

@Service
public class MyServiceImpl implements MyService {

    @Autowired
    private UserLocalService userService;

    @Override
    public void deleteUser(String username) {
        User user = userService.getUserByScreenName(username);
        userService.deleteUser(user);
    }

}

Spring Controller Integration

In addition, this module provides two mechanisms for exposing liferay's request scoped attributes to MVC controllers as automatically provided parameters. This means that you no longer need to call request.getAttribute(WebKeys.USER) and the casting that comes with that for controller methods.

For example (taken from sample-portlet), inside the portlet's application context (/WEB-INF/sample-portlet.xml if you're using spring's standard names):

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:liferay="http://hum.ph/schema/liferay"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://hum.ph/schema/liferay
                           http://hum.ph/schema/liferay/spring-liferay.xsd
                           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:component-scan base-package="ph.hum.sample.portlet" />

    <!-- This configures both mechanisms for fetching attributes,
         as well as validation if present on the classpath -->
    <liferay:portlet />

</beans>

Code can then take the form (note the automatically injected User parameter):

@Controller
public class MyController {

    @RenderMapping
    @RequestMapping("VIEW")
    // No more passing in the request objects just to cast an object you're getting out of request scope
    // public String renderView(RenderRequest request) {
    //     User user = (User) request.getAttribute(WebKeys.USER);
    // Instead, just let spring pass it straight through
    public String renderView(User user) {
        return user.isMale()
               ? "sample/male.jspx"
               : "sample/female.jspx";
    }

}

If you depend on a parameter which is not known (see the ph.hum.spring.web.bind.support.KnownRequestAttributeArgumentResolver class for a list of what is known) you can also explicitly declare the attribute name and have it passed in:

@Controller
public class MyOtherController {

    @RenderMapping
    @RequestMapping("VIEW")
    public String renderView(@RequestAttribute("COMPANY") Company company) {
        return String.format("sample/%s.jspx", company.getShortName());
    }

}

Either of these automatic argument resolving options (known classes or named via @RequestAttribute) can be disabled by setting the relevant attribute on the <liferay:portlet> element.

Finally, this <liferay:portlet> also enables the automatic detection of the javax.validation package, and if available also enables validation within spring controllers:

@Controller
public class MyValidatingController {

    public static class MyModel {

        @Length(min=8,
                max=64,
                message="Usernames must be beteen 8 and 64 characters")
        private String username;

        public String getUsername() {
            return username;
        }

        public void setUsername(String username) {
            this.username = username;
        }

    }

    @RenderMapping
    @RequestMapping("VIEW")
    public String renderView(@Valid @ModelAttribute("model") MyModel model, Errors errors) {
        if (errors.hasErrors) {
            return "sample/bad.jsp";
        }
        // Do something with model
        return "sample/good.jsp";
    }

}

If you'd rather not use the built in validation configuration, you can specify your own validator via the XML configuration and that will be used instead:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:liferay="http://hum.ph/schema/liferay"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://hum.ph/schema/liferay
                           http://hum.ph/schema/liferay/spring-liferay.xsd
                           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:component-scan base-package="ph.hum.sample.portlet" />

    <liferay:portlet validator="customValidator" />

    <bean class="ph.hum.validator.CustomValidator" id="customValidator" />

</beans>

Transaction Management

To enable transaction management using liferay's own transaction capabilities, you can define the following in your spring XML:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:liferay="http://hum.ph/schema/liferay"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://hum.ph/schema/liferay
                           http://hum.ph/schema/liferay/spring-liferay.xsd
                           http://www.springframework.org/schema/beans
                           http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/tx
                           http://www.springframework.org/schema/tx/spring-tx.xsd">

    <!-- This exposes liferay's platform transaction manager -->
    <liferay:transaction-manager />

    <tx:annotation-driven transaction-manager="liferayTransactionManager" />

</beans>

By default, the platform transaction manager is registered under the name liferayTransactionManager. An alias for this transaction manager can be defined by supplying the alias attribute to the XML if (as is the case with <tx:annotation-driven>) you have other code which expects a specific bean name for the transaction manager.

<!-- This exposes liferay's platform transaction manager under
     both of the names "liferayTransactionManager" and "transactionManager" -->
<liferay:transaction-manager alias="transactionManager" />

TODO

  • Create a class loader proxy equivalent of TransactionalPortalCacheHelper and incorporate it in the transaction-manager namespace handler.

  • Another namespace definition which allows more thourough configuration of what is a "known" class for parameter binding.

  • Annotation based hook definitions (ie @PostLogin public void doSomething(Request request) { ... }).