Do we need to Giftwrap Shrinkwrap?
I’ve been working on a bit of a framework to make it easier to write test archives for Arquillian based on Shrinkwrap. This post is a summary of the work so far and hopefully with some feedback, specifically on where ShrinkWrap is going, I can determine whether to polish it up and release it or just use it internally until the features are available elsewhere.
Arquillian is a test framework that lets you test your applications in-container, and it does this by using the Shrinkwrap API that lets you programmatically build a deployment archive. The steps to test are :
- Testing is initiated by jUnit or TestNG
- As part of the test, the java archive is built using java code and the Shrinkwrap framework
- The archive is shipped off to the container
- In the container, the tests are run and the results reported back
- On the client side, the results are reported to the user
When constructing the archive you need to add in any classes used by your code as well as any configuration files necessary. This is done in a procedure marked with a @Deployment
annotation such as :
taken from the Arquillian documentation
@Deployment public static JavaArchive createTestArchive() { return Archives.create("test.jar", JavaArchive.class) .addClasses(GreetingManager.class, GreetingManagerBean.class) .addManifestResource(new ByteArrayAsset(new byte[0])), ArchivePaths.create("beans.xml")); }
Two things strike me here, one is that I need to manually add the classes for every test, and the other is I need to manually construct the beans.xml
. Now I can fix the first problem by just adding the classes by using the package (.addPackage(SomeClass.class.getPackage())
). However, I don’t know whether just including all the classes is a good idea or not. The second will probably be fixed when the Shrinkwrap deployment descriptors DSL classes come out (which look great by the way, they put my simple attempts to shame!).
OK, so now I can think of a couple more problems. Lets say I have an interface called RatingProducer
and I have two implementations, one that is used by default (DefaultRatingProducer
, and the other one that is used for “the Northeast sites” (NorthEastRatingProducer
). So I have them both marked as alternatives, and therefore I need to define which one to use in the beans.xml
file. I cannot just add one to the archive and have CDI use that. By default alternative classes are ‘inactive’ until they are enabled in the beans.xml
. Do I have to manually write each beans.xml file out?
The other problem is that I probably want to run all my tests, at least the ones involving the RatingProducer
implementations for each implementation.
The same could apply to interceptors and decorators, and if the TDD overlords say we must test the persistence layer, then shouldn’t we be testing it for every supported databases as well? In fact, couldn’t we end up testing every container for every db layer and for every single test in the test suite? I think this is a really good idea as long as it can be automated and requires as little development as possible. Who cares if a spare machine is sat around all day whirring through these tests and you end up with a better product?
Anyways, back to the issue at hand, it struck me that Shrinkwrap lacks the ability to conditionally build archives without actually writing the code to say If (something) then add(this) else add(that);
when building the archive. It also sort of struck me that Shrinkwrap builds the archive, and Arquillian tests it, but there’s nothing to bridge the divide, there’s nothing to manage what goes into the test archive. I can have archives that have different configurations and different classes involved.
So, I started by writing a little set of utility classes that could handle some of the initial problems. It let you add a class and would automatically add any dependencies so in one line you could add all the classes for the tests. This kind of grew into a little framework which I called Giftwrap. It lets you define the contents of an archive in a hierarchy which can then be processed to generate a Shrinkwrap archive. It supports the concepts of profiles so certain nodes will only be active when the archive is generated for a specific profile. Profiles can be used to exclude other nodes.
It allows partial files so you can have multiple nodes contribute to file content and each node can be dependent on a profile.
//add beans header root.addElement(new PartialManifestFileElement("beans.xml","<beans>")); //add alternatives node root.addElement(new PartialManifestFileElement("beans.xml","<alternatives>")); //add alternative for NorthEast profile root.addElement(new PartialManifestFileElement("beans.xml","<class>com.package.NorthEastRatingProducer").includeIn("NE")); //add alternative for Default profile (all but NE profile) root.addElement(new PartialManifestFileElement("beans.xml","<class>com.package.DefaultRatingProducer").excludeIn("NE")); root.addElement(new PartialManifestFileElement("beans.xml","</alternatives>")); root.addElement(new PartialManifestFileElement("beans.xml","</beans>"));
(There is an ordering element to the file element class so you can retain the correct structure wherever the file fragments are added from.)
In this example, we construct a beans.xml
file to use one alternative if the NE
profile is active and use another if it is not active. When the archive is generated, we do so for a set of profiles that may or may not include the NE
profile.
I refined this a bit more to make persistence and beans xml files easier to create and wrote some helper classes that represent those file structures. However, to retain the ability to make different parts of it active and disabled based on profiles, each file has child elements that are subject to the active profiles.
Here’s a simple version of the BeansXml
class that sets up the default rating producer.
root.addElement(new BeansXml().addAlternative(DefaultRatingProducer.class));
Here’s another one with child versions that properly defines the rating producer based on a profile.
root.addElement(new BeansXml() //add alternative for NorthEast profile .add(new BeansXml().addAlternative(NorthEastRatingProducer.class).includeIn("NE"))); //add alternative for Default profile (not the NE profile) .add(new BeansXml().addAlternative(DefaultRatingProducer.class).excludeFrom("NE"))
When the archive is generated the file and all the child instances are merged based on the active profiles and then the final file is generated.
There is a JeeArchive
class that predefines these elements and makes them available as an attribute for convenience.
JeeArchive root = new JeeArchive(); root.getBeans().add(new BeansXml().addAlternative(DefaultRatingProducer.class).excludeFrom("NE")) root.getBeans().add(new BeansXml().addAlternative(NorthEastRatingProducer.class).includeIn("NE")));
This makes it easier for different test elements to add to the configuration files. We also do the same thing for persistence xml files :
root.getPersistence().addUnit("pu").useHibernate(); //add jndi datasource for Glassfish root.getPersistence().find("pu").add().dataSource("jdbc/__default").includeIn("GLASSFISH"); //add jndi datasource for JBoss root.getPersistence().find("pu").add().dataSource("java:/DefaultDS").includeIn("JBOSS");
Here we can create different persistence.xml
files for Glassfish or JBoss servers. When combined with the above we can do :
JavaArchive archive; //Get an archive for testing the NorthEast Profile in Glassfish archive = ArchiveDeployment.generate(root, new String[] {"NE","GLASSFISH"}); //Get an archive for testing the NorthEast Profile in JBoss archive = ArchiveDeployment.generate(root, new String[] {"NE","JBOSS"}); //Get an archive for testing the default in Glassfish archive = ArchiveDeployment.generate(root, "GLASSFISH"); //Get an archive for testing the default in JBoss archive = ArchiveDeployment.generate(root, "JBOSS");
This will get us all 4 combinations of rating producer and persistence datasource names.
A few more features are
- Hierarchical activation, if a container is inactive due to profiles, then its children are inactive letting you group the archive content by profile
- Add packages by name
- Simple XML definition language for creating xml documents rather than using a string
MockDeploymentContext
lets you test your archive creation code for each profile (testing your testing code?)- Add resources from “ContentProducers” that is a pluggable content producer that can source resources from either a constant string, a file, or an XML document based on the simple XML classes included in Giftwrap.
However…
So while this is all rather neat and spiffy it has a couple of problems. The first is that this is a node based deployment definition framework on top of Shrinkwrap which is itself a node based deployment definition framework. Talk about overkill. While the profiles idea is (I think) a pretty good one, I don’t think it would take a lot of work to add them into Shrinkwrap.
Partial files is a decent concept, but the wrapper classes for beans.xml are nicer, and they can also be used outside of the Giftwrap framework :
(not possible right now, it still needs a context to execute with)
BeansXml beans = new BeansXml().addAlternative(RatingProducer.class); return ShrinkWrap.create(JavaArchive.class, "test.jar") .... .... .addManifestResource(new ByteArrayAsset(beans.toBytes()),ArchivePaths.create("beans.xml"));
The ability to add dependencies when you add a class is just a couple of methods to reflect the class and add those that are needed, and again, could easily be added to Shrinkwrap.
So all in all, what features this framework brings could easily be added to Shrinkwrap, but might extend the scope of the library, but not by much. Currently, Giftwrap abstracts the ShrinkWrap Archive
class in a DeploymentContext
in case there are any frameworks to create an archive from code. I don’t think there are any so I’ll probably just allow direct access to the Archive from the element rather than abstracting it and re-producing most of the methods. This also means you can write really simple elements as anonymous classes without relying on the deployment context to have the particular feature you want :
ArchiveElement element = new AbstractArchiveElement() { @Override public void doAppend(DeploymentContext context) { context.getArchive() .addClass(SomeClass.class) .addResource("xyz") .addResource(someUrl); } }; element.includeIn("SOME_PROFILE"); root.addElement(element);
This element would be subject to being de/activated by the profiles set in the deployment context.
Anyways, here’s the Giftwrap Source, it currently is running under the glassfish-remote-3 profile, it may work in different profiles, but isn’t guaranteed. This code is strictly Alpha stage so its fairly rough around the edges, and if it does make it into a final library, the interface will probably change a bit. it comes as two maven projects, the Giftwrap library and a simple demo test app. Do a mvn clean install
on the Giftwrap project to install, and see the demo tests to see how to use it.