Maven Enforcer: How to access maven properties from beanshell rule - maven

I successfully created a evaluateBeanshell rule with the maven-enforcer-plugin that scans files in the workspace for common mistakes.
With a hardcoded path the rule works fine. When I want to use the ${project.basedir} variable from the surrounding pom, the script breaks on my Windows machine.
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
<version>${maven-enforcer-plugin.version}</version>
<executions>
<execution>
<id>enforce-banned-dependencies</id>
<inherited>true</inherited>
<goals>
<goal>enforce</goal>
</goals>
<configuration>
<rules>
<evaluateBeanshell>
<condition>
import scanner.MyScanner;
scanner = new MyScanner();
//hack to read root dir
//project.basedir crashes beanshell with its backslashes
rootPath = new File("");
root = new File(rootPath.getAbsolutePath());
print("Scanning in: " + root);
print("${project.artifactId}"); // works fine
print("${project.basedir}"); // breaks the code
scanner.loopThroughProjects(root);
return everythingIsFine;
</condition>
</evaluateBeanshell>
</rules>
<fail>true</fail>
</configuration>
</execution>
</executions>
In the debug output the line:
print("${project.basedir}");
was replaced by:
print("D:\code\my-maven-project");
Is there another maven property with sanitized slashes or is there another way to access ${project.basedir}?
The hack outlined in the code example kind of works, but I don't like hacks that force me to leave comments.

You could try ${project.baseUri}.
See https://maven.apache.org/ref/3.8.5/maven-model-builder/#Model_Interpolation
On my Windows 10 machine with Java 8 and Maven 3 the following test properties in pom.xml:
<test>${project.baseUri}</test>
<test2>${project.basedir}</test2>
become the following in the 'effective-pom' (via Intellij IDEA maven plugin)
<test>file:/D:/test/path/</test>
<test2>D:\test\path</test2>
This is just as a proof of concept to show the path separators change, and become valid as a Java String.
You could then transform the URI to a file for your needs in the beanshell script as follows:
uri = java.net.URI.create("${project.baseUri}");
root = new java.io.File(uri);
Via https://docs.oracle.com/javase/8/docs/api/java/io/File.html#File-java.net.URI-

Related

"Unit testing" a Maven build

I have a complex Maven build, and I would like to have some automated check that the JAR files and POM files produced by the build actually contain what I expect them to contain.
For instance, I would like to check for the presence of an Automatic-Module-Name entry in the manifest file. It's a multi-release JAR, so I would like to check that the proper class files exist inside of META-INF/versions. I'm going to publish to Maven Central, so I'd also like to check that the produced pom file contains the dependencies the project needs, but that the produced pom file for the fat jar that I also publish, doesn't contain these dependencies.
Basically, I'd like to unit test my build :).
Unfortunately, it's hard to google for this, because of the words I would use to describe this ("test", "verify") already have very specific different meanings in Maven.
Is there a nice way to do this? I would prefer a Maven plugin, since I'm obviously already using that, but I'm open to other things too.
I ended up, as #khmarbaise suggested in the comments, creating a new Maven submodule. In its pom I used the copy-rename-maven-plugin to copy over the files I actually want to check, like this:
<plugin>
<groupId>com.coderplus.maven.plugins</groupId>
<artifactId>copy-rename-maven-plugin</artifactId>
<version>${version.copy-rename-maven-plugin}</version>
<executions>
<execution>
<id>copy-artifacts</id>
<phase>compile</phase>
<goals>
<goal>copy</goal>
</goals>
<configuration>
<fileSets>
<fileSet>
<sourceFile>${project.basedir}../core/target/.flattened-pom.xml</sourceFile>
<destinationFile>${project.basedir}/src/test/resources/flattened.pom</destinationFile>
</fileSet>
<fileSet>
<sourceFile>${project.basedir}../core/target/myArtifactId-${project.version}.jar</sourceFile>
<destinationFile>${project.basedir}/src/test/resources/myArtifactId.jar</destinationFile>
</fileSet>
<!-- more fileSets here -->
</fileSets>
</configuration>
</execution>
</executions>
</plugin>
Then I was able to read the pom file and do assertions on it. I ended up using Java's built-in XPath API, but you can use whatever.
I was also able to read the JAR file by turning it into a NIO FileSystem:
var filename = "myArtifactId.jar"; // or "flattened.pom"
var file = getClass().getClassLoader().getResource(filename);
var uri = URI.create("jar:" + file.toURI().toString());
FileSystem fs = FileSystems.newFileSystem(uri, Map.of());
You can get a list of files:
var path = fs.getPath("/");
Set<String> filenames = StreamSupport
.stream(walk.spliterator(), false)
.map(Path::toString)
.collect(Collectors.toSet());
Or read the content of a file:
var path = fs.getPath("/META-INF/MANIFEST.MF");
var out = new ByteArrayOutputStream();
Files.copy(path, out);
String content = out.toString();
assertTrue(content.contains("Multi-Release: true"));
var path = fs.getPath("/com/example/MyClass.class");
var out = new ByteArrayOutputStream();
Files.copy(path, out);
byte[] content = out.toByteArray();
var actualVersion = content[7]; // the major version of the class file is at this location
assertEquals(52, actualVersion); // 52 = Java 8
(Note that for this answer, I didn't bother to handle exception or close resources; you'll have to do that yourself.)

How do I access environment variables in xquery?

My program in xquery has a few variables that are based on the environment where the function is being run. For example, dev points to "devserver", test to "testserver", prod to "server", etc.
How do I set this up in my application.xml file and how do I reference these in my .xqy functions?
"workaround" solution 1
use "switch" to determine host:
switch (xdmp:host-name(xdmp:host()))
case <dev environment as string> return "devserver"
case <test environment as string> return "testserver"
.
.
.
default return fn:error(xs:QName("ERROR"), "Unknown host: " || xdmp:host-name(xdmp:host()))
"workaround" solution 2
create an xml file in your project for each host, update your application.xml to place the xml file in a directory depending on the environment name, then refer to the document now built on installation.
application.xml:
<db dbName="!mydata-database">
<dir name="/config/" permissionsMode="set">
<uriPrivilege roles="*_uberuser"/>
<permissions roles="*_uberuser" access="riu"/>
<load env="DEV" root="/config/DEV/" merge="true" include="*.xml"/>
<load env="TEST" root="/config/TEST/" merge="true" include="*.xml"/>
documents located in project directory /config//environment.xml
<environment>
<services>
<damApi>https://stage.mydam.org</damApi>
<dimeApi>https://stage.mydime.org/api/Services</dimeApi>
</services>
</environment>
usage when I need to get the value
fn:doc("/config/environment.xml")/environment/services/damApi/fn:string()
neither solution seems the best to me.
If you use ml-gradle to deploy your project, it can do substitutions in your code. That means you can set up an XQuery library with code like this:
declare variable $ENV = "%%environmentName%%";
You can then import that library wherever you need.
Don't know if MarkLogic supports it, but XQuery 3.1 has functions available-environment-variables() and environment-variable().
You can consider using the tiny “properties” library at https://github.com/marklogic-community/commons/tree/master/properties
We wrote it long, long ago for MarkMail.org with the belief we didn’t want to put the configuration into a database document because configuration should be separate from data. Data get backed up, restored somewhere else, and the new location may not be the same environment as the old.
So instead we did a little hack and put config into the static namespace context (which each group and app server has). The configured prefix is the property name. The configured value is the property value (including type information). Here’s a screen shot from a MarkMail deployment showing it’s a production server, to email on errors, what static file version to serve, and what domain to output as its base.
This approach lets you configure properties administratively (via the Red GUI or REST) and they’re kept separate from the data. They’re statically available to the execution context without extra cost. You can configure them at the Group level or App Server level or both. The library is a convenience wrapper to pull the typed values.
Maybe by now there’s a better way like the XQuery 3.1 functions, but this one has been working well for 10+ years.
Not yet using gradle in our project, I managed to work out how to use maven profiles to find/replace the values I needed based on the environment it was deployed into. I just have to add to the proper profile the plugin to include, the files to update, and what to replace.
pom.xml:
<plugin>
<groupId>com.google.code.maven-replacer-plugin</groupId>
<artifactId>replacer</artifactId>
<version>1.5.2</version>
<executions>
<execution>
<phase>prepare-package</phase>
<goals>
<goal>replace</goal>
</goals>
</execution>
</executions>
<configuration>
<includes>
<include>**/modules/runTasks.xqy</include>
<include>**/imports/resetKey.xqy</include>
</includes>
<replacements>
<replacement>
<token>https://stage.mydime.org/api/Services</token>
<value>https://www.mydime.org/api/Services</value>
</replacement>
</replacements>
</configuration>
</plugin>

Modifying or control of HTML report generated by maven-cucumber-reporting/net.masterthought

<plugin>
<groupId>net.masterthought</groupId>
<artifactId>maven-cucumber-reporting</artifactId>
<version>${masterThougth.version}</version>
<executions>
<execution>
<id>execution</id>
<phase>verify</phase>
<goals>
<goal>generate</goal>
</goals>
<configuration>
<checkBuildResult>false</checkBuildResult>
<projectName>${project.artifactId}</projectName>
<buildNumber>${project.build}</buildNumber>
<parallelTesting>true</parallelTesting>
<outputDirectory>target/cucumber-report/</outputDirectory>
<cucumberOutput>target/cucumber-report/</cucumberOutput>
</configuration>
</execution>
</executions>
</plugin>
I am using above maven cucumber plugin to generate HTML test report.How can I add new attribute to each steps in the html report,is it possible to add additional attributes other then the standard one's like START,END feature file name etc in the html report.I want to add screenshot for even passed test case.
This can be achieved by storing the scenario object in a global variable then using it in the step definition. For example,
Class Common{
public static Scenario scenario;
#before
public void beforeScenario(Scenario scenarioObj){
scenario = scenarioObj;
}
In step definitions, we can use it like,
Class step definitions{
#Given("^I am on homepage$")
public void iamonhomepage(){
Common.scenario. embed(take screenshot());
}
}
This embed method will attach the screenshot to the report. Also we can add some text messages to the report using write method of scenario object like
scenario.write("I am inside step definitions ");
Also we can use the formatting HTML tag in write method like
scenario.write("I am <b> inside </b> step definitions ");

How do I access maven project version in javadoc overview page?

I am using PDFDoclet with maven-javadoc-plugin and I've come quite a long way with it now. I have the maven and javadoc config almost at a point that is good enough but my immediate problem now is that I can't work out how to push the project version number into the PDF title page.
Before you leap to answer my question by telling me to use maven's <resource> filtering, let me outline why that isn't working.
Filtering works by taking the original file from somewhere in the src folder, doing variable substitution and putting the output in the target folder.
Javadoc works by reading files in src/main/java and src/main/javadoc and AFAIK outputting the results into target. This means filtering is useless for Javadoc since it won't read anything from target
My results show that any maven variables in javadoc comments don't get substituted.
What trick can I use to get those variables substituted into the javadoc?
The solution can't involve filtering the javadoc output after the site:site task, unless resource filtering works on PDFs.
This is the configuration, FWIW:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>2.10.3</version>
<configuration>
<show>package</show>
<docfilessubdirs>true</docfilessubdirs>
<tags>
<tag>
<name>pdfInclude</name>
<placement>X</placement>
<head></head>
</tag>
</tags>
</configuration>
<reportSets>
<reportSet>
<id>PDF</id>
<reports>
<report>javadoc</report>
</reports>
<configuration>
<name>PDF</name>
<description>PDF doc</description>
<destDir>pdf</destDir>
<doclet>com.tarsec.javadoc.pdfdoclet.PDFDoclet</doclet>
<docletPath>${basedir}/pdfdoclet/pdfdoclet-1.0.3-all.jar</docletPath>
<useStandardDocletOptions>false</useStandardDocletOptions>
<additionalparam>-pdf my_tech_doc-${project.version}.pdf
-config ${basedir}/pdfdoclet/pdfdoclet.properties</additionalparam>
</configuration>
</reportSet>
</reportSets>
</plugin>
and the pdfdoclet.properties:
# http://pdfdoclet.sourceforge.net/configuration.html
#
#Lets the doclet print additional output in the console and to a logfile.
debug=true
#Print "Author" tags
author=false
#Print "Version" tags
version=true
#Print "since" tags
tag.since=true
#Create summary tables
summary.table=false
#Create hyperlinks
create.links=true
#Encrypt the PDF file
encrypted=false
#Allow printing of the PDF file
allow.printing=true
#Create a bookmark frame
create.frame=true
#Print a title page
api.title.page=true
api.copyright=None
api.author=Hansruedi
#Enables the tag-based filter
filter=true
filter.tags=pdfInclude
font.text.name=resources/arial.ttf
page.orientation=portrait
The PDFDoclet-specific api.* properties should result in a title page as the first page of the PDF, but it doesn't work. If there is a trick that I've missed here and I could get that title page produced, then that might also allow a solution for this somehow.
I realised I can do a quick and dirty hack with the maven <resources> functionality:
<resource>
<directory>${basedir}/src/main/resources</directory>
<targetPath>${basedir}/src/main/javadoc</targetPath>
<filtering>true</filtering>
<includes>
<include>**/overview.html</include>
</includes>
</resource>
This copies my overview.html and filters it, outputting it into the javadoc source directory.
The dirtiness is that this filtered version could then accidentally end up under version control, although using svn I can add it to the ignore list.

How can I ban a Maven dependency where the version contains a certain part?

I'm tryin to use Maven Enforcer's banned dependencies where I want to ban that there are compile and runtime dependencies to any artifact that contains -redhat-. The background of this: The JEE API and other stuff already exists in the JBoss AS and should never be included in the EAR.
This is what I'm trying, but it doesn't work:
<execution>
<id>banned-dependencies</id>
<goals>
<goal>enforce</goal>
</goals>
<configuration>
<rules>
<bannedDependencies>
<searchTransitive>false</searchTransitive>
<excludes>
<exclude>*:*:*-redhat-*:*:compile</exclude>
<exclude>*:*:*-redhat-*:*:runtime</exclude>
</excludes>
</bannedDependencies>
</rules>
<fail>true</fail>
</configuration>
</execution>
As you have discovered this wont work the way you are wanting. (I presume you are finding that the enforcer plugin is matching all dependencies listed in your pom?)
The problem is that Maven expects the version given to either be a single * or to conform to maven's Version Spec. (i.e. 1.0, [1.0,) etc) It can't handle the multiple wildcards that you are using.
Unfortunately I don't really have a solution for you. You could potentially
Write Your Own Rule and extend the BannedDependencies rule and have it work the way you would like.
What follows is a dive into the code that is causing your issue
In the BannedDependencies class there is the following check for the version given in the exclude string:
if (pattern[2].equals("*") || artifact.getVersion().equals(pattern[2]) ) {
result = true;
} else {
try {
result = AbstractVersionEnforcer.containsVersion(
VersionRange.createFromVersionSpec(pattern[2]),
new DefaultArtifactVersion(artifact.getBaseVersion()));
} catch ( InvalidVersionSpecificationException e ) {
throw new EnforcerRuleException("Invalid Version Range: ", e);
}
}
The specific problem for you is
AbstractVersionEnforcer.containsVersion(
VersionRange.createFromVersionSpec(pattern[2]),
new DefaultArtifactVersion(artifact.getBaseVersion()))
You can see that it is expecting a VersionRange due to VersionRange.createFromVersionSpec(). The code for that can be seen here:
VersionRange source code

Resources