Saturday, February 12, 2011

Integrating Atmosphere (WebSockets) and Appfuse (struts2, spring, hibernate etc)

For my latest project, i needed to play with Web Sockets - A way to push notifications/events to the browser without a pull. So while researching for toolkits and various frameworks out there i came across Atmosphere ((http://bit.ly/9CzDtq). This is a really cool framework that lets you code without worrying about browser support etc. Basically under the hoods it uses WebSockets if the browser supports it and Comet if it doesnt.

Atmosphere on its own was very easy to setup and run. The challenge was to get Atmosphere to play nicely with my Appfuse app that basically had Spring, Struts 2, Hibernate, CXF and what not.

Versions:
Spring - 3.0.5.RELEASE
Struts 2 - 2.18
Hibernate - 3.3.2.GA
CXF - 2.2.4


I had to go through many jar conflicts before i was even able to get the web app up and running. So my approach was a process of elimination - one error at a time.

Here's what i did:

1. Pom file edits

Firstly, i needed to load the Atmosphere related jars.

      <dependency>
            <groupId>org.atmosphere</groupId>
               <artifactId>atmosphere-runtime</artifactId>
               <version>0.6.4</version>
        </dependency>
        <dependency>
            <groupId>org.atmosphere</groupId>
               <artifactId>atmosphere-annotations</artifactId>
               <version>0.6.4</version>
        </dependency>
        <dependency>
            <groupId>org.atmosphere</groupId>
               <artifactId>atmosphere-jersey</artifactId>
               <version>0.6.4</version>
        </dependency>


Then, I needed Atmosphere to work nicely with Spring. For this I needed to load jersey-spring into my application. Note that i have excluded a bunch of spring libraries. If i didnt do that, it would load its dependent spring libraries and then i would get a whole load of jar conflicts.

<dependency>
        <groupId>com.sun.jersey.contribs</groupId>
        <artifactId>jersey-spring</artifactId>
        <version>1.5</version>
        <exclusions>
                <exclusion>
                    <artifactId>spring-beans</artifactId>
                    <groupId>org.springframework</groupId>
                </exclusion>
                <exclusion>
                    <artifactId>spring-context</artifactId>
                    <groupId>org.springframework</groupId>
                </exclusion>
                <exclusion>
                    <artifactId>spring-core</artifactId>
                    <groupId>org.springframework</groupId>
                </exclusion>
                <exclusion>
                    <artifactId>spring-web</artifactId>
                    <groupId>org.springframework</groupId>
                </exclusion>
                <exclusion>
                    <artifactId>spring-aop</artifactId>
                    <groupId>org.springframework</groupId>
                </exclusion>
                <exclusion>
                    <artifactId>spring-test</artifactId>
                    <groupId>org.springframework</groupId>
                </exclusion>
                <exclusion>
                    <artifactId>spring-jdbc</artifactId>
                    <groupId>org.springframework</groupId>
                </exclusion>
                <exclusion>
                    <artifactId>spring-orm</artifactId>
                    <groupId>org.springframework</groupId>
                </exclusion>
                <exclusion>
                    <artifactId>spring-beans</artifactId>
                    <groupId>org.springframework</groupId>
                </exclusion>
                <exclusion>
                    <artifactId>spring-aspects</artifactId>
                    <groupId>org.springframework</groupId>
                </exclusion>
                <exclusion>
                    <artifactId>spring-context-support</artifactId>
                    <groupId>org.springframework</groupId>
                </exclusion>
                <exclusion>
                    <artifactId>spring-webmvc</artifactId>
                    <groupId>org.springframework</groupId>
                </exclusion>
                <exclusion>
                    <artifactId>spring-security-core</artifactId>
                    <groupId>org.springframework</groupId>
                </exclusion>
                <exclusion>
                    <artifactId>spring-security-config</artifactId>
                    <groupId>org.springframework</groupId>
                </exclusion>
                 <exclusion>
                    <artifactId>spring-support</artifactId>
                    <groupId>org.springframework</groupId>
                </exclusion>
            </exclusions>
    </dependency>


Now to the errors. Once i started up my Jetty container the first error i got was,
java.lang.NoSuchMethodError: org.objectweb.asm.ClassReader.accept(Lorg/objectweb/asm/ClassVisitor;I)V
    at com.sun.jersey.spi.scanning.AnnotationScannerListener.onProcess(AnnotationScannerListener.java:133)

This was because hibernate was loading its dependent version of asm. To overcome this i had to explicitly load the asm version i needed - which was 3.1.

        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-core</artifactId>
            <version>${hibernate.version}</version>
              <exclusions>
                        <exclusion>
                                <groupId>asm</groupId>
                                <artifactId>asm</artifactId>
                        </exclusion>
                     
                </exclusions>
        </dependency>
        <dependency>
            <groupId>asm</groupId>
            <artifactId>asm</artifactId>
            <version>3.1</version>
        </dependency>
        <dependency>

The next error i got was,
java.lang.RuntimeException: The scope of the component class org.codehaus.jackson.jaxrs.JacksonJsonProvider must be a singleton
    at com.sun.jersey.core.spi.component.ioc.IoCProviderFactory.wrap(IoCProviderFactory.java:102)

This was because the bean definition of JacksonJsonProvider was not explicity given as singleton. The change was straightforward.
    <bean id="jsonProvider" class="org.codehaus.jackson.jaxrs.JacksonJsonProvider" scope="singleton"/>

The next error was,
Class javax.ws.rs.core.Response$Status does not implement the requested interface javax.ws.rs.core.Response$StatusType

For this i had to ensure the cxf-rt-frontend-jaxrs didnt load the dependent jsr311-api jar and instead explicitly load the version i needed.

         <dependency>
            <groupId>org.apache.cxf</groupId>
            <artifactId>cxf-rt-frontend-jaxrs</artifactId>
            <version>${cxf.version}</version>
            <exclusions>
              ...
                <exclusion>
                    <artifactId>jsr311-api</artifactId>
                    <groupId>javax.ws.rs</groupId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>javax.ws.rs</groupId>
            <artifactId>jsr311-api</artifactId>
            <version>1.1</version>
        </dependency>
        <dependency>

Getting closer now. The next error i got was,
com.sun.jersey.api.container.ContainerException: The ResourceConfig instance does not contain any root resource classes.

This is Jersey related and so what i had to do to overcome this was to provide the following in the web.xml

<servlet>
        <description>AtmosphereServlet</description>
        <servlet-name>AtmosphereServlet</servlet-name>
        <servlet-class>org.atmosphere.cpr.AtmosphereServlet</servlet-class>
        ...
        <init-param>
            <param-name>com.sun.jersey.config.property.packages</param-name>
            <param-value>com.howzat.util;org.codehaus.jackson.jaxrs</param-value>
        </init-param>
        ...

I also had to add the following to the atmosphere.xml file located in the META-INF folder
<?xml version="1.0" encoding="UTF-8"?>
  <atmosphere-handlers>
     <atmosphere-handler context-root="/*"
          class-name="org.atmosphere.handler.ReflectorServletProcessor">
          <property name="servletClass" value="com.sun.jersey.spi.spring.container.servlet.SpringServlet"/>
    </atmosphere-handler>
</atmosphere-handlers>

With these changes Jetty started up without any errors. The Classes i was using was from the Sample (atmosphere-jquery-pubsub-0.6.4-sources;  JQueryPubSub.java, FileResource.java, EventsLogger.java).

However, everything was not quite right yet.

I had to make sure the atmosphere was not hikacking all my requests, so i gave it its own path,
    <servlet-mapping>
        <servlet-name>AtmosphereServlet</servlet-name>
        <url-pattern>/atmosphere/*</url-pattern>
    </servlet-mapping>
  
Then i modified the html (index.html that came with the sample) to send app pub sub requests to the context i just provided for Atmosphere.

With that it worked! At least the basic app. Now to tweak it more...

No comments:

Search This Blog