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 *      Copyright 2015 ForgeRock AS.
024 */
025package org.forgerock.opendj.maven.doc;
026
027import static org.forgerock.opendj.maven.doc.DocsMessages.*;
028import static org.forgerock.opendj.maven.doc.Utils.applyTemplate;
029import static org.forgerock.opendj.maven.doc.Utils.writeStringToFile;
030import org.apache.maven.plugin.AbstractMojo;
031import org.apache.maven.plugin.MojoExecutionException;
032import org.apache.maven.plugin.MojoFailureException;
033import org.apache.maven.plugins.annotations.Mojo;
034import org.apache.maven.plugins.annotations.Parameter;
035import org.forgerock.opendj.ldap.DN;
036import org.forgerock.opendj.ldap.Entry;
037import org.forgerock.opendj.ldif.LDIFEntryReader;
038
039import java.io.BufferedReader;
040import java.io.File;
041import java.io.FileInputStream;
042import java.io.FileReader;
043import java.io.IOException;
044import java.text.SimpleDateFormat;
045import java.util.Date;
046import java.util.HashMap;
047import java.util.LinkedList;
048import java.util.List;
049import java.util.Map;
050import java.util.regex.Matcher;
051import java.util.regex.Pattern;
052
053/**
054 * Generates documentation source table listing global ACIs.
055 */
056@Mojo(name = "generate-global-acis-table")
057public class GenerateGlobalAcisTableMojo extends AbstractMojo {
058    /** The locale for which to generate the documentation. */
059    @Parameter(defaultValue = "en")
060    private String locale;
061
062    /** The config.ldif file containing default global ACIs. **/
063    @Parameter(defaultValue = "${basedir}/resource/config/config.ldif")
064    private File configDotLdif;
065
066    /** Output directory for source files. */
067    @Parameter(defaultValue = "${project.build.directory}/docbkx-sources/shared")
068    private File outputDirectory;
069
070    /** Holds descriptions for ACIs. */
071    private Map<String, String> descriptions;
072
073    /** Holds documentation for an ACI. */
074    private class Aci {
075        String name;
076        String description;
077        String definition;
078    }
079
080    /** Holds the list of global ACIs. */
081    private static List<Aci> allGlobalAcis = new LinkedList<>();
082
083    /**
084     * Writes documentation source table listing global ACIs.
085     * @throws MojoExecutionException   Not used.
086     * @throws MojoFailureException     Failed to read ACIs or to write the table file.
087     */
088    @Override
089    public void execute() throws MojoExecutionException, MojoFailureException {
090        try {
091            readAcis(getAciDescriptions());
092        } catch (IOException e) {
093            throw new MojoFailureException(e.getMessage(), e);
094        }
095
096        File table = new File(outputDirectory, "table-global-acis.xml");
097        try {
098            writeStringToFile(getGlobalAcisTable(), table);
099        } catch (IOException e) {
100            throw new MojoFailureException(e.getMessage(), e);
101        }
102    }
103
104    /**
105     * Populates map of {@code ds-cfg-global-aci} descriptions from comments in {@code config.ldif}.
106     * Keys are ACI names. Values are descriptions.
107     * <br>
108     * The format expected for ACI description comments is the following:
109     * {@code # @aci name: description},
110     * where {@code name} matches the name embedded in the ACI,
111     * and {@code description} is a longer description.
112     * @throws IOException  Failed to read the LDIF.
113     */
114    private Map<String, String> getAciDescriptions() throws IOException {
115        final Map<String, String> descriptions = new HashMap<>();
116        BufferedReader reader = new BufferedReader(new FileReader(configDotLdif));
117        String line;
118        while ((line = reader.readLine()) != null) {
119            if (line.startsWith("# @aci ")) {
120                String[] split = line.replace("# @aci ", "").split(":", 2);
121                descriptions.put(split[0], split[1]);
122            }
123        }
124        return descriptions;
125    }
126
127    /**
128     * Reads {@code ds-cfg-global-aci} values from {@code config.ldif} into the list of Acis.
129     * @param descriptions Long descriptions from comments in {@code config.ldif}.
130     *                     Keys are ACI names. Values are descriptions.
131     * @throws IOException  Failed to read the LDIF.
132     */
133    private void readAcis(Map<String, String> descriptions) throws IOException {
134        LDIFEntryReader reader = new LDIFEntryReader(new FileInputStream(configDotLdif));
135        reader.setIncludeBranch(DN.valueOf("cn=Access Control Handler,cn=config"));
136
137        while (reader.hasNext()) {
138            Entry entry = reader.readEntry();
139            for (String attribute : entry.parseAttribute("ds-cfg-global-aci").asSetOfString()) {
140                Aci aci = new Aci();
141                aci.name = getName(attribute);
142                if (descriptions != null) {
143                    aci.description = descriptions.get(aci.name);
144                }
145                aci.definition = attribute;
146                allGlobalAcis.add(aci);
147            }
148        }
149    }
150
151    /**
152     * Returns the user-friendly name embedded in the ACI.
153     * @param aci   The string representation of the ACI value.
154     * @return  The user-friendly name embedded in the ACI,
155     *          or an empty string if no name is found.
156     */
157    private String getName(String aci) {
158        // Extract the user-friendly string in
159        // {@code ...version 3.0; acl "user-friendly name"...}.
160        Pattern pattern = Pattern.compile(".+version 3.0; ?acl \"([^\"]+)\".+");
161        Matcher matcher = pattern.matcher(aci);
162        if (matcher.find()) {
163            return matcher.group(1);
164        }
165        return "";
166    }
167
168    /**
169     * Returns a DocBook XML table listing global ACIs.
170     * @return A DocBook XML table listing global ACIs.
171     */
172    private String getGlobalAcisTable() {
173        final Map<String, Object> map = new HashMap<>();
174        map.put("year", new SimpleDateFormat("yyyy").format(new Date()));
175        map.put("lang", locale);
176        map.put("title", DOC_GLOBAL_ACIS_TABLE_TITLE.get());
177        map.put("summary", DOC_GLOBAL_ACIS_TABLE_SUMMARY.get());
178        map.put("nameTitle", DOC_GLOBAL_ACIS_NAME_COLUMN_TITLE.get());
179        map.put("descTitle", DOC_GLOBAL_ACIS_DESCRIPTION_COLUMN_TITLE.get());
180        map.put("defTitle", DOC_GLOBAL_ACIS_DEFINITION_COLUMN_TITLE.get());
181        map.put("acis", getDefaultGlobalAciList());
182        return applyTemplate("table-global-acis.ftl", map);
183    }
184
185    /**
186     * Returns a list of information about default global ACIs.
187     * @return A list of information about default global ACIs.
188     */
189    private List<Map<String, Object>> getDefaultGlobalAciList() {
190        final List<Map<String, Object>> globalAciList = new LinkedList<>();
191        for (final Aci aci : allGlobalAcis) {
192            final Map<String, Object> map = new HashMap<>();
193            map.put("name", aci.name);
194            map.put("description", aci.description);
195            map.put("definition", aci.definition);
196            globalAciList.add(map);
197        }
198        return globalAciList;
199    }
200}