Question

In a good ol' war project, you would simply add a ContextLoaderListener to your web.xml and stuff would pretty much work - you could use

WebApplicationContextUtils.getWebApplicationContext(getServlet().getServletContext())

to access the application context from a Struts 1 Action class, for instance, and the whole configuration process is well documented. You could look up beans from JNDI if they are created by other applications.

But what if I'm porting this good ol' Web Application aRchive to a Web Application Bundle, and want to use OSGi service references instead of JNDI? The above method still works if all I want Spring to do is manage beans within my web application. I can instantiate beans and get them via the above utility method, and I've successfully set up Gemini Blueprint (formerly Spring DM) to resolve my OSGi service references.

The problem is that Gemini Blueprint and Spring Struts run in parallel and do not seem to know about each other. The context returned by the above utility method does not contain beans created by Gemini Blueprint, such as those imported from OSGi services, and dies horribly if I add a Blueprint-style OSGi service reference to the XML config parsed by Spring Struts.

What do I need to do to access a Gemini Blueprint application context from within a Struts 1 Action?

Logs

Some cherry-picked rows from the logs:

17:12:32,206 INFO  [org.jboss.as.server.deployment] (MSC service thread 1-1) JBAS015876: Starting deployment of "wfadmin-1.0-SNAPSHOT-82a5028.war" (runtime-name: "wfadmin-1.0-SNAPSHOT-82a5028.war")

17:12:36,744 INFO  [io.undertow.servlet] (MSC service thread 1-7) Initializing Spring root WebApplicationContext
17:12:36,745 INFO  [org.springframework.web.context.ContextLoader] (MSC service thread 1-7) Root WebApplicationContext: initialization started
17:12:36,751 INFO  [org.springframework.beans.factory.xml.XmlBeanDefinitionReader] (MSC service thread 1-7) Loading XML bean definitions from ServletContext resource [/META-INF/spring/wfadmin-context.xml]
17:12:38,026 FINE  [org.springframework.beans.factory.xml.XmlBeanDefinitionReader] (MSC service thread 1-7) Loaded 1 bean definitions from location pattern [/META-INF/spring/wfadmin-context.xml]
17:12:38,026 FINE  [org.springframework.web.context.support.XmlWebApplicationContext] (MSC service thread 1-7) Bean factory for Root WebApplicationContext: org.springframework.beans.factory.support.DefaultListableBeanFactory@440f89d6: defining beans [testBeanInMetaInfSpringWfadmiContextXml]; root of factory hierarchy
17:12:38,027 INFO  [org.springframework.beans.factory.support.DefaultListableBeanFactory] (MSC service thread 1-7) Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@440f89d6: defining beans [testBeanInMetaInfSpringWfadmiContextXml]; root of factory hierarchy
17:12:38,030 FINE  [org.springframework.beans.factory.support.DefaultListableBeanFactory] (MSC service thread 1-7) Finished creating instance of bean 'testBeanInMetaInfSpringWfadmiContextXml'
17:12:38,034 INFO  [org.springframework.web.context.ContextLoader] (MSC service thread 1-7) Root WebApplicationContext: initialization completed in 1289 ms

17:12:38,140 INFO  [org.eclipse.gemini.blueprint.extender.support.DefaultOsgiApplicationContextCreator] (MSC service thread 1-7) Discovered configurations {osgibundle:/META-INF/spring/*.xml} in bundle [Sunstone Workflow Admin (se.sunstone.workflow.web)]
17:12:38,208 FINEST [org.eclipse.gemini.blueprint.io.OsgiBundleResourcePatternResolver] (EclipseGeminiBlueprintExtenderThread-15) Resolved location pattern [osgibundle:/META-INF/spring/*.xml] to resources [URL [bundle://se.sunstone.workflow.web-87-1-0/META-INF/spring/wfadmin-context.xml], URL [bundle://se.sunstone.workflow.web-87-1-0/META-INF/spring/wfadmin-osgi-context.xml]]
17:12:38,258 FINE  [org.eclipse.gemini.blueprint.context.support.OsgiBundleXmlApplicationContext] (EclipseGeminiBlueprintExtenderThread-15) Bean factory for OsgiBundleXmlApplicationContext(bundle=se.sunstone.workflow.web, config=osgibundle:/META-INF/spring/*.xml): org.springframework.beans.factory.support.DefaultListableBeanFactory@22eccb06: defining beans [testBeanInMetaInfSpringWfadmiContextXml,testBeanInMetaInfSpringWfadminOsgiContextXml,wfEngine]; root of factory hierarchy
17:12:38,260 FINEST [org.eclipse.gemini.blueprint.extender.internal.dependencies.startup.MandatoryImporterDependencyFactory] (EclipseGeminiBlueprintExtenderThread-15) Discovered single proxy importers [&wfEngine]
17:12:38,266 FINE  [org.springframework.beans.factory.support.DefaultListableBeanFactory] (EclipseGeminiBlueprintExtenderThread-15) Finished creating instance of bean 'wfEngine'
17:12:38,266 FINEST [org.eclipse.gemini.blueprint.extender.internal.dependencies.startup.MandatoryImporterDependencyFactory] (EclipseGeminiBlueprintExtenderThread-15) Eager importer &wfEngine implies dependecy DependencyService[Name=&wfEngine][Filter=(objectClass=se.sunstone.workflow.WorkflowEngine)][Mandatory=true]
17:12:38,266 FINE  [org.eclipse.gemini.blueprint.extender.internal.dependencies.startup.DependencyServiceManager] (EclipseGeminiBlueprintExtenderThread-15) OSGi service dependency for importer [&wfEngine] is already satisfied
17:12:38,266 FINEST [org.eclipse.gemini.blueprint.extender.internal.dependencies.startup.DependencyServiceManager] (EclipseGeminiBlueprintExtenderThread-15) Total OSGi service dependencies beans [&wfEngine]
17:12:38,292 FINE  [org.springframework.beans.factory.support.DefaultListableBeanFactory] (EclipseGeminiBlueprintExtenderThread-16) Finished creating instance of bean 'testBeanInMetaInfSpringWfadmiContextXml'
17:12:38,293 FINE  [org.springframework.beans.factory.support.DefaultListableBeanFactory] (EclipseGeminiBlueprintExtenderThread-16) Finished creating instance of bean 'testBeanInMetaInfSpringWfadminOsgiContextXml'

17:12:46,978 FINEST [se.sunstone.util.web.AbstractAction] (default task-1) Beans defined in application context Root WebApplicationContext : 
17:12:46,978 FINEST [se.sunstone.util.web.AbstractAction] (default task-1) testBeanInMetaInfSpringWfadmiContextXml
17:12:46,978 FINEST [se.sunstone.util.web.AbstractAction] (default task-1) End of beans defined in application context Root WebApplicationContext
17:12:46,979 FINEST [org.springframework.beans.factory.support.DefaultListableBeanFactory] (default task-1) No bean named 'wfEngine' found in org.springframework.beans.factory.support.DefaultListableBeanFactory@440f89d6: defining beans [testBeanInMetaInfSpringWfadmiContextXml]; root of factory hierarchy
17:12:46,979 SEVERE [se.sunstone.util.web.AbstractAction] (default task-1) A requested bean does not exist.: org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'wfEngine' is defined
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeanDefinition(DefaultListableBeanFactory.java:568)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getMergedLocalBeanDefinition(AbstractBeanFactory.java:1108)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:278)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:194)
    at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1117)
    at se.sunstone.util.web.AbstractAction.getService(AbstractAction.java:53) [AbstractAction.class:]
    ...

The first section (excluding the lone row at the top) tells that the ContextLoaderListener starts up a Spring ContextLoader and processes META-INF/spring/wfadmin-context.xml, as it is configured to do.

The second section tells that Gemini Blueprint detects that this is a Blueprint bundle and starts up its own context from configurations META-INF/spring/wfadmin-{,osgi-}context.xml. We also see that the bean wfEngine is successfully imported from an OSGi service.

The third section shows how my se.sunstone.util.web.AbstractAction dies when it attempts to access the bean wfEngine in the Spring Struts application context. This is expected, since workflow-context.xml only contains the testBeanInMetaInfSpringWorkflowContextXml bean, but if I include

<osgi:reference id="wfEngineInMetaInfSpringWfAdminContextXml" interface="se.sunstone.workflow.WorkflowEngine"/>

in workflow-context.xml (with a suitable xmlns:osgi definition), the web application fails to even start up:

09:43:30,026 SEVERE [org.springframework.web.context.ContextLoader] (MSC service thread 1-4) Context initialization failed: org.springframework.beans.factory.parsing.BeanDefinitionParsingException: Configuration problem: Unable to locate Spring NamespaceHandler for XML schema namespace [http://www.springframework.org/schema/osgi]
Offending resource: ServletContext resource [/META-INF/spring/wfadmin-context.xml]

I would like for there to be a way to tell the Spring Strugs plugin to share its application context with Gemini Blueprint. Is this possible?

For completeness, the Spring-Blueprint configs look like so:

META-INF/spring/wfadmin-context.xml (Loaded by both):

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

  <bean id="testBeanInMetaInfSpringWfadmiContextXml" class="java.lang.Object"/>
  <!--<osgi:reference id="wfEngineInMetaInfSpringWfAdminContextXml" interface="se.sunstone.workflow.WorkflowEngine"/>-->

</beans>

META-INF/spring/wfadmin-osgi-context.xml (Loaded by Gemini Blueprint):

<?xml version="1.0" encoding="UTF-8"?>
<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0">

  <bean id="testBeanInMetaInfSpringWfadminOsgiContextXml" class="java.lang.Object"/>
  <reference id="wfEngine" interface="se.sunstone.workflow.WorkflowEngine" availability="mandatory"/>

</blueprint>
Était-ce utile?

La solution

With some effort, I got it to work. ContextLoaderListener is still the way to go, but it needs a few tweaks to make it OSGi aware.

The solution in my case consisted of several steps:

Step 1: Replace Gemini Blueprint with Spring OSGi

We need to make the ContextLoaderListener create an OsgiBundleXmlWebApplicationContext instead of a plain XmlWebApplicationContext. As far as I know there isn't yet any distribution of Gemini Blueprint providing this class, so we will need to use Spring OSGi instead which has distributed a spring-osgi-web bundle.

Instead of using the Gemini Blueprint extender jars, I used the following Spring OSGi jars:

(and of course their dependencies, omitted for brevity)

Since I had used the flashy new <blueprint> root element and namespace in some of my application context configurations, that needed to be exchanged for the Spring OSGi equivalents (notice in particular how availability="mandatory" now becomes cardinality="1..1"):

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:osgi="http://www.springframework.org/schema/osgi"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
           http://www.springframework.org/schema/osgi http://www.springframework.org/schema/osgi/spring-osgi.xsd">
    <reference id="wfEngine" interface="se.sunstone.workflow.WorkflowEngine" cardinality="1..1"/>
</beans>

In Gemini Blueprint the namespaces are interchangeable, but Spring OSGi predates Blueprint and therefore the Blueprint namespaces etc. don't work in Spring OSGi.

Step 2: Prevent Spring OSGi from recognizing the war as a Spring bundle

This was achieved by simply moving my application contexts out of META-INF/spring and instead placing them in WEB-INF/applicationContext.xml, which is the default location for the ContextLoaderListener to look for the application context configuration.

Step 3: Make the ContextLoaderListener OSGi aware

Next, I followed these instructions to configure the ContextLoaderListener to use org.springframework.osgi.web.context.support.OsgiBundleXmlWebApplicationContext as the type for the application context. This then greeted me with a new error:

java.lang.IllegalArgumentException: bundle context should be set before refreshing the application context

After some searching I happened upon this blog post and tried that out. The OsgiWebBundleContext supplied there didn't work for me, I still got the same error. By adding some trace output in this new context type, I could confirm that the bundle context in fact didn't exist:

17:15:20,233 FINEST [se.sunstone.workflow.web.osgi.workaround.OsgiBundleXmlWebApplicationContextWithCorrectBundleContextLookupName] (MSC service thread 1-5) Attributes in servletContext:
17:15:20,233 FINEST [se.sunstone.workflow.web.osgi.workaround.OsgiBundleXmlWebApplicationContextWithCorrectBundleContextLookupName] (MSC service thread 1-5) org.apache.jasper.JSP_PROPERTY_GROUPS
17:15:20,233 FINEST [se.sunstone.workflow.web.osgi.workaround.OsgiBundleXmlWebApplicationContextWithCorrectBundleContextLookupName] (MSC service thread 1-5) javax.servlet.context.tempdir
17:15:20,233 FINEST [se.sunstone.workflow.web.osgi.workaround.OsgiBundleXmlWebApplicationContextWithCorrectBundleContextLookupName] (MSC service thread 1-5) org.apache.jasper.JSP_TAG_LIBRARIES
17:15:20,233 FINEST [se.sunstone.workflow.web.osgi.workaround.OsgiBundleXmlWebApplicationContextWithCorrectBundleContextLookupName] (MSC service thread 1-5) javax.websocket.server.ServerContainer
17:15:20,234 FINEST [se.sunstone.workflow.web.osgi.workaround.OsgiBundleXmlWebApplicationContextWithCorrectBundleContextLookupName] (MSC service thread 1-5) org.apache.jasper.SERVLET_VERSION
17:15:20,234 FINEST [se.sunstone.workflow.web.osgi.workaround.OsgiBundleXmlWebApplicationContextWithCorrectBundleContextLookupName] (MSC service thread 1-5) org.jboss.as.jsf.FACES_ANNOTATIONS
17:15:20,234 FINEST [se.sunstone.workflow.web.osgi.workaround.OsgiBundleXmlWebApplicationContextWithCorrectBundleContextLookupName] (MSC service thread 1-5) org.apache.tomcat.InstanceManager
17:15:20,234 FINEST [se.sunstone.workflow.web.osgi.workaround.OsgiBundleXmlWebApplicationContextWithCorrectBundleContextLookupName] (MSC service thread 1-5) End of attributes in servletContext

But at least Spring has now been made OSGi aware, which is a start.

Step 4: Work around the missing BundleContext

It would seem that either the BundleContext is never set on the ServletContext, or Spring attempts to access it before it is set. Either way, this answer inspired me to modify the workaround class from the blog post to use FrameworkUtil.getBundle(ClassFromBundle).getBundleContext() to find the BundleContext:

public class OsgiBundleXmlWebApplicationContextSettingBundleContextFromFrameworkUtil
        extends OsgiBundleXmlWebApplicationContext {
    @Override
    public void setServletContext(ServletContext servletContext) {
        if(getBundleContext() == null) {
            BundleContext context = FrameworkUtil.getBundle(getClass()).getBundleContext();
            if(context != null) {
                setBundleContext(context);
            }
        }

        // to call "this.servletContext = servletContext;" in super
        super.setServletContext(servletContext);
    }

}

And that worked for me! It may not be the prettiest solution, but it's the only one so far I've gotten to work.*

(Well, I also got it to work by using a BundleActivator, but that probably wasn't any prettier.)

Autres conseils

The replacement for spring-dm-web is Gemini Web and it has a web extender component. It works for me, but I need to use a JavaConfig style of appContext. Somehow, it doesn't detect the blueprint namespace when I configure using an XML. I didn't try Struts, but tried Spring-MVC and that is working fine.

The catch is looking up an OSGi service from the service registry. I had to create an activator, and pick up the service from there, create a bean using @Bean and used @Autowired to inject that service into the required class.

Take a look at this project to see the implementation.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top