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