[OSGi] Spring error Unable to locate Spring NamespaceHandler for XML schema namespace

You're working with OSGi because it's flexible, you like components, and so on, and you also like Spring because you love writing XMLs instead of Java code.

You think it would be a good idea if you could join these technologies but reading is so boring, plus since both use some config files you just need to mash them together and you should be good with your Sprosgienstein creation. But then you realize that some small differences for example in classpath handling and dependency management are making your life difficult.

A common error you might get is: "Unable to locate Spring NamespaceHandler for XML schema namespace XXX"

Let's assume you're using Spring DM but you did not add all the necessary Spring bundles to your OSGi framework, for any reason, for example the fact that Spring stopped releasing OSGi bundles with version 3 - maybe you could try with the OSGified version provided by the Apache guys for their ServiceMix platform?

Let's also assume you just copy pasted same sample code which works as standalone unit test but fails when loaded inside the framework. Maybe this code uses the ClassPathXmlApplicationContext class as well eg:

 springContext = new ClassPathXmlApplicationContext("/path/to/your/bean_definition.xml");  
 BeanFactory factory = (BeanFactory) springContext;  
 yourBean = (YourBean) factory.getBean("YourBean", YourBean.class);  

So why do you get that error? Well, simply because Spring with this setup cannot find where the hell are its config files which it needs to locate its dependencies.

Here is a list of things you may try to work around this issue, without trying to go down the official Spring route, which I would suggest, unless for a variety of reasons you are prevented from following it.

1 - check your MANIFEST and verify that all resources are available on the configured classpath. Maybe you just need to add the META-INF folder to the Bundle-Classpath. Check if all required packages (import-package) and bundles (require-bundle) are correctly configured and actually available at runtime inside the framework; they don't necessarily have to be be in ACTIVE state though. Sometimes you're just missing a single jar that contains the required handler

2 - manually set the classloader in your code portion before calling the function to build the context:

 ClassLoader myLoader = Thread.currentThread().getContextClassLoader();  
 try {  
      //do something or set another loader above  
 catch(Exception e){  
      //do something  
 finally {  
      //revert to original loader  

3 - use the classpath*: keyword when specifying the xml location for your beans. WARNING: be aware that this way, you're telling Spring to search the whole bundle classpath, which might be huge, thus it may lead to poor performance:

 springContext = new ClassPathXmlApplicationContext("classpath*:/path/to/your/bean_definition.xml");  

4 - override the init method by specifying the classpath directly. WARNING: this might work in a specific case, but it's not recommended as new issues might arise further down the road:

 ApplicationContext ctx = new ClassPathXmlApplicationContext(SOMETHING)  
   protected void initBeanDefinitionReader(XmlBeanDefinitionReader reader)  
     reader.setBeanClassLoader(getClassLoader());//set your classloader here  
 springContext = ctx;  

5 - as you would do when creating a super/uber/fat jar, merge the required spring.handlers and spring.schemas files in a single one and save it in a location under the available classpath, eg META-INF or add some merge instructions to your MAVEN/ANT build:

                          <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">  
                          <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">  
                          <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">  
 <concat destfile="${build.classes.dir}/META-INF/spring.handlers" append="true">  
      <filelist dir="${build.tmp.dir}" files="META-INF/spring.handlers"/>  
 <concat destfile="${build.classes.dir}/META-INF/spring.schemas" append="true">  
      <filelist dir="${build.tmp.dir}" files="META-INF/spring.schemas"/>  

6 - use OsgiBundleXmlApplicationContext instead:

 String [] ctxLocations = {"path/to/your/bean_definition.xml"};//no need for the leading / if you added the path to the Bundle-Classpath  
 OsgiBundleXmlApplicationContext ctx = new OsgiBundleXmlApplicationContext(ctxLocations);  
 BundleContext bundleContext = FrameworkUtil.getBundle(this.getClass()).getBundleContext();//important, set the bundle context before calling refersh!  
 springContext = ctx;  

I found that methods 5 AND 6 together work best

No comments:

Post a Comment

With great power comes great responsibility.

Da grandi poteri derivano grandi responsabilità.