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 2006-2010 Sun Microsystems, Inc.
015 * Portions Copyright 2013-2016 ForgeRock AS.
016 */
017package org.forgerock.opendj.ldif;
018
019import static com.forgerock.opendj.ldap.CoreMessages.*;
020
021import java.io.IOException;
022import java.io.InputStream;
023import java.util.Collections;
024import java.util.HashMap;
025import java.util.LinkedList;
026import java.util.List;
027import java.util.Map;
028import java.util.NoSuchElementException;
029import java.util.Random;
030
031import org.forgerock.i18n.LocalizableMessage;
032import org.forgerock.opendj.ldap.DecodeException;
033import org.forgerock.opendj.ldap.Entry;
034import org.forgerock.opendj.ldap.schema.Schema;
035
036import org.forgerock.util.Reject;
037
038/**
039 * A template driven entry generator, as used by the make-ldif tool.
040 * <p>
041 * To build a generator with default values, including default template file,
042 * use the empty constructor:
043 *
044 * <pre>
045 * generator = new EntryGenerator();
046 * </pre>
047 * <p>
048 * To build a generator with some custom values, use the non-empty constructor
049 * and the <code>set</code> methods:
050 *
051 * <pre>
052 * generator = new EntryGenerator(templatePath).setResourcePath(path).setSchema(schema)
053 * </pre>
054 */
055public final class EntryGenerator implements EntryReader {
056
057    /** Template file that contains directives for generation of entries. */
058    private TemplateFile templateFile;
059
060    /** Warnings issued by the parsing of the template file. */
061    private final List<LocalizableMessage> warnings = new LinkedList<>();
062
063    /** Indicates if the generator is closed. */
064    private boolean isClosed;
065
066    /** Indicates if the generator is initialized, which means template file has been parsed. */
067    private boolean isInitialized;
068
069    /** Random seed is used to generate random data. */
070    private Random random = new Random();
071
072    /**
073     * Path to the directory that may contain additional resource files needed
074     * during the generation process. It may be {@code null}.
075     */
076    private String resourcePath;
077
078    /**
079     * Schema is used to create attributes. If not provided, the default schema
080     * is used.
081     */
082    private Schema schema;
083
084    /**
085     * Path of template file, can be {@code null} if template file has been
086     * provided through another way.
087     */
088    private String templatePath;
089
090    /**
091     * Lines of template file, can be {@code null} if template file has been
092     * provided through another way.
093     */
094    private String[] templateLines;
095
096    /**
097     * Input stream containing template file, can be {@code null} if template
098     * file has been provided through another way.
099     */
100    private InputStream templateStream;
101
102    /** Indicates whether branch entries should be generated.
103     *
104     *  Default is {@code true}
105     */
106    private boolean generateBranches = true;
107
108    /** Dictionary of constants to use in the template file. */
109    private Map<String, String> constants = new HashMap<>();
110
111    /**
112     * Creates a generator using default values.
113     * <p>
114     * The default template file will be used to generate entries.
115     */
116    public EntryGenerator() {
117        // nothing to do
118    }
119
120    /**
121     * Creates a generator from the provided template path.
122     *
123     * @param templatePath
124     *            Path of the template file.
125     */
126    public EntryGenerator(final String  templatePath) {
127        Reject.ifNull(templatePath);
128        this.templatePath = templatePath;
129    }
130
131    /**
132     * Creates a generator from the provided template lines.
133     *
134     * @param templateLines
135     *            Lines defining the template file.
136     */
137    public EntryGenerator(final String... templateLines) {
138        Reject.ifNull(templateLines);
139        this.templateLines = templateLines;
140    }
141
142    /**
143     * Creates a generator from the provided template lines.
144     *
145     * @param templateLines
146     *            Lines defining the template file.
147     */
148    public EntryGenerator(final List<String> templateLines) {
149        Reject.ifNull(templateLines);
150        this.templateLines = templateLines.toArray(new String[templateLines.size()]);
151    }
152
153    /**
154     * Creates a generator from the provided input stream.
155     *
156     * @param templateStream
157     *            Input stream to read the template file.
158     */
159    public EntryGenerator(final InputStream templateStream) {
160        Reject.ifNull(templateStream);
161        this.templateStream = templateStream;
162    }
163
164    /**
165     * Sets the random seed to use when generating entries.
166     *
167     * @param seed
168     *            The random seed to use.
169     * @return A reference to this {@code EntryGenerator}.
170     */
171    public EntryGenerator setRandomSeed(final int seed) {
172        random = new Random(seed);
173        return this;
174    }
175
176    /**
177     * Sets the resource path, used to looks for resources files like first
178     * names, last names, or other custom resources.
179     *
180     * @param path
181     *            The resource path.
182     * @return A reference to this {@code EntryGenerator}.
183     */
184    public EntryGenerator setResourcePath(final String path) {
185        Reject.ifNull(path);
186        resourcePath = path;
187        return this;
188    }
189
190    /**
191     * Sets the schema which should be when generating entries. The default
192     * schema is used if no other is specified.
193     *
194     * @param schema
195     *            The schema which should be used for generating entries.
196     * @return A reference to this {@code EntryGenerator}.
197     */
198    public EntryGenerator setSchema(final Schema schema) {
199        this.schema = schema;
200        return this;
201    }
202
203    /**
204     * Sets a constant to use in template file. It overrides the constant set in
205     * the template file.
206     *
207     * @param name
208     *            The name of the constant.
209     * @param value
210     *            The value of the constant.
211     * @return A reference to this {@code EntryGenerator}.
212     */
213    public EntryGenerator setConstant(String name, Object value) {
214        constants.put(name, value.toString());
215        return this;
216    }
217
218    /**
219     * Sets the flag which indicates whether branch entries should be generated.
220     *
221     * The default is {@code true}.
222     *
223     * @param generateBranches
224     *              Indicates whether the branches DN entries has to be generated.
225     * @return A reference to this {@code EntryGenerator}.
226     */
227    public EntryGenerator setGenerateBranches(boolean generateBranches) {
228        this.generateBranches = generateBranches;
229        return this;
230    }
231
232    /**
233     * Checks if there are some warning(s) after parsing the template file.
234     * <p>
235     * Warnings are available only after the first call to {@code hasNext()} or
236     * {@code readEntry()} methods.
237     *
238     * @return {@code true} if there is at least one warning.
239     */
240    public boolean hasWarnings() {
241        return !warnings.isEmpty();
242    }
243
244    /**
245     * Returns the warnings generated by the parsing of template file.
246     * <p>
247     * Warnings are available only after the first call to {@code hasNext()} or
248     * {@code readEntry()} methods.
249     *
250     * @return The list of warnings, which is empty if there are no warnings.
251     */
252    public List<LocalizableMessage> getWarnings() {
253        return Collections.unmodifiableList(warnings);
254    }
255
256    @Override
257    public void close() {
258        isClosed = true;
259    }
260
261    @Override
262    public boolean hasNext() throws IOException {
263        if (isClosed) {
264            return false;
265        }
266        ensureGeneratorIsInitialized();
267        return templateFile.hasNext();
268    }
269
270    @Override
271    public Entry readEntry() throws IOException {
272        if (!hasNext()) {
273            throw new NoSuchElementException();
274        } else {
275            return templateFile.nextEntry();
276        }
277    }
278
279    /**
280     * Check that generator is initialized, and initialize it
281     * if it has not been initialized.
282     */
283    private void ensureGeneratorIsInitialized() throws IOException {
284        if (!isInitialized) {
285            isInitialized = true;
286            initialize();
287        }
288    }
289
290    /**
291     * Initializes the generator, by retrieving template file and parsing it.
292     */
293    private void initialize() throws IOException {
294        if (schema == null) {
295            schema = Schema.getDefaultSchema();
296        }
297        templateFile = new TemplateFile(schema, constants, resourcePath, random, generateBranches);
298        try {
299            if (templatePath != null) {
300                templateFile.parse(templatePath, warnings);
301            } else if (templateLines != null) {
302                templateFile.parse(templateLines, warnings);
303            } else if (templateStream != null) {
304                templateFile.parse(templateStream, warnings);
305            } else {
306                // use default template file
307                templateFile.parse(warnings);
308            }
309        } catch (IOException e) {
310            throw e;
311        } catch (Exception e) {
312            throw DecodeException.fatalError(ERR_ENTRY_GENERATOR_EXCEPTION_DURING_PARSE.get(e.getMessage()), e);
313        }
314    }
315
316}