Setting maven properties depending on the presence of other properties - java

I have a maven property question I can't find referred to in the Docs.
From the command line I will input the property "from.command". This property will always be present.
mvn deploy -Dfrom.command=COMMANDVALUE
Inside the pom.xml I will specify another property:
<properties>
<from.pom>POMVALUE</from.pom>
</properties>
This property will some times be there and other times be missing.
I want to have a third property named used.value
I want used.value to be set to the value of "from.pom" if that property is present otherwise it should be set to the value of "from.command"
This need arrises because I need to run maven builds from another script and I don't want the script to have to inspect all pom files.
Is this possible?

You could use the build-helper-maven-plugin:bsh-property mojo.
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<version>1.10</version>
<executions>
<execution>
<id>bsh-property</id>
<goals>
<goal>bsh-property</goal>
</goals>
<configuration>
<properties>
<property>used.value</property>
</properties>
<source>
used.value = project.getProperties().getProperty("from.pom", session.getUserProperties().getProperty("from.command"));
</source>
</configuration>
</execution>
</executions>
</plugin>
This goal enables to write BeanShell scripts. It automatically defines the variable project as the actual Maven project and session as the executing Maven session.
The script above gets the property "from.pom" from the Maven project's properties, and defaults to the "from.command" property set on the command line. It sets it to the used.value variable, which is then exported as a Maven property by the plugin after execution of the script. Command line properties are retrieved from the Maven session with getUserProperties():
The user properties have been configured directly by the user on his discretion, e.g. via the -Dkey=value parameter on the command line.
This goal automatically binds to the validate phase, which is the first phase that is run in the default lifecycle, so you will be able to use ${used.value} as a property inside the rest of the build.

Related

maven-javadoc-plugin: How to update the module path dynamically

I have a Java (11.0.7) Maven (3.0.6) multi-module project that contains the following module declarations:
<modules>
<module>jdrum-commons</module>
<module>jdrum-datastore-base</module>
<module>jdrum-datastore-simple</module>
<module>jdrum</module>
</modules>
Each of these Maven modules contains a module-info that defines the necessary requirements and exports to restrict access and visibility.
As such, jdrum-datastore-simple has some test utility classes that I reuse in jdrum's tests. By configuring the surefire plugin in jdrum's config via the code snippet below I am able to package the whole project without any issues.
<build>
<plugins>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<argLine>
<!-- Allow the unnamed module access to the tests at test-time -->
--add-opens jdrum/at.rovo.drum.impl=ALL-UNNAMED
--illegal-access=deny
</argLine>
</configuration>
</plugin>
</plugins>
</build>
Within the parents POM I've also configured the generation of a report via the site argument, which also generates the Javadoc of the respective projects. The configuration for the JAR containing the javadoc as well as the configuration for the Javadoc generation as part of the report are both the same and look like this:
<!-- Generate Javadoc while reporting -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.2.0</version>
<inherited>true</inherited>
<configuration>
<verbose>true</verbose>
<source>${maven.compiler.source}</source>
<show>protected</show>
<failOnWarnings>false</failOnWarnings>
<release>${maven.compiler.release}</release>
<stylesheet>java</stylesheet>
</configuration>
<reportSets>
<reportSet>
<id>html</id>
<reports>
<report>javadoc</report>
</reports>
</reportSet>
</reportSets>
</plugin>
The Javadoc generation as part of the package step, which generates the project-version-javadoc.jar as output, succeeds as both, the jdrum-datastore-simple dependencies as well as its tests, are only included at test time:
<!-- Test data store to use for testing -->
<dependency>
<groupId>at.rovo</groupId>
<artifactId>jdrum-datastore-simple</artifactId>
<version>${project.parent.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>at.rovo</groupId>
<artifactId>jdrum-datastore-simple</artifactId>
<version>${project.parent.version}</version>
<scope>test</scope>
<type>test-jar</type>
</dependency>
If I'd change the scope from test to compile or provided the Javadoc generation would also fail with an error such as
Exit code: 1 - javadoc: error - The code being documented uses packages in the unnamed module, but the packages defined in https://github.com/RovoMe/JDrum/jdrum-datastore-simple/apidocs/ are in named modules.
The issue here, as far as I understood the problem, is, that the jdrum-datastore-simple module is not added to the module path of Javadoc. The next logical step was therefore to add that module to the configuration as such:
<reporting>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<configuration>
<additionalOptions>
<option>--add-modules</option>
<option>jdrum.datastore.simple</option>
</additionalOptions>
</configuration>
</plugin>
</plugins>
</reporting>
This adds the jdrum-datastore-simple module to the Javadoc configuration string, which can be seen in the jdrum/target/site/apidocs/options file that now contains an
...
--add-modules
jdrum.datastore.simple
...
entry. On further analyzing the generated options file it is apparent that the module path is missing out a reference to the actual JAR file and hence the Javadoc generation and thus the Maven process fails due to Javadoc not being able to locate the defined module. If I update that options file and add the path to the missing JAR file and then only perform a mvn package site the whole process succeeds and all is fine (as the pure invocation of the javadoc.bat located in the target/site/apidocs folder would as well).
Now, in order to make the whole process more dynamic I wanted to add or update the module path. However, the maven-javadoc-plugin does not directly allow this. Therefore I came up with adding a further maven-javadoc-plugin option of --module-path and a further option entry that contains the whole path. By the whole path I mean the path to every single dependency, so not only the path to jdrum-datastore-simple. This also works but due to hardcoding the path to the respective JAR files, the project is now not usable by other users unless they have the same system and path structure I used. To fix this I quickly replaced the respective path structure with ${settings.localRepository} and ${project.parent.basedir} properties on the respective modules in the module path. Unfortunately Javadoc is rather nitpicking on the path structure it accepts and it turns out that on my Windows machine Maven does return a path structure starting with C:\Users\... which Javadoc can't handle. If the path structure looks like C:/Users/... however Javadoc is fine with the values.
On further research I stumbled upon this thread which suggests to use Maven's build-helper-maven-plugin to define new properties for i.e. the M2 repository and use the built-in reg-ex capability to replace \ characters with /. However, adding a configuration such as
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<version>3.1.0</version>
<executions>
<execution>
<id>replace-local-repo-characters</id>
<goals>
<goal>regex-property</goal>
</goals>
<configuration>
<name>tag.m2repo</name>
<value>${settings.localRepository}</value>
<regex>\\</regex>
<replacement>/</replacement>
<failIfNoMatch>false</failIfNoMatch>
</configuration>
</execution>
<execution>
<id>replace-local-path-characters</id>
<goals>
<goal>regex-property</goal>
</goals>
<configuration>
<name>tag.basedir</name>
<value>${project.parent.basedir}</value>
<regex>\\</regex>
<replacement>/</replacement>
<failIfNoMatch>false</failIfNoMatch>
</configuration>
</execution>
</executions>
</plugin>
and using the introduced tags instead does not work at all as Maven is complaining about an invalid value provided. If I use $\{settings.localRepository} Maven is fine about the provided value, however in the final options file not the value of the actual settings.localRepository is updated but the provided string itself and I end up with something like $/{settings.localRepository}/org/slf4j/... which Javadoc can't resolve and therefore still misses out on the correct location to the jdrum-datastore-simple dependency.
So, how can I add the path to the missing dependency to maven-javadoc-plugin's module path defined in the generated options file so that the Maven is actually able to generate the whole report?
It seems that with java11 Update 9 (maybe also with update 8; not tested) maven-javadoc-plugin is able to correctly generate the Javadoc for multi-module projects without the need to alter the module-path.
For those interested how the actual Maven POM looks like:
Parent POM
POM for a shared module
POM for a sharing and consuming module
POM for the consuming module

Maven plugin CLI parameter overwriting <configuration> in pom.xml

How does/should Maven plugins behave with regards to the order in which they process configuration options? I would expect that properties passed via CLI overwrite those defined in a <configuration> block in pom.xml.
Here's an example.
pom.xml
<plugin>
<groupId>group</groupId>
<artifactId>artifact</artifactId>
<version>1.2.3</version>
<executions>
...
</executions>
<configuration>
<url>foo.com</url>
</configuration>
</plugin>
CLI
mvn group:artifact:1.2.3:doit -Dmymojo.url=bar.com
I am currently debugging a plugin (not mine) that gives precedence to the url value defined in the POM rather than the one passed on the CLI. Is that how mojos are supposed to behave i.e. a Maven feature rather than a plugin bug? I didn't find anything mentioned in the ref guide.
As per https://issues.apache.org/jira/browse/MNG-4979 this works as designed. I find it counter-intuitive and don't find the reasons given in MNG-4979 convincing.
If your setup allows to modify the pom.xml you can work around this behavior as suggested by JF Meier (and the issue above).
<properties>
<mymojo.url>foo.bar</mymojo.url>
</properties>
<plugin>
<groupId>group</groupId>
<artifactId>artifact</artifactId>
<version>1.2.3</version>
<executions>
...
</executions>
<configuration>
<url>${mymojo.url}</url>
</configuration>
</plugin>
Through the command line, you set a property url. This would override an entry <url>foo.com</url> in the <properties> section of the POM.
Many plugins allow to set configuration entries through properties, but these properties do not automatically have the same name. In the documentation, this is usually called user property. To see examples, look e.g. at
https://maven.apache.org/plugins/maven-dependency-plugin/get-mojo.html

Override property key's value in maven

I have global property file (global.properties) with below mentioned content
app.server.username=globalUser
I have another property file called sample.properties with below mentioned content
app.server.username=sampleUser
app.server.port=443
Now, I need to replace app.server.username key value from "sampleUser" to "globalUser" using Maven , with packaging of jar file happens.
These 2 properties file is placed in same folder within Java project.
So, during Maven build phase (or in package phase) , Maven should refer to global.properties, search for all key value pairs (defined in it) in sample.properties. And replace value of all matched keys.
So , after Maven build, sample.properties files should have below mentioned content
app.server.username=globalUser
app.server.port=443
Please suggest how to do it in Maven ?
You can use the properties plugin for that, specifying multiple files to be read:
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>properties-maven-plugin</artifactId>
<version>1.0.0</version>
<executions>
<execution>
<phase>initialize</phase>
<goals>
<goal>read-project-properties</goal>
</goals>
<configuration>
<files>
<file>global.properties</file>
<file>sample.properties</file>
</files>
</configuration>
</execution>
</executions>
</plugin>
From my test results, the latter files will cause duplicate property keys to override the former ones. So just ensure that your files are listed in the correct order.

How to pass parameter to Maven plugin from CLI?

<build>
<plugins>
<plugin>
<groupId>org.jvnet.jax-ws-commons</groupId>
<artifactId>jaxws-maven-plugin</artifactId>
<version>2.3</version>
<executions>
<execution>
<phase>generate-sources</phase>
<goals>
<goal>wsimport</goal>
</goals>
<id>generate-sei</id>
<configuration>
<sourceDestDir>${project.basedir}/src/main/java</sourceDestDir>
</configuration>
</execution>
</executions>
<dependencies>...</dependencies>
</plugin>
</plugins>
</build>
The above XML snippet is from a POM file in a Java project. In this snippet I've defined the jaxws-maven-plugin to use a wsdl file to generate the SEI code and place it in the src/main/java directory. This plugin is bound to the generate-sources phase, and works fine.
I want to make it so that if I issue the plugin directly, using:
mvn jaxws:wsimport
it should place the files in the above mentioned folder. From the plugins reference site (https://jax-ws-commons.java.net/jaxws-maven-plugin/wsimport-mojo.html), I can't figure out how to pass the parameter (sourceDestDir) as a command line argument. Is there someway I can do this?
WARNING /!\
You are trying to generate sources under the source folder src/main/java. Unless there is a very strong reason, don't do this. All generated content should always be placed under the build directory (target by default) and not be version-controlled. You can always add the generated sources as source folder using the build-helper-maven-plugin:add-source, if the plugin does not do it already itself.
To be able to set parameters directly on the command line, the plugin needs to define a user property. However, the org.jvnet.jax-ws-commons:jaxws-maven-plugin does not define a user property for the sourceDestDir parameter. This is noticeable because the documentation does not have a "User Property" set.
You can also find this in the source code:
#Parameter(defaultValue = "${project.build.directory}/generated-sources/wsimport")
private File sourceDestDir;
The #Parameter annotation, used to declare the parameter of the Maven plugin, does not have a corresponding property.
As such, you will need to have the following:
Define a Maven property jaxws.sourceDestDir with a value of ${project.basedir}/src/main/java with
<properties>
<jaxws.sourceDestDir>${project.basedir}/src/main/java</jaxws.sourceDestDir>
</properties>
Preferably, you would have ${project.build.directory}/some/path instead of src/main/java.
Configure the plugin to use this Maven property:
<configuration>
<sourceDestDir>${jaxws.sourceDestDir}</sourceDestDir>
</configuration>
If you want to override it, you can now do so directly on the command line with -Djaxws.sourceDestDir=/my/new/value. This system property will take precedence over the value of the Maven property.

Different Maven configurations for different goals

I have a Maven project which includes a Maven plugin (the Liquibase Maven plugin) which exposes different goals.
Two of these goals (update and diff) need different parameters which are in conflict between them (because the semantics of the two is different), so I need to give Maven different properties in the two goal executions.
That's what I've done
<plugin>
<groupId>org.liquibase</groupId>
<artifactId>liquibase-maven-plugin</artifactId>
<version>3.4.1</version>
<!-- This configuration is used for every goal except "diff" -->
<configuration>
<propertyFile>src/main/resources/liquibase.properties</propertyFile>
<promptOnNonLocalDatabase>false</promptOnNonLocalDatabase>
</configuration>
<executions>
<execution>
<id>default-cli</id>
<goals>
<goal>diff</goal>
</goals>
<!-- This configuration is used for the "diff" goal -->
<configuration>
<propertyFile>src/main/resources/liquibaseDiff.properties</propertyFile>
<promptOnNonLocalDatabase>false</promptOnNonLocalDatabase>
</configuration>
</execution>
</executions>
</plugin>
However, this configuration is wrong because for each goal (diff, update of the others) only the liquibaseDiff.properties file is used.
Is there any way to pass different configurations for different goals in Maven?
Configuration of plugins can be done in two different locations:
Globally for all executions. The global configuration is done with the <configuration> element under <plugin>. This configuration in inherited by all executions.
Per execution. This is done using the <configuration> element under <execution>.
In your example, consider this POM:
<plugin>
<groupId>org.liquibase</groupId>
<artifactId>liquibase-maven-plugin</artifactId>
<version>3.4.1</version>
<configuration>
<!-- put the configuration here that is common to all executions -->
</configuration>
<executions>
<execution>
<id>diff</id>
<goals>
<goal>diff</goal>
</goals>
<configuration>
<!-- put the specific configuration of the diff goal here, this will inherit from the global configuration -->
</configuration>
</execution>
<execution>
<id>update</id>
<goals>
<goal>update</goal>
</goals>
<configuration>
<!-- put the specific configuration of the update goal here, this will inherit from the global configuration -->
</configuration>
</execution>
</executions>
</plugin>
The default inheritance behavior is to merge the content of the configuration element according to element name. If the child POM has a particular element, that value becomes the effective value. If the child POM does not have an element, but the parent does, the parent value becomes the effective value.
In case of conflicts, you can control the default inheritance performed by Maven using combine.children and combine.self. Quoting the Maven docs:
combine.children="append" results in the concatenation of parent and child elements, in that order. combine.self="override", on the other hand, completely suppresses parent configuration.
In addition to this, you need to be aware that when executing a Maven command, on the command line, that directly invokes a goal, such as mvn liquibase:diff, it creates a new execution with an id that is default-cli. As such, since the specific configuration above of the goal diff is done in an execution with id diff, it will not be used. This is actually normal, since the same goal of the same plugin could be present in multiple execution blocks with different configuration: which one should be used if it is executed on the command line, without additional information?
Typically, this situation is solved in 2 manners:
Execute on the command line a specific execution, i.e. the one you configured. This is possible since Maven 3.3.1 and you would execute
mvn liquibase:diff#diff
The #diff in the command above refers to the unique <id> of the execution that is configured in the POM.
Bind your execution to a specific phase of the Maven lifecycle, and let it be executed with the normal flow of the lifecycle. This is generally the prefered solution. In the example above, we could, for example, add a <phase>test</phase> in the execution block of the diff execution; and then Maven will execute it when the test phase is ran during the course of the build.

Categories