mirror of https://github.com/OpenIdentityPlatform/OpenDJ.git

Mark Craig
17.09.2015 e1bd1505ae1dd09a564b6dd2c0021020d74c8422
CR-6372 OPENDJ-1786 Automate integration of generated content

Implement plugin for generating RefEntry source files

This patch adds a Mojo for generating DocBook XML RefEntry sources.

The tools to document must be on the project class path.

Apply fixes suggested by Jean-Noël in CR-6372
2 files added
6 files modified
517 ■■■■ changed files
opendj-cli/src/main/java/com/forgerock/opendj/cli/ArgumentParser.java 20 ●●●●● patch | view | raw | blame | history
opendj-cli/src/main/java/com/forgerock/opendj/cli/DocGenerationHelper.java 23 ●●●●● patch | view | raw | blame | history
opendj-cli/src/main/java/com/forgerock/opendj/cli/SubCommandArgumentParser.java 2 ●●● patch | view | raw | blame | history
opendj-cli/src/main/java/com/forgerock/opendj/cli/ToolRefDocContainer.java 72 ●●●●● patch | view | raw | blame | history
opendj-cli/src/main/resources/templates/refEntry.ftl 6 ●●●●● patch | view | raw | blame | history
opendj-maven-plugin/src/main/java/org/forgerock/opendj/maven/CommandLineTool.java 158 ●●●●● patch | view | raw | blame | history
opendj-maven-plugin/src/main/java/org/forgerock/opendj/maven/GenerateRefEntriesMojo.java 215 ●●●●● patch | view | raw | blame | history
opendj-server-legacy/pom.xml 21 ●●●●● patch | view | raw | blame | history
opendj-cli/src/main/java/com/forgerock/opendj/cli/ArgumentParser.java
@@ -698,24 +698,6 @@
    }
    /**
     * Additional paths to DocBook XML {@code RefSect1} documents
     * to be appended after generated content in reference documentation.
     */
    private String[] pathsToTrailingRefSect1s;
    /** {@inheritDoc} */
    @Override
    public String[] getPathsToTrailingRefSect1s() {
        return pathsToTrailingRefSect1s != null ? pathsToTrailingRefSect1s : new String[0];
    }
    /** {@inheritDoc} */
    @Override
    public void setPathsToTrailingRefSect1s(final String... paths) {
        this.pathsToTrailingRefSect1s = paths;
    }
    /**
     * Retrieves the set of unnamed trailing arguments that were provided on the
     * command line.
     *
@@ -787,7 +769,7 @@
            map.put("optionSection", getOptionsRefSect1(scriptName));
        }
        map.put("subcommands", null);
        map.put("trailingSections", pathsToXIncludes(getPathsToTrailingRefSect1s()));
        map.put("trailingSectionString", System.getProperty("org.forgerock.opendj.gendoc.trailing"));
        applyTemplate(builder, "refEntry.ftl", map);
    }
opendj-cli/src/main/java/com/forgerock/opendj/cli/DocGenerationHelper.java
@@ -32,8 +32,6 @@
import java.io.ByteArrayOutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
/**
@@ -136,25 +134,4 @@
        final String id = argument.getLongIdentifier();
        return ("add".equals(id) || "remove".equals(id) || "reset".equals(id) || "set".equals(id));
    }
    /**
     * Translate paths to XML files to XInclude elements.
     *
     * @return  XInclude elements corresponding to the paths.
     */
    static List<String> pathsToXIncludes(final String[] paths) {
        if (paths == null) {
            return new LinkedList<String>();
        }
        // Assume xmlns:xinclude="http://www.w3.org/2001/XInclude",
        // as in the declaration of resources/templates/refEntry.ftl.
        final String nameSpace = "xinclude";
        List<String> xIncludes = new LinkedList<String>();
        for (int i = 0; i < paths.length; ++i) {
            xIncludes.add("<" + nameSpace + ":include href=\"" + paths[i] + "\" />");
        }
        return xIncludes;
    }
}
opendj-cli/src/main/java/com/forgerock/opendj/cli/SubCommandArgumentParser.java
@@ -1153,7 +1153,7 @@
            map.put("optionSection", getOptionsRefSect1(scriptName));
        }
        map.put("subcommands", toRefSect1(scriptName, subCommands));
        map.put("trailingSections", pathsToXIncludes(getPathsToTrailingRefSect1s()));
        map.put("trailingSectionString", System.getProperty("org.forgerock.opendj.gendoc.trailing"));
        applyTemplate(builder, "refEntry.ftl", map);
    }
opendj-cli/src/main/java/com/forgerock/opendj/cli/ToolRefDocContainer.java
@@ -87,76 +87,4 @@
     *          intended for use in generated reference documentation.
     */
    void setDocSubcommandsDescriptionSupplement(final LocalizableMessage docSubcommandsDescriptionSupplement);
    /**
     * Get additional paths to DocBook XML {@code RefSect1} documents
     * to be appended after generated content in reference documentation.
     *
     * <br>
     *
     * DocBook represents a reference manual page with the {@code RefEntry}.
     * See <a href="http://www.docbook.org/tdg51/en/html/refentry.html">refentry</a>.
     *
     * <br>
     *
     * A {@code RefEntry} describing an OpenDJ tool contains
     * block elements in the following order:
     *
     * <pre>
     *     RefMeta
     *     RefNameDiv
     *     RefSynopsisDiv
     *     RefSect1 - Description (generated, potentially with a hand-written supplement)
     *     RefSect1 - Options (generated)
     *     RefSect1 - Subcommands (optional, hand-written intro + generated RefSect2s)
     *     RefSect1 - Filter (optional, hand-written)
     *     RefSect1 - Attribute (optional, hand-written)
     *     RefSect1 - Exit Codes (hand-written)
     *     RefSect1 - Files (optional, hand-written)
     *     RefSect1 - Examples (hand-written)
     *     RefSect1 - See Also (hand-written)
     * </pre>
     *
     * As the trailing RefSect1s following Subcommands are hand-written,
     * they are included in the generated content as XIncludes elements.
     *
     * @return  The paths to trailing {@code RefSect1} documents.
     */
    String[] getPathsToTrailingRefSect1s();
    /**
     * Set additional paths to DocBook XML {@code RefSect1} documents
     * to be appended after generated content in reference documentation.
     *
     * <br>
     *
     * DocBook represents a reference manual page with the {@code RefEntry}.
     * See <a href="http://www.docbook.org/tdg51/en/html/refentry.html">refentry</a>.
     *
     * <br>
     *
     * A {@code RefEntry} describing an OpenDJ tool contains
     * block elements in the following order:
     *
     * <pre>
     *     RefMeta
     *     RefNameDiv
     *     RefSynopsisDiv
     *     RefSect1 - Description (generated, potentially with a hand-written supplement)
     *     RefSect1 - Options (generated)
     *     RefSect1 - Subcommands (optional, hand-written intro + generated RefSect2s)
     *     RefSect1 - Filter (optional, hand-written)
     *     RefSect1 - Attribute (optional, hand-written)
     *     RefSect1 - Exit Codes (hand-written)
     *     RefSect1 - Files (optional, hand-written)
     *     RefSect1 - Examples (hand-written)
     *     RefSect1 - See Also (hand-written)
     * </pre>
     *
     * As the trailing RefSect1s following Subcommands are hand-written,
     * they are included in the generated content as XIncludes elements.
     *
     * @param paths The paths to trailing {@code RefSect1} documents.
     */
    public void setPathsToTrailingRefSect1s(final String... paths);
}
opendj-cli/src/main/resources/templates/refEntry.ftl
@@ -100,9 +100,7 @@
   ${subcommands}
 </#if>
 <#if trailingSections??>
   <#list trailingSections as section>
    ${section}
   </#list>
 <#if trailingSectionString??>
   ${trailingSectionString}
 </#if>
</refentry>
opendj-maven-plugin/src/main/java/org/forgerock/opendj/maven/CommandLineTool.java
New file
@@ -0,0 +1,158 @@
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
 * or http://forgerock.org/license/CDDLv1.0.html.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at legal-notices/CDDLv1_0.txt.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Copyright 2015 ForgeRock AS.
 */
package org.forgerock.opendj.maven;
import java.util.List;
/**
 * Represents a command-line tool as used in the configuration for {@see GenerateRefEntriesMojo}.
 * <br>
 * Command-line tools are associated with a script name, the Java class of the tool,
 * and a list of relative paths to hand-written files for trailing sections.
 * <br>
 * Trailing section paths are relative to the RefEntry file to write.
 */
public class CommandLineTool {
    /** The script name. */
    private String name;
    /** The tool class. */
    private String application;
    /**
     * Additional paths to DocBook XML {@code RefSect1} documents
     * to be appended after generated content in reference documentation.
     *
     * <br>
     *
     * DocBook represents a reference manual page with the {@code RefEntry}.
     * See <a href="http://www.docbook.org/tdg51/en/html/refentry.html">refentry</a>.
     *
     * <br>
     *
     * A {@code RefEntry} describing an OpenDJ tool contains
     * block elements in the following order:
     *
     * <pre>
     *     RefMeta
     *     RefNameDiv
     *     RefSynopsisDiv
     *     RefSect1 - Description (generated, potentially with a hand-written supplement)
     *     RefSect1 - Options (generated)
     *     RefSect1 - Subcommands (optional, hand-written intro + generated RefSect2s)
     *     RefSect1 - Filter (optional, hand-written)
     *     RefSect1 - Attribute (optional, hand-written)
     *     RefSect1 - Exit Codes (hand-written)
     *     RefSect1 - Files (optional, hand-written)
     *     RefSect1 - Examples (hand-written)
     *     RefSect1 - See Also (hand-written)
     * </pre>
     *
     * As the trailing RefSect1s following Subcommands are hand-written,
     * they are included in the generated content as XIncludes elements.
     * The paths in this case are therefore relative to the current RefEntry.
     */
    private List<String> trailingSectionPaths;
    /**
     * Returns the script name.
     * @return The script name.
     */
    public String getName() {
        return name;
    }
    /**
     * Set the script name.
     * @param name The script name.
     */
    public void setName(final String name) {
        this.name = name;
    }
    /**
     * Returns the tool class.
     * @return The tool class.
     */
    public String getApplication() {
        return application;
    }
    /**
     * Set the tool class.
     * @param application The tool class.
     */
    public void setApplication(final String application) {
        this.application = application;
    }
    /**
     * Returns additional paths to DocBook XML {@code RefSect1} documents
     * to be appended after generated content in reference documentation.
     *
     * <br>
     *
     * DocBook represents a reference manual page with the {@code RefEntry}.
     * See <a href="http://www.docbook.org/tdg51/en/html/refentry.html">refentry</a>.
     *
     * <br>
     *
     * A {@code RefEntry} describing an OpenDJ tool contains
     * block elements in the following order:
     *
     * <pre>
     *     RefMeta
     *     RefNameDiv
     *     RefSynopsisDiv
     *     RefSect1 - Description (generated, potentially with a hand-written supplement)
     *     RefSect1 - Options (generated)
     *     RefSect1 - Subcommands (optional, hand-written intro + generated RefSect2s)
     *     RefSect1 - Filter (optional, hand-written)
     *     RefSect1 - Attribute (optional, hand-written)
     *     RefSect1 - Exit Codes (hand-written)
     *     RefSect1 - Files (optional, hand-written)
     *     RefSect1 - Examples (hand-written)
     *     RefSect1 - See Also (hand-written)
     * </pre>
     *
     * As the trailing RefSect1s following Subcommands are hand-written,
     * they are included in the generated content as XIncludes elements.
     * The paths in this case are therefore relative to the current RefEntry.
     *
     * @return The relative paths to trailing section files.
     */
    public List<String> getTrailingSectionPaths() {
        return trailingSectionPaths;
    }
    /**
     * Set additional paths to DocBook XML {@code RefSect1} documents.
     * @param paths The paths relative to the current RefEntry.
     */
    public void setTrailingSectionPaths(final List<String> paths) {
        this.trailingSectionPaths = paths;
    }
}
opendj-maven-plugin/src/main/java/org/forgerock/opendj/maven/GenerateRefEntriesMojo.java
New file
@@ -0,0 +1,215 @@
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
 * or http://forgerock.org/license/CDDLv1.0.html.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at legal-notices/CDDLv1_0.txt.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Copyright 2015 ForgeRock AS.
 */
package org.forgerock.opendj.maven;
import org.apache.maven.artifact.DependencyResolutionRequiredException;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.plugins.annotations.ResolutionScope;
import org.apache.maven.project.MavenProject;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.LinkedList;
import java.util.List;
/**
 * Generate DocBook RefEntry source documents for command-line tools man pages.
 */
@Mojo(name = "generate-refentry", defaultPhase = LifecyclePhase.PREPARE_PACKAGE,
        requiresDependencyResolution = ResolutionScope.COMPILE_PLUS_RUNTIME)
public final class GenerateRefEntriesMojo extends AbstractMojo {
    /** The Maven Project. */
    @Parameter(property = "project", required = true, readonly = true)
    private MavenProject project;
    /** Tools for which to generate RefEntry files. */
    @Parameter
    private List<CommandLineTool> tools;
    /** Where to write the RefEntry files. */
    @Parameter(required = true)
    private File outputDir;
    /** End of line. */
    public static final String EOL = System.getProperty("line.separator");
    /**
     * Writes a RefEntry file to the output directory for each tool.
     * Files names correspond to script names: {@code man-&lt;name>.xml}.
     *
     * @throws MojoExecutionException   Encountered a problem writing a file.
     * @throws MojoFailureException     Failed to initialize effectively,
     *                                  or to write one or more RefEntry files.
     */
    @Override
    public void execute() throws MojoExecutionException, MojoFailureException {
        PrintStream out = System.out;
        // Set the magic property for generating DocBook XML.
        System.setProperty("org.forgerock.opendj.gendoc", "true");
        // A Maven plugin classpath does not include project files.
        // Prepare a ClassLoader capable of loading the command-line tools.
        URLClassLoader toolsClassLoader;
        try {
            List<String> runtimeClasspathElements = project.getRuntimeClasspathElements();
            List<URL> runtimeUrls = new LinkedList<URL>();
            for (String element : runtimeClasspathElements) {
                runtimeUrls.add(new File(element).toURI().toURL());
            }
            toolsClassLoader = new URLClassLoader(
                    runtimeUrls.toArray(new URL[runtimeClasspathElements.size()]),
                    Thread.currentThread().getContextClassLoader());
        } catch (DependencyResolutionRequiredException e) {
            throw new MojoFailureException("Failed to access the runtime classpath.", e);
        } catch (MalformedURLException e) {
            throw new MojoFailureException("Failed to add element to classpath.", e);
        }
        debugClassPathElements(toolsClassLoader);
        List<String> failures = new LinkedList<String>();
        for (CommandLineTool tool : tools) {
            final File manPage = new File(outputDir, "man-" + tool.getName() + ".xml");
            try {
                setSystemOut(refEntryFile(manPage.getPath()));
            } catch (FileNotFoundException e) {
                setSystemOut(out);
                failures.add(manPage.getPath());
                throw new MojoExecutionException("Failed to write " + manPage.getPath(), e);
            }
            // Set the properties for script name and list of trailing sections.
            System.setProperty("com.forgerock.opendj.ldap.tools.scriptName", tool.getName());
            final String xInclude = pathsToXIncludes(tool.getTrailingSectionPaths());
            System.setProperty("org.forgerock.opendj.gendoc.trailing", xInclude);
            try {
                final Class<?> toolClass = toolsClassLoader.loadClass(tool.getApplication());
                final Class[] argTypes = new Class[]{String[].class};
                final Method main = toolClass.getDeclaredMethod("main", argTypes);
                final String[] args = {"-?"};
                main.invoke(null, (Object) args);
            } catch (ClassNotFoundException e) {
                failures.add(manPage.getPath());
                throw new MojoExecutionException(tool.getApplication() + " not found", e);
            } catch (NoSuchMethodException e) {
                failures.add(manPage.getPath());
                throw new MojoExecutionException(tool.getApplication() + " has no main method.", e);
            } catch (IllegalAccessException e) {
                failures.add(manPage.getPath());
                throw new MojoExecutionException("Failed to run " + tool.getApplication() + ".main()", e);
            } catch (InvocationTargetException e) {
                failures.add(manPage.getPath());
                throw new MojoExecutionException("Failed to run " + tool.getApplication() + ".main()", e);
            } finally {
                setSystemOut(out);
            }
        }
        final StringBuilder list = new StringBuilder();
        if (!failures.isEmpty()) {
            for (final String failure : failures) {
                list.append(failure).append(EOL);
            }
            throw new MojoFailureException("Failed to write the following RefEntry files: " + list);
        }
    }
    /**
     * Logs what is on the classpath for debugging.
     * @param classLoader   The ClassLoader with the classpath.
     */
    private void debugClassPathElements(ClassLoader classLoader) {
        if (null == classLoader) {
            return;
        }
        getLog().debug("--------------------");
        getLog().debug(classLoader.toString());
        if (classLoader instanceof URLClassLoader) {
            final URLClassLoader ucl = (URLClassLoader) classLoader;
            int i = 0;
            for (URL url : ucl.getURLs()) {
                getLog().debug("url[" + (i++) + "]=" + url);
            }
        }
        debugClassPathElements(classLoader.getParent());
    }
    /**
     * Returns a PrintStream to a file to which to write a RefEntry.
     * @param   path                    Path to the file to be written.
     * @return                          PrintStream to a file to which to write a RefEntry.
     * @throws  FileNotFoundException   Failed to open the file for writing.
     */
    private PrintStream refEntryFile(final String path) throws FileNotFoundException {
        return new PrintStream(new BufferedOutputStream(new FileOutputStream(path)), true);
    }
    /**
     * Sets the System output stream.
     * @param out   The stream to use.
     */
    private void setSystemOut(PrintStream out) {
        if (out != null) {
            System.setOut(out);
        }
    }
    /**
     * Translates relative paths to XML files into XInclude elements.
     *
     * @param paths Paths to XInclude'd files, relative to the RefEntry.
     * @return      String of XInclude elements corresponding to the paths.
     */
    private String pathsToXIncludes(final List<String> paths) {
        if (paths == null) {
            return "";
        }
        // Assume xmlns:xinclude="http://www.w3.org/2001/XInclude",
        // as in the declaration of resources/templates/refEntry.ftl.
        final String nameSpace = "xinclude";
        final StringBuilder result = new StringBuilder();
        for (String path : paths) {
            result.append("<").append(nameSpace).append(":include href=\"").append(path).append("\" />").append(EOL);
        }
        return result.toString();
    }
}
opendj-server-legacy/pom.xml
@@ -529,6 +529,27 @@
              </messageFileNames>
            </configuration>
          </execution>
          <!-- TODO: Generate man page sources
          <execution>
             <id>generate-doc</id>
             <goals>
               <goal>generate-refentry</goal>
             </goals>
             <configuration>
               <outputDir>${project.build.directory}</outputDir>
               <tools>
                 <tool>
                  <name>ldapsearch</name>
                  <application>org.opends.server.tools.LDAPSearch</application>
                  <trailingSectionPaths>
                    <trailingSectionPath>../shared/refsect1.xml</trailingSectionPath>
                  </trailingSectionPaths>
                 </tool>
               </tools>
             </configuration>
           </execution>
          -->
        </executions>
      </plugin>