3
\$\begingroup\$

We have a Spring Boot parser application, that supports a bunch of formats. We need to divide this Parser application into several types. Each such type is simply defined by the set of formats supported.


A Provider sends a Message in its own format, which is described in an XML Schema. Example:

Schema:

<Message format="XML">
  <metric name="aaa"/>
  <metric name="bbb"/>
</Message>

Message:

<Message>
  <metric name="aaa">1</metric>
  <metric name="bbb">2</metric>
</Message>

For each message format, we have a Deserialized class and a MessageParser class. All of the message formats are defined inside enum MessageFormat.

public enum MessageFormat {

    CMS(new CMSMessageParser());

    public final MessageParser messageParser;

    MessageFormat(final MessageParser messageParser) {

        this.messageParser = messageParser;
    }
}

Here you can see, that the MessageParser is tied up with its MessageFormat.


For example, type CMS only should contain CMS field inside the enum, type ANY should support any format, etc.

First, we looked at Spring Profiles:

  • They allow to run the jar with an option, that will decide whether some of the classes/methods must be loaded into Spring Container or not. The classes/methods must be managed by the Spring Container, but it is quite problematic in our case since all the message parsers are tied to enum constructor, which is impossible to access by Spring without making up a weird workaround. So, we dropped the idea.

Second, we looked at Maven Profiles:

  • They allow to build the jar with an option, that will decide which classes to include while compiling (or which compiled classes to delete after compilation). That allows getting the jar which only contains the classes, that are actually going to be used. No options needed to start up the jar. We chose this option.

I created a package "maven_replace_by_parser_type" containing different versions of that MessageFormat.java file. Then, I excluded this package with the maven-compiler-plugin by default and marked as excluded in the IDE. Then, I created Maven profiles that all do the same thing:

  1. Overwrite current MessageFormat.java with maven_replace_by_parser_type/TYPE_FOLDER/MessageFormat.java
  2. Compile all classes
  3. Remove unsupported .class files from target directory. I found out, that Maven refuses to exclude the classes from the compilation, that "have calls to" or "are called by" other non-excluded classes. So, I chose to compile them all, and then clean up the non-needed ones.

This is the pom.xml with the code, that does exactly what I explained above, but I'm sure there must be a better way!

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.company</groupId>
  <artifactId>product</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <packaging>jar</packaging>

  <build>
    <plugins>

      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <configuration>
          <excludes> <!-- This gets excluded in any case -->
            <!-- This contains different "MessageFormat.java" versions for each type -->
            <exclude>com/company/product/format/maven_replace_by_collector_type/**</exclude>
          </excludes>
        </configuration>
      </plugin>

    </plugins>

  </build>

  <!-- Use maven goal with -P <profile_name> to compile a specific collector -->
  <profiles>

    <profile>
      <id>cms</id>

      <build>
        <plugins>
          <plugin>
            <artifactId>maven-antrun-plugin</artifactId>

            <executions> <!-- Replace the MessageFormat.java in the working directory from "format/maven_replace_by_collector_type" -->
              <execution>
                <id>Replace the MessageFormat.java</id>
                <phase>validate</phase> <!-- Before compilation -->
                <goals>
                  <goal>run</goal>
                </goals>
                <configuration>
                  <target>

                    <copy file="src/main/java/com/company/product/format/maven_replace_by_collector_type/cms/MessageFormat.java"
                          tofile="src/main/java/com/company/product/format/MessageFormat.java"
                          overwrite="true"/>

                  </target>
                </configuration>
              </execution>

              <execution>
                <id>Remove everything related to XML, Fixed Length String, JSON and Another Data Provider</id>
                <phase>compile</phase> <!-- After compilation -->
                <goals>
                  <goal>run</goal>
                </goals>
                <configuration>
                  <target>

                    <delete includeemptydirs="true">
                      <fileset
                              dir="${project.build.outputDirectory}/com/company/product/"
                              includes="**/xml*/**, **/Xml*
                                        **/Json*"/>
                    </delete>

                  </target>
                </configuration>
              </execution>
            </executions>
          </plugin>
        </plugins>
      </build>
    </profile>

    <profile>
      <id>another data provider</id>

      <build>
        <plugins>
          <plugin>
            <artifactId>maven-antrun-plugin</artifactId>

            <executions> <!-- Replace the MessageFormat.java in the working directory from "format/maven_replace_by_collector_type" -->
              <execution>
                <id>Replace the MessageFormat.java</id>
                <phase>validate</phase> <!-- Before compilation -->
                <goals>
                  <goal>run</goal>
                </goals>
                <configuration>
                  <target>

                    <copy file="src/main/java/com/company/product/format/maven_replace_by_collector_type/another_data_provider/MessageFormat.java"
                          tofile="src/main/java/com/company/product/format/MessageFormat.java"
                          overwrite="true"/>

                  </target>
                </configuration>
              </execution>

              <execution>
                <id>Remove everything related to XML, Fixed Length String, JSON and CMS</id>
                <phase>compile</phase> <!-- After compilation -->
                <goals>
                  <goal>run</goal>
                </goals>
                <configuration>
                  <target>

                    <delete includeemptydirs="true">
                      <fileset
                              dir="${project.build.outputDirectory}/com/company/product/"
                              includes="**/xml*/**, **/Xml*
                                        **/FixedLengthString*
                                        **/Json*
                                        **/cms*/**, **/Cms*"/>
                    </delete>

                  </target>
                </configuration>
              </execution>
            </executions>
          </plugin>
        </plugins>
      </build>
    </profile>

  </profiles>

</project>

It works fine, but it is quite difficult to maintain, because I cannot properly work on that MessageFormat file, since its real versions are excluded from IDE (and they contain "wrong package", so, even if I include it into IDE, then everything is broken and if I fix the package, then it doesn't compile, because maven just replaces the file, without package changing). Also, if I work on the "current" MessageFormat it then gets overridden by Maven. I also tried to put the format into Maven modules and include them on the particular profile, but it means I must delete the format from the base module and break all the links to it, which makes maintenance even worse.

\$\endgroup\$
1
  • \$\begingroup\$ I don't think that's the best approach. This problem can be easily solved within the code and with configuration management. \$\endgroup\$
    – slowy
    Commented Sep 4, 2017 at 12:25

1 Answer 1

2
\$\begingroup\$

You can use war overlays

You need something like this:

Parent pom:

<?xml version="1.0" encoding="UTF-8"?>
<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/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.greg</groupId>
  <artifactId>overlay</artifactId>
  <version>1.0-SNAPSHOT</version>

  <packaging>pom</packaging>

  <name>overlay Maven Webapp</name>
  <url>http://maven.apache.org</url>

  <build>
    <finalName>overlay</finalName>
  </build>

  <modules>
    <module>basewebapp</module>
    <module>webapp1</module>
    <module>webapp2</module>
  </modules>

</project>

The base war is your current war, then each of the versions will look like:

<?xml version="1.0"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <modelVersion>4.0.0</modelVersion>

  <parent>
    <groupId>com.greg</groupId>
    <artifactId>overlay</artifactId>
    <version>1.0-SNAPSHOT</version>
  </parent>

  <artifactId>webapp1</artifactId>

  <packaging>war</packaging>
  <name>webapp1 Maven Webapp</name>

  <url>http://maven.apache.org</url>
  <dependencies>
    <dependency>
          <groupId>com.greg</groupId>
          <artifactId>basewebapp</artifactId>
      <version>${project.version}</version>
          <type>war</type>
    </dependency>
  </dependencies>

  <build>
          <plugins>
                  <plugin>
                        <groupId>org.apache.maven.plugins</groupId>
                        <artifactId>maven-war-plugin</artifactId>
                        <version>3.1.0</version>
                        <configuration>
                          <overlays>
                                <overlay>
                                  <groupId>com.greg</groupId>
                                  <artifactId>basewebapp</artifactId>
                                </overlay>
                          </overlays>
                        </configuration>
                  </plugin>
                </plugins>
        </build>

</project>

Anything in webapp1/src... will overwrite anything in basewebapp/src....

So you can have 2 versions of your MessageFormat in the webapp1 and webapp2

This will also work for jar projects, the parent pom will be the same, as will the base pom (except of course they will be of type jar), the jar1 pom will be like :

<?xml version="1.0"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <modelVersion>4.0.0</modelVersion>

  <parent>
    <groupId>com.greg</groupId>
    <artifactId>overlay1</artifactId>
    <version>1.0-SNAPSHOT</version>
  </parent>
  <artifactId>jar1</artifactId>

  <name>jar1</name>

  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>com.greg</groupId>
      <artifactId>basejar</artifactId>
      <version>${project.version}</version>
    </dependency>
  </dependencies>

</project>

Anything in the jar1 project will overwrite anything in the basejar project, no need a plugin. So you can have all your code in the basejar project with a stubbed MessageFormat class then jar1 and jar2 will have the real MessageFormat.

\$\endgroup\$
8
  • \$\begingroup\$ Thank you very much! I will investigate and accept your answer if it fits. Look very good apart from war instead of jar. \$\endgroup\$
    – Sam
    Commented Sep 4, 2017 at 13:53
  • \$\begingroup\$ Oh dear I didn't read the jar bit, you can't do this with jars! \$\endgroup\$
    – Essex Boy
    Commented Sep 4, 2017 at 13:58
  • \$\begingroup\$ Damn, that's disappointing \$\endgroup\$
    – Sam
    Commented Sep 4, 2017 at 13:59
  • 1
    \$\begingroup\$ I've updated the answer to show my test pom, just take the plugin our altogether and make the projects of type jar. \$\endgroup\$
    – Essex Boy
    Commented Sep 4, 2017 at 14:41
  • 1
    \$\begingroup\$ Sorry it will not work with jars. \$\endgroup\$
    – Essex Boy
    Commented Sep 4, 2017 at 15:07

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.