5 Replies Latest reply on Sep 27, 2010 10:25 AM by aslak

    Spring integration first cut

    dan.j.allen

      I just pushed out a first draft of the Spring integration, available in my spring-prototype branch. For now, I've linked against Spring 3.0. We can easily refactor the code somewhat to support Spring 2.5 as well.

       

      http://github.com/mojavelinux/arquillian/tree/spring-prototype

       

      Overview

       

      This integration is interesting because it challenges some the assumptions we make in Arquillian.

       

      The embedded Spring container is straightforward. It's an embedded standalone akin to embedded Weld, OpenEJB and OpenWebBeans. However, Spring is the first programming model we've faced that is not available out of the box in a full embedded or remote container. And it doesn't make sense to create a remote or managed Spring container. Instead, it's implemented as a framework integration that's added to a container, sort of like JSFUnit. We have to extend the programming model of the target container, and thus of the test.

       

      In all, the Spring integration consists of three components:

       

      • Spring test enricher (generic)
      • Spring embedded container
      • Spring framework integration

       

      Example

       

      Here's an example of a Spring Arquillian test that uses the @Autowired annotation for dependency injection (similar in nature to @Inject).

       

      @RunWith(Arquillian.class)
      public class SpringEmbeddedAutoAnnotationConfigTestCase
      {
         @Deployment
         public static JavaArchive createDeployment()
         {
            return ShrinkWrap.create(JavaArchive.class)
               .addClasses(AutoWiredBean.class)
               .addResource(EmptyAsset.INSTANCE, "applicationContext.xml");
         }
      
         @Autowired
         SampleBean bean;
      
         /**
          * Ensures the annotation-based injection works.
          */
         @Test
         public void shouldInjectAutoWiredBean()
         {
            assertNotNull("Bean was not injected", bean);
            assertEquals("Spring", bean.getProgrammingModel());
         }
      }

       

      The location of the Spring configuration file is assumed to be applicationContext.xml in the root of the classpath. You control which configuration file is used by putting the one you want to use into the archive in this location. The default location can be changed using the container configuration.

       

      This test case takes advantage of one of the tricks I've implemented. If you add an empty applicationContext.xml, it will automatically enable the annotation-based wiring. Alternatively, you can:

       

      • use the typical Spring XML-based activation
      • set the autowire strategy in arquillian.xml (shown below)

       

      <arquillian xmlns="http://jboss.com/arquillian"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns:spring="urn:arq:org.jboss.arquillian.container.spring.embedded_3">
      
         <spring:container>
            <spring:autowire>annotated</spring:autowire>
         </spring:container>
      </arquillian>

       

      The annotated auto-wiring support is only working in the embedded Spring container at the moment.

       

      Class loading

       

      I've spent some more time thinking about the class loading issues in an embedded container. Spring is pretty flexible with regards to plugging in a custom classloader. That's a good fit for the ShrinkWrap class loader. However, there is still an issue.

       

      While the ShrinkWrap class loader makes assets in the ShrinkWrap archive available on the classpath, it does not enforce that only classes and resources in the archive should be visible to the test runner. This is important. Otherwise, the embedded containers could give false results by allowing external classes and resources to be visible, those which won't be there once the archive is deployed remotely.

       

      At first, the instinct is to pass in a null parent class loader. But this is asking for trouble since some classes will already be loaded by the time the test is run and you'll run into linkage errors. A better approach is to refuse to load a class which is not present in the archive, but then allow delegation through the normal chain. Here's how it looks in pseudo-code:

       

      @Override
      public Class<?> loadClass(String name) throws ClassNotFoundException
      {
         if (resource not in archive)
         {
            throw new ClassNotFoundException("Class not found in ShrinkWrap archive: " + name);
         }
         return parent.loadClass(name);
      }
      

       

      We never want to use the parent class loader for loading resources, so I just added:

       

      @Override
      public URL getResource(String name)
      {
         // bypass parent; the only place we should look for resources is in the archives
         return findResource(name);
      }

       

      I also found a problem with the way the ShrinkWrap class loader was caching input streams. When Spring attempted to use the input stream, it was marked as closed (can't get to the bottom of why).

       

      Packaging Spring

       

      When using Spring in a remote container, it's necessary to package Spring in the test archive so that it's available to the deployed application. This is where we are challenging some past assumptions because we've never really dealt with an "add-on" programming model (except for test integrations like JSFUnit).

       

      The hard part is knowing what to package. One approach is to simply package all the Spring classes available on the test classpath using an auxiliary archive appender:

       

      final JavaArchive archive = ShrinkWrap.create(JavaArchive.class, "arquillian-spring-libs.jar");
      archive.addPackages(true, Package.getPackage("org.springframework"));

       

      However, I ran into a major limitation with this API method. ShrinkWrap attempts to load each class as it's being added to the archive. If one of those classes implements or extends a class/interface that is not available, the operation blows up. What we really want is to just transfer the bytes of the class into the archive (no need to load it). I implemented a little hack using an ArchiveFilter to add the classes as resources. But we should think more about this case.

       

      Another approach is to have the user designate a location where all the Spring dependencies reside and merge all those libraries together into a spring-all.jar (or have them just package a spring-all.jar library).

       

      We want to minimize the burden on the developer without painting them into a corner. So I think we need to brainstorm the best way to ship a framework to the container.

       

      Looking for feedback

       

      This prototype is a start. Now that you have something to play with, perhaps it will begin to move along quickly as a complete replacement for Spring's testing framework (the point being to align w/ the portability and in-container options that Arquillian provides).

        • 1. Re: Spring integration first cut
          dan.j.allen

          Ah, I remembered something else I meant to include in the class loading section. Spring allows you to set the classloader it uses to resolve bean classes and resources. So it's pretty straightforward to pass in the ShrinkWrap classloader to Spring in the standalone embedded container (using more strict version, though).

           

          Where we get bitten is embedded deployable containers (Embedded GlassFish, Embedded Jetty, etc).

           

          In the embedded deployable containers, we hand the deployment off to the container and expect it to behave just like a remote container. However, the difference is that the container was started in the same JVM and hence under the test classpath. As a result, the application can see classes that are outside of the test archive.

           

          Fixing this is a complicated issue (in my initial thoughts about it). The container needs to have access to the whole test classpath, but the test archive running inside of it should not (it should only see the ShrinkWrap archive).

           

          If we try to modify how the programming model is initialized upon deployment of the archive, (such as controlling which classes and resources Weld or Spring use), we'll, we can't. We don't have access to the test archive object at that point and therefore wouldn't know what classes and resources to which to restrict the classpath.

           

          Another approach is to use the native features of the container to set the classloader. However, I tried this with Jetty and all of a sudden lots of things can't be found. It's a lot of spinning wheels in mud to find out what is going to work.

           

          WebAppContext wctx = archive.as(ShrinkWrapWebAppContext.class);
          ...
          List waivedPackages = new ArrayList();
          // waived packages are a lab technique to see beyond initial classloading issues
          waivedPackages.add("org.jboss.shrinkwrap");
          waivedPackages.add("org.jboss.arquillian");
          waivedPackages.add("org.eclipse.jetty");
          StrictShrinkWrapClassLoader cl = new StrictShrinkWrapClassLoader(
             archive.getClass().getClassLoader(), waivedPackages, archive);
          wctx.setClassLoader(cl);
          

           

          We may just want to chase this down in another thread. The way I see it, if Arquillian is going through the effort of using a ShrinkWrap archive, it shouldn't allow applications to load classes and resources outside of that archive. This is happening in embedded containers. We may just need to go with a higher level validator, such as ensuring that Spring does not access bean classes that aren't in the ShrinkWrap archive.

          • 2. Re: Spring integration first cut
            aslak

            Interesting.. nice work..

            Dan Allen wrote:

            Packaging Spring

             

            However, I ran into a major limitation with this API method. ShrinkWrap attempts to load each class as it's being added to the archive. If one of those classes implements or extends a class/interface that is not available, the operation blows up. What we really want is to just transfer the bytes of the class into the archive (no need to load it). I implemented a little hack using an ArchiveFilter to add the classes as resources. But we should think more about this case.

             

            This limitation is fixed in the following pull request:  http://github.com/shrinkwrap/shrinkwrap/pull/1 along with addPackage(String)

            • 3. Re: Spring integration first cut
              dan.j.allen

              Fantastic! You read my mind

              • 4. Re: Spring integration first cut
                dan.j.allen

                I'd like to discuss more about this requirement for an integration to add libraries to the deployment. It applies to the JSFUnit integration as well, to provide a more clean approach.

                 

                Certain integrations require shipping libraries (programming models, frameworks, etc) to the server. These libraries are JAR files. There are three concerns that have to be addressed:

                 

                • which version
                • where do we get them
                • what optional libraries should be included

                 

                Understand that shipping these libraries is not just about grabbing the right classes. Spring, for instance, requires resource files as well that are bundled in the libraries.

                 

                Also recognize that certain target servers may already have these libraries, so the developer will need to disable this feature.

                 

                In my prototype, I use this idea of a "libraries auxiliary archive". It works like this:

                 

                public class SpringLibrariesAuxiliaryArchiveAppender implements AuxiliaryArchiveAppender
                {
                   public static final String SPRING_LIBRARIES_PATH_PROPERTY = "arquillian.springLibrariesPath";
                
                   public static final String DEFAULT_SPRING_LIBRARIES_PATH = "target/spring-libs";
                
                   public Archive<?> createAuxiliaryArchive()
                   {
                      final JavaArchive archive = ShrinkWrap.create(JavaArchive.class, "arquillian-spring-libs.jar");
                      mergeJars(archive, getSpringLibrariesPath());
                      return archive;
                   }
                }
                

                 

                All the developer has to do is prepare the JARs in some directory, such as target/spring-libs, and Arquillian will take it from there. Here's one way to get them there:

                 

                <build>
                   <plugins>
                      <plugin>
                         <groupId>org.apache.maven.plugins</groupId>
                         <artifactId>maven-dependency-plugin</artifactId>
                         <version>2.1</version>
                         <executions>
                            <execution>
                               <id>copy-spring-libs</id>
                               <phase>process-test-resources</phase>
                               <configuration>
                                  <includeScope>provided</includeScope>
                                  <outputDirectory>
                                     ${project.build.directory}/spring-libs
                                  </outputDirectory>
                                  <stripVersion>true</stripVersion>
                               </configuration>
                               <goals>
                                  <goal>copy-dependencies</goal>
                               </goals>
                            </execution>
                         </executions>
                      </plugin>
                   </plugins>
                </build>

                 

                This isn't an ideal solution, I admit. My hope is that it communicates the requirement and spawns some other ideas.

                 

                * There are a load of filters that can be applied to which dependencies are copied. You can also switch to the copy task, which will copy individual JAR files. This task makes full use of dependency management.

                • 5. Re: Spring integration first cut
                  aslak

                  With the help of the upcoming ShrinkWrap Aether integration, we could do something like:

                   

                   

                  public Collection<Archive<?>> createAuxiliaryArchives()
                  {
                     return Dependencies.resolve(
                         "org.spring:spring:" + getClassPathVersion("org.spring"),
                         "org.spring:spring:" + getPomVersion("org.spring"),
                         ...
                     );
                  }
                  

                   

                  Of course this will hard code some repository coordinates..