In Maven 3.0.3, we introduced some improvements to the way plugin goals can be configured. This post summarizes these improvements and gives some examples of how Maven plugin configuration has been enhanced. We've focused on making improvements that will reduce the friction of plugin configuration for both plugin developers and Maven end-users.
First, a general tip for users interested in using these enhancements in POMs: use the requireMavenVersion rule from the Maven Enforcer Plugin and required Maven version 3.0.3. This will save other team members from running into strange build failures due to plugin misconfiguration if they are still using previous Maven versions. Likewise, plugin authors that take advantage of these enhancements should properly declare the Maven prerequisite in the plugin POM.
The plugin configurator can now employ the generic type argument of a collection parameter to determine the type of the elements. In practice this means plugins can use collections as input parameters more freely without having to worry about the user being forced to specify the proper element type in the POM.
For instance, the goal parameter:
/** @parameter */ java.util.List<java.io.File> files;
can be configured like this in the new Maven version:
<files> <!-- Maven 3.0.3 can take care of converting this to a java.io.File instance -->
<file>pom.xml</file>
<!-- The previously required implementation attribute is now superfluous but still works --> <file implementation="java.io.File">build.xml</file> </files>
Instead of having to specify the type of each element, Maven 3.0.3 will now automatically convert the first element in the previous example to an object of type "java.io.File".
In absence of the implementation attribute, Maven usually looks for a class named <goalPackage>.<xmlElement> to determine the element type. The recognition of generics now enables plugin authors to use bean classes for collection elements that reside in a different package than the plugin goals. In summary, collections now provide the same ease of configuration as classical arrays.
Continuing in the spirit of the previous improvement, the new plugin configurator will now automatically convert a collection obtained from a parameter expression to an array if that's the type expected by the plugin parameter. Likewise, an array would be converted to a collection if needed. The bottom line is that plugin authors targeting recent Maven versions can freely decide to use an array or a collection for a plugin parameter regardless of whether its default value actually yields a collection or array:
/** @parameter default-value="${project.compileSourceRoots}" */ List<String> sourceRootsAsCollection; /** @parameter default-value="${project.compileSourceRoots}" */ String[] sourceRootsAsArray;
Before this change was made to the configurator plugin developers and Maven users had to be very careful about passing an array to a method that expected a List or vice versa. While this might seem like a minor change, it is one of many that will make plugin development easier.
For many plugin parameters it is occasionally convenient to specify their values from the command line via system properties. In the past, this was limited to parameters of simple types like String or Boolean. The latest Maven release finally allows plugin users to configure collections or arrays from the command line via comma-separated strings. Take for example a plugin parameter like this:
/** @parameter expression="${includes}" */ String[] includes;
This can be configured from the command line as follows:
mvn <goal> -Dincludes=*Foo,Bar*
Plugin authors that wish to enable CLI-based configuration of arrays/collections just need to add the expression tag to their parameter annotation. Note that if compatibility with older Maven versions is to be kept, the parameter type must not be an interface but a concrete collection class or an array to avoid another shortcoming in the old configurator.
Many users complain that Maven POMs make heavy use of container elements for collections. Instead of just listing a series of dependency elements, you have to wrap all of your dependency elements in a dependencies element. The same is true for includes and excludes and other elements throughout the POM. This extra redundancy often adds up to some very large POMs. In Maven 3.0.3, we've made a change to the plugin API that will support plugin configuration without requiring container elements for collections. Here is an example:
<fileset> <directory>src/demo</directory> <include>Foo*</include> <include>Bar*</include> <exclude>*Bad</exclude> </fileset>
Previously, Maven would only look for a field or setter when processing the <include> elements. Now it also looks for an adder. So to support the above configuration, a plugin author would need to implement the configured bean like this:
public class Fileset { private List<String> includes = new ArrayList<String>(); public void addInclude( String include ) { includes.add( include ); } ... }
The adder needs to be named add<xmlElement>() and must be public, non-static and have a single argument; it may have a return value. In case where multiple such adders with the same name exists, it is undefined which one gets actually called so be sure to avoid overloading the adder.
Given that merging of plugin configuration during POM inheritance or profile injection is purely based on the XML structure and not guided by information about the actual data types, users that employ this compacted configuration are well-advised to not mix it with the usual collection-style configuration format for a given plugin parameter as the merged configuration likely doesn't produce the intended result.
We can take the idea of inlining configuration a little further. Have a look at this example configuration:
<resources> <resource> <directory>src/foo</directory> <filtering>true</filtering> </resource> <resource>src/bar</resource> </resources>
Looks odd at first, doesn't it? Obviously, the <resource> element describes some complex structure, but the second <resource> element in the example consists just of a mere string. It's not hard to guess that<resource>src/bar</resource> could be a shorthand form of <resource><directory>src/bar</directory></resource> by assuming the <directory> element is the default/primary property of a <resource>. With the new plugin configurator, this is actually possible now.
To enable the feature, a plugin author would have to add a method called set() to the bean class in question:
public class Resource { private File directory; public void set( File directory ) { this.directory = directory; } ... }
This set() method must be public, non-static and take a single argument; it may have a return value.
Besides saving users a few bits of XML, this feature can also be used to configure complex beans via system properties from the command line. Let's say we have a plugin parameter like this:
/** @parameter expression="${artifact}" */ Artifact artifact;
Now further assuming the Artifact bean used here implements a nice set() method that is smart enough to parse a string into artifact coordinates, one could invoke the plugin directly via:
mvn <goal> -Dartifact=org.apache.maven:maven-core:3.0
Last but not least, the default property feature also enables a smooth upgrade path in case plugin parameters need to be changed from simple values to complex structures. Going back to the initial example with the <resource>elements, consider the plugin author originally didn't anticipate the <filtering> element and designed the plugin parameter to be of type File[].
With previous Maven versions, the only way to extend the plugin to support the<filtering> element is to deprecate the existing <resources> parameter and introduce a new parameter using the complex type or alternatively just break compatibility with existing POMs that use the old plugin version. Using the default property support, a plugin can change the parameter type from File[] to Resource[] without affecting existing users.
While java.util.Properties is just a concrete implementation of a Map, the configuration required for parameters of type Properties is structurally different from the configuration format for Map parameters. For the sake of consistency and conciseness, plugin parameters of type Properties can now also be configured like a Map, that is:
<properties> <key1>value1</key1> <key2>value2</key2> </properties>
Previous Maven versions always used a TreeMap to configure parameters of type Map. In other words, neither the user nor the plugin author had any control over the map implementation being used. Just like with the collections, the updated plugin configurator recognizes an optional implementation attribute:
<map implementation="java.util.LinkedHashMap"> <key>value</key> </map>
In absence of both the attribute and a concrete implementation class in the parameter declaration, TreeMap continues to get used.
In some contexts, it's more convenient to specify a number in hexadecimal or octal notation. To support this, plugin authors previously had to declare the corresponding plugin parameters as strings and do the conversion themselves. Now, it's natively supported, using the prefix "0x" to denote hex notation and the prefix "0" to denote octal notation.
<rgb>0xFF00C0</rgb> <!-- hex number --> <perms>0664</perms> <!-- octal number -->