views:

519

answers:

2

I was faced to a problem how to version a configuration file in XML format. The easiest way is to write XSLT updates. Every release of the application has its own XSLT update. All these update files are small enough to be managable by the IDE, especially its DIFF tool.

Since the project is already been developed as Maven2 Java logical solution was to trigger these updates through maven build file.

This is how the section for applying a set of updates looks today:

<plugin>
  <groupId>org.codehaus.mojo</groupId>
  <artifactId>xml-maven-plugin</artifactId>
  <executions>
    <execution>
    <phase>compile</phase>
      <goals>
        <goal>transform</goal>
      </goals>
    </execution>
  </executions>
  <configuration>
    <transformationSets>
      <transformationSet>
        <dir>config/xsltUpdates/input</dir>
        <stylesheet>config/xsltUpdates/update1-8-3.xsl</stylesheet>
        <outputDir>config/xsltUpdates/update1-8-3</outputDir>
      </transformationSet>
      <transformationSet>
         <dir>config/xsltUpdates/update1-8-3</dir>
         <stylesheet>config/xsltUpdates/update1-8-9.xsl</stylesheet>
         <outputDir>config/xsltUpdates/update1-8-9</outputDir>
      </transformationSet>
      <transformationSet>
        <dir>config/xsltUpdates/update1-8-9</dir>
        <stylesheet>config/xsltUpdates/update1-9-0.xsl</stylesheet>
        <outputDir>config/xsltUpdates/update1-9-0</outputDir>
      </transformationSet>
      <transformationSet>
        <dir>config/xsltUpdates/update1-9-0</dir>
        <stylesheet>config/xsltUpdates/update1-10-0.xsl</stylesheet>
        <outputDir>config/xsltUpdates/update1-10-0</outputDir>
      </transformationSet>
      <transformationSet>
        <dir>config/xsltUpdates/update1-10-0</dir>
        <stylesheet>config/xsltUpdates/update1-10-0-1.xsl</stylesheet>
        <outputDir>config/xsltUpdates/update1-10-0-1</outputDir>
      </transformationSet>
      <transformationSet>
        <dir>config/xsltUpdates/update1-10-0-1</dir>
        <stylesheet>config/xsltUpdates/update1-10-0-2.xsl</stylesheet>
        <outputDir>config/xsltUpdates/update1-10-0-2</outputDir>
      </transformationSet>
      <transformationSet>
        <dir>config/xsltUpdates/update1-10-0-2</dir>
        <stylesheet>config/xsltUpdates/updateCurrent.xsl</stylesheet>
        <outputDir>config/xsltUpdates/output</outputDir>
      </transformationSet>
    </transformationSets>
  </configuration>
  <dependencies>
    <dependency>
      <groupId>net.sf.saxon</groupId>
      <artifactId>saxon</artifactId>
      <version>8.7</version>
    </dependency>
  </dependencies>
</plugin>

I would like to externalize information about transformationSet in some properties/xml file import. My pom.xml file will be cleaner and externalized info easier for maintenance.

How can I do that?

Can I use some iterating control statement inside the build file? Is there a way to import data from some external file?

+1  A: 

There may be other ways to do this, but you could have a pluginManagement section in a parent pom.

pluginManagement: is an element that is seen along side plugins. Plugin Management contains plugin elements in much the same way, except that rather than configuring plugin information for this particular project build, it is intended to configure project builds that inherit from this one. However, this only configures plugins that are actually referenced within the plugins element in the children. The children have every right to override pluginManagement definitions.

For example:

parent project POM (need to run mvn install to ensure this is visible to your child project)

<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"&gt;
  <modelVersion>4.0.0</modelVersion>
  <groupId>org.nkl</groupId>
  <artifactId>parent</artifactId>
  <packaging>pom</packaging>
  <version>0.0.1-SNAPSHOT</version>
  <build>
    <pluginManagement>
      <plugins>
        <plugin>
          <groupId>org.codehaus.mojo</groupId>
          <artifactId>xml-maven-plugin</artifactId>
          <executions>
            <execution>
              <phase>compile</phase>
              <goals>
                <goal>transform</goal>
              </goals>
            </execution>
          </executions>
          <configuration>
            <transformationSets>
              <transformationSet>
                <dir>config/xsltUpdates/input</dir>
                <stylesheet>config/xsltUpdates/update1-8-3.xsl</stylesheet>
                <outputDir>config/xsltUpdates/update1-8-3</outputDir>
              </transformationSet>
              <transformationSet>
                <dir>config/xsltUpdates/update1-8-3</dir>
                <stylesheet>config/xsltUpdates/update1-8-9.xsl</stylesheet>
                <outputDir>config/xsltUpdates/update1-8-9</outputDir>
              </transformationSet>
              <transformationSet>
                <dir>config/xsltUpdates/update1-8-9</dir>
                <stylesheet>config/xsltUpdates/update1-9-0.xsl</stylesheet>
                <outputDir>config/xsltUpdates/update1-9-0</outputDir>
              </transformationSet>
              <transformationSet>
                <dir>config/xsltUpdates/update1-9-0</dir>
                <stylesheet>config/xsltUpdates/update1-10-0.xsl</stylesheet>
                <outputDir>config/xsltUpdates/update1-10-0</outputDir>
              </transformationSet>
              <transformationSet>
                <dir>config/xsltUpdates/update1-10-0</dir>
                <stylesheet>config/xsltUpdates/update1-10-0-1.xsl</stylesheet>
                <outputDir>config/xsltUpdates/update1-10-0-1</outputDir>
              </transformationSet>
              <transformationSet>
                <dir>config/xsltUpdates/update1-10-0-1</dir>
                <stylesheet>config/xsltUpdates/update1-10-0-2.xsl</stylesheet>
                <outputDir>config/xsltUpdates/update1-10-0-2</outputDir>
              </transformationSet>
              <transformationSet>
                <dir>config/xsltUpdates/update1-10-0-2</dir>
                <stylesheet>config/xsltUpdates/updateCurrent.xsl</stylesheet>
                <outputDir>config/xsltUpdates/output</outputDir>
              </transformationSet>
            </transformationSets>
          </configuration>
          <dependencies>
            <dependency>
              <groupId>net.sf.saxon</groupId>
              <artifactId>saxon</artifactId>
              <version>8.7</version>
            </dependency>
          </dependencies>
        </plugin>
      </plugins>
    </pluginManagement>
  </build>
</project>

Child project POM

<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"&gt;
  <parent>
    <artifactId>parent</artifactId>
    <groupId>org.nkl</groupId>
    <version>0.0.1-SNAPSHOT</version>
  </parent>
  <modelVersion>4.0.0</modelVersion>
  <groupId>org.nkl</groupId>
  <artifactId>child</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <build>
    <plugins>
      <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>xml-maven-plugin</artifactId>
      </plugin>
    </plugins>
  </build>
</project>
toolkit
Current pom already has a parent file. Maven2 doesn't allow multiple inheritance. I can make my pom grand child of its current parent and add a new parent in between which will be holding externalized data...
Boris Pavlović
... Problem is that externalization will be done out of the project's scope which I would like to avoid
Boris Pavlović
Sorry I couldn't be more help Boris.
toolkit
+3  A: 

Some plugins allow you to use external descriptors (for example the maven-assembly-plugin). Unfortunately the xml-maven-plugin isn't yet one of them.

One option is to copy the relevant goals from the xml-maven-plugin and shoehorn the processing from maven-shared-io into the goal. I've been looking to do this myself with a view to raising requests against various plugins to use descriptor files and the LocatorStrategy approach to find the descriptors. Here's some processing that will modify the xml-maven-plugin to allow descriptors to be used. Note there is little validation of the files involved so it is a bit fragile as is, but it does work.

1) Create a new maven-plugin project (say called xml-ext-maven-plugin) with the following dependencies:

<dependencies>
  <dependency>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>xml-maven-plugin</artifactId>
    <version>1.0-beta-2</version>
  </dependency>
  <dependency>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-assembly-plugin</artifactId>
    <version>2.2-beta-2</version>
  </dependency>
</dependencies>

2) Copy the TransformMojo and the AbstractXmlMojo .java files from the xml-maven-plugin (you need the parent mojo to inherit the properties from its javadoc).

3) Add a descriptors property to the TransformMojo:

/**
 * A list of descriptor files to obtain the transformation sets from
 * 
 * @parameter
 */
private String[] descriptors;

4) modify the execute() method to read the descriptors for transformationSets

public void execute() throws MojoExecutionException, MojoFailureException {
    //insert at start of method to resolve transformationSets fronm descriptors
    if (descriptors != null && descriptors.length > 0) {
        transformationSets = readDescriptors(descriptors);
    }

    ...

5) Implement readDescriptors() to allow you to locate descriptors on the classpath or within the project (the reader processing is largely lifted from the assembly-plugin's DefaultAssemblyReader). Note there is little validation in this implementation, a proper plugin would check if values are set etc.

private TransformationSet[] readDescriptors(String[] descriptors)
        throws MojoExecutionException {

    List descriptorSets = new ArrayList();
    // add all the existing transformationSets
    if (transformationSets != null && transformationSets.length != 0) {
        descriptorSets.addAll(Arrays.asList(transformationSets));
    }

    for (int i = 0; i < descriptors.length; i++) {
        getLog().info(
                "Reading transformation descriptor: " + descriptors[i]);

        Location location = getLocation(descriptors[i]);

        Reader reader = null;
        try {
            reader = new InputStreamReader(location.getInputStream(),
                    "UTF-8");

            Xpp3Dom dom = Xpp3DomBuilder.build(reader);

            descriptorSets.addAll(parseTransformationSets(dom));
        } catch (IOException e) {
            throw new MojoExecutionException(
                    "Error reading transformation descriptor: "
                            + descriptors[i], e);
        } catch (XmlPullParserException e) {
            throw new MojoExecutionException(
                    "Error parsing transformation descriptor: "
                            + descriptors[i], e);
        } finally {
            IOUtil.close(reader);
        }
    }

    return (TransformationSet[]) descriptorSets
            .toArray(new TransformationSet[descriptorSets.size()]);
}

/**
 * Create transformationSets from the Xpp3Dom.
 * TODO use plexus utilities to resolve these elegantly?
 */
private List parseTransformationSets(Xpp3Dom dom) {
    // TODO validation of the input files!
    Xpp3Dom[] setDoms = dom.getChildren("transformationSet");

    List sets = new ArrayList();
    for (int i = 0; i < setDoms.length; i++) {
        TransformationSet set = new TransformationSet();
        set.setDir(new File(setDoms[i].getChild("dir").getValue()));
        set.setStylesheet(new File(setDoms[i].getChild("stylesheet")
                .getValue()));

        Xpp3Dom outDom = setDoms[i].getChild("outputDir");

        if (outDom != null) {
            set.setOutputDir(new File(outDom.getValue()));
        }

        sets.add(set);
    }
    return sets;
}

6) Implement getLocation() to use various strategies to discover the file either as a relative path, url, or from the classpath.

private Location getLocation(String path) {
    List strategies = new ArrayList();
    strategies.add(new RelativeFileLocatorStrategy(getBasedir()));
    strategies.add(new ClasspathResourceLocatorStrategy());
    strategies.add(new FileLocatorStrategy());
    strategies.add(new URLLocatorStrategy());

    List refStrategies = new ArrayList();
    refStrategies.add(classpathStrategy);

    Locator locator = new Locator();

    locator.setStrategies(strategies);

    Location location = locator.resolve(path);
    return location;
}

7) Override asAbsoluteFile() to resolve files using the locator strategy (allows us to define the xsl files in the descriptor project as well).

protected File asAbsoluteFile(File f) {
    String path = f.getPath();

    // ensure we're getting a path in the form that URL can handle
    if (path != null) {
        path = path.replaceAll("\\\\", "/");
    }
    Location location = getLocation(path);

    if (location == null) {
        //can't find the file, let the parent implementation have a try
        return super.asAbsoluteFile(f);
    }
    try {
        return location.getFile();
    } catch (IOException e) {
        throw new RuntimeException("unable to read file " + f.getPath(), e);
    }
}

8) Install the plugin to your repository.

9) Create a new maven project to host your transformationSets (say called xml-ext-test-descriptor). the process is the same as for the shared descriptors of the assembly-plugin, i.e. create a project, add some xml files under src/main/resources, and install the project. The xml files are of the form of the standard transformationSets configuration. For example put a couple of the transformations in src/main/resources/transformations1.xml:

<transformationSets>
  <transformationSet>
    <!--the config directory is in the root of the project -->
    <dir>config/xsltUpdates/input</dir>
    <!-- the stylesheet can be in the descriptor project-->
    <stylesheet>/stylesheets/update1-8-3.xsl</stylesheet>       
    <outputDir>config/xsltUpdates/update1-8-3</outputDir>
  </transformationSet>
  <transformationSet>
     <dir>config/xsltUpdates/update1-8-3</dir>
     <stylesheet>/stylesheets/update1-8-9.xsl</stylesheet>
     <outputDir>config/xsltUpdates/update1-8-9</outputDir>
  </transformationSet>
</transformationSets>

10) Put your xsl files in the descriptor project, e.g. src/main/resources/stylesheets/update1-8-3.xsl

11) Configure the new plugin in your project to reference the descriptor project as a dependency and reference the xml file as a descriptor:

<plugin>
  <groupId>org.codehaus.mojo</groupId>
  <artifactId>xml-maven-plugin</artifactId>
  <executions>
    <execution>
      <phase>compile</phase>
      <goals>
        <goal>transform</goal>
      </goals>
    </execution>
  </executions>
  <dependencies>
    <dependency>
      <groupId>name.seller.rich</groupId>
      <artifactId>xml-ext-test-descriptor</artifactId>
      <version>0.0.1</version>
    </dependency>
  </dependencies>
  <configuration>
   <descriptors>
     <!-- will be resolved from xml-ext-test-descriptor -->
     <descriptor>/transformationSet1.xml</descriptor>
   </descriptors>
 </plugin>

If all the above steps worked, when executed the custom plugin will resolve transformationSet1.xml and your xsl files from the xml-ext-test-descriptor dependency and process them as normal.

Rich Seller