1 Reply Latest reply on Jun 24, 2013 5:34 PM by blackbeltdev

    EAP 6.1.0 re-export not working with META-INF resources in the case of multiple JBoss modules

    blackbeltdev

      Hello,

       

      We are developing an application which leverages the JBoss module infrastructure to share common libraries (e.g. Groovy, Spring, Hibernate, Vaadin) between multiple web applications. To

      keep things simple we are currently splitting our dependencies between our code and third party dependencies (over time we expect to split them out in multiple modules) , e.g.

       

      com.acme (our shared application code)

      com.acme-thirdparty (Groovy, Spring, Hibernate, Guava, etc.)

       

      However I ran into a problem with Spring Security and what I believe is a META-INF visibility issue (/META-INF/spring.handlers to be specific) with re-exporting modules in EAP 6.1.0.

      Unfortunately Spring Security doesn't really have support yet for Java based configuration so I was forced to use their XML support.

      From my understanding of Spring XML configurations it needs to scan the classpath in order to find registered handlers for parsing it's configuration files.

       

      Aside: I found some hints here about spring configuration handling that others may found helpful:

      http://blog.idm.fr/2009/09/maven-assembly-plugin-and-spring-namespace-handlers.html

       

      I created a small demo WAR (simple JAX-RS + Spring example) which illustrates the problem.

       

      From what I gleaned from the JBoss documentation of JBoss AS 7 classloading behavior I knew I needed to make some changes in order for the WAR to see the META-INF directories inside of Spring's JARs.

      Using the "jboss-deployment-structure.xml" descriptor I was able to get it working when everthing was together in a single JBoss module but I ran into problems when I started

      to split off Spring from the rest of the dependencies.

       

      For example this works fine as a single JBoss module:

       

      /src/main/webapp/WEB-INF/jboss-deployment-structure.xml (notice that meta-inf="import" is working here)

       

      <?xml version="1.0" encoding="UTF-8"?>

      <jboss-deployment-structure xmlns="urn:jboss:deployment-structure:1.1">

          <deployment>

              <dependencies>

                  <module name="org.codehaus.jackson.jackson-jaxrs"/>

                  <module name="org.springframework" services="import" meta-inf="import"/>

              </dependencies>

          </deployment>

      </jboss-deployment-structure>

       

      Here's my custom "org.springframework" JBoss module for reference (C:\tools\jboss-eap-6.1\modules\org\springframework\main):

       

      <?xml version="1.0" encoding="UTF-8"?>

       

      <module xmlns="urn:jboss:module:1.1" name="org.springframework">

          <resources>

              <!-- Core -->

              <resource-root path="aopalliance-1.0.jar"/>

              <resource-root path="spring-aop-3.2.3.RELEASE.jar"/>

              <resource-root path="spring-beans-3.2.3.RELEASE.jar"/>

              <resource-root path="spring-context-3.2.3.RELEASE.jar"/>

              <resource-root path="spring-context-support-3.2.3.RELEASE.jar"/>

              <resource-root path="spring-core-3.2.3.RELEASE.jar"/>

              <resource-root path="spring-expression-3.2.3.RELEASE.jar"/>

              <resource-root path="spring-jdbc-3.2.3.RELEASE.jar"/>

              <resource-root path="spring-orm-3.2.3.RELEASE.jar"/>

              <resource-root path="spring-oxm-3.2.3.RELEASE.jar"/>

              <resource-root path="spring-tx-3.2.3.RELEASE.jar"/>

              <resource-root path="spring-web-3.2.3.RELEASE.jar"/>

              <resource-root path="spring-webmvc-3.2.3.RELEASE.jar"/>

       

              <!-- Spring LDAP -->

              <resource-root path="spring-ldap-core-1.3.1.RELEASE.jar"/>

              <resource-root path="spring-ldap-core-tiger-1.3.1.RELEASE.jar"/>

             

              <!-- Spring Security -->

              <resource-root path="spring-security-acl-3.1.4.RELEASE.jar"/>

              <resource-root path="spring-security-aspects-3.1.4.RELEASE.jar"/>

              <resource-root path="spring-security-core-3.1.4.RELEASE.jar"/>

              <resource-root path="spring-security-config-3.1.4.RELEASE.jar"/>

              <resource-root path="spring-security-crypto-3.1.4.RELEASE.jar"/>

              <resource-root path="spring-security-openid-3.1.4.RELEASE.jar"/>

              <resource-root path="spring-security-remoting-3.1.4.RELEASE.jar"/>

              <resource-root path="spring-security-ldap-3.1.4.RELEASE.jar"/>

              <resource-root path="spring-security-web-3.1.4.RELEASE.jar"/>

             

              <!-- AMQP -->

              <resource-root path="spring-amqp-1.1.4.RELEASE.jar"/>

              <resource-root path="spring-rabbit-1.1.4.RELEASE.jar"/>

              <resource-root path="spring-retry-1.0.0.RELEASE.jar"/>       

          </resources>

          <dependencies>

              <module name="org.apache.commons.lang"/>

              <module name="javaee.api"/>       

              <module name="org.dom4j"/>

              <module name="org.jboss.vfs"/>

              <module name="org.slf4j"/>

              <module name="org.slf4j.jcl-over-slf4j"/>

              <module name="sun.jdk"/>

          </dependencies>

      </module>

       

      As I said this all works just fine. However the following example which attempts to re-export the Spring module does not work:

       

      /src/main/webapp/WEB-INF/jboss-deployment-structure.xml (notice that meta-inf="import" is NOT working here)

       

      <?xml version="1.0" encoding="UTF-8"?>

      <jboss-deployment-structure xmlns="urn:jboss:deployment-structure:1.1">

          <deployment>

              <dependencies>

                  <module name="org.codehaus.jackson.jackson-jaxrs"/>

                  <module name="test" services="import" meta-inf="import"/>

              </dependencies>

          </deployment>

      </jboss-deployment-structure>

       

      C:\tools\jboss-eap-6.1\modules\test\main

       

      <?xml version="1.0" encoding="UTF-8"?>

       

      <module xmlns="urn:jboss:module:1.1" name="test">

          <resources>

          </resources>

          <dependencies>

              <module name="org.springframework" export="true"/>

          </dependencies>

      </module>

       

       

      This results in a deployment failure:

       

      10:59:32,929 ERROR - U: - O: [org.springframework.web.context.ContextLoader] (ServerService Thread Pool -- 54) 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/security]

      Offending resource: ServletContext resource [/WEB-INF/spring/security.xml]

       

      So it seems from experimentation that export doesn't include the META-INF information. However I was able to get the "test" JBoss module working by extracting /META-INF/spring.handlers and /META-INF/spring.schemas

      and including them inside the WAR artifact.

       

      $ unzip -l build/libs/demo.war

      ...

            109  06-10-2013 11:17   WEB-INF/classes/META-INF/spring.handlers

           1068  06-10-2013 11:17   WEB-INF/classes/META-INF/spring.schemas

       

      While this works it is less than ideal as I have to maintain Spring resources in multiple locations (outside of the Spring standard distribution as well). Is there an option

      to re-export META-INF information like I attempted to do or is there another way of accomplishing this? What is the best practice here?

       

      I hope all that made sense. Let me know if you have any questions. We are soon to be an EAP customer so any help would be greatly appreaciated.

       

      p.s.

       

      The sample project I have is using JBoss's RESTEasy JAW-RS implementation along with the Spring plugin support. It's pretty simple example which uses LDAP authentication and

      authorization via Spring Security.

       

      @Named

      @Path("/Hello")

      @RolesAllowed("ADMINS")

      public class HelloWorldResource {

       

          private static final Logger logger = LoggerFactory.getLogger(HelloWorldResource.class);

       

          @Inject

          private HelloService helloService;

       

          public HelloWorldResource() {

              logger.info("Creating HelloWorldResource");

          }

       

          @GET

          @Path("text")

          @Produces(MediaType.APPLICATION_FORM_URLENCODED)

          public Response sayTextHello(@QueryParam("msg") String msg) {

       

              final String resp = helloService.sayTextHello(msg);

       

              logger.info("sayTextHello({}): {}", msg, resp);

       

              return Response.ok(resp).build();

          }

       

          @POST

          @Path("javabean")

          @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })

          @Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })

          public Response sayJavaBeanHello(final HelloMessage msg) {

       

              final HelloResponse resp = helloService.sayJavaBeanHello(msg);

       

              logger.info("sayJavaBeanHello({}): {}", msg, resp);

       

              return Response.ok(resp).build();

          }

       

      }

       

      Here's my Spring configration for reference:

       

      @Configuration

      @ImportResource("/WEB-INF/spring/security.xml")

      public class MyRestConfig {

       

          // @bean method signature below has to be public for the following code to work

          // org.jboss.resteasy.plugins.spring.SpringBeanProcessor.getBeanClass() (line 399)

          // (Method method : getBeanClass(factoryClassName).getMethods())

          @Bean

          public HelloWorldResource helloWorldResource() {

       

              logger.info("Creating helloWorldResource bean");

       

              final HelloWorldResource helloWorldResource = new HelloWorldResource();

              return helloWorldResource;

          }

      }

       

      /WEB-INF/spring/security.xml

       

      <?xml version="1.0" encoding="UTF-8"?>

      <beans:beans xmlns="http://www.springframework.org/schema/security"

                   xmlns:beans="http://www.springframework.org/schema/beans"

                   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

                   xsi:schemaLocation="http://www.springframework.org/schema/beans

                 http://www.springframework.org/schema/beans/spring-beans-3.1.xsd

                 http://www.springframework.org/schema/security

                 http://www.springframework.org/schema/security/spring-security-3.1.xsd">

       

          <http auto-config='true' use-expressions='true'>

              <intercept-url pattern='/**' access='isFullyAuthenticated()' />

              <http-basic />

          </http>

       

          <ldap-server url="ldap://127.0.0.1:10389/dc=acmecorp,dc=com" manager-dn="uid=admin,ou=system" manager-password="secret" />

       

          <authentication-manager>

              <ldap-authentication-provider user-dn-pattern="uid={0},cn=users" group-search-base="cn=groups" role-prefix="none">

              </ldap-authentication-provider>

          </authentication-manager>

       

          <global-method-security jsr250-annotations="enabled" />

      </beans:beans>

       

       

       

      public class CpmsRestWebApplicationInitializer implements WebApplicationInitializer{

       

          static class MyCustomSpringContextLoader extends ContextLoader {

              private SpringContextLoaderSupport springContextLoaderSupport = new SpringContextLoaderSupport();

       

              public MyCustomSpringContextLoader() {

                  super(createContext());

              }

       

              private static WebApplicationContext createContext() {

                  final AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext();

                  rootContext.setParent(createSharedContext());

                  rootContext.register(CpmsRestConfig.class);

                  return rootContext;

              }

       

              private static ApplicationContext createSharedContext() {

                  final AnnotationConfigApplicationContext sharedContext = new AnnotationConfigApplicationContext();

                  sharedContext.setDisplayName("Shared Spring Application Context");

                  sharedContext.register(CpmsSharedConfig.class);

                  sharedContext.refresh();

                  return sharedContext;

              }

       

              protected void customizeContext(ServletContext servletContext, ConfigurableWebApplicationContext configurableWebApplicationContext) {

                  super.customizeContext(servletContext, configurableWebApplicationContext);

                  springContextLoaderSupport.customizeContext(servletContext, configurableWebApplicationContext);

              }

          }

       

          static class RestSpringContextLoaderListener extends SpringContextLoaderListener {

       

              @Override

              protected ContextLoader createContextLoader() {

       

                  logger.info("Creating ContextLoader");

       

                  return new MyCustomSpringContextLoader();

              }

          }

       

          private static final Logger logger = LoggerFactory.getLogger(CpmsRestWebApplicationInitializer.class);

       

          @Override

          public void onStartup(final ServletContext servletContext) throws ServletException {

       

              logger.info("onStartup()");

       

              configure(servletContext);

          }

       

          private void configure(final ServletContext servletContext) {

       

              servletContext.setInitParameter("resteasy.servlet.mapping.prefix", "/rest");

       

              final FilterRegistration.Dynamic springSecurityFilterChain =

                      servletContext.addFilter("springSecurityFilterChain", new DelegatingFilterProxy());

              springSecurityFilterChain.addMappingForUrlPatterns(null, false, "/*");

       

              servletContext.addListener(new ResteasyBootstrap());

       

              servletContext.addListener(new RestSpringContextLoaderListener());

       

              final ServletRegistration.Dynamic dispatcher = servletContext.addServlet("resteasy", new HttpServletDispatcher());

              dispatcher.setLoadOnStartup(1);

              dispatcher.addMapping("/rest/*");

          }

      }