Using #project.version# with Liquibase in a multimodule Maven project - java

It was a little bit hard to come up with a meaningful title, hope it will become clear enough after the explanation. I have searched through a number of Qs and As on SO, and they were all very close to the problem I am experiencing, but still not close enough.
In general, what I want to accomplish is to store project version in DB by accessing the maven property #project.version# from a .csv file which is loaded by a Liquibase script.
My maven project structure looks like this:
parentModule
pom.xml
|
---moduleA
|__pom.xml
---moduleB
|__pom.xml
---moduleC
|__pom.xml
...
Pom.xml are defined as:
**PARENT POM**
<project ...>
<groupId>com.parent</groupId>
<artifactId>parent</artifactId>
<version>1.0</version>
<packaging>pom</packaging>
<name>parent</name>
<parent>
<artifactId>spring-boot-starter-parent</artifactId>
<groupId>org.springframework.boot</groupId>
<version>2.2.1.RELEASE</version>
<relativePath />
</parent>
<properties>
<java.version>8</java.version>
</properties>
<modules>
<module>moduleA</module>
<module>moduleB</module>
<module>moduleC</module>
...
</modules>
<build>
<defaultGoal>package</defaultGoal>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
</plugins>
</build>
</project>
----------------------------------------------------------------------
**CHILD POM**
<project ...>
<artifactId>moduleC</artifactId>
<name>moduleC</name>
<parent>
<groupId>com.parent</groupId>
<artifactId>parent</artifactId>
<version>1.0</version>
</parent>
<dependencies>
<dependency>
...
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>moduleC/src/main/resources/db/changelog/</directory>
<filtering>true</filtering>
<includes>
<include>**/app_version.csv/</include>
</includes>
</resource>
</resources>
</build>
</project>
Liquibase scripts are defined in moduleC/src/main/resources/db/changelog/changelog-master.xml etc., while the .csv files with initial values are located in moduleC/src/main/resources/db/users.csv etc. In one of those csv files, I want to push #project.version# value, like this:
id;app_key;app_value;created_by;last_modified_by
1;app-version;#project.version#;system;system
Since that file is located in moduleC, I used maven resource filtering even inparentModule <build/> to filter that file so it can resolve #project.version# property, but with no luck:
<build>
<resources>
<resource>
<directory>moduleC/src/main/resources/db/changelog/</directory>
<filtering>true</filtering>
<includes>
<include>**/app_version.csv/</include>
</includes>
</resource>
</resources>
<defaultGoal>package</defaultGoal>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
</plugins>
</build>
There are errors, one that says that master changelog cannot be found, while in other cases just string value #project.version# is stored. Seems to me I should include app_version.csv and its location (moduleC) as resource inside <build> tag withing parentModule pom.xml, but every combination of referencing it fails. Is there a solution to reference it properly (either from parentModule or moduleC pom.xml) or there might be an easier way to store #project.version# with liquibase?

I am extremely sorry for not replying on time, was temporarily removed from the project after posting the question and was not able to access the git repository due to change of location. So far, I have tried all of the proposed actions, but with no result. In the end, what I found to work was an accepted answer posted here. I have added the build block inside mavenC module pom.xml and it worked. Nevertheless, thank you all immensely for posting and helping.

I think you need to use the maven-replacer-plugin in your build cycle. It will be configured to process the 'app_version.csv' file and output the substituted file content to the 'target/classes' folder. The subsequent packaging phase will ensure the csv file with the current pom version will be bundled into the artifact that the liquidbase tool then handles.

Looks like you're using the wrong syntax for filtering in the CSV-file. Instead of using #project.version#, try ${project.version} instead:
Check the <directory>moduleC/src/main/resources/...</directory>, if the resources plugin lives in the child POM of moduleC then there is no need for the prefix.
Try replacing with <directory>src/main/resources/..</directory>

Resources filtering of a module should be in the build of the module itself.
The project.version property will be inherited from the parent.
Be careful so the filtering does not mess with your xml files.
I think the syntax should be ${project.version}
Can you share your liquibase maven plugin configuration ?
The markup should point to your master changelog.
Here is two other solutions:
Another solution would be to create a liquibase changeset every time you create a new version. You can do it programmatically using liquibase java SDK in a spring component which run on startup of your project or create the changeset yourself.
Another solution would be to use Spring Boot Actuator to retrieve project version.
For this you need to add <goal>build-info</goal> in the goals of spring-boot-maven-plugin

Related

Can I use the maven war as a form of overwriting the other projects that already exist?

I am beginner on Spring and Maven.
In Spring Framework,
I want to manage a separate version control flow.
1. Main Project that already exist.
2. Module of the partial use that packaged by maven war.
Two projects should be treated separately when Push and Pull.
But Files on two projects may be present in the same folders.
How can I use this?
This is actually related with version control system you currently use. Git for example supports submodules. You can create a maven module directly in your root project folder and define it as git submodule. So they have different git tracks and may seperately maintained.
I believe what you need might be achievable by using 'war-overlays' as documented here
To summarize, you specify the 'child' project as a dependency in the 'Main' project:
...
<dependencies>
<dependency>
<groupId>com.example.projects</groupId>
<artifactId>documentedprojectdependency</artifactId>
<version>1.0-SNAPSHOT</version>
<type>war</type>
<scope>runtime</scope>
</dependency>
...
</dependencies>
...
And you define the overlay in the maven-war-plugin's configuration:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>2.6</version>
<configuration>
<overlays>
<overlay>
<id>my-webapp-index.jsp</id>
<groupId>com.example.projects</groupId>
<artifactId>my-webapp</artifactId>
<includes>
<include>index.jsp</include>
</includes>
</overlay>
<overlay>
<!-- empty groupId/artifactId represents the current build -->
</overlay>
</overlays>
</configuration>
</plugin>

Move All Dependencies in EAR to Subfolder in Maven

I am currently building an EAR file in Maven. When I build it, I get the following in my target folder:
-target/
-MyProject.ear
-MyProject/
-MainProject.jar
-Dependency.jar
If I open up MyProject.ear in 7Zip, I see the same file structure that exists in MyProject/
I have been asked to change this so that all dependencies are in a subfolder, like this:
-target/
-MyProject.ear
-MyProject/
-MainProject.jar
-lib/
-Dependency.jar
Now I can make this work for the one dependency by making the following change to my pom.xml file:
<build>
[...]
<plugins>
<plugin>
<artifactId>maven-ear-plugin</artifactId>
<version>2.9</version>
<configuration>
<modules>
<jarModule>
<groupId>Dependency</groupId>
<artifactId>Dependency</artifactId>
<bundleDir>lib</bundleDir>
</jarModule>
</modules>
</configuration>
</plugin>
</plugins>
</build>
However in the real project, I have about 30 dependencies. I could manually add a <jarModule> tag for each and every dependency, but ideally I would like for it to automatically move all of the dependencies to a subdirectory (making an exclusion for MainProject.jar).
I had hoped that I could declare <jarModule> once and use wild card characters for the groupId and artifactId, like Maven allows with exclusions:
<!-- Moving all for the time being, add the exclusion if it works -->
<jarModule>
<groupId>*</groupId>
<artifactId>*</artifactId>
<bundleDir>lib</bundleDir>
</jarModule>
I would expect the above to take all artifacts and put them into a lib folder. Unfortunatley, this doesn't seem to work in Maven 3.2.1, providing an error saying that Artifact[jar:*:*] is not a dependency of the project
So how can I move all of my dependencies to a subfolder when building an EAR in Maven?
You should move the bundle directory restriction under your maven-ear-plugin build configuration as below:
<build>
<finalName>${project.artifactId}</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-ear-plugin</artifactId>
<version>${maven-ear-plugin.version}</version>
<configuration>
<modules>
<jarModule>
<groupId>${group_id}</groupId>
<artifactId>${artifact_id}</artifactId>
<bundleDir>lib</bundleDir>
</jarModule>
<webModule>
...
</webModule>
</modules>
<defaultLibBundleDir>/lib</defaultLibBundleDir>
</configuration>
</plugin>
</plugins>
Hope it helps.
Thanks to a happy accident of failing to fully clean up my pom file after a test, I discovered how to do this.
In the <configuration> tag, add <defaultLibBundleDir>/lib</defaultLibBundleDir>. This will put all modules by default in a lib folder in a root of a directory. No wildcards are needed; in fact, Maven doesn't support wild cards in this part, probably because there are no clear bounds (it could be interpreted as "move all artifacts ever"). Wild cards only work for exclusions because there are clear bounds; it's a subset of a clearly defined set of artifacts.
If you'd like to make an exception, then you specify the module and type in <bundleDir>/</bundleDir>, which will place it back in the root. The full XML looks like this:
<build>
[...]
<plugins>
<plugin>
<artifactId>maven-ear-plugin</artifactId>
<version>2.9</version>
<!-- All dependencies not otherwise specified are going in /lib -->
<defaultLibBundleDir>/lib</defaultLibBundleDir>
<modules>
<jarModule>
<!-- An exception to keep the main project in the root -->
<groupId>MainProject</groupId>
<artifactId>MainProject</artifactId>
<bundleDir>/</bundleDir>
</jarModule>
</modules>
</configuration>
</plugin>
</plugins>
This results in the original goal of putting all dependencies in a lib folder except for MainProject.jar:
-target/
-MyProject.ear
-MyProject/
-MainProject.jar
-lib/
-Dependency.jar

Classloader with Maven built jar

I am working on a project which contains a number of subprojects. The structure is something like Project 1, Project 2 and ProjectClassLoader.
Using separate configuration files, I pass in the binary names of the classes from Projects 1 and 2 that need to be loaded each time as arguments to the ProjectClassLoader project.
The ProjectClassLoader gets a handle to the system classloader
ClassLoader loader = ClassLoader.getSystemClassLoader();
which in theory allows it to load any classes which are contained in the classpath.
I'm using Maven to build the projects and handle their associated dependences. Thus each project has it's own individual pom.xml file. The ProjectClassLoader defines a parent pom.xml over Projects 1 and 2 which inherit from this. The parent pom contains dependency entries for both Project 1 and 2.
My understanding was that any dependencies specified in the pom.xml files of these projects would get added to the classpath at runtime. However when trying to load classes using the system classloader, I'm getting class not found execptions.
I have tried using the mvn:exec plugin which I understand includes the dependencies in the classpath when executing jars on the command line but this has not worked.
I'd grately appreciate any help in furthering my understanding of why I can load the classes even though the dependencies are defined in the pom...Thanks in advance
Can you check if your pom matches this configuration a bit?
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
...
<properties>
...
<exec.maven.plugin.version>1.2.1</exec.maven.plugin.version>
...
</properties>
<build>
<plugins>
...
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
</plugin>
...
</plugins>
<pluginManagement>
<plugins>
...
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>${exec.maven.plugin.version}</version>
<configuration>
<executable>java</executable>
<arguments>
<argument>-classpath</argument>
<!-- automatically creates the classpath using all project dependencies, also adding the project build directory -->
<classpath />
<argument>com.example.Main</argument><!-- your mainClass -->
</arguments>
</configuration>
</plugin>
...
</plugins>
</pluginManagement>
</build>
<dependencies>
...
<dependency>
<groupId>groupId.for.project1</groupId>
<artifactId>project1</artifactId>
</dependency>
<dependency>
<groupId>groupId.for.project2</groupId>
<artifactId>project2</artifactId>
</dependency>
...
</dependencies>
<dependencyManagement>
<dependencies>
...
<dependency>
<groupId>groupId.for.project1</groupId>
<artifactId>project1</artifactId>
<version>${project1.version}</version>
</dependency>
<dependency>
<groupId>groupId.for.project2</groupId>
<artifactId>project2</artifactId>
<version>${project2.version}</version>
</dependency>
...
</dependencies>
</dependencyManagement>
...
</project>
Where you fill them with the correct artifacts.
You should then be able to start it with:
mvn exec:exec
Can you post your configuration for your pom plz if it doesn't match, that way it's easier to understand what exactly you currently have in your pom.

How to export interfaces from maven project as new artifact

I have a maven project that creates a JCA connector (.rar file). Now to use this connector in another project I build this thing for, I want to import the interfaces for the connector.
Can I somehow add a build target to the connector pom.xml, which creates a new maven artifact with just the interfaces from the connector project (lets say connector-interfaces)?
Thanks!
Maven has a strong rule, that 1 project -> 1 artifact. I suggest to split your project:
connector
+ pom.xml
++ connector-interfaces
+++ pom.xml
++ connector-impl
+++ pom.xml
The pom.xml of the connector would contain 2 modules:
<modules>
<module>connector-interfaces</module>
<module>connector-impl</module>
</modules>
In the module connector-impl, you will need to add a dependency on connector-interfaces. Add this in the pom.xml of connector-impl:
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>connector-impl</artifactId>
<version>${project.version}</version>
</dependency>
If you use the maven release plugin, add this to the parent pom:
<project>
<build>
<plugins>
<plugin>
<artifactId>maven-release-plugin</artifactId>
<configuration>
<autoVersionSubmodules>true</autoVersionSubmodules>
</configuration>
</plugin>
</plugins>
</build>
</project>
That way, maven will only ask for the version 1 time when you release.
Although #WimDeblauwe is suggesting a best practice and it would probably be best to follow it his way, there is another way you can accomplish this. There is an assembly plugin that can be used to do this. Normally it's meant to package your whole project in one jar, but it can also be used to make a subset of classes and make a jar out of that.
It can be a little tricky though. To do it you'll probably need to create a custom descriptor and reference this documentation to see how to format your descriptor file. Here's an example from the documentation of how to point to a custom descriptor:
<project>
[...]
<build>
[...]
<plugins>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<version>2.4</version>
<configuration>
<descriptors>
<descriptor>src/main/assembly/src.xml</descriptor>
</descriptors>
</configuration>
[...]
</project>
In that src.xml file you'll want to <excludes> all classes except for the interfaces and set <includeDependencies> to false.

How to run a plugin only for "war" package type in Maven?

Provided two Maven projects: J (jar), W (war); both depend on one parent P (pom). The parent has a plugin, which must only run for the project "W".
How does one go about doing this:
without creating separate parent projects
without using a profile (so build must still be executed with mvn clean package)
J (jar)
<project>
<parent>
<artifactId>P</artifactId>
</parent>
<artifactId>J</artifactId>
<packaging>jar</packaging>
</project>
W (war)
<project>
<parent>
<artifactId>P</artifactId>
</parent>
<artifactId>W</artifactId>
<packaging>war</packaging>
</project>
P (pom)
<project>
<artifactId>P</artifactId>
<packaging>pom</packaging>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>jar</goal>
</goals>
<configuration>
<classifier>classes</classifier>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
I think you can achieve this using Profile Activation. Ideally, the activation condition would be something like "packaging type is war", but apparently, this condition cannot be implemented in Maven. However, in your case, there is a condition that can be implemented and that is probably equivalent in practice: "there is a src/main/webapp directory".
This is how your pom.xml might look like:
<profiles>
<profile>
<activation>
<file>
<exists>src/main/webapp</exists>
</file>
</activation>
<build>
[plugin configuration]
</build>
</profile>
</profiles>
One way to do this would be to place the plugin in the parent pom within <pluginManagement> section. Thereafter specify the plugin in the project(s) that you want it to run.
In your case, you would specify the plugin for project J and not W.
P
<build>
...
<pluginManagement>
<plugins>
<plugin>
<groupId>...</groupId>
<artifactId>...</artifactId>
... other plugin details ...
</plugin>
</plugins>
</pluginManagement>
...
</build>
J
<build>
...
<plugins>
<plugin>
<groupId>...</groupId>
<artifactId>...</artifactId>
<plugin>
</plugins>
...
</build>
Short answer: Maven doesn't appear to have a good way to do what you're trying to do. I've spent a fair amount of time trying to solve a similar problem and haven't found anything satisfactory.
You've already discovered two of the possible solutions: introduction of an additional parent pom for the wars (perhaps the additional pom extends the original parent so you don't have to duplicate all of its config), or duplicating the jar plugin config in all of the war poms. As you've said, neither of these is ideal.
Another possibility is to use the maven-assembly-plugin instead of the jar plugin to build the classified jar for the war projects. The assembly plugin is not included in the default lifecycles for either jar or war packaging, so you could configure it in the parent's <pluginManagement> section and then only reference it in the war projects as Raghuram described. If you need a custom assembly descriptor you will probably want to follow the sharing the assembly descriptors example.

Categories