ShrinkWrap Resolvers
Java
Pull request Compare This branch is 12 commits behind shrinkwrap:master.
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Failed to load latest commit information.
api-gradle-embedded-archive
api-maven-archive
api-maven
api
bom
build-resources
depchain-gradle
depchain
impl-gradle-embedded-archive
impl-maven-archive
impl-maven-integration-tests
impl-maven
maven-plugin
spi-maven-archive
spi-maven
spi
.gitignore
.travis.yml
LICENSE
README.asciidoc
pom.xml

README.asciidoc

ShrinkWrap Resolvers Build Status

Introduction to ShrinkWrap Resolvers

While ShrinkWrap offer a precise control of what is packaged into the deployment, it is difficult to be used with third party libraries. Often we don’t control the construction of these libraries, and we certainly shouldn’t be in the business of re-assembling them (and hence further differentiating our tests from the our production runtime deployments). With the advent of Maven and other build systems, typically thirdparty libraries and our own dependent modules are obtained from a backing software repository. In this case we supply a series of coordinates which uniquely identifies an artifact in the repository, and resolve the target files from there.

That is precisely the aim of the ShrinkWrap Resolvers project; it is a Java API to obtain artifacts from a repository system. Currently implemented are grammars and support for Maven-based repository structures (this is separate from the use of Maven as a project management system or build tool; it’s possible to use a Maven repository layout with other build systems) and basic support Gradle based projects.

ShrinkWrap Resolvers is comprised of the following modules:

Name

Maven Coordinates

API

org.jboss.shrinkwrap.resolver:shrinkwrap-resolver-api

SPI

org.jboss.shrinkwrap.resolver:shrinkwrap-resolver-spi

Maven API

org.jboss.shrinkwrap.resolver:shrinkwrap-resolver-api-maven

Maven SPI

org.jboss.shrinkwrap.resolver:shrinkwrap-resolver-spi-maven

Maven Implementation

org.jboss.shrinkwrap.resolver:shrinkwrap-resolver-impl-maven

Maven Implementation with Archive Integration

org.jboss.shrinkwrap.resolver:shrinkwrap-resolver-impl-maven-archive

Embedded Gradle API

org.jboss.shrinkwrap.resolver:shrinkwrap-resolver-api-gradle-embedded-archive

Embedded Gradle Implementation with Archive Integration

org.jboss.shrinkwrap.resolver:shrinkwrap-resolver-impl-gradle-embedded-archive

The separation between the Maven and non-Maven modules is there to enforce modular design and separate out generic resolution from Maven-specific grammars, should the project support other mechanisms in the future.

Adding ShrinkWrap Resolvers to your project

Obtaining ShrinkWrap Resolvers for use in your system can be done in a single pass by declaring a dependency upon the depchain module in a Maven pom.xml:

<dependencies>
    ...
    <dependency>
      <groupId>org.jboss.shrinkwrap.resolver</groupId>
      <artifactId>shrinkwrap-resolver-depchain</artifactId>
      <version>${version.shrinkwrap.resolvers}</version>
      <scope>test</scope>
      <type>pom</type>
    </dependency>
    ...
</dependencies>

This will bring the APIs into the test classpath and the SPIs and Implementation modules into the runtime classpaths (which will not be transitively inherited, as per Maven rules in runtime scope).

Alternatively, you can have finer-grained control over using ShrinkWrap Resolvers by bringing in each module manually:

 <dependencies>
    ...
    <dependency>
      <groupId>org.jboss.shrinkwrap.resolver</groupId>
      <artifactId>shrinkwrap-resolver-api</artifactId>
      <version>${version.shrinkwrap.resolvers}</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.jboss.shrinkwrap.resolver</groupId>
      <artifactId>shrinkwrap-resolver-spi</artifactId>
      <version>${version.shrinkwrap.resolvers}</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.jboss.shrinkwrap.resolver</groupId>
      <artifactId>shrinkwrap-resolver-api-maven</artifactId>
      <version>${version.shrinkwrap.resolvers}</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.jboss.shrinkwrap.resolver</groupId>
      <artifactId>shrinkwrap-resolver-spi-maven</artifactId>
      <version>${version.shrinkwrap.resolvers}</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.jboss.shrinkwrap.resolver</groupId>
      <artifactId>shrinkwrap-resolver-impl-maven</artifactId>
      <version>${version.shrinkwrap.resolvers}</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.jboss.shrinkwrap.resolver</groupId>
      <artifactId>shrinkwrap-resolver-impl-maven-archive</artifactId>
      <version>${version.shrinkwrap.resolvers}</version>
      <scope>test</scope>
    </dependency>
    ...
  </dependencies>
Important

If you happen to use Arquillian BOM, version prior 1.1.0.Final in <dependencyManagement>, it contains a ShrinkWrap Resolvers 1.x version. You must import ShrinkWrap Resolvers BOMs preceding Arquillian BOM in order to get 2.0.x version. Adding a ShrinkWrap BOM is recommended in any case.

ShrinkWrap resolved BOM can be imported via following snippet:

<dependencyManagement>
  <dependencies>
    ...
    <!-- Override dependency resolver with latest version.
         This must go *BEFORE* the Arquillian BOM. -->
    <dependency>
      <groupId>org.jboss.shrinkwrap.resolver</groupId>
      <artifactId>shrinkwrap-resolver-bom</artifactId>
      <version>${version.shrinkwrap.resolvers}</version>
      <scope>import</scope>
      <type>pom</type>
    </dependency>
    ...
  </dependencies>
</dependencyManagement>

Resolving dependencies

The general entry point for resolution is the convenience org.jboss.shrinkwrap.resolver.api.maven.Maven class, which has static hooks to obtain a new org.jboss.shrinkwrap.resolver.api.maven.MavenResolverSystem. Let’s cover most popular use cases for ShrinkWrap Resolver.

Resolution of artifacts specified by Maven coordinates

Maven coordinates, in their canonical form, are specified as following groupId:artifactId:[packagingType:[classifier]]:version. Often, those are referred as G (groupId), A (artifactId), P (packagingType), C (classifier) and V (version). If you omit P and C, they will get their default value, which is packaging of jar and an empty classifier. ShrinkWrap Resolver additionally allows you to skip V in case it has version information available, that would be explained later on.

Resolve a file using Maven coordinates

Here, resolver locates artifact defined by G:A:V and resolves it including all transitive dependencies. Result is formatted as array of File.

File[] files = Maven.resolver().resolve("G:A:V").withTransitivity().asFile();
Avoid transitive dependencies resolution

You might want to change default Maven behavior and resolve only artifact specified by G:A:V, avoiding its transitive dependencies. For such use case, ShrinkWrap Resolvers provides a shorthand for changing resolution strategy, called withoutTransitivity(). Additionally, you might want to return a single File instead of an array.

Maven.resolver().resolve("G:A:V").withoutTransitivity().asSingleFile();
Resolution of multiple artifacts

Very often, you need to resolve more than one artifact. The method resolve(String...) allows you to specify more artifacts at the same time. The result of the call will be an array of File composed by artifacts defined by G1:A1:V1 and G2:A2:V2 including their transitive dependencies.

Maven.resolver().resolve("G1:A1:V1", "G2:A1:V1").withTransitivity().asFile();
Specifying dependency type

Packaging type is specified by P in G:A:P:V coordinates description.

Maven.resolver().resolve("G:A:war:V").withTransitivity().asFile();

Packaging can be of any type, the most common are listed in following table.

Table 1. Packaging types

jar

war

ear

ejb

rar

par

pom

test-jar

maven-plugin

Specifying dependency classifier

With classifier, such as tests, you need to include all G:A:P:C:V parts of coordinates string.

Maven.resolver().resolve("G:A:test-jar:tests:V").withTransitivity().asFile();
Returning resolved artifacts as different type than file

ShrinkWrap Resolvers provides shorthands for returning an InputStream or URL instead of File. Additionally, with shrinkwrap-resolver-impl-maven-archive, you can additionally return results MavenCoordinate or as ShrinkWrap archives, such as JavaArchive, WebArchive or EnterpriseArchive.

Maven.resolver().resolve("G:A:V").withTransitivity().as(File.class);
Maven.resolver().resolve("G:A:V").withTransitivity().as(InputStream.class);
Maven.resolver().resolve("G:A:V").withTransitivity().as(URL.class);
Maven.resolver().resolve("G:A:V").withTransitivity().as(JavaArchive.class);
Maven.resolver().resolve("G:A:war:V").withoutTransitivity().asSingle(WebArchive.class);
Maven.resolver().resolve("G:A:war:V").withTransitivity().as(MavenCoordinate.class);
Note

It’s the responsibility of caller to close InputStream.

Working with artifact metadata

Sometimes, you are more interested in metadata, such as dependencies of a given artifacts instead of artifact itself. ShrinkWrap Resolvers provides you an API for such use cases:

MavenResolvedArtifact artifact = Maven.resolver().resolve("G:A:war:V").withoutTransitivity()
  .asSingle(MavenResolvedArtifact.class);

MavenCoordinate coordinates = artifact.getCoordinate();
MavenArtifactInfo[] dependencies = artifact.getDependencies();
String version = artifact.getResolvedVersion();
ScopeType scope = artifact.getScope();

You can still retrieve resolved artifact from MavenResolvedArtifact:

File file = artifact.asFile();
Working with artifact coordinates

You can also retrieve resolved artifact directly as MavenCoordinate, if you are not interested in more details:

MavenCoordinate[] coordinates = Maven.resolver().resolve("G:A:V")
   .withTransitivity().as(MavenCoordinate.class);
Resolution of artifacts as collection

It might be convenient to work with List interface instead of an array. For such cases, you can wrap the results of resolution using following call:

List<File> files = Maven.resolver().resolve("G:A:V")
    .withTransitivity().asList(File.class);
List<JavaArchive> jars = Maven.resolver().resolve("G:A:V")
    .withTransitivity().asList(JavaArchive.class);
List<MavenCoordinate> coordinates = Maven.resolver().resolve("G:A:V")
    .withTransitivity().asList(MavenCoordinate.class);
Transitive dependency exclusion

In case you need to resolve an artifact while avoiding some of its dependencies, you can follow concept of <exclusions> known for Maven. Following snippet shows how to exclude G:B while resolving G:A:V.

Maven.resolver()
  .addDependencies(
    MavenDependencies.createDependency("G:A:V", ScopeType.COMPILE, false,
      MavenDependencies.createExclusion("G:B"))).resolve().withTransitivity().asFile();
Control resolution results by using a strategy

In special cases, excluding a single dependency is not the behaviour you want to achieve. For instance, you want to resolve all test scoped dependencies of an artifact, you want to completely avoid some dependency while resolving multiple artifacts or maybe you’re interested in optional dependencies. For those cases, ShrinkWrap Resolvers allows you to specify a MavenResolutionStrategy. For instance, you can exclude G:B from G:A:V (e.g. the same as previous examples) via following snippet:

Maven.resolver().resolve("G:A:V").using(new RejectDependenciesStrategy(false, "G:B")).asFile();
Note

Methods withTransitivity() and withoutTransitivity() are just a convenience methods to avoid you writing down strategy names. The first one calls TransitiveStrategy while the latter calls NotTransitiveStrategy.

Strategies are composed of an array of MavenResolutionFilter instances and TransitiveExclusionPolicy instance. While defining the first allows you to transform dependency graph of resolved artifacts, the latter allows you to change default behavior when resolving transitive dependencies. By default, Maven does not resolve any dependencies in provided and test scope and it also skips optional dependencies. ShrinkWrap resolver behaves the same way by default, but allows you to change that behaviour. This comes handy especially if when you want to for instance resolve all provided dependencies of G:A:V. For your convenience, ShrinkWrap Resolvers ships with strategies described in following table.

Table 2. Strategies available in ShrinkWrap Resolver

AcceptAllStrategy

Accepts all dependencies of artifacts. Equals TransitiveStrategy.

AcceptScopesStrategy

Accepts only dependencies that have defined scope type.

CombinedStrategy

This allows you to combine multiple strategies together. The behaviour defined as logical AND between combined strategies.

NonTransitiveStrategy

Rejects all dependencies that were not directly specified for resolution. This means that all transitive dependencies of artifacts for resolution are rejected.

RejectDependenciesStrategy

Rejects dependencies defined by G:A (version is not important for comparison, so it can be omitted altogether). By default, it is transitive: RejectDependenciesStrategy("G:A", "G:B") means that all dependencies that origin at G:A or G:B are removed as well. If you want to change that behavior to reject defined dependencies but to keep their descendants, instantiate strategy as following: RejectDependenciesStrategy(false, "G:A", "G:B")

TransitiveStrategy

Acceps all dependencies of artifacts. Equals AcceptAllStrategy.

Control sources of resolution

ShrinkWrap Resolvers allows you to specify where do you want to resolve artifacts from. By default, it uses classpath (also known as Maven Reactor) and Maven Central repository, however you can programmatically alter the behavior.

Maven.configureResolver().withClassPathResolution(false).resolve("G:A:V").withTransitivity().asFile();
Maven.configureResolver().withMavenCentralRepo(false).resolve("G:A:V").withTransitivity().asFile();
Maven.configureResolver().workOffline().resolve("G:A:V").withTransitivity().asFile();
Maven.configureResolver().useLegacyLocalRepo(true).resolve("G:A:V").withTransitivity().asFile();

While classpath resolution is handy for testing SNAPSHOT artifacts that are not yet installed in any of the Maven repository, making ShrinkWrap Resolvers offline avoids accessing any repositories but local cache. You can also set to ignore origin of artifacts present in local repository via useLegacyLocalRepo(true) method.

Note

If offline mode is activated, original of artifacts in local repository is automatically ignored. This is a difference from default Maven behavior. See Legacy local repository for further reference.

Specify settings.xml

While controlling classpath resolution and Maven Central comes handy, sometimes you might want to specify completely different settings.xml file than default for your test execution. This can be done via following API calls:

Maven.configureResolver().fromFile("/path/to/settings.xml")
  .resolve("G:A:V").withTransitivity().asFile();

Maven.configureResolver().fromClassloaderResource("path/to/settings.xml")
  .resolve("G:A:V").withTransitivity().asFile();
Warning

ShrinkWrap Resolvers will not consume settings.xml you specified on command line (-s settings.xml) or in the IDE. It reads settings.xml files at their standard locations, which are ~/.m2/settings.xml and $M2_HOME/conf/settings.xml unless overridden in the API or via System property.

Define Maven repositories manually

Ultimately, it is possible to define and/or override Maven repositories defined in settings.xml or pom.xml. Repositories defined via API always take precedence. In case there is a repository with same id configured in either settings.xml or pom.xml file, it will be ignored.

Maven.configureResolver().withRemoteRepo("my-repository-id", "url://to/my/repository", "layout")
  .resolve("G:A:V").withTransitivity().asFile();

Maven.configureResolver().withRemoteRepo(MavenRemoteRepositories.createRemoteRepository("my-repository-id", "url://to/my/repository", "layout"))
  .resolve("G:A:V").withTransitivity().asFile();

Resolution of artifacts defined in POM files

While previous calls allow you to manually define what you want to resolve, in Maven projects, you have very likely specified this information already, in you pom.xml file. ShrinkWrap Resolvers allows you to follow DRY principle and it is able to load metadata included there.

ShrinkWrap Resolvers constructs so called effective POM model (simplified, that is your pom.xml file plus parent hierarchy and Super POM, Maven default POM file). In order to construct the model, it uses all local repository, classpath repository and remote repositories. Once the model is loaded, you can use the metadata in there to be automatically added to artifacts to be resolved.

Tip

You can use Maven.configureResolver() to tune what repositories will be questioned during effective POM model construction.

Resolving an artifact with version defined in effective POM

In case, you want to resolve G:A:V, you can simply specify G:A instead. For artifacts with non JAR packaging type or classifier, you must use alternative syntax with question mark '?', such as G:A:P:? or G:A:P:C:?.

Maven.resolver().loadPomFromFile("/path/to/pom.xml").resolve("G:A").withTransitivity().asFile();

Maven.resolver().loadPomFromClassLoaderResource("/path/to/pom.xml").resolve("G:A:P:?")
  .withTransitivity().asFile();
Resolving artifacts defined in effective POM

ShrinkWrap Resolvers allows you to artifacts defined with specific scope into list of artifacts to be resolved. This way, you don’t need to alter your tests if you change dependencies of your application. You can either use importDependencies(ScopeType...) or convenience methods, that cover the most frequent usages (importRuntimeDependencies(), importTestDependencies() and importRuntimeAndTestDependencies():

Maven.resolver().loadPomFromFile("/path/to/pom.xml")
  .importDependencies(ScopeType.TEST, ScopeType.PROVIDED)
  .resolve().withTransitivity().asFile();

Maven.resolver().loadPomFromFile("/path/to/pom.xml").importRuntimeDependencies()
  .resolve().withTransitivity().asFile();
Tip

Runtime in convenience methods means all the Maven scopes that are used in application runtime, which are compile, runtime, import and system. If you need to select according to Maven scopes, go for importDependencies(ScopeType...) instead.

Specifying profiles to be activated

By default, ShrinkWrap Resolvers activates profiles based on property value, file presence, active by default profiles, operating system and JDK. However, you can force profiles in same way as you would do via -P in Maven.

Maven.resolver().loadPomFromFile("/path/to/pom.xml", "activate-profile-1", "!disable-profile-2")
        .importRuntimeAndTestDependencies().resolve().withTransitivity().asFile();

Version Range Resolution

The ShrinkWrap Resolver API allows for resolution of available versions info from a requested range. The Maven documentation specifies the version range syntax; examples of obtaining info about versions greater or equal to 1.0.0 for a specific coordinate is presented below.

final MavenVersionRangeResult versionRangeResult = Maven.resolver().resolveVersionRange("G:A:[1.0.0]");

MavenVersionRangeResult provides three methods:

  • getLowestVersion() for obtaining the lowest resolved version coordinate,

  • getHighestVersion() for the highest version,

  • getVersions() which returns a List of obtained coordinates, ordered from lowest to highest version.

final MavenCoordinate lowest = versionRangeResult.getLowestVersion();
final MavenCoordinate highest = versionRangeResult.getHighestVersion();
final List<MavenCoordinate> versions = versionRangeResult.getVersions();

System properties

ShrinkWrap Resolvers allows you to override any programmatic configuration via System properties.

Table 3. System properties altering behavior of ShrinkWrap Resolvers

org.apache.maven.user-settings

Path to user settings.xml file. In case org.apache.maven.global-settings settings is provided too, they both are merged, user one has the priority.

org.apache.maven.global-settings

Path to global settings.xml file. In case org.apache.maven.user-settings settings is provided too, they are merged, user one has the priority.

settings.security (prior 2.2.0 org.apache.maven.security-settings)

Path to settings-security.xml, that contains encrypted master password for password protected Maven repositories.

org.apache.maven.offline

Flag there to work in offline mode.

maven.repo.local

Path to local repository with cached artifacts. Overrides value defined in any of the settings.xml files.

maven.legacyLocalRepo

Flag whether to ignore origin tracking for artifacts present in local repository

Experimental features

Warning

Following features are in their early development stages. However, they should work for the most common use case. Feel free to report a bug in SHRINKRES project if that not your case.

Debugging and logging

ShrinkWrap Resolver allows you to get internal details of its session. This is handy especially if you are resolving artifacts from a pom file or if you are interested what dependency coordinates will have their version automatically resolved in tests. In order to get access to internal data, perform cast of resolver object (in any stage) to MavenWorkingSessionContainer and retrieve the session. Important: MavenWorkingSession represents an interal API and can be changed in future versions. Use it only for debugging or in ShrinkWrap Resolver extensions, interacting with the session from tests should be avoided.

MavenResolverSystem resolver = Maven.resolver();
MavenWorkingSession session = ((MavenWorkingSessionContainer) resolver).getMavenWorkingSession();

ShrinkWrap Resolvers uses Java Util Logging for logging purposes. If you want to increase verbosity, provide logging.properties file and make sure it is loaded in Java VM by specifying -Djava.util.logging.config.file=/path/to/logging.properties. See following example, which enables logging of interaction with Maven Repositories into console output:

# Specify the handlers to create in the root logger
# (all loggers are children of the root logger)
# The following creates two handlers
handlers= java.util.logging.ConsoleHandler

# Set the default logging level for new ConsoleHandler instances
java.util.logging.ConsoleHandler.level= FINEST

# Set global verbose level
.level= INFO

# Set log verbose level for ShrinkWrap Resolvers
org.jboss.shrinkwrap.resolver.impl.maven.logging.LogTransferListener.level= FINEST
org.jboss.shrinkwrap.resolver.impl.maven.logging.LogRepositoryListener.level= FINEST
org.jboss.shrinkwrap.resolver.impl.maven.logging.LogModelProblemCollector.level= FINEST

ShrinkWrap Resolver Maven Plugin

ShrinkWrap Resolver Maven plugin allows you to propagate settings you specified on command line into test execution. Settings comprises of: paths to the pom.xml file and settings.xml files, activated/disabled profiles, offline flag and path to local repository. No support for IDE exists at this moment.

In order to activate the plugin, you need to add following snippet into <build> section of your pom.xml file.

<plugin>
  <groupId>org.jboss.shrinkwrap.resolver</groupId>
  <artifactId>shrinkwrap-resolver-maven-plugin</artifactId>
  <version>${version.shrinkwrap.resolvers}</version>
  <executions>
    <execution>
      <goals>
        <goal>propagate-execution-context</goal>
      </goals>
    </execution>
  </executions>
</plugin>

Then, in your test you can do the following:

Maven.configureResolverViaPlugin().resolve("G:A").withTransitivity().asFile();

Maven Importer

MavenImporter is the most advanced feature of ShrinkWrap Resolvers. Instead of you being responsible for specifying how testing archive should look like, it reuses information defined in your pom.xml in order to construct the archive. So, no matter how your project looks like, you can get a full archive, as you would deploy it into the application server within a single line of code.

MavenImporter is able to compile sources, construct MANIFEST.MF, fetch the dependencies and construct archive as Maven would do. It does not required any data to be prepared by Maven, however it can profit from those if they exist.

ShrinkWrap.create(MavenImporter.class)
  .loadPomFromFile("/path/to/pom.xml").importBuildOutput().as(WebArchive.class);

ShrinkWrap.create(MavenImporter.class)
  .loadPomFromFile("/path/to/pom.xml", "activate-profile-1", "!disable-profile-2")
  .importBuildOutput().as(WebArchive.class);

ShrinkWrap.create(MavenImporter.class).configureFromFile("/path/to/settings.xml")
  .loadPomFromFile("/path/to/pom.xml").importBuildOutput().as(JavaArchive.class);
Important

Maven Importer does not currently support other packagings but JAR and WAR. Also, it does not honor many of Maven plugins, currently it supports their limited subset.

Gradle Importer

Gradle Importer realizes functions similar to Maven Importer however for Gradle using Gradle Tooling API. It includes support for multi-module projects. Importer is configured to execute by default build --exclude-task test, what means it skips the tests execution. It’s possible to alter this behaviour just by using appropriate API methods like e.g. forTasks or withArguments.

ShrinkWrap.create(EmbeddedGradleImporter.class)
  .forThisProjectDirectory().importBuildOutput().as(WebArchive.class);

ShrinkWrap.create(EmbeddedGradleImporter.class)
  .forProjectDirectory("/path/to/dir").importBuildOutput("/path/to/result/war").as(WebArchive.class);

ShrinkWrap.create(EmbeddedGradleImporter.class)
  .forProjectDirectory("/path/to/dir").forTasks("task1","task2").withArguments("arg1","arg2")
  .importBuildOutput().as(WebArchive.class);

If you execute some custom tasks which modifies the built result you might want to perform Gradle Importer build in a custom directory. To do this you need to pass an argument -PbuildDir=your-build-directory and then use the importBuildOutput("your-build-directory/libs/your-output-file") method.

Additional Gradle Importer full usage example can be found under https://github.com/mmatloka/arquillian-gradle-sample .

Other

Additionally, using different JDK for running tests and compiling sources is not supported, although it should work if you are for instance compiling sources targeting JDK6 while being bootstrapped on JDK7.

Sometimes running tests from IDE might not work correctly. The most common cause is the working directory is set to project when it should be the module.