001/*
002 * The contents of this file are subject to the terms of the Common Development and
003 * Distribution License (the License). You may not use this file except in compliance with the
004 * License.
005 *
006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
007 * specific language governing permission and limitations under the License.
008 *
009 * When distributing Covered Software, include this CDDL Header Notice in each file and include
010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
011 * Header, with the fields enclosed by brackets [] replaced by your own identifying
012 * information: "Portions Copyright [year] [name of copyright owner]".
013 *
014 * Copyright 2015 ForgeRock AS.
015 */
016package org.forgerock.opendj.maven;
017
018import java.io.BufferedReader;
019import java.io.BufferedWriter;
020import java.io.File;
021import java.io.FileReader;
022import java.io.FileWriter;
023import java.io.IOException;
024import java.util.LinkedList;
025import java.util.List;
026import java.util.TreeSet;
027
028import org.apache.maven.plugin.AbstractMojo;
029import org.apache.maven.plugin.MojoExecutionException;
030import org.apache.maven.plugin.MojoFailureException;
031import org.apache.maven.plugins.annotations.LifecyclePhase;
032import org.apache.maven.plugins.annotations.Mojo;
033import org.apache.maven.plugins.annotations.Parameter;
034import org.apache.maven.project.MavenProject;
035
036/**
037 * Concatenates the contents of the files in the schema directory to create a
038 * base schema that may be used during the upgrade process. Each element will
039 * also include the X-SCHEMA-FILE extension to indicate the source schema file.
040 * <p>
041 * There is a single goal that generates the base schema.
042 * <p>
043 */
044@Mojo(name = "concat", defaultPhase = LifecyclePhase.GENERATE_SOURCES)
045public final class ConcatSchemaMojo extends AbstractMojo {
046
047    /**
048     * The Maven Project.
049     */
050    @Parameter(property = "project", required = true, readonly = true)
051    private MavenProject project;
052
053    /**
054     * The path to the directory containing the schema files.
055     */
056    @Parameter(required = true, defaultValue = "${basedir}/resource/schema")
057    private String schemaDirectory;
058
059    /**
060     * The directory path of the concatenated schema file to create. Must be in ${project.build.directory}
061     */
062    @Parameter(required = true)
063    private String outputDirectory;
064
065    /**
066     * The file name of the concatenated schema file to create.
067     */
068    @Parameter(required = true)
069    private String outputFile;
070
071    /** {@inheritDoc} */
072    @Override
073    public void execute() throws MojoExecutionException, MojoFailureException {
074        String projectBuildDir = project.getBuild().getDirectory();
075        String outputFilePath = outputDirectory + System.getProperty("file.separator") + outputFile;
076
077        if (!outputDirectory.contains(projectBuildDir)) {
078            String errorMsg = String.format("outputDirectory parameter (%s) must be included "
079                    + "in ${project.build.directory} (%s)", outputDirectory, projectBuildDir);
080            getLog().error(errorMsg);
081            throw new MojoExecutionException(errorMsg);
082        }
083        getLog().info(String.format("Concatenating all ldif files from directory: %s", schemaDirectory));
084        getLog().info(String.format("Concatenated file: %s", outputFilePath));
085
086        new File(outputFilePath).getParentFile().mkdirs();
087
088        // Get a sorted list of the files in the schema directory.
089        TreeSet<String> schemaFileNames = new TreeSet<>();
090        for (File f : new File(schemaDirectory).listFiles()) {
091            if (f.isFile()) {
092                schemaFileNames.add(f.getName());
093            }
094        }
095
096        // Create a set of lists that will hold the schema elements read from the files.
097        LinkedList<String> attributeTypes = new LinkedList<>();
098        LinkedList<String> objectClasses = new LinkedList<>();
099        LinkedList<String> nameForms = new LinkedList<>();
100        LinkedList<String> ditContentRules = new LinkedList<>();
101        LinkedList<String> ditStructureRules = new LinkedList<>();
102        LinkedList<String> matchingRuleUses = new LinkedList<>();
103        LinkedList<String> ldapSyntaxes = new LinkedList<>();
104        int curLineNumber = 0;
105
106        // Open each of the files in order and read the elements that they contain,
107        // appending them to the appropriate lists.
108        for (String name : schemaFileNames) {
109            // Read the contents of the file into a list with one schema element per
110            // list element.
111            LinkedList<StringBuilder> lines = new LinkedList<>();
112            try {
113                BufferedReader reader = new BufferedReader(new FileReader(new File(schemaDirectory, name)));
114                String line = reader.readLine();
115                while (line != null) {
116                    curLineNumber++;
117                    if (line.length() > 0 && !line.startsWith("#")) {
118                        if (line.startsWith(" ")) {
119                            lines.getLast().append(line.substring(1));
120                        } else {
121                            lines.add(new StringBuilder(line));
122                        }
123                    }
124                    line = reader.readLine();
125                }
126                reader.close();
127            } catch (Exception e) {
128                getLog().error(String.format(
129                        "Error while reading schema file %s at line %d: %s", name, curLineNumber, e.getMessage()));
130                throw new MojoExecutionException(e.getMessage(), e);
131            }
132
133            // Iterate through each line in the list. Find the colon and get the
134            // attribute name at the beginning. If it's someting that we don't
135            // recognize, then skip it. Otherwise, add the X-SCHEMA-FILE extension
136            // and add it to the appropriate schema element list.
137            for (StringBuilder buffer : lines) {
138                // Get the line and add the X-SCHEMA-FILE extension to the end of it.
139                // All of them should end with " )" but some might have the parenthesis
140                // crammed up against the last character so deal with that as well.
141                String line = buffer.toString().trim();
142                if (line.endsWith(" )")) {
143                    line = line.substring(0, line.length() - 1) + "X-SCHEMA-FILE '" + name + "' )";
144                } else if (line.endsWith(")")) {
145                    line = line.substring(0, line.length() - 1) + " X-SCHEMA-FILE '" + name + "' )";
146                } else {
147                    continue;
148                }
149
150                String lowerLine = line.toLowerCase();
151                if (lowerLine.startsWith("attributetypes:")) {
152                    attributeTypes.add(line);
153                } else if (lowerLine.startsWith("objectclasses:")) {
154                    objectClasses.add(line);
155                } else if (lowerLine.startsWith("nameforms:")) {
156                    nameForms.add(line);
157                } else if (lowerLine.startsWith("ditcontentrules:")) {
158                    ditContentRules.add(line);
159                } else if (lowerLine.startsWith("ditstructurerules:")) {
160                    ditStructureRules.add(line);
161                } else if (lowerLine.startsWith("matchingruleuse:")) {
162                    matchingRuleUses.add(line);
163                } else if (lowerLine.startsWith("ldapsyntaxes:")) {
164                    ldapSyntaxes.add(line);
165                }
166            }
167        }
168
169        // Write the resulting output to the merged schema file.
170        try {
171            BufferedWriter writer = new BufferedWriter(new FileWriter(outputFilePath));
172            writer.write("dn: cn=schema");
173            writer.newLine();
174            writer.write("objectClass: top");
175            writer.newLine();
176            writer.write("objectClass: ldapSubentry");
177            writer.newLine();
178            writer.write("objectClass: subschema");
179            writer.newLine();
180
181            writeSchemaElements(ldapSyntaxes, writer);
182            writeSchemaElements(attributeTypes, writer);
183            writeSchemaElements(objectClasses, writer);
184            writeSchemaElements(nameForms, writer);
185            writeSchemaElements(ditContentRules, writer);
186            writeSchemaElements(ditStructureRules, writer);
187            writeSchemaElements(matchingRuleUses, writer);
188
189            writer.close();
190        } catch (Exception e) {
191            getLog().error(
192                    String.format("Error while writing concatenated schema file %s:  %s", outputFile, e.getMessage()));
193            throw new MojoExecutionException(e.getMessage(), e);
194        }
195    }
196
197    private void writeSchemaElements(List<String> schemaElements, BufferedWriter writer) throws IOException {
198        for (String line : schemaElements) {
199            writer.write(line);
200            writer.newLine();
201        }
202    }
203
204}