JEE Musings

Sunday, March 27, 2011

Exposing LifeRay Through JBoss EJB3 MDB

We recently did a POC on exposing some Liferay functionality through an MDB in JBoss. Liferay provides a rich web service API available via the portal-web.war. It appears that Liferay did provide native JMS support in the past but has removed this support as of 6.0.

For our purpose we wanted to provide a more fault tolerant solution than web services alone and so looked to expose this functionality via JMS and employ JMS bridging (part of JBoss JMS) to move the messages from disparate applications to the Liferay instance for processing.

Luckily Liferay also provides a rich set of static instance utility classes and to gain access to these we can tear a page from the portal-web.war. Within portal-web.war Liferay exposes a RemotingServlet within the WEB-INF/web.xml and a corresponding WEB-INF/remoting-servlet.xml which then uses Spring provided proxies for the WS interfaces.

In our case we can slim down the remoting-servlet.xml considerably as we are looking to allow our MDB access to Liferay not to extend or change any of the functionality.

To accomplish this we create a war that provides a hook int liferay with a web.xml like this:


<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
    <!-- Provides access to locally deployed LifeRay -->

    <servlet>
        <servlet-name>Spring Servlet</servlet-name>
        <servlet-class>com.liferay.portal.kernel.servlet.PortalClassLoaderServlet</servlet-class>
        <init-param>

            <param-name>servlet-class</param-name>
            <param-value>com.liferay.portal.spring.servlet.RemotingServlet</param-value>
        </init-param>
        <load-on-startup>0</load-on-startup>
    </servlet>


    <session-config>
        <session-timeout>1</session-timeout>
    </session-config>
</web-app>


And a remoting-servlet.xml such as this:


<?xml version="1.0"?>

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:jee="http://www.springframework.org/schema/jee"
       xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation=
               "http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-2.5.xsd
                http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">




</beans>

Once that is complete the next step is to create an MDB. In our case we create this as an EJB jar


@MessageDriven(name = "UserConsumerBean", activationConfig = {
        @ActivationConfigProperty(propertyName = "destinationType", propertyValue = "javax.jms.Queue"),
        @ActivationConfigProperty(propertyName = "destination", propertyValue = "queue/LR_USER_QUEUE")

})

@TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
public class UserConsumer implements MessageListener {
    private static final Log LOG = LogFactory.getLog(UserConsumer.class);

    @Resource
    private MessageDrivenContext context;


    protected void setContext(MessageDrivenContext context) {
        this.context = context;
    }

    public void onMessage(Message message) {
        String name = null;
        TextMessage om = (TextMessage) message;
        try {
            if (LOG.isInfoEnabled()) {
                LOG.info("Message Text : " + om.getText());
            }

  User user = UserLocalServiceUtil.getUserById(1L); //some user that exists
  System.out.println("User Name :"+user.getScreenName());

        } catch (Exception e) {
            LOG.error(e);
            throw new EJBException(e); //rollback
        }
    }


}


Once this is all bundled as an ear and deployed you should be able to send a message to the queue and see your output.

Saturday, January 15, 2011

AXIS wsdl2java POM


<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>liferayIntegration</groupId>
<artifactId>liferayIntegration</artifactId>
<version>1.0</version>
 <build>
 <plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>axistools-maven-plugin</artifactId>
<version>1.4</version>
<configuration>
<sourceDirectory>${basedir}/src/main/wsdl</sourceDirectory>
<wsdlFiles>
<wsdlFile>Portal_CompanyService.wsdl</wsdlFile>
</wsdlFiles>
<packageSpace>com.example.generated</packageSpace>
<testCases>false</testCases>
<serverSide>true</serverSide>
<subPackageByFileName>true</subPackageByFileName>
</configuration>
<executions>
<execution>
<goals>
<goal>wsdl2java</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.apache.axis</groupId>
<artifactId>axis</artifactId>
<version>1.4</version>
</dependency>
<dependency>
<groupId>javax.xml</groupId>
<artifactId>jaxrpc-api</artifactId>
<version>1.1</version>
</dependency>
<dependency>
<groupId>javax.mail</groupId>
<artifactId>mail</artifactId>
<version>1.4.1</version>
</dependency>
<dependency>
<groupId>javax.activation</groupId>
<artifactId>activation</artifactId>
<version>1.1</version>
</dependency>
</dependencies>
</project>

Friday, April 30, 2010

Spring Security Authorizer with JBoss JAAS

We've recently been working to integrate OpenAM (formerly OpenSSO) as our SSO solution via JAAS. Spring Security comes with OOB functionality for JAAS Authentication, but not for JAAS Authorization.  So in order to accept the roles and principals returned by SSO we put together the following.



public class JaasSpringSecurity implements AuthorityGranter {
    
    public Set grant(Principal principal) {
        Set returnSet = new HashSet();
        
        if (principal instanceof SimpleGroup) {        
            SimpleGroup sg = (SimpleGroup) principal;
            returnSet.addAll(getNestedRoles(sg));
        }
        return returnSet;
    }

    private Set getNestedRoles(SimpleGroup sg) {
        Enumeration members = sg.members();
        Set tmpSet = new HashSet();
        if (members.hasMoreElements()) {

            while (members.hasMoreElements()) {
                Object o = members.nextElement();
                if (o instanceof SimpleGroup) {
                    tmpSet.addAll(getNestedRoles((SimpleGroup) o));
                } else if(o instanceof SimplePrincipal){
                    tmpSet.add( ((SimplePrincipal) o).getName());
                } 
            }
        } else {

        }
        return tmpSet;
    }
}


It should be noted that in this case we are running under JBoss and so we are passed objects of Type :

org.jboss.security.SimpleGroup and org.jboss.security.SimplePrincipal.  This may differ based on provider.

Cannot invoke method containsKey() on null object

I spent about an hour on this one today.  I have a Grails Integration test with mock objects, but every time I tried to instantiate the Mock (via mockFor(MyService) ) I was receiving the error :

Cannot invoke method containsKey() on null object

After toying with it for too long I googled it up and found that I had forgotten the super.setUp() in my setUp() method. After correcting that :

  protected void setUp() {
    super.setUp()
...

All was well. Ugh..

Friday, October 2, 2009

Exposing Spring's DefaultMessageListenerContainer via JMX in JBoss

Recently I had received the request to allow the admins to change some of the properties on our queue listeners on the fly in our JBoss environment. The intent here being that they wanted to be able to change the number of active consumers on a particular server instance without having to cycle the instance.

Well... a little while and some keystrokes later we had something like this :




<!-- Listener for requests -->
    <bean id="requestListener" class="com.test.jms.MessageListener"/>
    <bean id="requestListenerContainer"
          class="org.springframework.jms.listener.DefaultMessageListenerContainer">
        <property name="connectionFactory" ref="qcf"/>
        <property name="destinationResolver" ref="destinationResolver"/>
        <property name="destinationName" value="queue/REQUEST_QUEUE"/>
        <property name="messageListener" ref="attributeRequestListener"/>
        <property name="concurrentConsumers" value="5"/>
        <property name="maxConcurrentConsumers" value="20"/>
...
    </bean>

    <bean id="mbeanServer" class="org.springframework.jmx.support.MBeanServerFactoryBean">
        <property name="locateExistingServerIfPossible" value="true"/>
    </bean>

    <!-- Expose through JMX -->
    <bean id="jmxExporter" class="org.springframework.jmx.export.MBeanExporter">
        <property name="server" ref="mbeanServer"/>
        <property name="beans">
            <map>
                <entry key="cma:name=requestListenerContainer" value-ref="requestListenerContainer"/>
            </map>
        </property>

   </bean>


After restarting and looking at the jmx-console, my values were all present and so I, in a moment of sloppiness, decided the job was complete and off I went onto my next task. A couple of days later I heard that this functionality was not working and was in fact throwing the following exception when any updates were attempted.


10:12:21,787 ERROR [ContainerBase] Servlet.service() for servlet HtmlAdaptor threw exception
java.lang.ClassNotFoundException: org.springframework.jms.support.destination.DestinationResolver
 at org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1358)
 at org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1204)
 at org.jboss.util.propertyeditor.PropertyEditors.convertValue(PropertyEditors.java:250)
 at org.jboss.jmx.adaptor.control.Server.setAttributes(Server.java:190)
 at org.jboss.jmx.adaptor.html.HtmlAdaptorServlet.updateAttributes(HtmlAdaptorServlet.java:236)
 at org.jboss.jmx.adaptor.html.HtmlAdaptorServlet.processRequest(HtmlAdaptorServlet.java:98)
 at org.jboss.jmx.adaptor.html.HtmlAdaptorServlet.doPost(HtmlAdaptorServlet.java:82)
 at javax.servlet.http.HttpServlet.service(HttpServlet.java:710)
 at javax.servlet.http.HttpServlet.service(HttpServlet.java:803)
 at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:290)
 at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
 at org.jboss.web.tomcat.filters.ReplyHeaderFilter.doFilter(ReplyHeaderFilter.java:96)
 at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
 at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
 at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:230)
 at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:175)
 at org.jboss.web.tomcat.security.SecurityAssociationValve.invoke(SecurityAssociationValve.java:179)
 at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:525)
 at org.jboss.web.tomcat.security.JaccContextValve.invoke(JaccContextValve.java:84)
 at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:128)
 at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:104)
 at org.jboss.web.tomcat.service.jca.CachedConnectionValve.invoke(CachedConnectionValve.java:157)
 at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109)
 at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:241)
 at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:844)
 at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:580)
 at org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:447)
 at java.lang.Thread.run(Thread.java:595)


A quick look and we can see that JBoss isn't able to locate the Spring's DestinationResolver. A gander at the Spring/JMX documentation (1) showed a couple of options were available. The MBeanExporter(2) exposes the assembler through which you can configure the properties and operations to be exposed.

The first one I tried as a PC was to directly name the properties that I wanted to expose so as a quick test I added the following :


                                                                                    
   <bean id="jmxExporter" class="org.springframework.jmx.export.MBeanExporter">
        <property name="server" ref="mbeanServer"/>
        <property name="beans">
            <map>
                <entry key="cma:name=requestListenerContainer" value-ref="requestListenerContainer"/>
            </map>
        </property>
        <property name="assembler">
            <bean class="org.springframework.jmx.export.assembler.MethodNameBasedMBeanInfoAssembler">
                <property name="managedMethods">
                    <value>setMaxConcurrentConsumers,getMaxConcurrentConsumers </value>
                </property>
            </bean>
        </property> 
  </bean>
                                           

Restarted and viola, when I go view the JMX info for this bean the only items available to me were maxConcurrentConsumers (Read/Write) and destinationResolver (Read only) and the operations start and stop. I ran some manual tests against the system to ensure that I was able to update the maxConcurrentConsumers without error and that the changes stuck.

That was all fine and well, but I'm not sure that I want that info stored in XML in the context file, so rather than stopping here I created a template interface that stores the items that I want exposed

package com.test.jmx;

/**
 * Provides a template to expose Springs DefaultMessageListenerContainer
 * The following items are not supported via this interface and should be
 * enabled in the inheriting interfaces to keep the dependancies of this the
 * common package to a miniumum :
 *
 * 
 */

public interface IJmxExposedDefaultMessageListenerContainer {

    //
    // managed properties
    //


    int getMaxConcurrentConsumers();
    void setMaxConcurrentConsumers(int maxConcurrentConsumers);
   
    //
    // Managed Operations
    //

    void start();
    void stop();
}

and then wired this up to the assembler thusly :

          <property name="assembler">
            <bean class="org.springframework.jmx.export.assembler.InterfaceBasedMBeanInfoAssembler">
                <property name="managedInterfaces">
                    <value>com.ideas.cma.common.jmx.IJmxExposedDefaultMessageListenerContainer</value>
                </property>
            </bean>
        </property>  

And off we went...

Resources :
(3) http://static.springsource.org/spring/docs/2.5.x/api/org/springframework/jms/listener/DefaultMessageListenerContainer.html

Wednesday, December 31, 2008

JDBCTemplate in Grails

Adding Spring's JDBCTemplate support to your grails appliation is really quite painless. This covers adding JDBCTemplate to a test class but this can be extended to any area of the application Edit the file conf\spring\resources.groovy so that it contains the following:
import org.springframework.jdbc.core.JdbcTemplate
import org.apache.commons.dbcp.BasicDataSource
// Place your Spring DSL code here

beans = {
// uses the grails dataSource from DataSource.groovy
jdbcTemplate(JdbcTemplate) {
   dataSource = ref('dataSource')
}

// use a different datasource
otherDataSource(BasicDataSource) {
   driverClassName = "com.mysql.jdbc.Driver"
   url = "jdbc:mysql://localhost:3306/otherDatabase"
   username = "uname"
   password = "pass"
}

otherJdbcTemplate(JdbcTemplate) {
   dataSource = otherDataSource
}
}
Then allow dependency injection to do its work by putting the following at the top of the test class:
class someTests extends GroovyTestCase {
   def jdbcTemplate
   def otherJdbcTemplate
...
}
and finally create a test something like this to exercise the template:
void testJdbcTemplate() {

def myResult = jdbcTemplate.queryForList("select * from table1")
def myOtherResult = otherJdbcTemplate.queryForList("select * from table2")
println "myResult size : ${myResult.size()}"
println "myOtherResult size : ${myOtherResult.size()}"
}