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