Creating Deployable Archives with ShrinkWrap

Author: Andrew Lee Rubinger Translations:
Level: First Steps Tags: shrinkwrap, arquillian Last Update:Sep 21, 2017

ShrinkWrap is the simplest way to create archives in Java, and it powers the Arquillian deployment mechanism. This guide serves as a crash course in creating the objects which will come to represent your deployments. We’ll cover:

  • The motivation behind and benefits of ShrinkWrap over traditional file-based archives
  • Creation of a new archive from scratch
  • Various mechanisms used to add content
  • Importing archives from existing File structures

Justification

From the onset, ShrinkWrap was born from a need to more easily test Java Enterprise deployments. Traditionally defined as flat-file archives adhering to the ZIP standard, these have necessitated the introduction of some build step to package up all application resources. And a build step takes time:

$ mvn clean install
... terrifying output trace ...
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 1:13.492s
[INFO] ------------------------------------------------------------------------

But as developers, we live in our coding environments. Switching out of that mindset to run a build is wasteful.

So we asked: “What if we could declare, in Java, an object to represent that archive?”

What resulted was a Java API analouge to the “jar” tool, a virtual filesystem with an intuitive syntax.

Creating a ShrinkWrap Archive
JavaArchive archive = ShrinkWrap.create(JavaArchive.class,"myarchive.jar") 
   .addClasses(MyClass.class, MyOtherClass.class)
   .addResource("mystuff.properties");

What resulted was a way to take advantage of the IDE’s incremental compilation features, allowing us to skip the build.

What resulted was a way to run tests straight from the IDE.

What resulted was ShrinkWrap.

Getting Started

The first step is getting your hands on the ShrinkWrap binaries. The Core is composed of three pieces:

Name Maven Coordinates
API org.jboss.shrinkwrap:shrinkwrap-api
SPI org.jboss.shrinkwrap:shrinkwrap-spi
Implementation org.jboss.shrinkwrap:shrinkwrap-impl-base

Only the API should be available upon your compilation ClassPath, while the SPI and the Implementation modules are both required for the runtime. This is to enforce good separation between classes intended for direct use and the project’s internals.

In Maven, these may be brought in under the proper scopes easily by using the ShrinkWrap Dependency Chain POM, available in Maven Central:

Your Project’s pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="
  http://maven.apache.org/POM/4.0.0
  http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <!-- snip -->
  
  <dependency>
    <groupId>org.jboss.shrinkwrap</groupId>
    <artifactId>shrinkwrap-depchain</artifactId>
    <version>${version.shrinkwrap}</version>
    <type>pom</type>
  </dependency>

  <!-- snip -->
</project>

For projects outside use of the Maven repository system, the ShrinkWrap Distribution makes all modules available as a download, and you may set up the dependencies manually to suit your needs.

Prerequisites

  • JRE5+ Runtime
  • No additional dependencies

ShrinkWrap may run on any Java5 runtime or higher, but requires at least JDK6 for compilation.

API Documentation

JavaDoc for each release is located here .

Open Source Coding

Fork us and get involved with Development .

Archive Creation

The primary entry point to the ShrinkWrap library is the org.jboss.shrinkwrap.api.ShrinkWrap class. From here you may call the create method to make a new Archive, the a generic view of the virtual filesystem which allows the addition of content called Asset s into a location called an ArchivePath. The following table more easily shows ShrinkWrap nomenclature next to more common terms:

Common Term ShrinkWrap Class Description
Archive org.jboss.shrinkwrap.api.Archive A collection of resources, essentially a virtual filesystem
File org.jboss.shrinkwrap.api.Node An entry in an Archive; may represent content or a directory
Path org.jboss.shrinkwrap.api.ArchivePath Location in an Archive under which a Node lives
Asset org.jboss.shrinkwrap.api.Asset Byte-based content within a Node

Additionally, Archive s have many views, and you won’t typically be dealing with the Archive class directly. Instead, ShrinkWrap supplies a few Archive extensions which offer helpful ways of manipulating content relevant to their type.

Archive Type Description
org.jboss.shrinkwrap.api.GenericArchive Simplest type of concrete user-view of an Archive; supports generic operations
org.jboss.shrinkwrap.api.spec.JavaArchive JAR type; allows addition of Class es, Package s, and Manifest operations
org.jboss.shrinkwrap.api.spec.EnterpriseArchive Java EE EAR type; supports Manifest and related spec operations
org.jboss.shrinkwrap.api.spec.WebArchive Java EE WAR type; supports operations common to web application deployments
org.jboss.shrinkwrap.api.spec.ResourceAdaptorArchive Java EE RAR type; supports operations common to resource adaptor deployments

To create an Archive, simply choose your desired archive type and optionally supply a name to the static ShrinkWrap:create method:

GenericArchive myArchive = ShrinkWrap.create(GenericArchive.class,"myArchive.jar");

That’s it! You’ve got your first ShrinkWrap archive!

Adding Content

Of course, an object representing an empty archive is pretty useless. So let’s have a look at adding in some content. As we noted before, content is modeled by the Asset class, so let’s first take a look at some of the Asset implementations provided by ShrinkWrap:

Asset Represents
org.jboss.shrinkwrap.api.asset.ArchiveAsset Nested Archive content
org.jboss.shrinkwrap.api.asset.ByteArrayAsset byte[] or InputStream content
org.jboss.shrinkwrap.api.asset.ClassAsset Java Class content
org.jboss.shrinkwrap.api.asset.ClassLoaderAsset A resource which can be loaded by an optionally-specified ClassLoader
org.jboss.shrinkwrap.api.asset.FileAsset File content
org.jboss.shrinkwrap.api.asset.StringAsset String content
org.jboss.shrinkwrap.api.asset.UrlAsset Content located at a given URL
org.jboss.shrinkwrap.api.asset.EmptyAsset Empty (0-byte) content

Additionally, because Asset is an interface, you may provide your own implementation to supply any byte-based content that may be represented as an InputStream . For instance, the snippet below shows how to present an Activation Framework DataSource as an Asset :

final DataSource dataSource = null; // Assume you have this
  Asset asset = new Asset() {
  @Override
  public InputStream openStream() {
    try {
      return dataSource.getInputStream();
    } catch (final IOException e) {
      throw new RuntimeException(e);
    }
  }
};

The Archive:add method allows us to pass in some Asset content and add it under an ArchivePath.

myArchive.add(myAsset,"path/to/content");
System.out.println(myArchive.toString(true));

Passing a true verbosity flag into the toString method of Archive creates a recursive "ls -l" -style output:

myArchive.jar:
/path/
/path/to/
/path/to/content

The Archive views we covered before are also really helpful, depending upon the type of content you’re working with. For instance, a standard JAR file typically contains .class files and other resources, so the JavaArchive type lets you add these.

ShrinkWrap supports a simple mechanism allowing you to switch “views” of your archive, and it’s provided by the as method of the org.jboss.shrinkwrap.api.Assignable interface; each view in turn extends Assignable. So in order to get your archive to use the JavaArchive view in order to easily add Class resources, you could simply:

myArchive.as(JavaArchive.class).addClasses(String.class, Integer.class);
System.out.println(myArchive.toString(true));
archive.jar:
/java/
/java/lang/
/java/lang/String.class
/java/lang/Integer.class

Using this mechanism is central to keeping ShrinkWrap’s usage clean and intuitive, while providing for a versatility typically found in true multiple-inheritence languages.

Working with File Content

While ShrinkWrap has its roots in Java EE and close ties to the Arquillian Testing Platform, it’s certainly not limited to these domains. In fact, ShrinkWrap on its own intentionally scoped to go no further than act as a virtual filesystem for archives. As such, it provides a simple mechanism for playing nicely with flat-file structures.

Borrowing from our example above, perhaps we’d like to use ShrinkWrap to packageup all of the .class files in the current package and output these as a standard JAR in ZIP format. The code for that would actually be pretty simple:

JavaArchive archive = ShrinkWrap.create(JavaArchive.class,
  "myPackage.jar").addPackage(this.getClass().getPackage());
  System.out.println(archive.toString(true));
  archive.as(ZipExporter.class).exportTo(
    new File("/home/alr/Desktop/myPackage.jar"), true);
javalang.jar:
/org/
/org/alr/
/org/alr/test/
/org/alr/test/TestClass.class

So let’s see what’s going on here. First we create a JavaArchive and add all contents of the current Class ‘s Package . Then we dump the output to the console, just to see what’s included. In the final line, we again use the Assignable facilities of the JavaArchive view to get us into a new view: one capable of exporting to ZIP format. In this case we use the appropriately-named ZipExporter, allowing us to export to a File, OutputStream, or even get the contents as an InputStream so we can deal with the bytes ourselves.

There are 3 types of exporters which ship with ShrinkWrap:

Exporter Output Format
org.jboss.shrinkwrap.api.exporter.TarExporter TAR
org.jboss.shrinkwrap.api.exporter.TarGzExporter TAR.GZ
org.jboss.shrinkwrap.api.exporter.ZipExporter ZIP

Of course, we can also obtain a ShrinkWrap archive from a flat-file in a similar fashion by using one of the standard importers:

Importer Output Format
org.jboss.shrinkwrap.api.importer.TarImporter TAR
org.jboss.shrinkwrap.api.importer.TarGzImporter TAR.GZ
org.jboss.shrinkwrap.api.importer.ZipImporter ZIP

The code for running an import to roundtrip the previous example might look like this:

JavaArchive roundtrip = ShrinkWrap
  .create(ZipImporter.class, "myPackageRoundtrip.jar")
  .importFrom(new File("/home/alr/Desktop/myPackage.jar"))
  .as(JavaArchive.class);

Note how we can pass ZipImporter into the ShrinkWrap.create method, as it’s Assignable as well! Beginning to notice a theme here?

This concludes our brief introduction into manipulating archive content with ShrinkWrap. We hope you’ll find the API to be intuitive and consistent, and welcome you to our community.

Share the Knowledge

Find this guide useful?

There’s a lot more about Arquillian to cover. If you’re ready to learn more, check out the other available guides.

Feedback

Find a bug in the guide? Something missing? You can fix it by forking this website, making the correction and sending a pull request. If you’re just plain stuck, feel free to ask a question in the user discussion forum.

Recent Changelog

  • Sep 21, 2017: Fix(scripts) timeout for waiting for timestamp available on pages is by Matous Jobanek

See full history »