Maven Specifications and Environments: Part 2

In the previous post, we looked at the concept of environments—implementations of specifications (a concept discussed two posts ago).

When you have a Maven project that you want to be runtime-independent, but coded to a particular JCP specification, things get a little hairy. Your code must compile against an API jar (or API jars, or, if you’ve read this series of posts, a specification), but you want it to run at test time against a particular environment, and you want that environment to dictate which API jars should be used.

This sounds like a contradiction until we realize that Maven respects the order in which <dependency> elements are listed.

The TL;DR here is: when you are putting together your <dependency> elements, list them in your pom.xml in typically reverse order: list test-scoped dependencies first, then runtime-scoped dependencies, then provided-scoped dependencies and finally compile-scoped dependencies.

Why?

When you compile, none of the test-scoped dependencies will be “in scope”, so they will simply be ignored. Next on the classpath will be your provided-scoped dependencies followed by your compile-scoped dependencies. At compile time, none of this really matters: where your provided-scoped dependencies appear on your classpath is typically irrelevant.

But when you test, the order is suddenly quite important. If your test-scoped dependencies come first, then any transitive API jars that they pull in (for example) will be the ones that are actually used on your classpath—they’ll “come before” any provided-scoped API jars you have in order to support runtime environment-independent compilation. Those provided-scoped dependencies will still be on your classpath at test time, but they’ll never be referenced: the runtime environment you’re testing against should (must!) dictate its own API jars to be used, so those are the ones you’ll end up using.

To make this slightly more concrete, let’s say you have a CDI project and you want to be able to run it on either Weld or OpenWebBeans. Let’s say further that you’re going to run unit tests using Weld. Now recall that Weld uses the JBoss-authored version of javax.interceptor packages reified in the org.jboss.spec.javax.interceptor:jboss-interceptors-api_1.2_spec:jar artifact, not the javax.interceptor:javax.interceptor-api:jar artifact. To preserve runtime environment independence, you want to compile (let’s say) against the javax.interceptor:javax.interceptor-api:jar artifact, but when you test with Weld you want Weld to use whatever API jars it wants, not the runtime-environment-independent ones you happen to have selected and compiled against.

To do this, you’ll add Weld first in test scope, then javax.interceptor:javax.interceptor-api:jar in provided scope. When you compile, you’ll use javax.interceptor:javax.interceptor-api:jar; when you test you’ll end up transitively using javax.interceptor:javax.interceptor-api:jar.

When you expand this strategy to include environments and specifications as defined earlier in this series of posts, component-oriented development becomes much simpler.

Maven Specifications and Environments: Part 1

In the previous post, we took a look at specifications: Maven pom.xml files that represent pom-typed artifacts that house dependencies in compile scope, but are themselves depended upon in provided scope.

Specifications give you the artifacts you need to compile your code. But (if built properly) they don’t come with implementations. This lets your code be coupled to, say, the CDI specification without being coupled to, say, Weld (instead of, say, OpenWebBeans).

I’ve settled on the term environment to describe another Maven pattern I use to back a particular specification with the jars needed to implement it. The challenge here is that a given environment is—like the specification it implements—comprised of many jars. To reduce boilerplate, we want to find a good way to declare our runtime dependence on various jars that implement the classes and interfaces defined by the API jars our code relies upon during compilation.

Another challenge is that strictly speaking an environment often correctly and legally brings in its own implementations of API jars. For example, Weld does not depend on javax.interceptor:javax.interceptor-api:jar (which houses the javax.interceptor packages that reify the interceptors specification), but on org.jboss.spec.javax.interceptor:jboss-interceptors-api_1.2_spec:jar. This jar file also houses the javax.interceptor packages that reify the interceptors specification. This plurality of API jars reifying the same (English) specification is entirely legal, but you should only have one API jar at runtime, and the runtime (Weld, OpenWebBeans) should pick it (org.jboss.spec.javax.interceptor:jboss-interceptors-api_1.2_spec:jar, javax.interceptor:javax.interceptor-api:jar). That is, at runtime you want the runtime environment to dictate which API jars are actually on the classpath, not your own Maven pom.xml file.

The idea behind an environment as represented by a pom.xml file is basically the same as that of a specification as represented by a pom.xml file. You define your pom.xml to have a packaging type of pom, and you list your elements, but this time you put them in runtime scope. Then you depend on this new environment by depending on it in runtime scope. The net effect is that its runtime-scoped dependencies become your project’s transitive dependencies in runtime scope.

Note that an environment defined like this doesn’t (necessarily) depend on a specification as we defined it in the previous post. An environment always supplies what it needs, and code designed to run in any environment of a particular kind is compiled against a specification as defined in the prior post.

Environments can be composed, and can be abstract or concrete. Here’s an example of one of the environments I use in my microBean projects. It is abstract, in the sense that it sketches out a modular runtime but lacks a CDI implementation. It does, however, specify a javax.validation implementation (Hibernate) and a Java Expression Language implementation (Glassfish):

<groupId>org.microbean</groupId>
<artifactId>microbean-abstract-environment</artifactId>
<version>0.5.3-SNAPSHOT</version>
<packaging>pom</packaging>
<dependencyManagement>
<dependencies>
<!– Normal dependencies. –>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>${project.artifactId}</artifactId>
<version>${project.version}</version>
<type>pom</type>
</dependency>
<dependency>
<groupId>com.fasterxml</groupId>
<artifactId>classmate</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>javax.el</artifactId>
<type>jar</type>
<version>3.0.1-b10</version>
</dependency>
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator-cdi</artifactId>
<version>6.0.13.Final</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>org.jboss.logging</groupId>
<artifactId>jboss-logging</artifactId>
<version>3.3.2.Final</version>
</dependency>
<dependency>
<groupId>org.microbean</groupId>
<artifactId>microbean-configuration</artifactId>
<version>0.4.4</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>org.microbean</groupId>
<artifactId>microbean-configuration-api</artifactId>
<version>0.4.4</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>org.microbean</groupId>
<artifactId>microbean-configuration-cdi</artifactId>
<version>0.4.5</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>org.microbean</groupId>
<artifactId>microbean-main</artifactId>
<version>7</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.8.0-beta2</version>
<type>jar</type>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!– Runtime-scoped dependencies. –>
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>javax.el</artifactId>
<type>jar</type>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator-cdi</artifactId>
<type>jar</type>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.microbean</groupId>
<artifactId>microbean-configuration</artifactId>
<type>jar</type>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.microbean</groupId>
<artifactId>microbean-configuration-api</artifactId>
<type>jar</type>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.microbean</groupId>
<artifactId>microbean-configuration-cdi</artifactId>
<type>jar</type>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.microbean</groupId>
<artifactId>microbean-main</artifactId>
<type>jar</type>
<scope>runtime</scope>
</dependency>
</dependencies>
view raw 03.pom.xml hosted with ❤ by GitHub

Here is another example of an environment that I use in my microBean projects that uses the abstract environment above:

<groupId>org.microbean</groupId>
<artifactId>microbean-weld-se-environment</artifactId>
<version>0.5.4-SNAPSHOT</version>
<packaging>pom</packaging>
<dependencyManagement>
<dependencies>
<!– Imports. –>
<dependency>
<groupId>org.microbean</groupId>
<artifactId>microbean-abstract-environment</artifactId>
<version>0.5.3-SNAPSHOT</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.jboss.weld</groupId>
<artifactId>weld-core-bom</artifactId>
<version>3.0.5.Final</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!– Normal dependencies. –>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>${project.artifactId}</artifactId>
<version>${project.version}</version>
<type>pom</type>
</dependency>
<dependency>
<groupId>org.jboss</groupId>
<artifactId>jandex</artifactId>
<version>2.0.5.Final</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-jdk14</artifactId>
<version>1.8.0-beta2</version>
<type>jar</type>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!– Runtime-scoped dependencies. –>
<dependency>
<groupId>org.jboss</groupId>
<artifactId>jandex</artifactId>
<type>jar</type>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.jboss.weld.se</groupId>
<artifactId>weld-se-core</artifactId>
<type>jar</type>
<scope>runtime</scope>
<exclusions>
<exclusion>
<groupId>org.jboss.spec.javax.el</groupId>
<artifactId>jboss-el-api_3.0_spec</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.microbean</groupId>
<artifactId>microbean-abstract-environment</artifactId>
<type>pom</type>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-jdk14</artifactId>
<type>jar</type>
<scope>runtime</scope>
</dependency>
</dependencies>
view raw 04.pom.xml hosted with ❤ by GitHub

Line 85 is where we pull in the abstract environment. Note that it is in runtime scope. That means we pull in everything that environment defines, as well as whatever else is listed here. The end result is the contents of that abstract environment, plus the Jandex runtime, a SLF4J logging binding and Weld itself. At line 76, you can see that because the abstract environment has already pulled in a runtime implementation of the Java Expression Language, it by definition has supplied its own EL API jar, so we want to make sure that that is the API jar in use for that specification, so we exclude JBoss’ EL API jar here.

Once this environment has been defined, then we just have to use it. Let’s say we want to build a program that will run in this environment. In our project’s pom.xml, we would simply do this:

<dependency>
<groupId>org.microbean</groupId>
<artifactId>microbean-weld-se-environment</artifactId>
<version>0.5.4-SNAPSHOT</version>
<type>pom</type>
<scope>runtime</scope> <!– or test –>
</dependency>
view raw 05.pom.xml hosted with ❤ by GitHub

The last remaining hurdle is testing. Even when compiling code against a specification, you often want to test it in one of possibly many environments. When you do this, you want to make sure you’re testing against the environment and its dependencies, and not the provided-scoped specification that you’re compiling against. (Often the differences are merely academic, but not always.) We’ll look at this in the next post.

Maven Specifications and Environments: Part 0

I’d like to blog about two concepts that I’ve finally figured out how to express in Maven.

The first is that of a specification.  By specification, I mean loosely a collection of versioned artifacts that you code to, but without relying on any particular underlying implementation of those versioned artifacts.

Consider the CDI specification—the online document.  It is expressed in code form in terms of Java packages like javax.enterprise.inject, javax.enterprise.event, and so on.  These packages, in turn, and their classes, are reified in so-called API jars such as javax.enterprise:cdi-api:2.0:jar.  While an API jar is not itself the specification (different vendors may supply their own definitionally functionally equivalent reifications of the specification document) nor an implementation of it, it is one of possibly several reifications of the APIs described by the specification, and so when we’re talking about specifications in terms of Java code, it’s useful to just treat the API jars (from any given vendor) as the specification itself.

Note as well that the CDI specification (specifically) is actually a directed acyclic graph of API jars.  For example, the javax.enterprise:cdi-api:2.0:jar artifact depends on javax.el:el-api:3.0.0:jar, javax.inject:javax.inject:1:jar and javax.interceptors:interceptor-api:1.2:jar.  (It also has to depend on a version of the javax.annotation:javax.annotation-api:jar artifact, but omits this requirement for some reason, as does the specification document.)

So broadly speaking these jars comprise one possible reification of the CDI specification.  (They are not its implementation.)

These jars are also, of course, on their own useful only to prevent compilation errors.  Without a backing implementation, such as Weld, they’re otherwise useless.

Now, if you are a Maven user and you depend on javax.enterprise:cdi-api:2.0:jar in compile scope, then you will also depend on its dependencies in compile scope.  That also means perhaps less obviously that you’ll drag these jars with you into any runtime environment you might find your code in, even if that runtime environment happens to already have those jars.  In fact, in many cases, these jars—or functionally definitionally equivalent ones supplied by another vendor—will already be present, because the runtime that implements CDI 2.0 will include them.  So (as you probably know) you want to depend on javax.enterprise:cdi-api:2.0:jar in provided scope.  This is a means of instructing Maven that javax.enterprise:cdi-api:2.0:jar is provided by some implementation of the specification it represents.

But a dependency in provided scope does not also drag in its transitive dependencies!  So if you do this, you’ll miss the rest of the jars that comprise the CDI specification.  So you’ll also want to add explicit dependencies on javax.annotation:javax.annotation-api:1.3:jarjavax.el:el-api:3.0.0:jar, javax.inject:javax.inject:1:jar and javax.interceptors:interceptor-api:1.2:jar.

So this might look like this:


<dependencies>
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.3</version>
<type>jar</type>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.el</groupId>
<artifactId>javax.el-api</artifactId>
<version>3.0.1-b04</version>
<type>jar</type>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.enterprise</groupId>
<artifactId>cdi-api</artifactId>
<version>2.0</version>
<type>jar</type>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<version>1</version>
<type>jar</type>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.interceptor</groupId>
<artifactId>javax.interceptor-api</artifactId>
<version>1.2</version>
<type>jar</type>
<scope>provided</scope>
</dependency>
</dependencies>

view raw

00.pom.xml

hosted with ❤ by GitHub

That is a lot of boilerplate to remember each time you want, in practical terms, to depend on the CDI specification without coupling yourself to, say, Weld (one of several possible CDI-compliant runtimes).  Can we reduce this boilerplate?

We can.  A neat little trick of Maven is that your pom.xml can depend on another pom.xml in whatever scope you like.  Let’s see how this helps us out.

First, though, a slight digression.  Many of you may be familiar with import scope.  That’s not what I’m talking about here.  Maven defines a weird scope called import that (a) isn’t really a scope, (b) can be used only from within a  stanza, and (c) is applicable only to artifacts of type pom.  Briefly, if you add a  element in your pom.xml‘s  stanza that references an artifact of type pom with a scope of import, then that pom.xml‘s  stanza’s contents are effectively copied by value into your pom.xml‘s  stanza in place of the import-scoped  itself.  So what you’re really “importing” is the  stanza of the target pom.xml and nothing else.  You can read more about import scope in the official Maven documentation.  But remember the shorthand takeaway: it’s basically a textual templating mechanism, not an actual scope.

Whether or not you use import scope in your  stanza, you can depend on artifacts not just of type jar or war or zip and so on, but also on artifacts of type pom.  When you do this, the artifact of type pom in question becomes a direct dependency of your project, and any dependencies it declares become transitive dependencies of your project.  In terms of dependency graphs, this is no different from what happens when you depend on, say, JUnit (a jar artifact) and it drags in Hamcrest (a jar artifact): Hamcrest becomes a transitive dependency of your project.

This is a powerful tool for constructing specifications.  If we create a pom.xml whose stanza contains API jars and whose element is of type pom, then if we depend on this newly created artifact of type pom, we’ll get all those API jars as transitive dependencies. Here’s an example, showing only the important bits of such a pom.xml:


<groupId>com.foobar</groupId>
<artifactId>cdi-specification-pom</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>pom</packaging>
<dependencies>
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.3</version>
<type>jar</type>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>javax.el</groupId>
<artifactId>javax.el-api</artifactId>
<version>3.0.1-b04</version>
<type>jar</type>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>javax.enterprise</groupId>
<artifactId>cdi-api</artifactId>
<version>2.0</version>
<type>jar</type>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<version>1</version>
<type>jar</type>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>javax.interceptor</groupId>
<artifactId>javax.interceptor-api</artifactId>
<version>1.2</version>
<type>jar</type>
<scope>compile</scope>
</dependency>
</dependencies>

view raw

01.pom.xml

hosted with ❤ by GitHub

One thing you’ll notice is that each is declared to be in compile scope. I could have omitted this line (in which case compile is assumed to be the default) but I wanted to make it explicit. If I had used provided scope, then if you depended on this pom-type artifact its dependencies—the very jar files you’re interested in—would not appear in your project, since provided-scoped dependencies are not transitive!

Instead, we declare them as compile-scoped dependencies, and then you can depend on this pom-type artifact in provided scope! The net result is that all of its dependencies—the jars you’re interested in—will show up in your project as transitive provided-scoped dependencies: exactly what we want! We replace twenty-odd lines of boilerplate with about four.

So let’s refine our definition of what a specification is (for the purposes of this discussion): it’s a pom.xml:

  • whose packaging type is pom
  • whose dependency elements are in compile scope
  • whose dependencies are “API jars” as sketchily defined above and their dependencies

Here’s a partial example showing what I mean. Suppose the com.foobar:cdi-specification-pom:pom artifact is essentially the pom.xml listed above. Then if you wanted to use it as a specification, you could simply do:


<dependencies>
<dependency>
<groupId>com.foobar</groupId>
<artifactId>cdi-specification-pom</artifactId>
<version>0.0.1-SNAPSHOT</version>
<type>pom</type>
<scope>provided</scope>
</dependency>
</dependencies>

view raw

02.pom.xml

hosted with ❤ by GitHub

Now you’ll get javax.el:el-api:3.0.0:jar, javax.inject:javax.inject:1:jar, javax.interceptors:interceptor-api:1.2:jar and javax.annotation:javax.annotation-api:jar as transitive provided-scoped dependencies.

In the next post, I’ll cover the idea of environments—runtime implementations of these specifications also expressed as pom.xml files.

microBean Helm: Using Maven Repositories

About half a year ago, I wrote microBean Helm, a port of the Helm ecosystem to Java.  I was motivated in part to do this by the recognition that Helm chart repositories could be seen as fledgling, partial implementations of Maven repositories.  What if a Maven repository ecosystem—complete with caching, mirroring and all the rest—could be used as a chart repository?

I’m proud to introduce microBean Helm Maven Integration, a project that implements an AbstractChartResolver in terms of Maven’s repository ecosystem.

The MavenRepositoryChartResolver class will produce a Java object representing a Helm chart in memory from Maven repository coordinates, resolving it if necessary from a remote Maven repository, taking into account a user’s .m2/settings.xml file.  From there, it can be plugged into the other Helm-related tooling in the microBean Helm ecosystem.

Components and Environments

I am playing around with the concepts of components and environments in my microbean experiments.

The fundamental idea behind microbean is that with CDI 2.0 every last piece of your application can be its own self-contained artifact (including the main class with its main() method).  Each such artifact is a CDI bean archive.  When you put them all together on the classpath and run java org.microbean.main.Main, then your application works.  This might include running servers, deploying things, and so on, but it’s all loosely coupled and tiny. It is the act of bringing these archives together—drawing a lasso around them with a CLASSPATH—that constitutes deployment.

As we’ve seen, CDI has the notion of a bean archive, but it doesn’t really have a notion of a group of bean archives.  I am going to use the wonderfully overloaded term component to describe this concept.

These components might need some other facilities provided by various portable extensions.  There is a hazy way in which a group of portable extensions providing services is different from a group of bean archives providing business logic.  I’ll call this aggregation an environment.

With these provisional terms in place, we can thus say that components run in environments.  We can also say that environments support components.

Then the act of deploying a microbean application becomes: pick one or more environments and one or more components and put them on the classpath, and then run java org.microbean.main.Main (or any other main class that simply starts up a CDI SE container and shuts it down; this just happens to be a convenient prefabricated one).

How could we represent these components?  On disk, we don’t really want to represent them at all.  A component as used here is a notional construct, after all: it’s a bunch of jars that belong together in some way.  For management purposes, however, representing them in Maven pom.xml files of a particular kind looks like a good way to go.

This also seems to be a good way to represent environments as well.

Maven’s pom.xml file and the dependency graph it implies is one of the most misunderstood artifacts in all of Java software engineering.  Most developers know that it describes a project and its dependencies, but did you know you can include a non-jar project pom.xml file (an artifact of type pom) as a dependency itself, thus effectively pulling in (transitively) its dependencies?  This can lead to version surprises, but did you know that a pom.xml‘s section can be used to lock down the version of a given artifact, whether it appears directly or transitively as a dependency? Finally, to complete the puzzle, did you know that a pom.xml can, itself, appear in its own <dependencyManagement> section, thus offering a way to lock its own version down as well as those of its dependencies?

These features mean that we can define both components and environments as artifacts of type pom.  I’ll be exploring these concepts over the next few blog posts.

MicroBean Launcher

This is the eleventh of a series of posts on some of the personal projects I’ve been working on.  As I hope you’ll see, they all fit together.  The previous post covered MicroBean Commons CLI.

This post covers MicroBean Launcher.  Its website is here, its source code is here and its binaries are available from Maven Central.

MicroBean Launcher lets you run a Java SE CDI 2.0 application from the command line by specifying Maven artifact coordinates to other bean archives, transitively resolving them and their dependencies, using MicroBean Maven CDI, to your local Maven repository.

To install MicroBean Launcher, place it (and its minimal dependencies) on your classpath.

Here is an example of its usage, assuming it and its dependencies are on your classpath already:

java org.microbean.launcher.main.Main --artifactPath com.foobar:frobnicator:1.0,com.foobar:caturgiator:2.0

This command will download, if necessary, com.foobar‘s frobnicator jar artifact at version 1.0 and com.foobar‘s caturgiator jar artifact at version 2.0.  These files and their transitive compile- and runtime-scoped dependencies will be placed in the current user’s local Maven repository (~/.m2/repository by default, but the user’s ~/.m2/settings.xml file, which can dictate where the local repository is, is honored).  (If the artifacts already exist, then no download happens.)  A classpath will be built out of all of these artifacts and effectively appended to the current classpath, and MicroBean Main will be invoked.

Why is this useful?  For one, specifying a classpath can now be done in terms of Maven artifact coordinates instead of local filesystem references.  Because MicroBean Maven CDI is in charge of dependency resolution using Maven’s own resolution machinery under the covers, you don’t need to be aware of whether the artifacts in question were downloaded or already present.

But more than this, note that in the last ten blog posts the installation instructions have been the same: you place whatever bean archive is being described on your classpath.  This is one of the nice things about CDI: CDI archives are loosely coupled modules that can be discovered.

This means you can compose a Java SE CDI 2.0 application together by simply referring to Maven artifacts.

Specifically, assuming you have MicroBean Launcher and its dependencies on your CLASSPATH, if you write, say, a JAX-RS Application and a root resource class that it exposes, and place them on your classpath, then you can run that application immediately, downloading only what you need and only what you don’t already have, by running a command line similar to the following:

java org.microbean.launcher.main.Main --artifactPath org.microbean:microbean-jersey-container-grizzly2-http-cdi-extension,org.microbean:microbean-jersey-container-grizzly2-http-cdi,org.microbean:microbean-grizzly-http-server-cdi

Note in particular that your application remains standards-compliant, and you selected the server you wanted to use to run it dynamically from the command line.  You wrote only the code you needed to and none other.

Note that the second time you run this all of the artifacts will already be present on your system.

These are early days for these personal projects and I’ll have plenty more to say about them in the future.  Thanks for reading!

MicroBean Maven CDI

This is the ninth of a series of posts on some of the personal projects I’ve been working on.  As I hope you’ll see, they all fit together.  The previous post covered MicroBean Jersey Container Grizzly2 HTTP CDI Extension.  The next post covers MicroBean Commons CLI.

This post covers MicroBean Maven CDI.  Its website is here, its source code is here and its binaries are available from Maven Central.

MicroBean Maven CDI adapts the inner workings of the Maven Artifact Resolver project so that it can be exposed in a CDI 2.0 environment.

To install MicroBean Maven CDI, place it on your classpath.

The Maven Artifact Resolver project is the Maven-as-build-tool-independent “guts” inside of Maven responsible for transitive dependency resolution and management.  (I’ve written before on this topic.)  MicroBean Maven CDI makes this tooling available inside a CDI 2.0 environment (including Java SE CDI 2.0 applications), along with the common Maven conventions of user-specific settings.xml files and local Maven repositories.  Any interesting magic that it performs is really confined to the translation of Plexus annotations such as Component and Requirement to CDI injection points, which it does by virtue of the power of the CDI portable extension SPI.

There are many ways this could be useful.  Consider—from within your CDI bean—taking in a Maven-style groupId:artifactId:version String identifier and having it resolve to a local file automatically if that file is not already present in exactly the same way that Maven resolves it (jokes about “downloading the Internet” are hereby routed to /dev/null, as usually these indicate an ignorance of the (well-documented) updatePolicy element).  As we’ll see, there are even more powerful things you can do with these capabilities.  I’ve touched on some of them earlier.

In the next post, we’ll touch on the integration of the Apache Commons CLI project with CDI 2.0 by way of MicroBean Commons CLI.

A Blog Post A Day

I’ve got some interesting things cooking in the personal projects department related to Java, CDI 2.0, configuration and Maven and I am going to blog about them with (I hope!) a post (or several!) a day for a little bit.

We’ll start with configuration.

You may recall my earlier posts on the subject.

Now my ramblings have code behind them!  See MicroBean Configuration API, MicroBean Configuration and MicroBean Configuration CDI.

MicroBean Configuration API is the Java API that defines an API around my notion of configuration coordinates.

MicroBean Configuration is a Java SE implementation of that API.

MicroBean Configuration CDI is a CDI extension that uses MicroBean Configuration to expose configuration values to CDI environments.

I’ll have more to say about these (early days!) projects and others over the coming days.  Thanks for reading.

Calling Maven Artifact Resolver From Within CDI 2.0

So I’ve been continuing to play with my CDI-and-linking idea, and central to it is the ability to locate CDI “modules” Out There In The World™.  The world, in this case, is Maven Central.  Well, or perhaps a local mirror of it.  Or maybe your employer’s private Nexus repository fronting it.  Oh jeez, we’re going to have to really use Maven’s innards to do this, aren’t we?

As noted earlier, even just figuring out what innards to use is hard.  So I figured out that the project formerly known as Æther, Maven Artifact Resolver, whose artifact identifier is maven-resolver, is the one to grab.

Then, upon receiving it and opening it up, I realized that the whole thing is driven by Guice—or, if you aren’t into that sort of thing, by a homegrown service locator (which itself is a service, which leads to all sorts of other Jamie Zawinski-esque questions).

The only recipes left over are from the old Æther days and require a bit of squinting to make work.  They are also staggeringly complicated.  Here’s a gist that downloads the (arbitrarily selected) org.microbean:microbean-configuration-cdi:0.1.0 artifact and its transitive, compile-scoped dependencies, taking into account local repositories, the user’s Maven ~/.m2/settings.xml file, active Maven profiles and other things that we all take for granted:


import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.apache.maven.settings.Mirror;
import org.apache.maven.settings.Profile;
import org.apache.maven.settings.Repository;
import org.apache.maven.settings.Settings;
import org.apache.maven.repository.internal.MavenRepositorySystemUtils;
import org.apache.maven.settings.building.DefaultSettingsBuilder;
import org.apache.maven.settings.building.DefaultSettingsBuilderFactory;
import org.apache.maven.settings.building.DefaultSettingsBuildingRequest;
import org.apache.maven.settings.building.SettingsBuildingResult;
import org.apache.maven.settings.building.SettingsBuilder;
import org.apache.maven.settings.building.SettingsBuildingException;
import org.apache.maven.settings.building.SettingsProblem;
import org.eclipse.aether.DefaultRepositoryCache;
import org.eclipse.aether.DefaultRepositorySystemSession;
import org.eclipse.aether.RepositorySystem;
import org.eclipse.aether.RepositorySystemSession;
import org.eclipse.aether.artifact.Artifact;
import org.eclipse.aether.artifact.DefaultArtifact;
import org.eclipse.aether.collection.CollectRequest;
import org.eclipse.aether.connector.basic.BasicRepositoryConnectorFactory;
import org.eclipse.aether.graph.Dependency;
import org.eclipse.aether.graph.DependencyFilter;
import org.eclipse.aether.impl.DefaultServiceLocator;
import org.eclipse.aether.impl.DefaultServiceLocator.ErrorHandler;
import org.eclipse.aether.internal.impl.DefaultRepositorySystem;
import org.eclipse.aether.repository.LocalRepository;
import org.eclipse.aether.repository.LocalRepositoryManager;
import org.eclipse.aether.repository.RemoteRepository;
import org.eclipse.aether.repository.RemoteRepository.Builder;
import org.eclipse.aether.resolution.ArtifactDescriptorRequest;
import org.eclipse.aether.resolution.ArtifactDescriptorResult;
import org.eclipse.aether.resolution.ArtifactResult;
import org.eclipse.aether.resolution.DependencyRequest;
import org.eclipse.aether.resolution.DependencyResult;
import org.eclipse.aether.spi.connector.RepositoryConnectorFactory;
import org.eclipse.aether.spi.connector.transport.TransporterFactory;
import org.eclipse.aether.spi.locator.ServiceLocator;
import org.eclipse.aether.transfer.AbstractTransferListener;
import org.eclipse.aether.transfer.TransferEvent;
import org.eclipse.aether.transport.file.FileTransporterFactory;
import org.eclipse.aether.transport.http.HttpTransporterFactory;
import org.eclipse.aether.util.artifact.JavaScopes;
import org.eclipse.aether.util.filter.DependencyFilterUtils;
import org.eclipse.aether.util.repository.DefaultMirrorSelector;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
public class TestMavenResolverUsage {
public TestMavenResolverUsage() {
super();
}
@Test
public void testEverything() throws Exception {
// See
// https://github.com/eclipse/aether-demo/blob/master/aether-demo-snippets/src/main/java/org/eclipse/aether/examples/util/Booter.java
// et al. for general (undocumented) recipe.
final DefaultServiceLocator serviceLocator = MavenRepositorySystemUtils.newServiceLocator();
assertNotNull(serviceLocator);
serviceLocator.addService(RepositoryConnectorFactory.class, BasicRepositoryConnectorFactory.class);
serviceLocator.addService(TransporterFactory.class, FileTransporterFactory.class);
serviceLocator.addService(TransporterFactory.class, HttpTransporterFactory.class);
serviceLocator.setErrorHandler(new ErrorHandler() {
@Override
public final void serviceCreationFailed(final Class<?> type, final Class<?> impl, final Throwable exception) {
if (exception != null) {
exception.printStackTrace();
}
}
});
final RepositorySystem repositorySystem = serviceLocator.getService(RepositorySystem.class);
assertNotNull(repositorySystem);
final Settings settings = getSettings();
assertNotNull(settings);
final DefaultRepositorySystemSession repositorySystemSession = MavenRepositorySystemUtils.newSession();
assertNotNull(repositorySystemSession);
repositorySystemSession.setTransferListener(new TransferListener());
repositorySystemSession.setOffline(settings.isOffline());
repositorySystemSession.setCache(new DefaultRepositoryCache());
final Collection<? extends Mirror> mirrors = settings.getMirrors();
if (mirrors != null && !mirrors.isEmpty()) {
final DefaultMirrorSelector mirrorSelector = new DefaultMirrorSelector();
for (final Mirror mirror : mirrors) {
assert mirror != null;
mirrorSelector.add(mirror.getId(),
mirror.getUrl(),
mirror.getLayout(),
false, /* not a repository manager; settings.xml does not encode this information */
mirror.getMirrorOf(),
mirror.getMirrorOfLayouts());
}
repositorySystemSession.setMirrorSelector(mirrorSelector);
}
String localRepositoryString = settings.getLocalRepository();
if (localRepositoryString == null) {
localRepositoryString = System.getProperty("user.home") + "/.m2/repository";
}
final LocalRepository localRepository = new LocalRepository(localRepositoryString);
final LocalRepositoryManager localRepositoryManager = repositorySystem.newLocalRepositoryManager(repositorySystemSession, localRepository);
assertNotNull(localRepositoryManager);
repositorySystemSession.setLocalRepositoryManager(localRepositoryManager);
List<RemoteRepository> remoteRepositories = new ArrayList<>();
final Map<String, Profile> profiles = settings.getProfilesAsMap();
if (profiles != null && !profiles.isEmpty()) {
final Collection<String> activeProfileKeys = settings.getActiveProfiles();
if (activeProfileKeys != null && !activeProfileKeys.isEmpty()) {
for (final String activeProfileKey : activeProfileKeys) {
final Profile activeProfile = profiles.get(activeProfileKey);
if (activeProfile != null) {
final Collection<Repository> repositories = activeProfile.getRepositories();
if (repositories != null && !repositories.isEmpty()) {
for (final Repository repository : repositories) {
if (repository != null) {
Builder builder = new Builder(repository.getId(), repository.getLayout(), repository.getUrl());
final org.apache.maven.settings.RepositoryPolicy settingsReleasePolicy = repository.getReleases();
if (settingsReleasePolicy != null) {
final org.eclipse.aether.repository.RepositoryPolicy releasePolicy = new org.eclipse.aether.repository.RepositoryPolicy(settingsReleasePolicy.isEnabled(), settingsReleasePolicy.getUpdatePolicy(), settingsReleasePolicy.getChecksumPolicy());
builder = builder.setReleasePolicy(releasePolicy);
}
final org.apache.maven.settings.RepositoryPolicy settingsSnapshotPolicy = repository.getSnapshots();
if (settingsSnapshotPolicy != null) {
final org.eclipse.aether.repository.RepositoryPolicy snapshotPolicy = new org.eclipse.aether.repository.RepositoryPolicy(settingsSnapshotPolicy.isEnabled(), settingsSnapshotPolicy.getUpdatePolicy(), settingsSnapshotPolicy.getChecksumPolicy());
builder = builder.setSnapshotPolicy(snapshotPolicy);
}
final RemoteRepository remoteRepository = builder.build();
assert remoteRepository != null;
remoteRepositories.add(remoteRepository);
}
}
}
}
}
}
}
final RemoteRepository mavenCentral = new Builder("central", "default", "http://central.maven.org/maven2/&quot;).build();
assert mavenCentral != null;
remoteRepositories.add(mavenCentral);
remoteRepositories = repositorySystem.newResolutionRepositories(repositorySystemSession, remoteRepositories);
assertNotNull(remoteRepositories);
final Artifact artifact = new DefaultArtifact("org.microbean", "microbean-configuration-cdi", "jar", "0.1.0");
final DependencyFilter classpathFilter = DependencyFilterUtils.classpathFilter(JavaScopes.COMPILE);
final CollectRequest collectRequest = new CollectRequest();
collectRequest.setRoot(new Dependency(artifact, JavaScopes.COMPILE));
collectRequest.setRepositories(remoteRepositories);
// collectRequest.setRepositories(Collections.singletonList(mavenCentral));
final DependencyRequest dependencyRequest = new DependencyRequest(collectRequest, classpathFilter);
final DependencyResult dependencyResult = repositorySystem.resolveDependencies(repositorySystemSession, dependencyRequest);
assertNotNull(dependencyResult);
final List<ArtifactResult> artifactResults = dependencyResult.getArtifactResults();
assertNotNull(artifactResults);
}
public static final Settings getSettings() throws SettingsBuildingException {
final SettingsBuilder settingsBuilder = new DefaultSettingsBuilderFactory().newInstance(); // this method should be static!
assert settingsBuilder != null;
final DefaultSettingsBuildingRequest settingsBuildingRequest = new DefaultSettingsBuildingRequest();
settingsBuildingRequest.setSystemProperties(System.getProperties());
// settingsBuildingRequest.setUserProperties(userProperties); // TODO: implement this
settingsBuildingRequest.setGlobalSettingsFile(new File("/usr/local/maven/conf/settings.xml")); // TODO: do this for real
settingsBuildingRequest.setUserSettingsFile(new File(new File(System.getProperty("user.home")), ".m2/settings.xml"));
final SettingsBuildingResult settingsBuildingResult = settingsBuilder.build(settingsBuildingRequest);
assert settingsBuildingResult != null;
final List<SettingsProblem> settingsBuildingProblems = settingsBuildingResult.getProblems();
if (settingsBuildingProblems != null && !settingsBuildingProblems.isEmpty()) {
throw new SettingsBuildingException(settingsBuildingProblems);
}
return settingsBuildingResult.getEffectiveSettings();
}
private static final class TransferListener extends AbstractTransferListener {
private TransferListener() {
super();
}
@Override
public void transferInitiated(final TransferEvent event) {
System.out.println("*** transfer initiated: " + event);
}
@Override
public void transferStarted(final TransferEvent event) {
System.out.println("*** transfer started: " + event);
}
@Override
public void transferProgressed(final TransferEvent event) {
System.out.println("*** transfer progressed: " + event);
}
@Override
public void transferSucceeded(final TransferEvent event) {
System.out.println("*** transfer succeeded: " + event);
}
@Override
public void transferCorrupted(final TransferEvent event) {
System.out.println("*** transfer corrupted: " + event);
}
@Override
public void transferFailed(final TransferEvent event) {
System.out.println("*** transfer failed: " + event);
}
}
}

That seems like an awful lot of work to have to do just to get some stuff over the wire.  It also uses the cheesy homegrown service locator which as we all know is not The Future™.

For my purposes, I wanted to junk the service locator and run this from within a CDI 2.0 environment, both because it would be cool and dangerous and unexpected, and because the whole library was written assuming dependency injection in the first place.

So I wrote a portable extension that basically does the job that the cheesy homegrown service locator does, but deferring all the wiring and validation work to CDI, where it belongs.

As if this whole thing weren’t hairy enough already, a good number of the components involved are Plexus components.  Plexus was a dependency injection framework and container from a ways back now that also had a notion of what constituted beans and injection points.  They called them components and requirements.

So a good portion of some of the internal Maven objects are annotated with Component and Requirement.  These correspond roughly—very, very roughly—to bean-defining annotations and injection points, respectively.

So I wrote two portable extension methods.  One uses the role element from Component to figure out what kind of Typed annotation to add to Plexus components.  The other turns a Requirement annotation with a hint into a valid CDI injection point with an additional qualifier.

(Along the way, these uncovered MNG-6190, which indicates that not very many people are even using the Maven Artifact Resolver project in any way, or at least not from within a dependency injection container, which is, of course, how it is designed to be used.  That’s a shame, because although it is overengineered and fiddly to the point of being virtually inscrutable, it is, as a result, perhaps, quite powerful.)

Then the rest of the effort was split between finding the right types to add into the CDI container, and figuring out how to adapt certain Guice-ish conventions to the CDI world.

The end result is that the huge snarling gist above gets whittled down to five or so lines of code, with CDI doing the scope management and wiring for you.

This should mean that you can now relatively easily incorporate the guts of Maven into your CDI applications for the purposes of downloading and resolving artifacts on demand.  See the microbean-maven-cdi project for more information.  Thanks for reading.