Sunday, January 27, 2013

Resteasy & Gemini Blueprint (Springdm) & Pax Web

inspired by sarbarian, I have created a gemini blueprint version which registered to pax web (in fact any other HTTP service will do)

import webcontainer from pax web in osgi-context.xml.

<?xml version="1.0" encoding="UTF-8"?>
<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.osgi.org/xmlns/blueprint/v1.0.0
            http://www.osgi.org/xmlns/blueprint/v1.0.0/blueprint.xsd">
    <reference id="webContainer" interface="org.ops4j.pax.web.service.WebContainer" />
    <reference id="customFilterChain" interface="javax.servlet.Filter"/>
</blueprint>

define reateasy server bean in module-context.xml.

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:osgi="http://www.eclipse.org/gemini/blueprint/schema/blueprint"
       xmlns:osgix="http://www.eclipse.org/gemini/blueprint/schema/blueprint-compendium"
       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.eclipse.org/gemini/blueprint/schema/blueprint
                        http://www.eclipse.org/gemini/blueprint/schema/blueprint/gemini-blueprint.xsd
                        http://www.eclipse.org/gemini/blueprint/schema/blueprint-compendium
                        http://www.eclipse.org/gemini/blueprint/schema/blueprint-compendium/gemini-blueprint-compendium.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <bean id="resteasy-server" class="...impl.ResteasyServiceImpl" init-method="init" destroy-method="cleanup">
        <property name="webContainer" ref="webContainer"/>
        <property name="securityFilter" ref="customFilterChain" />
        <property name="resteasyServlet" ref="resteasyServlet" />
        <property name="resteasyBootstrap" ref="resteasyBootstrap" />
    </bean>

    <bean id="resteasyServlet" class="...ResteasyServlet" />
    <bean id="resteasyBootstrap" class="...servlet.ResteasyBootstrap" />

</beans>
In the init method, register Resteasy as if you are doing that in the web.xml, which in our case, we don't have web.xml (we didn't use WAB)
            /*
                <listener>
                    <listener-class>org.jboss.resteasy.plugins.server.servlet.ResteasyBootstrap</listener-class>
                </listener>

                <servlet>
                    <servlet-name>Resteasy</servlet-name>
                    <servlet-class>org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher</servlet-class>
                </servlet>

                <servlet-mapping>
                    <ervlet-name>Resteasy</servlet-name>
                    <url-pattern>/osgi/*</url-pattern>
                </servlet-mapping>

                <context-param>
                    <param-name>resteasy.servlet.mapping.prefix</param-name>
                    <param-value>/osgi</param-value>
                </context-param>
                <context-param>
                    <param-name>resteasy.scan</param-name>
                    <param-value>false</param-value>
                </context-param>
             */

private void init() {
        try {
            HttpContext httpContext = webContainer.getDefaultSharedHttpContext();
            // context param must be set first to the http context
            Dictionary contextParam = new Hashtable();
            contextParam.put("resteasy.servlet.mapping.prefix", "/osgi" );
            contextParam.put("resteasy.scan", "false" );
            webContainer.setContextParam(contextParam, httpContext);

            // add security filter
            webContainer.registerFilter(securityFilter,new String[]{"/osgi/*"}, new String[] {"customSecurityFilterChain"}, null, httpContext );
            logger.info( "Security Filter has been registered to /osgi/*" );
            // register resteasy bootstrap listener which would initialize resteasy framework
            // it will also add the registry to the servlet context
            webContainer.registerEventListener( resteasyBootstrap, httpContext );

            // register resteasy dispatcher servlet
            webContainer.registerServlet( resteasyServlet, "Resteasy", new String[]{ "/osgi/*" }, null, httpContext );
            servletContext = resteasyServlet.getServletConfig().getServletContext();

            //ResteasyProviderFactory providerFactory = ResteasyProviderFactory.getInstance();
            ResteasyProviderFactory providerFactory = (ResteasyProviderFactory) servletContext.getAttribute(ResteasyProviderFactory.class.getName());
            ResteasyJacksonProvider jacksonProvider = new ResteasyJacksonProvider();
            ObjectMapper objectMapper = jaxbUtils.getJsonMapper();
            jacksonProvider.setMapper( objectMapper );
            providerFactory.addBuiltInMessageBodyWriter(jacksonProvider);
            providerFactory.addBuiltInMessageBodyReader(jacksonProvider);

            try {
                ByteArrayProvider byteArrayProvider = new ByteArrayProvider();
                providerFactory.addBuiltInMessageBodyWriter(byteArrayProvider);

                DefaultTextPlain defaultTextPlain = new DefaultTextPlain();
                providerFactory.addBuiltInMessageBodyWriter(defaultTextPlain);

                StringTextStar stringTextStar = new StringTextStar();
                providerFactory.addBuiltInMessageBodyWriter(stringTextStar);
            }catch( Throwable ignore ) {
                logger.debug( "defect in reasteasy-jackson-provider, should not export org.jboss.resteasy.plugins.providers");
            }
            logger.info("RESTEasy Framework started on osgi");

            /* register service listener to add JAX-RS annotated class to resteasy */
            bundleContext.addServiceListener(this, serviceFilter);
            ServiceReference[] references = bundleContext.getServiceReferences( (String)null, serviceFilter);
            for(ServiceReference reference : references) {
                try {
                    Object service = bundleContext.getService(reference);
                    addResource(service);
                    bundleContext.ungetService(reference);
                } catch ( Throwable ignore ) {
                    logger.debug( "exception in adding jaxrs resource to resteasy" );
                }
            }

        } catch (Throwable e) {
            logger.error("Error registering resteasy servlet", e);
        }

    }

    @Override
    public void serviceChanged(ServiceEvent event) {
        ServiceReference sr = event.getServiceReference();
        switch(event.getType()) {
            case ServiceEvent.REGISTERED:
                try {
                    addResource(bundleContext.getService(sr));
                    bundleContext.ungetService(sr);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                break;

            case ServiceEvent.UNREGISTERING:
                try {
                    removeResource(bundleContext.getService(sr));
                    bundleContext.ungetService(sr);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                break;
        }
    }

    @Override
    public void addResource(Object resource) {
        if (resource != null ) {
            for( Class _interface : resource.getClass().getInterfaces() ){
                if( _interface.isAnnotationPresent( Path.class ) ) {
                    logger.info("Adding JAX-RS resource: " + resource.getClass().getName());
                    Registry resteasyRegistry = getRegistry();
                    if( resteasyRegistry != null )
                        resteasyRegistry.addSingletonResource(resource);
                    logger.debug("resource " + resource.getClass().getName() + " added to resteasy");
                    break;
                }
            }
        }
    }

    @Override
    public void removeResource(Object resource) {
        if (resource != null && resource.getClass().isAnnotationPresent(Path.class)) {
            logger.info("Removing JAX-RS resource: " + resource.getClass().getName());
            getRegistry().removeRegistrations(resource.getClass());
        }
    }

    public Registry getRegistry() {
        if (servletContext != null) {
            return (Registry) servletContext.getAttribute(Registry.class.getName());
        }else{
            return null;
        }
    }

Please note that you'll have to implement BundleContextAware to get the BundleContext and ServiceListener to listen to new services.

at last, to clean up when the bundle is being unload (or refresh)


    private void cleanup() {
        try {
            webContainer.unregisterEventListener(resteasyBootstrap);
        }catch ( Exception ignored ) {}
        try {
            webContainer.unregisterServlet(resteasyServlet);
        }catch ( Exception ignored ) {}
        try {
            webContainer.unregisterFilter(securityFilter);
        }catch ( Exception ignored ) {}
        try {
            webContainer.getDefaultSharedHttpContext().deregisterBundle(bundleContext.getBundle());
        }catch ( Exception ignored ) {}
        try {
            bundleContext.removeServiceListener(this);
        }catch ( Exception ignored ) {}

    }