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

Mark Craig
27.14.2015 8a81bad1a27eb157108f63c6cc5d313a13b17be0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
 * or http://forgerock.org/license/CDDLv1.0.html.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at legal-notices/CDDLv1_0.txt.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Copyright 2015 ForgeRock AS.
 */
package org.forgerock.opendj.maven;
 
import org.apache.maven.artifact.DependencyResolutionRequiredException;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.plugins.annotations.ResolutionScope;
import org.apache.maven.project.MavenProject;
 
import java.io.BufferedReader;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
 
/**
 * Generate DocBook RefEntry source documents for command-line tools man pages.
 */
@Mojo(name = "generate-refentry", defaultPhase = LifecyclePhase.PREPARE_PACKAGE,
        requiresDependencyResolution = ResolutionScope.COMPILE_PLUS_RUNTIME)
public final class GenerateRefEntriesMojo extends AbstractMojo {
 
    /** The Maven Project. */
    @Parameter(property = "project", required = true, readonly = true)
    private MavenProject project;
 
    /** Tools for which to generate RefEntry files. */
    @Parameter
    private List<CommandLineTool> tools;
 
    /** Where to write the RefEntry files. */
    @Parameter(required = true)
    private File outputDir;
 
    /**
     * Writes a RefEntry file to the output directory for each tool.
     * Files names correspond to script names: {@code man-&lt;name>.xml}.
     *
     * @throws MojoExecutionException   Encountered a problem writing a file.
     * @throws MojoFailureException     Failed to initialize effectively,
     *                                  or to write one or more RefEntry files.
     */
    @Override
    public void execute() throws MojoExecutionException, MojoFailureException {
        // A Maven plugin classpath does not include project files.
        // Prepare a ClassLoader capable of loading the command-line tools.
        URLClassLoader toolsClassLoader = getBootToolsClassLoader();
 
        if (!isOutputDirAvailable()) {
            throw new MojoFailureException("Output directory " + outputDir.getPath() + " not available");
        }
 
        for (CommandLineTool tool : tools) {
            generateManPageForTool(toolsClassLoader, tool);
        }
    }
 
    /**
     * Returns a ClassLoader capable of loading the command-line tools.
     * @return A ClassLoader capable of loading the command-line tools.
     * @throws MojoFailureException     Failed to build classpath.
     */
    private URLClassLoader getBootToolsClassLoader() throws MojoFailureException {
        URLClassLoader toolsClassLoader;
        try {
            List<String> runtimeClasspathElements = project.getRuntimeClasspathElements();
            Set<URL> runtimeUrls = new LinkedHashSet<URL>();
            for (String element : runtimeClasspathElements) {
                runtimeUrls.add(new File(element).toURI().toURL());
            }
            toolsClassLoader = new URLClassLoader(
                    runtimeUrls.toArray(new URL[runtimeClasspathElements.size()]),
                    Thread.currentThread().getContextClassLoader());
        } catch (DependencyResolutionRequiredException e) {
            throw new MojoFailureException("Failed to access the runtime classpath.", e);
        } catch (MalformedURLException e) {
            throw new MojoFailureException("Failed to add element to classpath.", e);
        }
        debugClassPathElements(toolsClassLoader);
        return toolsClassLoader;
    }
 
    /**
     * Generate a RefEntry file to the output directory for a tool.
     * The files name corresponds to the tool name: {@code man-&lt;name>.xml}.
     * @param toolsClassLoader          The ClassLoader to run the tool.
     * @param tool                      The tool to run in order to generate the page.
     * @throws MojoExecutionException   Failed to run the tool.
     * @throws MojoFailureException     Tool did not exit successfully.
     */
    private void generateManPageForTool(final URLClassLoader toolsClassLoader, final CommandLineTool tool)
            throws MojoExecutionException, MojoFailureException {
        final File   manPage    = new File(outputDir, "man-" + tool.getName() + ".xml");
        final String toolScript = tool.getName();
        final String toolSects  = pathsToXIncludes(tool.getTrailingSectionPaths());
        final String toolClass  = tool.getApplication();
        List<String> commands   = new LinkedList<String>();
        commands.add(getJavaCommand());
        commands.addAll(getJavaArgs(toolScript, toolSects));
        commands.add("-classpath");
        commands.add(getClassPath(toolsClassLoader));
        commands.add(toolClass);
        commands.add(getUsageArgument(toolScript));
        getLog().info("Writing man page: " + manPage.getPath());
        try {
            // Tools tend to use System.exit() so run them as separate processes.
            ProcessBuilder builder = new ProcessBuilder(commands);
            Process process = builder.start();
            writeToFile(process.getInputStream(), manPage);
            process.waitFor();
            final int result = process.exitValue();
            if (result != 0) {
                throw new MojoFailureException("Failed to write page. Tool exit code: " + result);
            }
        }  catch (InterruptedException e) {
            throw new MojoExecutionException(toolClass + " interrupted", e);
        } catch (IOException e) {
            throw new MojoExecutionException(toolClass + " not found", e);
        }
    }
 
    /**
     * Returns true if the output directory is available.
     * Attempts to create the directory if it does not exist.
     * @return True if the output directory is available.
     */
    private boolean isOutputDirAvailable() {
        return outputDir != null && (outputDir.exists() && outputDir.isDirectory() || outputDir.mkdirs());
    }
 
    /**
     * Returns the path to the current Java executable.
     * @return The path to the current Java executable.
     */
    private String getJavaCommand() {
        return System.getProperty("java.home") + File.separator + "bin" + File.separator + "java";
    }
 
    /**
     * Returns the Java args for running a tool.
     * @param scriptName        The name of the tool.
     * @param trailingSections  The man page sections to Xinclude.
     * @return The Java args for running a tool.
     */
    private List<String> getJavaArgs(final String scriptName, final String trailingSections) {
        List<String> args = new LinkedList<String>();
        args.add("-Dorg.forgerock.opendj.gendoc=true");
        args.add("-Dorg.opends.server.ServerRoot=" + System.getProperty("java.io.tmpdir"));
        args.add("-Dcom.forgerock.opendj.ldap.tools.scriptName=" + scriptName);
        args.add("-Dorg.forgerock.opendj.gendoc.trailing=" + trailingSections + "");
        return args;
    }
 
    /**
     * Returns the classpath for the class loader.
     * @param classLoader   Contains the URLs of the class path to return.
     * @return The classpath for the class loader.
     */
    private String getClassPath(final URLClassLoader classLoader) {
        final StringBuilder stringBuilder = new StringBuilder();
        final URL[] urls = classLoader.getURLs();
        for (int i = 0; i < urls.length; i++) {
            if (i > 0) {
                stringBuilder.append(File.pathSeparator);
            }
            try {
                stringBuilder.append(new File(urls[i].toURI()).getPath());
            } catch (URISyntaxException e) {
                getLog().info("Failed to add classpath element", e);
            }
        }
        return stringBuilder.toString();
    }
 
    /**
     * Logs what is on the classpath for debugging.
     * @param classLoader   The ClassLoader with the classpath.
     */
    private void debugClassPathElements(ClassLoader classLoader) {
        if (null == classLoader) {
            return;
        }
        getLog().debug("--------------------");
        getLog().debug(classLoader.toString());
        if (classLoader instanceof URLClassLoader) {
            final URLClassLoader ucl = (URLClassLoader) classLoader;
            int i = 0;
            for (URL url : ucl.getURLs()) {
                getLog().debug("url[" + (i++) + "]=" + url);
            }
        }
        debugClassPathElements(classLoader.getParent());
    }
 
    /**
     * Translates relative paths to XML files into XInclude elements.
     *
     * @param paths Paths to XInclude'd files, relative to the RefEntry.
     * @return      String of XInclude elements corresponding to the paths.
     */
    private String pathsToXIncludes(final List<String> paths) {
        if (paths == null) {
            return "";
        }
 
        // Assume xmlns:xinclude="http://www.w3.org/2001/XInclude",
        // as in the declaration of resources/templates/refEntry.ftl.
        final String nameSpace = "xinclude";
        final StringBuilder result = new StringBuilder();
        for (String path : paths) {
            result.append("<").append(nameSpace).append(":include href='").append(path).append("' />");
        }
        return result.toString();
    }
 
    /**
     * Returns the usage argument.
     * @param scriptName The name of the tool.
     * @return The usage argument.
     */
    private String getUsageArgument(final String scriptName) {
        return scriptName.equals("dsjavaproperties") ? "-H" : "-?";
    }
 
    /**
     * Write the content of the input stream to the output file.
     * @param input     The input stream to write.
     * @param output    The file to write it to.
     * @throws IOException  Failed to write the content of the input stream.
     */
    private void writeToFile(final InputStream input, final File output) throws IOException {
        FileWriter writer = null;
        try {
            writer = new FileWriter(output);
            BufferedReader reader = new BufferedReader(new InputStreamReader(input));
            String line;
            while ((line = reader.readLine()) != null) {
                writer.write(line);
                writer.write(System.getProperty("line.separator"));
            }
        } finally {
            if (writer != null) {
                writer.close();
            }
        }
    }
}