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

Mark Craig
18.12.2015 8990a259a41f2f90606233139c4937fc1c8182cc
CR-6114 OPENDJ-1822 Separate generated content from formatting

This patch moves to FreeMarker templates to lay out generated content.
It also moves English strings to properties files.

It would no doubt be possible to reduce the number of templates for dsconfig
by appending to the FreeMarker model instead of appending strings.

This patch is part of the work for
OPENDJ-386 Single reference document, especially for dsconfig.
7 files added
7 files modified
1239 ■■■■■ changed files
opendj-cli/pom.xml 7 ●●●● patch | view | raw | blame | history
opendj-cli/src/main/java/com/forgerock/opendj/cli/ArgumentParser.java 90 ●●●● patch | view | raw | blame | history
opendj-cli/src/main/java/com/forgerock/opendj/cli/DocGenerationHelper.java 137 ●●●●● patch | view | raw | blame | history
opendj-cli/src/main/java/com/forgerock/opendj/cli/SubCommandArgumentParser.java 165 ●●●● patch | view | raw | blame | history
opendj-cli/src/main/java/com/forgerock/opendj/cli/SubCommandUsageHandler.java 16 ●●●●● patch | view | raw | blame | history
opendj-cli/src/main/resources/com/forgerock/opendj/cli/cli.properties 7 ●●●●● patch | view | raw | blame | history
opendj-cli/src/main/resources/templates/dscfgAppendProps.ftl 35 ●●●●● patch | view | raw | blame | history
opendj-cli/src/main/resources/templates/dscfgListSubtypes.ftl 56 ●●●●● patch | view | raw | blame | history
opendj-cli/src/main/resources/templates/dscfgVarListEntry.ftl 32 ●●●●● patch | view | raw | blame | history
opendj-cli/src/main/resources/templates/dscfgVariableList.ftl 51 ●●●●● patch | view | raw | blame | history
opendj-cli/src/main/resources/templates/refEntry.ftl 135 ●●●●● patch | view | raw | blame | history
opendj-cli/src/main/resources/templates/refSect2.ftl 76 ●●●●● patch | view | raw | blame | history
opendj-config/src/main/java/org/forgerock/opendj/config/dsconfig/DSConfig.java 363 ●●●●● patch | view | raw | blame | history
opendj-config/src/main/resources/com/forgerock/opendj/dsconfig/dsconfig.properties 69 ●●●●● patch | view | raw | blame | history
opendj-cli/pom.xml
@@ -21,7 +21,7 @@
  !
  ! CDDL HEADER END
  !
  !      Copyright 2014 ForgeRock AS
  !      Copyright 2014-2015 ForgeRock AS.
  !    
-->
<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">
@@ -60,6 +60,11 @@
      <version>${forgerockBuildToolsVersion}</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.freemarker</groupId>
      <artifactId>freemarker</artifactId>
      <version>2.3.21</version>
    </dependency>
  </dependencies>
  <properties>
    <opendj.osgi.import>
opendj-cli/src/main/java/com/forgerock/opendj/cli/ArgumentParser.java
@@ -28,18 +28,23 @@
import static com.forgerock.opendj.cli.ArgumentConstants.*;
import static com.forgerock.opendj.cli.CliMessages.*;
import static com.forgerock.opendj.cli.DocGenerationHelper.*;
import static com.forgerock.opendj.cli.Utils.*;
import static com.forgerock.opendj.util.StaticUtils.*;
import java.io.File;
import java.io.FileInputStream;
import java.io.OutputStream;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.SortedSet;
@@ -658,74 +663,63 @@
        final StringBuilder buffer = new StringBuilder();
        usageOrVersionDisplayed = true;
        if (System.getProperty("org.forgerock.opendj.gendoc") != null) {
            toRefSect2(buffer);
            toRefEntry(buffer);
        } else {
            getUsage(buffer);
        }
        return buffer.toString();
    }
    private void toRefSect2(StringBuilder sb) {
    /**
     * Appends a generated DocBook XML RefEntry (man page) to the StringBuilder.
     *
     * @param sb    Append the RefEntry element to this.
     */
    private void toRefEntry(StringBuilder sb) {
        final String scriptName = getScriptName();
        if (scriptName == null) {
            throw new RuntimeException("The script name should have been set via the environment property '"
                    + PROPERTY_SCRIPT_NAME + "'.");
        }
        sb.append("<refsect2 xml:id=\"").append(scriptName).append("\">").append(EOL);
        sb.append(" <title>").append(scriptName).append("</title>").append(EOL);
        sb.append(" <para>").append(getToolDescription()).append("</para>").append(EOL);
        // Model for a FreeMarker template.
        Map<String, Object> map = new HashMap<String, Object>();
        map.put("locale", Locale.getDefault().getLanguage());
        map.put("year", new SimpleDateFormat("yyyy").format(new Date()));
        map.put("name", scriptName);
        map.put("descTitle", REF_TITLE_DESCRIPTION.get());
        map.put("optsTitle", REF_TITLE_OPTIONS.get());
        map.put("optsIntro", REF_INTRO_OPTIONS.get(scriptName));
        String args = null;
        if (allowsTrailingArguments) {
            if (trailingArgsDisplayName != null) {
                args = trailingArgsDisplayName;
            } else {
                args = INFO_ARGPARSER_USAGE_TRAILINGARGS.get().toString();
            }
        }
        map.put("args", args);
        map.put("description", getToolDescription());
        // If there is a supplement to the description for this utility,
        // then it is formatted for use in generated reference documentation.
        // In other words, it is already DocBook XML, so append it as is.
        final LocalizableMessage toolDocDescriptionSupplement = getDocToolDescriptionSupplement();
        if (!LocalizableMessage.EMPTY.equals(toolDocDescriptionSupplement)) {
            sb.append(toolDocDescriptionSupplement.toString()).append(EOL);
        }
        // then it is already DocBook XML, so use it as is.
        map.put("info", getDocToolDescriptionSupplement());
        if (!argumentList.isEmpty()) {
            sb.append(" <variablelist>").append(EOL);
            List<Map<String, Object>> options = new LinkedList<Map<String, Object>>();
            for (Argument a : argumentList) {
                sb.append("  <varlistentry>").append(EOL);
                sb.append("    <term><option>");
                final Character shortID = a.getShortIdentifier();
                if (shortID != null) {
                    sb.append("-").append(shortID.charValue());
                }
                final String longID = a.getLongIdentifier();
                if (shortID != null && longID != null) {
                    sb.append(" | ");
                }
                if (longID != null) {
                    sb.append("--").append(longID);
                }
                if (a.needsValue()) {
                    sb.append(" ").append(a.getValuePlaceholder());
                }
                sb.append("</option></term>").append(EOL);
                sb.append("    <listitem>").append(EOL);
                sb.append("      <para>").append(a.getDescription()).append("</para>").append(EOL);
                final String defaultValue = a.getDefaultValue();
                if (defaultValue != null && !defaultValue.isEmpty()) {
                    sb.append("      <para>Default: ").append(defaultValue).append("</para>").append(EOL);
                }
                Map<String, Object> option = new HashMap<String, Object>();
                option.put("synopsis", getOptionSynopsis(a));
                option.put("description", a.getDescription());
                option.put("default", REF_DEFAULT.get(a.getDefaultValue()));
                // If there is a supplement to the description for this argument,
                // then for now it is already formatted in DocBook XML.
                final LocalizableMessage aDocDescriptionSupplement = a.getDocDescriptionSupplement();
                if (!LocalizableMessage.EMPTY.equals(aDocDescriptionSupplement)) {
                    sb.append(aDocDescriptionSupplement.toString()).append(EOL);
                }
                sb.append("    </listitem>").append(EOL);
                sb.append("  </varlistentry>").append(EOL);
                // then it is already DocBook XML, so use it as is.
                option.put("info", a.getDocDescriptionSupplement());
                options.add(option);
            }
            sb.append(" </variablelist>").append(EOL);
            map.put("options", options);
        }
        sb.append("</refsect2>").append(EOL);
        applyTemplate(sb, "refEntry.ftl", map);
    }
    /**
opendj-cli/src/main/java/com/forgerock/opendj/cli/DocGenerationHelper.java
New file
@@ -0,0 +1,137 @@
/*
 * 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 com.forgerock.opendj.cli;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateExceptionHandler;
import java.io.ByteArrayOutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.Map;
/**
 * This class provides utility functions to help generate reference documentation.
 */
public final class DocGenerationHelper {
    /** Prevent instantiation. */
    private DocGenerationHelper() {
        // Do nothing.
    }
    /** FreeMarker template configuration. */
    private static Configuration configuration;
    /**
     * Gets a FreeMarker configuration for applying templates.
     *
     * @return              A FreeMarker configuration.
     */
    private static Configuration getConfiguration() {
        if (configuration == null) {
            configuration = new Configuration(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS);
            configuration.setClassForTemplateLoading(DocGenerationHelper.class, "/templates");
            configuration.setDefaultEncoding("UTF-8");
            configuration.setTemplateExceptionHandler(TemplateExceptionHandler.DEBUG_HANDLER);
        }
        return configuration;
    }
    /**
     * Appends the String result from applying a FreeMarker template.
     *
     * @param builder       Append the result to this.
     * @param template      The name of a template file found in {@code resources/templates/}.
     * @param map           The map holding the data to use in the template.
     */
    public static void applyTemplate(StringBuilder builder, final String template, final Map<String, Object> map) {
        // FixMe: This method is public so it can be used by the SubCommandUsageHandler
        // in org.forgerock.opendj.config.dsconfig.DSConfig.
        // FreeMarker requires a configuration to find the template.
        configuration = getConfiguration();
        // FreeMarker takes the data and a Writer to process the template.
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        Writer writer = new OutputStreamWriter(outputStream);
        try {
            Template configurationTemplate = configuration.getTemplate(template);
            configurationTemplate.process(map, writer);
            builder.append(outputStream.toString());
        } catch (Exception e) {
            throw new RuntimeException(e.getMessage(), e);
        } finally {
            org.forgerock.util.Utils.closeSilently(writer, outputStream);
        }
    }
    /**
     * Returns an option synopsis.
     *
     * <br>
     *
     * Note: The synopsis might contain characters that must be escaped in XML.
     *
     * @param argument  The argument option.
     * @return          A synopsis.
     */
    static String getOptionSynopsis(final Argument argument) {
        StringBuilder builder = new StringBuilder();
        final Character shortID = argument.getShortIdentifier();
        if (shortID != null) {
            builder.append("-").append(shortID.charValue());
        }
        final String longID = argument.getLongIdentifier();
        if (shortID != null && longID != null) {
            builder.append(" | ");
        }
        if (longID != null) {
            builder.append("--").append(longID);
        }
        if (argument.needsValue()) {
            builder.append(" ").append(argument.getValuePlaceholder());
        }
        return builder.toString();
    }
    /**
     * Returns true when the argument handles properties.
     *
     * @param argument  The argument.
     * @return True if the argument handles properties.
     */
    public static boolean doesHandleProperties(final Argument argument) {
        // FixMe: This method is public so it can be used by the SubCommandUsageHandler
        // in org.forgerock.opendj.config.dsconfig.DSConfig.
        final String id = argument.getLongIdentifier();
        return ("add".equals(id) || "remove".equals(id) || "reset".equals(id) || "set".equals(id));
    }
}
opendj-cli/src/main/java/com/forgerock/opendj/cli/SubCommandArgumentParser.java
@@ -28,6 +28,7 @@
import static com.forgerock.opendj.cli.ArgumentConstants.*;
import static com.forgerock.opendj.cli.CliMessages.*;
import static com.forgerock.opendj.cli.DocGenerationHelper.*;
import static com.forgerock.opendj.cli.Utils.*;
import static com.forgerock.opendj.util.StaticUtils.*;
@@ -1102,7 +1103,17 @@
        }
    }
    /** Generate reference documentation for dsconfig subcommands. */
    /**
     * Appends a list generated DocBook XML RefSect2 elements to the StringBuilder, one per subcommand.
     *
     * <br>
     *
     * Note: The result is not a complete XML document.
     * Instead you must wrap the resulting list of RefSect2 elements in a RefSect1.
     *
     * @param builder   Append the list of RefSect2 elements to this.
     * @param values    The SubCommands containing the reference information.
     */
    private void generateReferenceDoc(final StringBuilder builder, Collection<SubCommand> values) {
        for (SubCommand s : values) {
            toRefSect2(s, builder);
@@ -1110,58 +1121,12 @@
    }
    /**
     * Generate reference documentation for dsconfig subcommands in DocBook 5 XML format. As the number of categories is
     * large, the subcommand entries are sorted here by name for inclusion in a &lt;refsect1&gt; covering all dsconfig
     * Subcommands as part of the &lt;refentry&gt; for dsconfig (in man-dsconfig.xml).
     * <p>
     * Although it would be possible to categorize subcommands in the same way as they are categorized in dsconfig
     * interactive mode, this generator does not use the categories.
     * <p>
     * It would also be possible to generate the sort of information provided by the configuration reference, such that
     * this reference would not stop at simply listing an option like --set {PROP:VAL}, but instead would also provide
     * the list of PROPs and their possible VALs. A future improvement could no doubt merge the configuration reference
     * with this content, though perhaps the problem calls for hypertext rather than the linear structure of a
     * &lt;refentry&gt;.
     * <p>
     * Each individual subcommand results in a &lt;refsect2&gt; element similar to the following.
     *
     * <pre>
     *     &lt;refsect2 xml:id=&quot;dsconfig-create-local-db-index&quot;&gt;
     *      &lt;title&gt;dsconfig create-local-db-index&lt;/title&gt;
     *      &lt;para&gt;Creates Local DB Indexes&lt;/para&gt;
     *      &lt;variablelist&gt;
     *       &lt;varlistentry&gt;
     *        &lt;term&gt;&lt;option&gt;--backend-name {name}
     *         &lt;/option&gt;&lt;/term&gt;
     *        &lt;listitem&gt;
     *         &lt;para&gt;The name of the Local DB Backend&lt;/para&gt;
     *        &lt;/listitem&gt;
     *       &lt;/varlistentry&gt;
     *       &lt;varlistentry&gt;
     *        &lt;term&gt;&lt;option&gt;--index-name {OID}&lt;/option&gt;&lt;/term&gt;
     *        &lt;listitem&gt;
     *         &lt;para&gt;The name of the new Local DB Index which will also be used
     *         as the value of the &quot;attribute&quot; property: Specifies the name
     *         of the attribute for which the index is to be maintained.&lt;/para&gt;
     *        &lt;/listitem&gt;
     *       &lt;/varlistentry&gt;
     *       &lt;varlistentry&gt;
     *        &lt;term&gt;&lt;option&gt;--set {PROP:VALUE}&lt;/option&gt;&lt;/term&gt;
     *        &lt;listitem&gt;
     *         &lt;para&gt;Assigns a value to a property where PROP is the name of the
     *         property and VALUE is the single value to be assigned. Specify the same
     *         property multiple times in order to assign more than one value to
     *         it&lt;/para&gt;
     *        &lt;/listitem&gt;
     *       &lt;/varlistentry&gt;
     *      &lt;/variablelist&gt;
     *      &lt;/refsect2&gt;
     * </pre>
     * Appends a generated DocBook XML RefSect2 element for a single subcommand to the StringBuilder.
     *
     * @param sc
     *            The SubCommand containing reference information.
     * @param sb
     *            The string builder where to output the Refsect2 representation of the subcommand
     *            Append the RefSect2 element to this.
     */
    private void toRefSect2(SubCommand sc, StringBuilder sb) {
        final String scriptName = getScriptName();
@@ -1170,90 +1135,50 @@
                    + PROPERTY_SCRIPT_NAME + "'.");
        }
        final String idRef = scriptName + "-" + sc.getName();
        final String nameRef = scriptName + " " + sc.getName();
        sb.append("<refsect2 xml:id=\"").append(idRef).append("\">").append(EOL);
        sb.append(" <title>").append(nameRef).append("</title>").append(EOL);
        sb.append(" <para>").append(sc.getDescription()).append("</para>").append(EOL);
        // Model for a FreeMarker template.
        Map<String, Object> map = new HashMap<String, Object>();
        map.put("id", scriptName + "-" + sc.getName());
        final String name = scriptName + " " + sc.getName();
        map.put("name", name);
        map.put("description", sc.getDescription());
        map.put("optsTitle", REF_TITLE_OPTIONS.get());
        map.put("optsIntro", REF_INTRO_OPTIONS.get(name));
        // If there is a supplement to the description for this subcommand,
        // then it is formatted for use in generated reference documentation.
        // In other words, it is already DocBook XML, so append it as is.
        final LocalizableMessage scDocDescriptionSupplement = sc.getDocDescriptionSupplement();
        if (!LocalizableMessage.EMPTY.equals(scDocDescriptionSupplement)) {
            sb.append(scDocDescriptionSupplement.toString()).append(EOL);
        }
        // then it is already DocBook XML, so use it as is.
        map.put("info", sc.getDocDescriptionSupplement());
        if (!sc.getArguments().isEmpty()) {
            sb.append(" <refsect3 xml:id=\"").append(idRef).append("-options\">").append(EOL);
            sb.append("   <title>Options</title>").append(EOL);
            sb.append("   <variablelist>").append(EOL);
            sb.append("     <para>").append(EOL);
            sb.append("       The <command>").append(nameRef)
              .append("</command> command supports the following options.").append(EOL);
            sb.append("     </para>").append(EOL);
            List<Map<String, Object>> options = new LinkedList<Map<String, Object>>();
            String nameOption = null;
            for (Argument a : sc.getArguments()) {
                sb.append("     <varlistentry>").append(EOL);
                sb.append("       <term>");
                final String option = getOption(a);
                sb.append(option);
                sb.append("</term>").append(EOL);
                sb.append("       <listitem>").append(EOL);
                sb.append("         <para>").append(a.getDescription()).append("</para>").append(EOL);
                final String longID = a.getLongIdentifier();
                if (!"set".equals(longID)
                        && !"reset".equals(longID)
                        && !"add".equals(longID)
                        && !"remove".equals(longID)) {
                    nameOption = option;
                }
                Map<String, Object> option = new HashMap<String, Object>();
                String optionSynopsis = getOptionSynopsis(a);
                option.put("synopsis", optionSynopsis);
                option.put("description", a.getDescription());
                Map<String, Object> info = new HashMap<String, Object>();
                if (subCommandUsageHandler != null) {
                    subCommandUsageHandler.appendArgumentAdditionalInfo(sb, sc, a, nameOption);
                } else {
                    final String defaultValue = a.getDefaultValue();
                    if (defaultValue != null && !defaultValue.isEmpty()) {
                        sb.append("         <para>Default: ").append(defaultValue).append("</para>").append(EOL);
                    if (!doesHandleProperties(a)) {
                        nameOption = "<option>" + optionSynopsis + "</option>";
                    }
                    // Let this build its own arbitrarily formatted additional info.
                    info.put("usage", subCommandUsageHandler.getArgumentAdditionalInfo(sc, a, nameOption));
                } else {
                    info.put("default", REF_DEFAULT.get(a.getDefaultValue()));
                    // If there is a supplement to the description for this argument,
                    // then for now it is already formatted in DocBook XML.
                    final LocalizableMessage aDocDescriptionSupplement = a.getDocDescriptionSupplement();
                    if (!LocalizableMessage.EMPTY.equals(aDocDescriptionSupplement)) {
                        sb.append(aDocDescriptionSupplement.toString()).append(EOL);
                    }
                    // then it is already DocBook XML, so use it as is.
                    info.put("doc", a.getDocDescriptionSupplement());
                }
                sb.append("       </listitem>").append(EOL);
                sb.append("     </varlistentry>").append(EOL);
                option.put("info", info);
                options.add(option);
            }
            sb.append("   </variablelist>").append(EOL);
            sb.append(" </refsect3>").append(EOL);
            map.put("options", options);
        }
        if (subCommandUsageHandler != null) {
            subCommandUsageHandler.appendProperties(sb, sc);
            map.put("properties", subCommandUsageHandler.getProperties(sc));
        }
        sb.append("</refsect2>").append(EOL);
    }
    private String getOption(Argument a) {
        final StringBuilder sb = new StringBuilder();
        sb.append("<option>");
        final Character shortID = a.getShortIdentifier();
        if (shortID != null) {
            sb.append("-").append(shortID.charValue());
        }
        final String longID = a.getLongIdentifier();
        if (shortID != null && longID != null) {
            sb.append(" | ");
        }
        if (longID != null) {
            sb.append("--").append(longID);
        }
        if (a.needsValue()) {
            sb.append(" ").append(a.getValuePlaceholder());
        }
        sb.append("</option>");
        return sb.toString();
        applyTemplate(sb, "refSect2.ftl", map);
    }
}
opendj-cli/src/main/java/com/forgerock/opendj/cli/SubCommandUsageHandler.java
@@ -21,7 +21,7 @@
 * CDDL HEADER END
 *
 *
 *      Copyright 2015 ForgeRock AS
 *      Copyright 2015 ForgeRock AS.
 */
package com.forgerock.opendj.cli;
@@ -32,27 +32,25 @@
public interface SubCommandUsageHandler {
    /**
     * Appends properties information for the sub-command.
     * Returns properties information for the sub-command.
     *
     * @param builder
     *          the string builder where to append
     * @param subCommand
     *          the sub command for which to print usage information
     * @return  The properties information for the sub-command.
     */
    void appendProperties(StringBuilder builder, SubCommand subCommand);
    String getProperties(SubCommand subCommand);
    /**
     * Appends additional information for the provided sub-command argument.
     * Returns additional information for the provided sub-command argument.
     *
     * @param builder
     *          the string builder where to append
     * @param subCommand
     *          the sub command for which to print usage information
     * @param arg
     *          the argument for which to append additional information
     * @param nameOption
     *          the string representing the name option
     * @return  The additional information for the sub-command argument.
     */
    void appendArgumentAdditionalInfo(StringBuilder builder, SubCommand subCommand, Argument arg, String nameOption);
    String getArgumentAdditionalInfo(SubCommand subCommand, Argument arg, String nameOption);
}
opendj-cli/src/main/resources/com/forgerock/opendj/cli/cli.properties
@@ -973,6 +973,13 @@
ERR_ERROR_CANNOT_READ_BIND_NAME=Unable to read bind name
ERR_ERROR_CANNOT_READ_HOST_NAME=Cannot read the host name
# Strings for generated reference documentation.
REF_TITLE_DESCRIPTION=Description
REF_TITLE_OPTIONS=Options
REF_INTRO_OPTIONS=The <command>%s</command> command takes the following options:
REF_DEFAULT=Default: %s
REF_TITLE_SUBCOMMANDS=Subcommands
# Supplements to descriptions for generated reference documentation.
SUPPLEMENT_DESCRIPTION_CONTROLS=<para>                                             \
    For some <replaceable>controloid</replaceable> values,                         \
opendj-cli/src/main/resources/templates/dscfgAppendProps.ftl
New file
@@ -0,0 +1,35 @@
<#--
 # 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.
 #
 #-->
<refsect3 xml:id="${id}">
  <title>${title}</title>
  <para>
   ${intro}
  </para>
  ${list}
</refsect3>
opendj-cli/src/main/resources/templates/dscfgListSubtypes.ftl
New file
@@ -0,0 +1,56 @@
<#--
 # 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.
 #
 #-->
<variablelist>
 <para>
   ${dependencies}
 </para>
 <para>
   ${typesIntro}
 </para>
 <#list children as child>
   <varlistentry>
     <term>${child.name}</term>
     <listitem>
       <para>
         ${child.default}
       </para>
       <para>
         ${child.enabled}
       </para>
       <para>
         ${child.link}
       </para>
     </listitem>
   </varlistentry>
 </#list>
</variablelist>
opendj-cli/src/main/resources/templates/dscfgVarListEntry.ftl
New file
@@ -0,0 +1,32 @@
<#--
 # 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.
 #
 #-->
<varlistentry>
  <term>${term}</term>
  <listitem>
    ${definition}
  </listitem>
</varlistentry>
opendj-cli/src/main/resources/templates/dscfgVariableList.ftl
New file
@@ -0,0 +1,51 @@
<#--
 # 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.
 #
 #-->
<variablelist>
<#list properties as property>
  <varlistentry xml:id="${property.id}">
    <term>${property.term}</term>
    <listitem>
      <variablelist>
        <varlistentry>
          <term>${property.descTitle}</term>
          <listitem>
            <para>
              ${property.description}
            </para>
          </listitem>
        </varlistentry>
        ${property.list}
      </variablelist>
    </listitem>
  </varlistentry>
</#list>
</variablelist>
opendj-cli/src/main/resources/templates/refEntry.ftl
New file
@@ -0,0 +1,135 @@
<#--
 # 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.
 #
 #-->
<?xml version="1.0" encoding="UTF-8"?>
<!--
  ! CCPL HEADER START
  !
  ! This work is licensed under the Creative Commons
  ! Attribution-NonCommercial-NoDerivs 3.0 Unported License.
  ! To view a copy of this license, visit
  ! http://creativecommons.org/licenses/by-nc-nd/3.0/
  ! or send a letter to Creative Commons, 444 Castro Street,
  ! Suite 900, Mountain View, California, 94041, USA.
  !
  ! You can also obtain a copy of the license at
  ! trunk/opendj/legal-notices/CC-BY-NC-ND.txt.
  ! See the License for the specific language governing permissions
  ! and limitations under the License.
  !
  ! If applicable, add the following below this CCPL HEADER, with the fields
  ! enclosed by brackets "[]" replaced with your own identifying information:
  !      Portions Copyright [yyyy] [name of copyright owner]
  !
  ! CCPL HEADER END
  !
  !      Copyright 2011-${year} ForgeRock AS.
  !
-->
<refentry xml:id="${name}-1"
          xmlns="http://docbook.org/ns/docbook" version="5.0" xml:lang="${locale}"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://docbook.org/ns/docbook
                              http://docbook.org/xml/5.0/xsd/docbook.xsd"
          xmlns:xlink="http://www.w3.org/1999/xlink"
          xmlns:xinclude="http://www.w3.org/2001/XInclude">
 <info>
  <copyright>
   <year>2011-${year}</year>
   <holder>ForgeRock AS.</holder>
  </copyright>
 </info>
 <refmeta>
  <refentrytitle>${name}</refentrytitle><manvolnum>1</manvolnum>
  <refmiscinfo class="software">OpenDJ</refmiscinfo>
  <refmiscinfo class="version">${r"${project.version}"}</refmiscinfo>
 </refmeta>
 <refnamediv>
  <refname>${name}</refname>
  <refpurpose>TODO short description</refpurpose>
 </refnamediv>
 <refsynopsisdiv>
  <cmdsynopsis>
   <command>${name}</command>
   <arg choice="plain">${args}</arg>
  </cmdsynopsis>
 </refsynopsisdiv>
 <refsect1 xml:id="${name}-description">
   <title>${descTitle}</title>
   <para>
     ${description}
   </para>
   <#if info??>${info}</#if>
 </refsect1>
 <#if options??>
   <refsect1>
    <title>${optsTitle}</title>
    <variablelist>
     <para>
      ${optsIntro}
     </para>
     <#list options as option>
       <varlistentry>
         <term><option>${option.synopsis?xml}</option></term>
         <listitem>
           <para>
             ${option.description}
           </para>
           <#if option.default??>
             <para>
               ${option.default}
             </para>
           </#if>
           <#if option.info??>${option.info}</#if>
         </listitem>
       </varlistentry>
     </#list>
     </variablelist>
   </refsect1>
 </#if>
 <!-- TODO: subcommands -->
 <!-- TODO: filter -->
 <!-- TODO: attribute -->
 <!-- TODO: exitCodes -->
 <!-- TODO: files -->
 <!-- TODO: examples -->
 <!-- TODO: seeAlso -->
</refentry>
opendj-cli/src/main/resources/templates/refSect2.ftl
New file
@@ -0,0 +1,76 @@
<#--
 # 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.
 #
 #-->
<refsect2 xml:id="${id}">
  <title>${name}</title>
  <para>
   ${description}
  </para>
  <#if info??>${info}</#if>
  <#if options??>
    <refsect3 xml:id="${id}-options">
      <title>${optsTitle}</title>
      <variablelist>
        <para>
         ${optsIntro}
        </para>
        <#list options as option>
        <varlistentry>
          <term><option>${option.synopsis?xml}</option></term>
          <listitem>
           <para>
             ${option.description}
           </para>
           <#if option.info??>
             <#if info.usage??>${option.info.usage}</#if>
             <#if info.default??>
                <para>
                  ${option.info.default}
                </para>
             </#if>
             <#if info.doc??>${option.info.doc}</#if>
           </#if>
          </listitem>
        </varlistentry>
        </#list>
      </variablelist>
    </refsect3>
  </#if>
  <#if properties??>
    ${properties}
  </#if>
</refsect2>
opendj-config/src/main/java/org/forgerock/opendj/config/dsconfig/DSConfig.java
@@ -22,12 +22,13 @@
 *
 *
 *      Copyright 2007-2010 Sun Microsystems, Inc.
 *      Portions Copyright 2012-2015 ForgeRock AS
 *      Portions Copyright 2012-2015 ForgeRock AS.
 */
package org.forgerock.opendj.config.dsconfig;
import static com.forgerock.opendj.cli.ArgumentConstants.*;
import static com.forgerock.opendj.cli.CliMessages.*;
import static com.forgerock.opendj.cli.DocGenerationHelper.*;
import static com.forgerock.opendj.cli.Utils.*;
import static com.forgerock.opendj.dsconfig.DsconfigMessages.*;
import static com.forgerock.opendj.util.StaticUtils.*;
@@ -54,6 +55,7 @@
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
@@ -127,93 +129,61 @@
 */
public final class DSConfig extends ConsoleApplication {
    private static final String ALLOW_UNLIMITED = "A value of \"-1\" or \"unlimited\" for no limit.";
    private static final String ACI_SYNTAX_REL_URL =
        "<link" + EOL
            + " xlink:show=\"new\"" + EOL
            + " xlink:href=\"admin-guide#about-acis\"" + EOL
            + " xlink:role=\"http://docbook.org/xlink/role/olink\">" + EOL
            + "<citetitle>About Access Control Instructions</citetitle></link>" + EOL;
    private static final String DURATION_SYNTAX_REL_URL =
        "  <itemizedlist>" + EOL
            + "    <para>Some property values take a time duration. Durations are expressed" + EOL
            + "    as numbers followed by units. For example <literal>1 s</literal> means" + EOL
            + "    one second, and <literal>2 w</literal> means two weeks. Some durations" + EOL
            + "    have minimum granularity or maximum units, so you cannot necessary specify" + EOL
            + "    every duration in milliseconds or weeks for example. Some durations allow" + EOL
            + "    you to use a special value to mean unlimited. Units are specified as" + EOL
            + "    follows.</para>" + EOL
            + "    <listitem><para><literal>ms</literal>: milliseconds</para></listitem>" + EOL
            + "    <listitem><para><literal>s</literal>: seconds</para></listitem>" + EOL
            + "    <listitem><para><literal>m</literal>: minutes</para></listitem>" + EOL
            + "    <listitem><para><literal>h</literal>: hours</para></listitem>" + EOL
            + "    <listitem><para><literal>d</literal>: days</para></listitem>" + EOL
            + "    <listitem><para><literal>w</literal>: weeks</para></listitem>" + EOL
            + "  </itemizedlist>" + EOL;
    // FIXME: I18n support. Today all the strings are hardcoded in this file
    /**
     * This class provides additional information about subcommands for generated reference documentation.
     */
    private final class DSConfigSubCommandUsageHandler implements SubCommandUsageHandler {
        /** Marker to open a DocBook XML paragraph. */
        private String op = "<para>";
        /** Marker to close a DocBook XML paragraph. */
        private String cp = "</para>";
        /** {@inheritDoc} */
        @Override
        public void appendArgumentAdditionalInfo(StringBuilder sb, SubCommand sc, Argument a, String nameOption) {
        public String getArgumentAdditionalInfo(SubCommand sc, Argument a, String nameOption) {
            StringBuilder sb = new StringBuilder();
            final AbstractManagedObjectDefinition<?, ?> defn = getManagedObjectDefinition(sc);
            if (defn == null) {
                return;
                return "";
            }
            final String longID = a.getLongIdentifier();
            if ("set".equals(longID)
                    || "reset".equals(longID)
                    || "add".equals(longID)
                    || "remove".equals(longID)) {
                sb.append("         <para>").append(EOL);
            if (doesHandleProperties(a)) {
                final LocalizableMessage name = defn.getUserFriendlyName();
                sb.append("           ").append(name).append(" properties depend on the ").append(name)
                  .append(" type, which depends on the ").append(nameOption).append(" option.").append(EOL);
                sb.append("         </para>").append(EOL);
                sb.append(op).append(REF_DSCFG_ARG_ADDITIONAL_INFO.get(name, name, nameOption)).append(cp).append(EOL);
            } else {
                listSubtypes(sb, a, defn);
            }
            return;
            return sb.toString();
        }
        private void listSubtypes(StringBuilder sb, Argument a, AbstractManagedObjectDefinition<?, ?> defn) {
            final LocalizableMessage placeholder = a.getValuePlaceholder();
            sb.append("         <variablelist>").append(EOL);
            sb.append("           <para>").append(EOL);
            final LocalizableMessage name = defn.getUserFriendlyName();
            sb.append("             ").append(name).append(" properties depend on the ").append(name)
                .append(" type, which depends on the ").append(placeholder).append(" you provide.").append(EOL);
            sb.append("           </para>").append(EOL);
            sb.append("           <para>").append(EOL);
            sb.append("             By default, OpenDJ directory server supports the following ")
                .append(name).append(" types:").append(EOL);
            sb.append("           </para>").append(EOL);
            Map<String, Object> map = new HashMap<String, Object>();
            final LocalizableMessage name = defn.getUserFriendlyName();
            map.put("dependencies", REF_DSCFG_SUBTYPE_DEPENDENCIES.get(name, name, placeholder));
            map.put("typesIntro", REF_DSCFG_SUBTYPE_TYPES_INTRO.get(name));
            List<Map<String, Object>> children = new LinkedList<Map<String, Object>>();
            for (AbstractManagedObjectDefinition<?, ?> childDefn : getLeafChildren(defn)) {
                sb.append("           <varlistentry>").append(EOL);
                sb.append("             <term>").append(childDefn.getName()).append("</term>").append(EOL);
                sb.append("             <listitem>").append(EOL);
                sb.append("               <para>").append(EOL);
                sb.append("                 Default ").append(placeholder).append(": ")
                    .append(childDefn.getUserFriendlyName()).append(EOL);
                sb.append("               </para>").append(EOL);
                sb.append("               <para>").append(EOL);
                final boolean isEnabled = propertyExists(childDefn, "enabled");
                sb.append("                 Enabled by default: ").append(isEnabled).append(EOL);
                sb.append("               </para>").append(EOL);
                sb.append("               <para>").append(EOL);
                sb.append("                 See <xref linkend=\"")
                    .append(getScriptName()).append("-")
                    .append(a.getLongIdentifier()).append("-")
                    .append(defn.getName()).append("-prop-").append(childDefn.getName())
                    .append("\" /> for the properties of this ").append(defn.getUserFriendlyName())
                    .append(" type.").append(EOL);
                sb.append("               </para>").append(EOL);
                sb.append("             </listitem>").append(EOL);
                sb.append("           </varlistentry>").append(EOL);
                Map<String, Object> child = new HashMap<String, Object>();
                child.put("name", childDefn.getName());
                child.put("default", REF_DSCFG_CHILD_DEFAULT.get(placeholder, childDefn.getUserFriendlyName()));
                child.put("enabled", REF_DSCFG_CHILD_ENABLED_BY_DEFAULT.get(propertyExists(childDefn, "enabled")));
                final String link = getLink(getScriptName() + "-" + a.getLongIdentifier()
                        + "-" + defn.getName() + "-prop-" + childDefn.getName());
                child.put("link", REF_DSCFG_CHILD_LINK.get(link, defn.getUserFriendlyName()));
                children.add(child);
            }
            sb.append("         </variablelist>").append(EOL);
            map.put("children", children);
            applyTemplate(sb, "dscfgListSubtypes.ftl", map);
        }
        private boolean propertyExists(AbstractManagedObjectDefinition<?, ?> defn, String name) {
@@ -226,27 +196,26 @@
        /** {@inheritDoc} */
        @Override
        public void appendProperties(StringBuilder sb, SubCommand sc) {
        public String getProperties(SubCommand sc) {
            StringBuilder sb = new StringBuilder();
            final AbstractManagedObjectDefinition<?, ?> defn = getManagedObjectDefinition(sc);
            if (defn == null) {
                return;
                return "";
            }
            for (AbstractManagedObjectDefinition<?, ?> childDefn : getLeafChildren(defn)) {
                final List<PropertyDefinition<?>> props =
                    new ArrayList<PropertyDefinition<?>>(childDefn.getAllPropertyDefinitions());
                Collections.sort(props);
                Map<String, Object> map = new HashMap<String, Object>();
                final String propPrefix = getScriptName() + "-" + sc.getName() + "-" + childDefn.getName();
                sb.append(" <refsect3 xml:id=\"").append(propPrefix).append("\">").append(EOL);
                sb.append("   <title>").append(childDefn.getUserFriendlyName()).append("</title>").append(EOL);
                sb.append("   <para>").append(EOL);
                sb.append("     ").append(defn.getUserFriendlyPluralName()).append(" of type ")
                  .append(childDefn.getName()).append(" have the following properties:").append(EOL);
                sb.append("   </para>").append(EOL);
                toVariableList(props, defn, propPrefix, sb);
                sb.append(" </refsect3>").append(EOL);
                map.put("id", propPrefix);
                map.put("title", childDefn.getUserFriendlyName());
                map.put("intro", REF_DSCFG_PROPS_INTRO.get(defn.getUserFriendlyPluralName(), childDefn.getName()));
                map.put("list", toVariableList(props, defn, propPrefix));
                applyTemplate(sb, "dscfgAppendProps.ftl", map);
            }
            return sb.toString();
        }
        private AbstractManagedObjectDefinition<?, ?> getManagedObjectDefinition(SubCommand sc) {
@@ -298,57 +267,57 @@
            return null;
        }
        private void toVariableList(List<PropertyDefinition<?>> props, AbstractManagedObjectDefinition<?, ?> defn,
                String propPrefix, StringBuilder b) {
            final String indent = "            ";
            b.append("    <variablelist>").append(EOL);
        private String toVariableList(List<PropertyDefinition<?>> props, AbstractManagedObjectDefinition<?, ?> defn,
                String propPrefix) {
            StringBuilder b = new StringBuilder();
            Map<String, Object> map = new HashMap<String, Object>();
            List<Map<String, Object>> properties = new LinkedList<Map<String, Object>>();
            for (PropertyDefinition<?> prop : props) {
                b.append("      <varlistentry xml:id=\"")
                    .append(propPrefix).append("-").append(prop.getName()).append("\">").append(EOL);
                b.append("        <term>").append(prop.getName()).append("</term>").append(EOL);
                b.append("        <listitem>").append(EOL);
                b.append("          <variablelist>").append(EOL);
                appendVarlistentry(b, "Description", getDescriptionString(prop), indent);
                appendDefaultBehavior(b, indent, prop);
                appendAllowedValues(b, prop, indent);
                appendVarlistentry(b, "Multi-valued", getYN(prop, MULTI_VALUED), indent);
                appendVarlistentry(b, "Required", getYN(prop, MANDATORY), indent);
                appendVarlistentry(b, "Admin Action Required", getAdminActionRequired(prop, defn), indent);
                appendVarlistentry(b, "Advanced Property", getYNAdvanced(prop, ADVANCED), indent);
                appendVarlistentry(b, "Read-only", getYN(prop, READ_ONLY), indent);
                b.append("          </variablelist>").append(EOL);
                b.append("        </listitem>").append(EOL);
                b.append("      </varlistentry>").append(EOL);
                Map<String, Object> property = new HashMap<String, Object>();
                property.put("id", propPrefix + "-" + prop.getName());
                property.put("term", prop.getName());
                property.put("descTitle", REF_TITLE_DESCRIPTION.get());
                property.put("description", getDescriptionString(prop));
                final StringBuilder sb = new StringBuilder();
                appendDefaultBehavior(sb, prop);
                appendAllowedValues(sb, prop);
                appendVarListEntry(sb, REF_DSCFG_PROPS_LABEL_MULTI_VALUED.get().toString(), getYN(prop, MULTI_VALUED));
                appendVarListEntry(sb, REF_DSCFG_PROPS_LABEL_REQUIRED.get().toString(), getYN(prop, MANDATORY));
                appendVarListEntry(sb, REF_DSCFG_PROPS_LABEL_ADMIN_ACTION_REQUIRED.get().toString(),
                        getAdminActionRequired(prop, defn));
                appendVarListEntry(sb, REF_DSCFG_PROPS_LABEL_ADVANCED_PROPERTY.get().toString(),
                        getYNAdvanced(prop, ADVANCED));
                appendVarListEntry(sb, REF_DSCFG_PROPS_LABEL_READ_ONLY.get().toString(), getYN(prop, READ_ONLY));
                property.put("list", sb.toString());
                properties.add(property);
            }
            b.append("    </variablelist>").append(EOL);
            map.put("properties", properties);
            applyTemplate(b, "dscfgVariableList.ftl", map);
            return b.toString();
        }
        private StringBuilder appendVarlistentry(StringBuilder b, String term, Object para, String indent) {
            b.append(indent).append("<varlistentry>").append(EOL);
            b.append(indent).append("  <term>").append(term).append("</term>").append(EOL);
            b.append(indent).append("  <listitem>").append(EOL);
            b.append(indent).append("    <para>").append(para).append("</para>").append(EOL);
            b.append(indent).append("  </listitem>").append(EOL);
            b.append(indent).append("</varlistentry>").append(EOL);
        private StringBuilder appendVarListEntry(StringBuilder b, String term, Object definition) {
            Map<String, Object> map = new HashMap<String, Object>();
            map.put("term", term);
            map.put("definition", definition);
            applyTemplate(b, "dscfgVarListEntry.ftl", map);
            return b;
        }
        private void appendDefaultBehavior(StringBuilder b, final String indent, PropertyDefinition<?> prop) {
            b.append(indent).append("<varlistentry>").append(EOL);
            b.append(indent).append("  <term>").append("Default Value").append("</term>").append(EOL);
            b.append(indent).append("  <listitem>").append(EOL);
            appendDefaultBehaviorString(b, indent + "    ", prop);
            b.append(indent).append("  </listitem>").append(EOL);
            b.append(indent).append("</varlistentry>").append(EOL);
        private void appendDefaultBehavior(StringBuilder b, PropertyDefinition<?> prop) {
            StringBuilder sb = new StringBuilder();
            appendDefaultBehaviorString(sb, prop);
            appendVarListEntry(b, REF_DSCFG_PROPS_LABEL_DEFAULT_VALUE.get().toString(), sb.toString());
        }
        private void appendAllowedValues(StringBuilder b, PropertyDefinition<?> prop, String indent) {
            b.append(indent).append("<varlistentry>").append(EOL);
            b.append(indent).append("  <term>").append("Allowed Values").append("</term>").append(EOL);
            b.append(indent).append("  <listitem>").append(EOL);
            appendSyntax(b, prop, indent + "    ");
            b.append(indent).append("  </listitem>").append(EOL);
            b.append(indent).append("</varlistentry>").append(EOL);
        private void appendAllowedValues(StringBuilder b, PropertyDefinition<?> prop) {
            StringBuilder sb = new StringBuilder();
            appendSyntax(sb, prop);
            appendVarListEntry(b, REF_DSCFG_PROPS_LABEL_ALLOWED_VALUES.get().toString(), sb.toString());
        }
        private Object getDescriptionString(PropertyDefinition<?> prop) {
@@ -363,152 +332,146 @@
                final Type actionType = adminAction.getType();
                final StringBuilder action = new StringBuilder();
                if (actionType == Type.COMPONENT_RESTART) {
                    action.append("The ").append(defn.getUserFriendlyName())
                             .append(" must be disabled and re-enabled for changes to this setting to take effect");
                    action.append(op)
                            .append(REF_DSCFG_ADMIN_ACTION_COMPONENT_RESTART.get(defn.getUserFriendlyName()))
                            .append(cp);
                } else if (actionType == Type.SERVER_RESTART) {
                    action.append("Restart the server");
                    action.append(op).append(REF_DSCFG_ADMIN_ACTION_SERVER_RESTART.get()).append(cp);
                } else if (actionType == Type.NONE) {
                    action.append("None");
                    action.append(op).append(REF_DSCFG_ADMIN_ACTION_NONE.get()).append(cp);
                }
                if (synopsis != null) {
                    if (action.length() > 0) {
                        action.append(". ");
                    }
                    action.append(synopsis);
                    action.append(op).append(synopsis).append(cp);
                }
                return action.toString();
            }
            return "None";
            return op + REF_DSCFG_ADMIN_ACTION_NONE.get() + cp;
        }
        private String getYN(PropertyDefinition<?> prop, PropertyOption option) {
            return prop.hasOption(option) ? "Yes" : "No";
            LocalizableMessage msg = prop.hasOption(option) ? REF_DSCFG_PROP_YES.get() : REF_DSCFG_PROP_NO.get();
            return op + msg + cp;
        }
        private String getYNAdvanced(PropertyDefinition<?> prop, PropertyOption option) {
            return prop.hasOption(option) ? "Yes (Use --advanced in interactive mode.)" : "No";
            LocalizableMessage msg = prop.hasOption(option)
                    ? REF_DSCFG_PROP_YES_ADVANCED.get() : REF_DSCFG_PROP_NO.get();
            return op + msg + cp;
        }
        private void appendDefaultBehaviorString(StringBuilder b, String indent, PropertyDefinition<?> prop) {
            b.append(indent);
        private void appendDefaultBehaviorString(StringBuilder b, PropertyDefinition<?> prop) {
            final DefaultBehaviorProvider<?> defaultBehavior = prop.getDefaultBehaviorProvider();
            if (defaultBehavior instanceof UndefinedDefaultBehaviorProvider) {
                b.append("<para>None</para>").append(EOL);
                return;
                b.append(op).append(REF_DSCFG_DEFAULT_BEHAVIOR_NONE.get()).append(cp).append(EOL);
            } else if (defaultBehavior instanceof DefinedDefaultBehaviorProvider) {
                DefinedDefaultBehaviorProvider<?> behavior = (DefinedDefaultBehaviorProvider<?>) defaultBehavior;
                final Collection<String> defaultValues = behavior.getDefaultValues();
                if (defaultValues.size() == 0) {
                    b.append("<para>None</para>").append(EOL);
                    b.append(op).append(REF_DSCFG_DEFAULT_BEHAVIOR_NONE.get()).append(cp).append(EOL);
                } else if (defaultValues.size() == 1) {
                    b.append("<para>").append(defaultValues.iterator().next()).append("</para>").append(EOL);
                    b.append(op).append(REF_DSCFG_DEFAULT_BEHAVIOR.get(defaultValues.iterator().next()))
                            .append(cp).append(EOL);
                } else {
                    final Iterator<String> it = defaultValues.iterator();
                    b.append("<para>").append(it.next()).append("</para>");
                    b.append(op).append(REF_DSCFG_DEFAULT_BEHAVIOR.get(it.next())).append(cp);
                    for (; it.hasNext();) {
                        final String str = it.next();
                        b.append(EOL).append(indent).append("<para>").append(str).append("</para>");
                        b.append(EOL).append(op).append(REF_DSCFG_DEFAULT_BEHAVIOR.get(it.next())).append(cp);
                    }
                    b.append(EOL);
                }
                return;
            } else if (defaultBehavior instanceof AliasDefaultBehaviorProvider) {
                AliasDefaultBehaviorProvider<?> behavior = (AliasDefaultBehaviorProvider<?>) defaultBehavior;
                b.append("<para>").append(behavior.getSynopsis()).append("</para>").append(EOL);
                return;
                b.append(op).append(REF_DSCFG_DEFAULT_BEHAVIOR.get(behavior.getSynopsis())).append(cp).append(EOL);
            } else if (defaultBehavior instanceof RelativeInheritedDefaultBehaviorProvider) {
                final RelativeInheritedDefaultBehaviorProvider<?> behavior =
                        (RelativeInheritedDefaultBehaviorProvider<?>) defaultBehavior;
                appendDefaultBehaviorString(b, indent,
                appendDefaultBehaviorString(b,
                        behavior.getManagedObjectDefinition().getPropertyDefinition(behavior.getPropertyName()));
                return;
            } else if (defaultBehavior instanceof AbsoluteInheritedDefaultBehaviorProvider) {
                final AbsoluteInheritedDefaultBehaviorProvider<?> behavior =
                        (AbsoluteInheritedDefaultBehaviorProvider<?>) defaultBehavior;
                appendDefaultBehaviorString(b, indent,
                appendDefaultBehaviorString(b,
                        behavior.getManagedObjectDefinition().getPropertyDefinition(behavior.getPropertyName()));
                return;
            }
        }
        private void appendSyntax(final StringBuilder b, PropertyDefinition<?> prop, final String indent) {
        private void appendSyntax(final StringBuilder b, PropertyDefinition<?> prop) {
            // Create a visitor for performing syntax specific processing.
            PropertyDefinitionVisitor<String, Void> visitor = new PropertyDefinitionVisitor<String, Void>() {
                @Override
                public String visitACI(ACIPropertyDefinition prop, Void p) {
                    b.append(indent).append("<para>").append(ACI_SYNTAX_REL_URL).append("</para>").append(EOL);
                    b.append(op).append(REF_DSCFG_ACI_SYNTAX_REL_URL.get()).append(cp).append(EOL);
                    return null;
                }
                @Override
                public String visitAggregation(AggregationPropertyDefinition prop, Void p) {
                    b.append(indent).append("<para>");
                    b.append(op);
                    final RelationDefinition<?, ?> rel = prop.getRelationDefinition();
                    final String linkStr = getLink(rel.getName() + ".html");
                    b.append("The DN of any ").append(linkStr).append(". ");
                    final String linkStr = getLink(rel.getName());
                    b.append(REF_DSCFG_AGGREGATION.get(linkStr)).append(". ");
                    final LocalizableMessage synopsis = prop.getSourceConstraintSynopsis();
                    if (synopsis != null) {
                        b.append(synopsis);
                    }
                    b.append("</para>").append(EOL);
                    b.append(cp).append(EOL);
                    return null;
                }
                @Override
                public String visitAttributeType(AttributeTypePropertyDefinition prop, Void p) {
                    b.append(indent).append("<para>");
                    b.append("The name of an attribute type defined in the server schema.");
                    b.append("</para>").append(EOL);
                    b.append(op).append(REF_DSCFG_ANY_ATTRIBUTE.get()).append(".").append(cp).append(EOL);
                    return null;
                }
                @Override
                public String visitBoolean(BooleanPropertyDefinition prop, Void p) {
                    b.append(indent).append("<para>true</para>").append(EOL);
                    b.append(indent).append("<para>false</para>").append(EOL);
                    b.append(op).append("true").append(cp).append(EOL);
                    b.append(op).append("false").append(cp).append(EOL);
                    return null;
                }
                @Override
                public String visitClass(ClassPropertyDefinition prop, Void p) {
                    b.append(indent).append("<para>");
                    b.append("A java class that implements or extends the class(es) :")
                        .append(Utils.joinAsString(EOL, prop.getInstanceOfInterface()));
                    b.append("</para>").append(EOL);
                    b.append(op).append(REF_DSCFG_JAVA_PLUGIN.get()).append(" ")
                            .append(Utils.joinAsString(EOL, prop.getInstanceOfInterface())).append(cp).append(EOL);
                    return null;
                }
                @Override
                public String visitDN(DNPropertyDefinition prop, Void p) {
                    b.append(indent).append("<para>");
                    b.append("A valid DN.");
                    b.append(op).append(REF_DSCFG_VALID_DN.get());
                    final DN baseDN = prop.getBaseDN();
                    if (baseDN != null) {
                        b.append(" ").append(baseDN);
                        b.append(": ").append(baseDN);
                    } else {
                        b.append(".");
                    }
                    b.append("</para>").append(EOL);
                    b.append(cp).append(EOL);
                    return null;
                }
                @Override
                public String visitDuration(DurationPropertyDefinition prop, Void p) {
                    b.append(indent).append("<para>");
                    b.append(DURATION_SYNTAX_REL_URL).append(". ");
                    b.append(REF_DSCFG_DURATION_SYNTAX_REL_URL.get()).append(EOL);
                    b.append(op);
                    if (prop.isAllowUnlimited()) {
                        b.append(ALLOW_UNLIMITED).append(" ");
                        b.append(REF_DSCFG_ALLOW_UNLIMITED.get()).append(" ");
                    }
                    if (prop.getMaximumUnit() != null) {
                        b.append("Maximum unit is \"").append(prop.getMaximumUnit().getLongName()).append("\". ");
                        final String maxUnitName = prop.getMaximumUnit().getLongName();
                        b.append(REF_DSCFG_DURATION_MAX_UNIT.get(maxUnitName)).append(".");
                    }
                    final DurationUnit baseUnit = prop.getBaseUnit();
                    b.append("Lower limit is ").append(valueOf(baseUnit, prop.getLowerLimit()))
                     .append(" ").append(baseUnit.getLongName()).append(". ");
                    final long lowerLimit = valueOf(baseUnit, prop.getLowerLimit());
                    final String unitName = baseUnit.getLongName();
                    b.append(REF_DSCFG_DURATION_LOWER_LIMIT.get(lowerLimit, unitName)).append(".");
                    if (prop.getUpperLimit() != null) {
                        b.append("Upper limit is ").append(valueOf(baseUnit, prop.getUpperLimit()))
                         .append(" ").append(baseUnit.getLongName()).append(". ");
                        final long upperLimit = valueOf(baseUnit, prop.getUpperLimit());
                        b.append(REF_DSCFG_DURATION_UPPER_LIMIT.get(upperLimit, unitName)).append(".");
                    }
                    b.append("</para>").append(EOL);
                    b.append(cp).append(EOL);
                    return null;
                }
@@ -518,79 +481,77 @@
                @Override
                public String visitEnum(EnumPropertyDefinition prop, Void p) {
                    b.append(indent).append("<para>").append(EOL);
                    b.append(indent).append("  <variablelist>").append(EOL);
                    b.append("<variablelist>").append(EOL);
                    final Class<?> en = prop.getEnumClass();
                    final Object[] constants = en.getEnumConstants();
                    for (Object enumConstant : constants) {
                        final LocalizableMessage valueSynopsis = prop.getValueSynopsis((Enum) enumConstant);
                        appendVarlistentry(b, enumConstant.toString(), valueSynopsis, indent + "    ");
                        appendVarListEntry(b, enumConstant.toString(), valueSynopsis);
                    }
                    b.append(indent).append("  </variablelist>").append(EOL);
                    b.append(indent).append("</para>").append(EOL);
                    b.append("</variablelist>").append(EOL);
                    return null;
                }
                @Override
                public String visitInteger(IntegerPropertyDefinition prop, Void p) {
                    b.append(indent).append("<para>");
                    b.append("An integer value. Lower value is ").append(prop.getLowerLimit()).append(".");
                    b.append(op).append(REF_DSCFG_INT.get()).append(". ")
                            .append(REF_DSCFG_INT_LOWER_LIMIT.get(prop.getLowerLimit())).append(".");
                    if (prop.getUpperLimit() != null) {
                        b.append(" Upper value is ").append(prop.getUpperLimit()).append(".");
                        b.append(" ").append(REF_DSCFG_INT_UPPER_LIMIT.get(prop.getUpperLimit())).append(".");
                    }
                    if (prop.isAllowUnlimited()) {
                        b.append(" ").append(ALLOW_UNLIMITED);
                        b.append(" ").append(REF_DSCFG_ALLOW_UNLIMITED.get());
                    }
                    if (prop.getUnitSynopsis() != null) {
                        b.append(" Unit is ").append(prop.getUnitSynopsis()).append(".");
                        b.append(" ").append(REF_DSCFG_INT_UNIT.get(prop.getUnitSynopsis())).append(".");
                    }
                    b.append("</para>").append(EOL);
                    b.append(cp).append(EOL);
                    return null;
                }
                @Override
                public String visitIPAddress(IPAddressPropertyDefinition prop, Void p) {
                    b.append(indent).append("<para>An IP address</para>").append(EOL);
                    b.append(op).append(REF_DSCFG_IP_ADDRESS.get()).append(cp).append(EOL);
                    return null;
                }
                @Override
                public String visitIPAddressMask(IPAddressMaskPropertyDefinition prop, Void p) {
                    b.append(indent).append("<para>An IP address mask</para>").append(EOL);
                    b.append(op).append(REF_DSCFG_IP_ADDRESS_MASK.get()).append(cp).append(EOL);
                    return null;
                }
                @Override
                public String visitSize(SizePropertyDefinition prop, Void p) {
                    b.append(indent).append("<para>");
                    b.append(op);
                    if (prop.getLowerLimit() != 0) {
                        b.append(" Lower value is ").append(prop.getLowerLimit()).append(".");
                        b.append(REF_DSCFG_INT_LOWER_LIMIT.get(prop.getLowerLimit())).append(".");
                    }
                    if (prop.getUpperLimit() != null) {
                        b.append(" Upper value is ").append(prop.getUpperLimit()).append(" .");
                        b.append(REF_DSCFG_INT_UPPER_LIMIT.get(prop.getUpperLimit())).append(".");
                    }
                    if (prop.isAllowUnlimited()) {
                        b.append(" ").append(ALLOW_UNLIMITED);
                        b.append(REF_DSCFG_ALLOW_UNLIMITED.get());
                    }
                    b.append("</para>").append(EOL);
                    b.append(cp).append(EOL);
                    return null;
                }
                @Override
                public String visitString(StringPropertyDefinition prop, Void p) {
                    b.append(indent).append("<para>");
                    b.append(op);
                    if (prop.getPatternSynopsis() != null) {
                        b.append(prop.getPatternSynopsis());
                    } else {
                        b.append("A String");
                        b.append(REF_DSCFG_STRING.get());
                    }
                    b.append("</para>").append(EOL);
                    b.append(cp).append(EOL);
                    return null;
                }
                @Override
                public String visitUnknown(PropertyDefinition prop, Void p) {
                    b.append(indent).append("<para>Unknown</para>").append(EOL);
                    b.append(op).append(REF_DSCFG_UNKNOWN.get()).append(cp).append(EOL);
                    return null;
                }
            };
@@ -600,7 +561,7 @@
        }
        private String getLink(String target) {
            return " <xref linkend=" + target + " />";
            return " <xref linkend=\"" + target + "\" />";
        }
    }
opendj-config/src/main/resources/com/forgerock/opendj/dsconfig/dsconfig.properties
@@ -20,7 +20,7 @@
# CDDL HEADER END
#
#      Copyright 2006-2010 Sun Microsystems, Inc.
#      Portions Copyright 2011-2014 ForgeRock AS
#      Portions Copyright 2011-2015 ForgeRock AS.
#
# Format string definitions
#
@@ -436,3 +436,70 @@
INFO_DSCFG_BATCH_FILE_PATH_256=Path to a batch file containing \
a set of dsconfig commands to be executed
# Strings for generated reference documentation.
REF_DSCFG_ALLOW_UNLIMITED_1000=A value of "-1" or "unlimited" for no limit.
REF_DSCFG_ACI_SYNTAX_REL_URL_1001=<link xlink:show="new" \
 xlink:href="admin-guide#about-acis" \
 xlink:role="http://docbook.org/xlink/role/olink" \
 ><citetitle>About Access Control Instructions</citetitle></link>
REF_DSCFG_DURATION_SYNTAX_REL_URL_1002=<itemizedlist>                    \
 <para>                                                                  \
   Some property values take a time duration.                            \
   Durations are expressed as numbers followed by units.                 \
   For example <literal>1 s</literal> means one second,                  \
   and <literal>2 w</literal> means two weeks.                           \
   Some durations have minimum granularity or maximum units,             \
   so you cannot necessary specify every duration                        \
   in milliseconds or weeks for example.                                 \
   Some durations allow you to use a special value to mean unlimited.    \
   Units are specified as follows.                                       \
 </para>                                                                 \
                                                                         \
 <listitem><para><literal>ms</literal>: milliseconds</para></listitem>   \
 <listitem><para><literal>s</literal>: seconds</para></listitem>         \
 <listitem><para><literal>m</literal>: minutes</para></listitem>         \
 <listitem><para><literal>h</literal>: hours</para></listitem>           \
 <listitem><para><literal>d</literal>: days</para></listitem>            \
 <listitem><para><literal>w</literal>: weeks</para></listitem>           \
</itemizedlist>
REF_DSCFG_ARG_ADDITIONAL_INFO_1003=%s properties depend on the %s type, \
 which depends on the %s option.
REF_DSCFG_SUBTYPE_DEPENDENCIES_1004=%s properties depend on the %s type, \
 which depends on the %s you provide.
REF_DSCFG_SUBTYPE_TYPES_INTRO_1005=By default, OpenDJ directory server \
 supports the following %s types:
REF_DSCFG_CHILD_DEFAULT_1006=Default %s: %s
REF_DSCFG_CHILD_ENABLED_BY_DEFAULT_1007=Enabled by default: %b
REF_DSCFG_CHILD_LINK_1008=See %s for the properties of this %s type.
REF_DSCFG_PROPS_INTRO_1009=%s of type %s have the following properties:
REF_DSCFG_PROPS_LABEL_DEFAULT_VALUE_1010=Default Value
REF_DSCFG_PROPS_LABEL_ALLOWED_VALUES_1011=Allowed Values
REF_DSCFG_PROPS_LABEL_MULTI_VALUED_1012=Multi-valued
REF_DSCFG_PROPS_LABEL_REQUIRED_1013=Required
REF_DSCFG_PROPS_LABEL_ADMIN_ACTION_REQUIRED_1014=Admin Action Required
REF_DSCFG_PROPS_LABEL_ADVANCED_PROPERTY_1015=Advanced Property
REF_DSCFG_PROPS_LABEL_READ_ONLY_1016=Read-only
REF_DSCFG_ADMIN_ACTION_COMPONENT_RESTART_1017=The %s must be disabled \
 and re-enabled for changes to this setting to take effect
REF_DSCFG_ADMIN_ACTION_SERVER_RESTART_1018=Restart the server
REF_DSCFG_ADMIN_ACTION_NONE_1019=None
REF_DSCFG_PROP_YES_1020=Yes
REF_DSCFG_PROP_YES_ADVANCED_1021=Yes (Use --advanced in interactive mode.)
REF_DSCFG_PROP_NO_1022=No
REF_DSCFG_DEFAULT_BEHAVIOR_NONE_1023=None
REF_DSCFG_DEFAULT_BEHAVIOR_1024=%s
REF_DSCFG_AGGREGATION_1025=The DN of any %s
REF_DSCFG_ANY_ATTRIBUTE_1026=The name of an attribute type defined in the server schema
REF_DSCFG_JAVA_PLUGIN_1027=A Java class that implements or extends the class(es):
REF_DSCFG_VALID_DN_1028=A valid DN
REF_DSCFG_DURATION_MAX_UNIT_1029=Maximum unit is "%s"
REF_DSCFG_DURATION_LOWER_LIMIT_1030=Lower limit is %d %s
REF_DSCFG_DURATION_UPPER_LIMIT_1031=Upper limit is %d %s
REF_DSCFG_INT_1032=An integer value
REF_DSCFG_INT_LOWER_LIMIT_1033=Lower value is %d
REF_DSCFG_INT_UPPER_LIMIT_1034=Upper value is %d
REF_DSCFG_INT_UNIT_1035=Unit is %s
REF_DSCFG_IP_ADDRESS_1036=An IP address
REF_DSCFG_IP_ADDRESS_MASK_1037=An IP address mask
REF_DSCFG_STRING_1038=A String
REF_DSCFG_UNKNOWN_1039=Unknown