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

Jean-Noel Rouvignac
03.02.2015 98a7494fc691c5a5af0bfac786fad827af567746
OPENDJ-1242 (CR-5940) Enable dsconfig to generate doc for properties changed through subcommand options

Left code generating the reference documentation in opendj-cli SubCommandArgumentParser, so it can be reused with other tools using SubCommandArgumentParser.
Created SubCommandUsageHandler interface to allow outputing additional documentation + implemented it in opendj-config DSConfig, this way the code can access all the config classes and generate the reference documentation inline with the command usage.


SubCommandArgumentParser.java:
Added subCommandUsageHandler field + setter.
In toRefSect2(), used subCommandUsageHandler to output additional documentation.

DSConfig.java:
Created inner class DSConfigSubCommandUsageHandler + copied code from ConfigGuideGeneration to here.
1 files added
2 files modified
448 ■■■■■ changed files
opendj-cli/src/main/java/com/forgerock/opendj/cli/SubCommandArgumentParser.java 17 ●●●● patch | view | raw | blame | history
opendj-cli/src/main/java/com/forgerock/opendj/cli/SubCommandUsageHandler.java 46 ●●●●● patch | view | raw | blame | history
opendj-config/src/main/java/org/forgerock/opendj/config/dsconfig/DSConfig.java 385 ●●●●● patch | view | raw | blame | history
opendj-cli/src/main/java/com/forgerock/opendj/cli/SubCommandArgumentParser.java
@@ -75,6 +75,7 @@
    /**The subcommand requested by the user as part of the command-line arguments.     */
    private SubCommand subCommand;
    private SubCommandUsageHandler subCommandUsageHandler;
    /**
     * Creates a new instance of this subcommand argument parser with no arguments.
@@ -416,6 +417,15 @@
    }
    /**
     * Sets the sub-command usage handler which will be used to display the usage information.
     *
     * @param subCommandUsageHandler the sub-command usage handler
     */
    public void setUsageHandler(SubCommandUsageHandler subCommandUsageHandler) {
        this.subCommandUsageHandler = subCommandUsageHandler;
    }
    /**
     * Parses the provided set of arguments and updates the information associated with this parser accordingly. Default
     * values for unspecified arguments may be read from the specified properties if any are provided.
     *
@@ -1193,9 +1203,10 @@
                }
                sb.append("</option></term>").append(EOL);
                sb.append("   <listitem>").append(EOL);
                sb.append("    <para>");
                sb.append(a.getDescription());
                sb.append("</para>").append(EOL);
                sb.append("    <para>").append(a.getDescription()).append("</para>").append(EOL);
                if (subCommandUsageHandler != null) {
                    subCommandUsageHandler.appendUsage(sb, sc, longID);
                }
                sb.append("   </listitem>").append(EOL);
                sb.append("  </varlistentry>").append(EOL);
            }
opendj-cli/src/main/java/com/forgerock/opendj/cli/SubCommandUsageHandler.java
New file
@@ -0,0 +1,46 @@
/*
 * 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;
/**
 * A handler for printing sub-command usage information.
 */
//@FunctionalInterface
public interface SubCommandUsageHandler {
    /**
     * Invoked when the sub-command usage information should be printed.
     *
     * @param builder
     *          the string builder
     * @param sc
     *          the sub command for which to print usage information
     * @param argLongID
     *          the argument long identifier
     */
    void appendUsage(StringBuilder builder, SubCommand sc, String argLongID);
}
opendj-config/src/main/java/org/forgerock/opendj/config/dsconfig/DSConfig.java
@@ -22,7 +22,7 @@
 *
 *
 *      Copyright 2007-2010 Sun Microsystems, Inc.
 *      Portions Copyright 2012-2014 ForgeRock AS
 *      Portions Copyright 2012-2015 ForgeRock AS
 */
package org.forgerock.opendj.config.dsconfig;
@@ -32,6 +32,7 @@
import static com.forgerock.opendj.dsconfig.DsconfigMessages.*;
import static com.forgerock.opendj.util.StaticUtils.*;
import static org.forgerock.opendj.config.PropertyOption.*;
import static org.forgerock.opendj.config.dsconfig.ArgumentExceptionFactory.*;
import static org.forgerock.util.Utils.*;
@@ -51,6 +52,7 @@
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
@@ -60,15 +62,43 @@
import java.util.TreeSet;
import org.forgerock.i18n.LocalizableMessage;
import org.forgerock.opendj.config.ACIPropertyDefinition;
import org.forgerock.opendj.config.AbsoluteInheritedDefaultBehaviorProvider;
import org.forgerock.opendj.config.AbstractManagedObjectDefinition;
import org.forgerock.opendj.config.AdministratorAction;
import org.forgerock.opendj.config.AdministratorAction.Type;
import org.forgerock.opendj.config.AggregationPropertyDefinition;
import org.forgerock.opendj.config.AliasDefaultBehaviorProvider;
import org.forgerock.opendj.config.AttributeTypePropertyDefinition;
import org.forgerock.opendj.config.BooleanPropertyDefinition;
import org.forgerock.opendj.config.ClassPropertyDefinition;
import org.forgerock.opendj.config.ConfigurationFramework;
import org.forgerock.opendj.config.DNPropertyDefinition;
import org.forgerock.opendj.config.DefaultBehaviorProvider;
import org.forgerock.opendj.config.DefinedDefaultBehaviorProvider;
import org.forgerock.opendj.config.DurationPropertyDefinition;
import org.forgerock.opendj.config.DurationUnit;
import org.forgerock.opendj.config.EnumPropertyDefinition;
import org.forgerock.opendj.config.IPAddressMaskPropertyDefinition;
import org.forgerock.opendj.config.IPAddressPropertyDefinition;
import org.forgerock.opendj.config.InstantiableRelationDefinition;
import org.forgerock.opendj.config.IntegerPropertyDefinition;
import org.forgerock.opendj.config.PropertyDefinition;
import org.forgerock.opendj.config.PropertyDefinitionVisitor;
import org.forgerock.opendj.config.PropertyOption;
import org.forgerock.opendj.config.RelationDefinition;
import org.forgerock.opendj.config.RelativeInheritedDefaultBehaviorProvider;
import org.forgerock.opendj.config.SetRelationDefinition;
import org.forgerock.opendj.config.SizePropertyDefinition;
import org.forgerock.opendj.config.StringPropertyDefinition;
import org.forgerock.opendj.config.Tag;
import org.forgerock.opendj.config.UndefinedDefaultBehaviorProvider;
import org.forgerock.opendj.config.client.ManagedObjectDecodingException;
import org.forgerock.opendj.config.client.MissingMandatoryPropertiesException;
import org.forgerock.opendj.config.client.OperationRejectedException;
import org.forgerock.opendj.config.server.ConfigException;
import org.forgerock.opendj.ldap.DN;
import org.forgerock.util.Utils;
import com.forgerock.opendj.cli.ArgumentException;
import com.forgerock.opendj.cli.ArgumentGroup;
@@ -87,6 +117,7 @@
import com.forgerock.opendj.cli.StringArgument;
import com.forgerock.opendj.cli.SubCommand;
import com.forgerock.opendj.cli.SubCommandArgumentParser;
import com.forgerock.opendj.cli.SubCommandUsageHandler;
import com.forgerock.opendj.cli.VersionHandler;
/**
@@ -94,6 +125,355 @@
 */
public final class DSConfig extends ConsoleApplication {
    // FIXME: I18n support. Today all the strings are hardcoded in this file
    private final class DSConfigSubCommandUsageHandler implements SubCommandUsageHandler {
        private static final String ALLOW_UNLIMITED = "A value of \"-1\" or \"unlimited\" for no limit.";
        private static final String ACI_SYNTAX_REL_URL =
            "<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>";
        private static final String DURATION_SYNTAX_REL_URL =
            "  <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>";
        /** {@inheritDoc} */
        @Override
        public void appendUsage(StringBuilder sb, SubCommand sc, String argLongID) {
            final String toolName = "dsconfig";
            final SubCommandHandler sch = handlers.get(sc);
            final RelationDefinition<?, ?> rd = getRelationDefinition(sch);
            if (rd instanceof InstantiableRelationDefinition) {
                final PropertyDefinition<?> pd =
                        ((InstantiableRelationDefinition<?, ?>) rd).getNamingPropertyDefinition();
                if (pd != null) {
                    final AbstractManagedObjectDefinition<?, ?> defn = pd.getManagedObjectDefinition();
                    final List<PropertyDefinition<?>> props =
                            new ArrayList<PropertyDefinition<?>>(defn.getAllPropertyDefinitions());
                    Collections.sort(props);
                    final String propPrefix = toolName + "-" + sc.getName() + "-" + argLongID + "-";
                    sb.append(EOL);
                    toSimpleList(props, propPrefix, sb);
                    sb.append(EOL);
                    toVariableList(props, defn, propPrefix, sb);
                }
            }
        }
        private RelationDefinition<?, ?> getRelationDefinition(final SubCommandHandler sch) {
            if (sch instanceof CreateSubCommandHandler) {
                return ((CreateSubCommandHandler<?, ?>) sch).getRelationDefinition();
            } else if (sch instanceof DeleteSubCommandHandler) {
                return ((DeleteSubCommandHandler) sch).getRelationDefinition();
            } else if (sch instanceof ListSubCommandHandler) {
                return ((ListSubCommandHandler) sch).getRelationDefinition();
            } else if (sch instanceof GetPropSubCommandHandler) {
                return ((GetPropSubCommandHandler) sch).getRelationDefinition();
            } else if (sch instanceof SetPropSubCommandHandler) {
                return ((SetPropSubCommandHandler) sch).getRelationDefinition();
            }
            return null;
        }
        private void toSimpleList(List<PropertyDefinition<?>> props, String propPrefix, StringBuilder b) {
            b.append("    <simplelist>").append(EOL);
            for (PropertyDefinition<?> prop : props) {
                b.append("      <member><xref linkend=\"")
                    .append(propPrefix).append(prop.getName()).append("\" /></member>").append(EOL);
            }
            b.append("    </simplelist>").append(EOL);
        }
        private void toVariableList(List<PropertyDefinition<?>> props, AbstractManagedObjectDefinition<?, ?> defn,
                String propPrefix, StringBuilder b) {
            final String indent = "            ";
            b.append("    <variablelist>").append(EOL);
            for (PropertyDefinition<?> prop : props) {
                b.append("      <varlistentry xml:id=\"")
                    .append(propPrefix).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);
                appendVarlistentry(b, "Default Value", getDefaultBehaviorString(prop), indent);
                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);
            }
            b.append("    </variablelist>").append(EOL);
        }
        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);
            return b;
        }
        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);
            if (prop instanceof EnumPropertyDefinition) {
                b.append(indent).append("  <listitem>").append(EOL);
                b.append(indent).append("    <variablelist>").append(EOL);
                appendSyntax(b, prop, indent + "      ");
                b.append(indent).append("    </variablelist>").append(EOL);
                b.append(indent).append("  </listitem>").append(EOL);
            } else if (prop instanceof BooleanPropertyDefinition) {
                b.append(indent).append("  <listitem><para>true</para></listitem>").append(EOL);
                b.append(indent).append("  <listitem><para>false</para></listitem>").append(EOL);
            } else {
                b.append(indent).append("  <listitem>").append(EOL);
                b.append(indent).append("    <para>");
                appendSyntax(b, prop, indent);
                b.append("</para>").append(EOL);
                b.append(indent).append("  </listitem>").append(EOL);
            }
            b.append(indent).append("</varlistentry>").append(EOL);
        }
        private Object getDescriptionString(PropertyDefinition<?> prop) {
            return ((prop.getSynopsis() != null) ? prop.getSynopsis() + " " : "")
                    + ((prop.getDescription() != null) ? prop.getDescription() : "");
        }
        private String getAdminActionRequired(PropertyDefinition<?> prop, AbstractManagedObjectDefinition<?, ?> defn) {
            final AdministratorAction adminAction = prop.getAdministratorAction();
            if (adminAction != null) {
                final LocalizableMessage synopsis = adminAction.getSynopsis();
                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");
                } else if (actionType == Type.SERVER_RESTART) {
                    action.append("Restart the server");
                } else if (actionType == Type.NONE) {
                    action.append("None");
                }
                if (synopsis != null) {
                    if (action.length() > 0) {
                        action.append(". ");
                    }
                    action.append(synopsis);
                }
                return action.toString();
            }
            return "None";
        }
        private String getYN(PropertyDefinition<?> prop, PropertyOption option) {
            return prop.hasOption(option) ? "Yes" : "No";
        }
        private String getYNAdvanced(PropertyDefinition<?> prop, PropertyOption option) {
            return prop.hasOption(option) ? "Yes (Use --advanced in interactive mode.)" : "No";
        }
        private String getDefaultBehaviorString(PropertyDefinition<?> prop) {
            DefaultBehaviorProvider<?> defaultBehavior = prop.getDefaultBehaviorProvider();
            if (defaultBehavior instanceof UndefinedDefaultBehaviorProvider) {
                return "None";
            } else if (defaultBehavior instanceof DefinedDefaultBehaviorProvider) {
                DefinedDefaultBehaviorProvider<?> behavior = (DefinedDefaultBehaviorProvider<?>) defaultBehavior;
                final StringBuilder res = new StringBuilder();
                for (Iterator<String> it = behavior.getDefaultValues().iterator(); it.hasNext();) {
                    String str = it.next();
                    res.append(str).append(it.hasNext() ? "\n" : "");
                }
                return res.toString();
            } else if (defaultBehavior instanceof AliasDefaultBehaviorProvider) {
                AliasDefaultBehaviorProvider<?> behavior = (AliasDefaultBehaviorProvider<?>) defaultBehavior;
                return behavior.getSynopsis().toString();
            } else if (defaultBehavior instanceof RelativeInheritedDefaultBehaviorProvider) {
                final RelativeInheritedDefaultBehaviorProvider<?> behavior =
                        (RelativeInheritedDefaultBehaviorProvider<?>) defaultBehavior;
                return getDefaultBehaviorString(
                        behavior.getManagedObjectDefinition().getPropertyDefinition(behavior.getPropertyName()));
            } else if (defaultBehavior instanceof AbsoluteInheritedDefaultBehaviorProvider) {
                final AbsoluteInheritedDefaultBehaviorProvider<?> behavior =
                        (AbsoluteInheritedDefaultBehaviorProvider<?>) defaultBehavior;
                return getDefaultBehaviorString(
                        behavior.getManagedObjectDefinition().getPropertyDefinition(behavior.getPropertyName()));
            }
            return "";
        }
        private void appendSyntax(final StringBuilder b, PropertyDefinition<?> prop, final String indent) {
            // 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(ACI_SYNTAX_REL_URL);
                    return null;
                }
                @Override
                public String visitAggregation(AggregationPropertyDefinition prop, Void p) {
                    final RelationDefinition<?, ?> rel = prop.getRelationDefinition();
                    final String linkStr = getLink(rel.getName() + ".html");
                    b.append("The DN of any ").append(linkStr).append(". ");
                    final LocalizableMessage synopsis = prop.getSourceConstraintSynopsis();
                    if (synopsis != null) {
                        b.append(synopsis);
                    }
                    return null;
                }
                @Override
                public String visitAttributeType(AttributeTypePropertyDefinition prop, Void p) {
                    b.append("The name of an attribute type defined in the server schema.");
                    return null;
                }
                @Override
                public String visitBoolean(BooleanPropertyDefinition prop, Void p) {
                    throw new RuntimeException("This case should be handled by the calling code.");
                }
                @Override
                public String visitClass(ClassPropertyDefinition prop, Void p) {
                    b.append("A java class that implements or extends the class(es) :")
                        .append(Utils.joinAsString(EOL, prop.getInstanceOfInterface()));
                    return null;
                }
                @Override
                public String visitDN(DNPropertyDefinition prop, Void p) {
                    final DN baseDN = prop.getBaseDN();
                    b.append("A valid DN.");
                    if (baseDN != null) {
                        b.append(baseDN);
                    }
                    return null;
                }
                @Override
                public String visitDuration(DurationPropertyDefinition prop, Void p) {
                    b.append(DURATION_SYNTAX_REL_URL).append(". ");
                    if (prop.isAllowUnlimited()) {
                        b.append(ALLOW_UNLIMITED).append(" ");
                    }
                    if (prop.getMaximumUnit() != null) {
                        b.append("Maximum unit is \"").append(prop.getMaximumUnit().getLongName()).append("\". ");
                    }
                    final DurationUnit baseUnit = prop.getBaseUnit();
                    b.append("Lower limit is ").append(valueOf(baseUnit, prop.getLowerLimit()))
                     .append(" ").append(baseUnit.getLongName()).append(". ");
                    if (prop.getUpperLimit() != null) {
                        b.append("Upper limit is ").append(valueOf(baseUnit, prop.getUpperLimit()))
                         .append(" ").append(baseUnit.getLongName()).append(". ");
                    }
                    return null;
                }
                private long valueOf(final DurationUnit baseUnit, long upperLimit) {
                    return Double.valueOf(baseUnit.fromMilliSeconds(upperLimit)).longValue();
                }
                @Override
                public String visitEnum(EnumPropertyDefinition prop, Void p) {
                    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);
                    }
                    return null;
                }
                @Override
                public String visitInteger(IntegerPropertyDefinition prop, Void p) {
                    b.append("An integer value. Lower value is ").append(prop.getLowerLimit()).append(".");
                    if (prop.getUpperLimit() != null) {
                        b.append(" Upper value is ").append(prop.getUpperLimit()).append(".");
                    }
                    if (prop.isAllowUnlimited()) {
                        b.append(" ").append(ALLOW_UNLIMITED);
                    }
                    if (prop.getUnitSynopsis() != null) {
                        b.append(" Unit is ").append(prop.getUnitSynopsis()).append(".");
                    }
                    return null;
                }
                @Override
                public String visitIPAddress(IPAddressPropertyDefinition prop, Void p) {
                    b.append("An IP address");
                    return null;
                }
                @Override
                public String visitIPAddressMask(IPAddressMaskPropertyDefinition prop, Void p) {
                    b.append("An IP address mask");
                    return null;
                }
                @Override
                public String visitSize(SizePropertyDefinition prop, Void p) {
                    if (prop.getLowerLimit() != 0) {
                        b.append(" Lower value is ").append(prop.getLowerLimit()).append(".");
                    }
                    if (prop.getUpperLimit() != null) {
                        b.append(" Upper value is ").append(prop.getUpperLimit()).append(" .");
                    }
                    if (prop.isAllowUnlimited()) {
                        b.append(" ").append(ALLOW_UNLIMITED);
                    }
                    return null;
                }
                @Override
                public String visitString(StringPropertyDefinition prop, Void p) {
                    if (prop.getPatternSynopsis() != null) {
                        b.append(prop.getPatternSynopsis());
                    } else {
                        b.append("A String");
                    }
                    return null;
                }
                @Override
                public String visitUnknown(PropertyDefinition prop, Void p) {
                    b.append("Unknown");
                    return null;
                }
            };
            // Invoke the visitor against the property definition.
            prop.accept(visitor, null);
        }
        private String getLink(String target) {
            return " <xref linkend=" + target + " />";
        }
    }
    /** The name of this tool. */
    static final String DSCONFIGTOOLNAME = "dsconfig";
@@ -423,6 +803,9 @@
                return "";
            }
        });
        if (System.getProperty("org.forgerock.opendj.gendoc") != null) {
            this.parser.setUsageHandler(new DSConfigSubCommandUsageHandler());
        }
    }
    /** {@inheritDoc} */