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

Mark Craig
29.53.2015 61938176444abcda5eabd268a65e91f5c5b19fbb
CR-7098 OPENDJ-2009 Result codes in logs should be explained

This patch adds LDAP result code appendices
to the LDAP SDK dev guide and to the server reference.

The implementation is quite closely coupled to
the code in org.forgerock.opendj.ldap.ResultCode,
and because it uses Javadoc comment text
it does not provide for generating a localized version.

This patch also introduces a new opendj-doc-maven-plugin
dependency on QDox, which has an Apache 2 license.
2 files added
8 files modified
483 ■■■■ changed files
opendj-core/src/main/java/org/forgerock/opendj/ldap/ResultCode.java 6 ●●●● patch | view | raw | blame | history
opendj-doc-maven-plugin/pom.xml 5 ●●●●● patch | view | raw | blame | history
opendj-doc-maven-plugin/src/main/java/org/forgerock/opendj/maven/doc/GenerateResultCodeDocMojo.java 184 ●●●●● patch | view | raw | blame | history
opendj-doc-maven-plugin/src/main/java/org/forgerock/opendj/maven/doc/GenerateSchemaDocMojo.java 79 ●●●●● patch | view | raw | blame | history
opendj-doc-maven-plugin/src/main/java/org/forgerock/opendj/maven/doc/Utils.java 79 ●●●●● patch | view | raw | blame | history
opendj-doc-maven-plugin/src/main/resources/templates/appendix-ldap-result-codes.ftl 82 ●●●●● patch | view | raw | blame | history
opendj-server-legacy/pom.xml 12 ●●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/docbkx/reference/index.xml 8 ●●●●● patch | view | raw | blame | history
pom.xml 20 ●●●●● patch | view | raw | blame | history
src/main/docbkx/dev-guide/index.xml 8 ●●●●● patch | view | raw | blame | history
opendj-core/src/main/java/org/forgerock/opendj/ldap/ResultCode.java
@@ -196,9 +196,6 @@
    private static final Map<Integer, ResultCode> ELEMENTS = new LinkedHashMap<>();
    private static final List<ResultCode> IMMUTABLE_ELEMENTS = Collections.unmodifiableList(new ArrayList<ResultCode>(
            ELEMENTS.values()));
    /**
     * The result code that should only be used if the actual result code has
     * not yet been determined.
@@ -867,6 +864,9 @@
        return result;
    }
    private static final List<ResultCode> IMMUTABLE_ELEMENTS = Collections.unmodifiableList(new ArrayList<ResultCode>(
            ELEMENTS.values()));
    /**
     * Returns an unmodifiable list containing the set of available result codes
     * indexed on their integer value as defined in RFC 4511 section 4.1.9.
opendj-doc-maven-plugin/pom.xml
@@ -66,6 +66,11 @@
      <version>2.2.0</version>
    </dependency>
    <dependency>
      <groupId>com.thoughtworks.qdox</groupId>
      <artifactId>qdox</artifactId>
      <version>2.0-M3</version>
    </dependency>
    <dependency>
      <groupId>org.apache.maven.plugin-tools</groupId>
      <artifactId>maven-plugin-annotations</artifactId>
      <version>3.2</version>
opendj-doc-maven-plugin/src/main/java/org/forgerock/opendj/maven/doc/GenerateResultCodeDocMojo.java
New file
@@ -0,0 +1,184 @@
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
 * or http://forgerock.org/license/CDDLv1.0.html.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at legal-notices/CDDLv1_0.txt.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Copyright 2015 ForgeRock AS.
 */
package org.forgerock.opendj.maven.doc;
import static org.forgerock.opendj.maven.doc.Utils.*;
import com.thoughtworks.qdox.JavaProjectBuilder;
import com.thoughtworks.qdox.model.JavaClass;
import com.thoughtworks.qdox.model.JavaField;
import com.thoughtworks.qdox.model.JavaType;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.forgerock.opendj.ldap.ResultCode;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
/**
 * Generates documentation source for LDAP result codes based on
 * {@code org.forgerock.opendj.ldap.ResultCode}.
 * <br>
 * This implementation parses the source to match Javadoc comments with result codes.
 * It is assumed that the class's ResultCode fields are named with result code enum values,
 * and that those fields have Javadoc comments describing each result code.
 */
@Mojo(name = "generate-result-code-doc", defaultPhase = LifecyclePhase.COMPILE)
public class GenerateResultCodeDocMojo extends AbstractMojo {
    /**
     * The Java file containing the source of the ResultCode class,
     * {@code org.forgerock.opendj.ldap.ResultCode}.
     * <br>
     * For example, {@code opendj-core/src/main/java/org/forgerock/opendj/ldap/ResultCode.java}.
     */
    @Parameter(required = true)
    private File resultCodeSource;
    /** The XML file to generate. */
    @Parameter(required = true)
    private File xmlFile;
    /**
     * Generates documentation source for LDAP result codes.
     *
     * @throws MojoExecutionException   Generation failed
     * @throws MojoFailureException     Not used
     */
    @Override
    public void execute() throws MojoExecutionException, MojoFailureException {
        final Map<String, Object> map = new HashMap<>();
        map.put("year", new SimpleDateFormat("yyyy").format(new Date()));
        // The overall explanation in the generated doc is the class comment.
        final JavaClass resultCodeClass;
        try {
            resultCodeClass = getJavaClass();
        } catch (IOException e) {
            throw new MojoExecutionException("Could not read " + resultCodeSource.getPath(), e);
        }
        map.put("classComment", cleanComment(resultCodeClass.getComment()));
        // Documentation for each result code comes from the Javadoc for the code,
        // and from the value and friendly name of the code.
        final Map<String, Object> comments = new HashMap<>();
        for (final JavaField field : resultCodeClass.getFields()) {
            final JavaType type = field.getType();
            if (type.getValue().equals("ResultCode")) {
                comments.put(field.getName(), cleanComment(field.getComment()));
            }
        }
        map.put("resultCodes", getResultCodesDoc(comments));
        final String template = "appendix-ldap-result-codes.ftl";
        try {
            writeStringToFile(applyTemplate(template, map), xmlFile);
        } catch (IOException e) {
            throw new MojoExecutionException("Could not write to " + xmlFile.getPath(), e);
        }
        getLog().info("Wrote " + xmlFile.getPath());
    }
    /**
     * Returns an object to access to the result code Java source.
     * @return An object to access to the result code Java source.
     * @throws IOException  Could not read the source
     */
    private JavaClass getJavaClass() throws IOException {
        final JavaProjectBuilder builder = new JavaProjectBuilder();
        builder.addSource(resultCodeSource);
        return builder.getClassByName("org.forgerock.opendj.ldap.ResultCode");
    }
    /**
     * Returns a clean string for use in generated documentation.
     * @param comment   The comment to clean.
     * @return A clean string for use in generated documentation.
     */
    private String cleanComment(String comment) {
        return stripCodeValueSentences(stripTags(convertLineSeparators(comment))).trim();
    }
    /**
     * Returns a string with line separators converted to spaces.
     * @param string    The string to convert.
     * @return A string with line separators converted to spaces.
     */
    private String convertLineSeparators(String string) {
        return string.replaceAll(System.lineSeparator(), " ");
    }
    /**
     * Returns a string with the HTML tags removed.
     * @param string    The string to strip.
     * @return A string with the HTML tags removed.
     */
    private String stripTags(String string) {
        return string.replaceAll("<[^>]*>", "");
    }
    /**
     * Returns a string with lines sentences of the following form removed:
     * This result code corresponds to the LDAP result code value of &#x7b;&#x40;code 0&#x7d;.
     * @param string    The string to strip.
     * @return A string with lines sentences of the matching form removed.
     */
    private String stripCodeValueSentences(String string) {
        return string
                .replaceAll("This result code corresponds to the LDAP result code value of \\{@code \\d+\\}.", "");
    }
    /**
     * Returns a list of documentation objects for all result codes.
     * @param comments  A map of field names to the clean comments.
     * @return A list of documentation objects for all result codes.
     */
    private List<Map<String, Object>> getResultCodesDoc(Map<String, Object> comments) {
        final List<Map<String, Object>> list = new LinkedList<>();
        if (comments == null || comments.isEmpty()) {
            return list;
        }
        for (ResultCode resultCode : ResultCode.values()) {
            final Map<String, Object> doc = new HashMap<>();
            doc.put("intValue", resultCode.intValue());
            doc.put("name", resultCode.getName());
            final Object comment = comments.get(resultCode.asEnum().toString());
            doc.put("comment", comment);
            list.add(doc);
        }
        return list;
    }
}
opendj-doc-maven-plugin/src/main/java/org/forgerock/opendj/maven/doc/GenerateSchemaDocMojo.java
@@ -25,10 +25,8 @@
package org.forgerock.opendj.maven.doc;
import static com.forgerock.opendj.ldap.CoreMessages.*;
import static org.forgerock.opendj.maven.doc.Utils.*;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateExceptionHandler;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
@@ -36,13 +34,8 @@
import org.apache.maven.plugins.annotations.Parameter;
import org.forgerock.opendj.ldap.schema.CoreSchemaSupportedLocales;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.Writer;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
@@ -77,12 +70,9 @@
        final String localeReference = getLocalesAndSubTypesDocumentation(currentLocale);
        final File localeReferenceFile = new File(outputDirectory, "sec-locales-subtypes.xml");
        try {
            createOutputDirectory();
            writeStringToFile(localeReference, localeReferenceFile);
        } catch (FileNotFoundException e) {
            throw new MojoFailureException("Failed to write doc reference file.", e);
        } catch (IOException e) {
            throw new MojoExecutionException("Failed to create output directory");
            throw new MojoExecutionException("Failed to write " + localeReferenceFile.getPath());
        }
    }
@@ -102,74 +92,9 @@
        return applyTemplate("sec-locales-subtypes.ftl", map);
    }
    /**
     * Create the output directory if it does not exist.
     * @throws IOException  Failed to create the directory.
     */
    private void createOutputDirectory() throws IOException {
        if (outputDirectory != null && !outputDirectory.exists()) {
            if (!outputDirectory.mkdirs()) {
                throw new IOException("Failed to create output directory.");
            }
        }
    }
    /**
     * Writes a string to a file.
     * @param string    The string to write.
     * @param file      The file to write to.
     * @throws FileNotFoundException The file did not exist, or could not be created for writing.
     */
    private void writeStringToFile(final String string, final File file) throws FileNotFoundException {
        PrintWriter printWriter = new PrintWriter(file);
        printWriter.print(string);
        printWriter.close();
    }
    private final Map<String, String> localeTagsToOids =
            CoreSchemaSupportedLocales.getJvmSupportedLocaleNamesToOids();
    /** FreeMarker template configuration. */
    private Configuration configuration;
    /**
     * Returns a FreeMarker configuration for applying templates.
     * @return A FreeMarker configuration for applying templates.
     */
    private Configuration getConfiguration() {
        if (configuration == null) {
            configuration = new Configuration(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS);
            configuration.setClassForTemplateLoading(GenerateSchemaDocMojo.class, "/templates");
            configuration.setDefaultEncoding("UTF-8");
            configuration.setTemplateExceptionHandler(TemplateExceptionHandler.DEBUG_HANDLER);
        }
        return configuration;
    }
    /**
     * Returns the String result from applying a FreeMarker template.
     * @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.
     * @return The String result from applying a FreeMarker template.
     */
    private String applyTemplate(final String template, final Map<String, Object> map) {
        // 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);
            return outputStream.toString();
        } catch (Exception e) {
            throw new RuntimeException(e.getMessage(), e);
        } finally {
            org.forgerock.util.Utils.closeSilently(writer, outputStream);
        }
    }
    /** Container for documentation regarding a locale. */
    private class LocaleDoc {
        String tag;
opendj-doc-maven-plugin/src/main/java/org/forgerock/opendj/maven/doc/Utils.java
@@ -27,16 +27,23 @@
import static org.forgerock.util.Utils.*;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateExceptionHandler;
import org.apache.maven.artifact.DependencyResolutionRequiredException;
import org.apache.maven.plugin.logging.Log;
import org.apache.maven.project.MavenProject;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.Writer;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL;
@@ -44,6 +51,7 @@
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
@@ -96,9 +104,7 @@
        if (original == null) {
            throw new IOException("Could not read input to copy.");
        }
        if (!copy.exists() && !copy.createNewFile()) {
            throw new IOException("Failed to create " + copy);
        }
        createFile(copy);
        OutputStream outputStream = new FileOutputStream(copy);
        int bytesRead;
        byte[] buffer = new byte[4096];
@@ -108,6 +114,32 @@
        closeSilently(original, outputStream);
    }
    /**
     * Writes a string to a file.
     * @param string    The string to write
     * @param file      The file to write to
     * @throws IOException  The file did not exist, or could not be created for writing.
     */
    static void writeStringToFile(final String string, final File file) throws IOException {
        createFile(file);
        PrintWriter printWriter = new PrintWriter(file);
        printWriter.print(string);
        printWriter.close();
    }
    /**
     * Creates a file including parent directories if it does not yet exist.
     * @param file          The file to create
     * @throws IOException  Failed to create the file
     */
    private static void createFile(File file) throws IOException {
        if (!file.exists()) {
            createDirectory(file.getParent());
            if (!file.createNewFile()) {
                throw new IOException("Failed to create " + file.getPath());
            }
        }
    }
    /**
     * Returns the classpath for the class loader and its parent.
@@ -171,6 +203,47 @@
        debugClassPathElements(classLoader.getParent(), log);
    }
    /** FreeMarker template configuration. */
    static Configuration configuration;
    /**
     * Returns a FreeMarker configuration for applying templates.
     * @return A FreeMarker configuration for applying templates.
     */
    static Configuration getConfiguration() {
        if (configuration == null) {
            configuration = new Configuration(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS);
            configuration.setClassForTemplateLoading(Utils.class, "/templates");
            configuration.setDefaultEncoding("UTF-8");
            configuration.setTemplateExceptionHandler(TemplateExceptionHandler.DEBUG_HANDLER);
        }
        return configuration;
    }
    /**
     * Returns the String result from applying a FreeMarker template.
     * @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.
     * @return The String result from applying a FreeMarker template.
     */
    static String applyTemplate(final String template, final Map<String, Object> map) {
        // 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);
            return outputStream.toString();
        } catch (Exception e) {
            throw new RuntimeException(e.getMessage(), e);
        } finally {
            org.forgerock.util.Utils.closeSilently(writer, outputStream);
        }
    }
    private Utils() {
        // Not used.
    }
opendj-doc-maven-plugin/src/main/resources/templates/appendix-ldap-result-codes.ftl
New file
@@ -0,0 +1,82 @@
<?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 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 ${year} ForgeRock AS.
  !
-->
<#-- Comment text comes from the Javadoc, so the language is English. -->
<appendix xml:id="appendix-ldap-result-codes"
          xmlns="http://docbook.org/ns/docbook" version="5.0" xml:lang="en"
          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">
 <title>LDAP Result Codes</title>
 <para>
  ${classComment}
 </para>
 <indexterm>
  <primary>LDAP</primary>
  <secondary>Result codes</secondary>
 </indexterm>
 <table pgwide="1">
  <title>OpenDJ LDAP Result Codes</title>
  <tgroup cols="3">
   <colspec colnum="1" colwidth="1*" />
   <colspec colnum="2" colwidth="2*" />
   <colspec colnum="3" colwidth="3*" />
   <thead>
    <row>
     <entry>Result Code</entry>
     <entry>Name</entry>
     <entry>Description</entry>
    </row>
   </thead>
   <tbody>
    <#list resultCodes as resultCode>
    <row valign="top">
     <entry>
      <para>
       ${resultCode.intValue}
      </para>
     </entry>
     <entry>
      <para>
       ${resultCode.name}
      </para>
     </entry>
     <entry>
      <para>
       ${resultCode.comment}
      </para>
     </entry>
    </row>
    </#list>
   </tbody>
  </tgroup>
 </table>
</appendix>
opendj-server-legacy/pom.xml
@@ -1950,6 +1950,18 @@
                  </messageFileNames>
                </configuration>
              </execution>
              <execution>
                <id>generate-result-code-doc</id>
                <phase>pre-site</phase>
                <goals>
                  <goal>generate-result-code-doc</goal>
                </goals>
                <configuration>
                  <resultCodeSource>../opendj-core/src/main/java/org/forgerock/opendj/ldap/ResultCode.java</resultCodeSource>
                  <xmlFile>${project.build.directory}/docbkx-sources/reference/appendix-ldap-result-codes.xml</xmlFile>
                </configuration>
              </execution>
            </executions>
          </plugin>
opendj-server-legacy/src/main/docbkx/reference/index.xml
@@ -194,6 +194,14 @@
 <xinclude:include href="../shared/glossary.xml" />
 <xinclude:include href="appendix-rest2ldap.xml" />
 <xinclude:include href="appendix-ldap-result-codes.xml">
  <xinclude:fallback>
   <appendix>
    <title>LDAP Result Codes Missing</title>
    <para>The generated LDAP result codes appendix is missing.</para>
   </appendix>
  </xinclude:fallback>
 </xinclude:include>
 <xinclude:include href='appendix-file-layout.xml' />
 <xinclude:include href='appendix-ports-used.xml' />
 <xinclude:include href='appendix-standards.xml' />
pom.xml
@@ -196,6 +196,26 @@
      </plugin>
      <plugin>
        <groupId>org.forgerock.opendj</groupId>
        <artifactId>opendj-doc-maven-plugin</artifactId>
        <version>${project.version}</version>
        <inherited>false</inherited>
        <executions>
          <execution>
            <id>generate-result-code-doc</id>
            <phase>pre-site</phase>
            <goals>
              <goal>generate-result-code-doc</goal>
            </goals>
            <configuration>
              <resultCodeSource>opendj-core/src/main/java/org/forgerock/opendj/ldap/ResultCode.java</resultCodeSource>
              <xmlFile>${project.build.directory}/docbkx-sources/dev-guide/appendix-ldap-result-codes.xml</xmlFile>
            </configuration>
          </execution>
        </executions>
      </plugin>
      <plugin>
        <groupId>org.forgerock.commons</groupId>
        <artifactId>forgerock-doc-maven-plugin</artifactId>
        <inherited>false</inherited>
src/main/docbkx/dev-guide/index.xml
@@ -131,6 +131,14 @@
  </xinclude:include>
 </reference>
 <xinclude:include href="appendix-ldap-result-codes.xml">
  <xinclude:fallback>
   <appendix>
    <title>LDAP Result Codes Missing</title>
    <para>The generated LDAP result codes appendix is missing.</para>
   </appendix>
  </xinclude:fallback>
 </xinclude:include>
 <xinclude:include href="../shared/glossary.xml" />
 <index />