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.opends.server.tools.makeldif;
018
019import static com.forgerock.opendj.cli.ArgumentConstants.*;
020import static com.forgerock.opendj.cli.Utils.*;
021import static com.forgerock.opendj.cli.CommonArguments.*;
022
023import static org.opends.messages.ToolMessages.*;
024import static org.opends.server.util.StaticUtils.*;
025
026import java.io.File;
027import java.io.IOException;
028import java.io.OutputStream;
029import java.io.PrintStream;
030import java.util.LinkedList;
031import java.util.Random;
032
033import org.forgerock.i18n.LocalizableMessage;
034import org.opends.server.core.DirectoryServer;
035import org.opends.server.core.DirectoryServer.DirectoryServerVersionHandler;
036import org.opends.server.loggers.JDKLogging;
037import org.forgerock.opendj.ldap.schema.AttributeType;
038import org.opends.server.types.ExistingFileBehavior;
039import org.opends.server.types.InitializationException;
040import org.opends.server.types.LDIFExportConfig;
041import org.opends.server.types.NullOutputStream;
042import org.opends.server.util.BuildVersion;
043import org.opends.server.util.LDIFWriter;
044
045import com.forgerock.opendj.cli.ArgumentException;
046import com.forgerock.opendj.cli.ArgumentParser;
047import com.forgerock.opendj.cli.BooleanArgument;
048import com.forgerock.opendj.cli.IntegerArgument;
049import com.forgerock.opendj.cli.StringArgument;
050
051/**
052 * This class defines a program that can be used to generate LDIF content based
053 * on a template.
054 */
055public class MakeLDIF
056       implements EntryWriter
057{
058  /**
059   * The fully-qualified name of this class.
060   */
061  private static final String CLASS_NAME =
062       "org.opends.server.tools.makeldif.MakeLDIF";
063
064  /** The LDIF writer that will be used to write the entries. */
065  private LDIFWriter ldifWriter;
066
067  /** The total number of entries that have been written. */
068  private long entriesWritten;
069
070  private PrintStream out = System.out;
071  private PrintStream err = System.err;
072
073  /**
074   * Invokes the <CODE>makeLDIFMain</CODE> method with the provided set of
075   * arguments.
076   *
077   * @param  args  The command-line arguments provided for this program.
078   */
079  public static void main(String[] args)
080  {
081    MakeLDIF makeLDIF = new MakeLDIF();
082    int returnCode = makeLDIF.makeLDIFMain(args);
083    if (returnCode != 0)
084    {
085      System.exit(filterExitCode(returnCode));
086    }
087  }
088
089  /**
090   * Provides the command-line arguments to the main application for
091   * processing and returns the exit code as an integer.
092   *
093   * @param args
094   *          The set of command-line arguments provided to this
095   *          program.
096   * @param outStream
097   *          The output stream for standard output.
098   * @param errStream
099   *          The output stream for standard error.
100   * @return Zero to indicate that the program completed successfully,
101   *         or non-zero to indicate that an error occurred.
102   */
103  public static int main(final String[] args, final OutputStream outStream, final OutputStream errStream)
104  {
105    return new MakeLDIF().makeLDIFMain(args, false, false, outStream, errStream);
106  }
107
108  /**
109   * Creates a new instance of this utility.  It should just be used for
110   * invoking the <CODE>makeLDIFMain</CODE> method.
111   */
112  public MakeLDIF()
113  {
114    ldifWriter     = null;
115    entriesWritten = 0L;
116  }
117
118  /**
119   * Processes the provided set of command-line arguments and begins generating
120   * the LDIF content.
121   *
122   * @param  args  The command-line arguments provided for this program.
123   * @param  initializeServer  Indicates whether to initialize the server.
124   * @param  initializeSchema  Indicates whether to initialize the schema.
125   * @param  outStream         The output stream to use for standard output, or
126   *                           {@code null} if standard output is not needed.
127   * @param  errStream         The output stream to use for standard error, or
128   *                           {@code null} if standard error is not needed.
129   * @return  A result code of zero if all processing completed properly, or
130   *          a nonzero result if a problem occurred.
131   *
132   */
133  public int makeLDIFMain(String[] args, boolean initializeServer,
134      boolean initializeSchema,
135      OutputStream outStream,
136      OutputStream errStream)
137  {
138    out = NullOutputStream.wrapOrNullStream(outStream);
139    err = NullOutputStream.wrapOrNullStream(errStream);
140    JDKLogging.disableLogging();
141
142
143//  Create and initialize the argument parser for this program.
144    LocalizableMessage toolDescription = INFO_MAKELDIF_TOOL_DESCRIPTION.get();
145    ArgumentParser  argParser = new ArgumentParser(CLASS_NAME, toolDescription,
146                                                   false);
147    argParser.setShortToolDescription(REF_SHORT_DESC_MAKELDIF.get());
148    argParser.setVersionHandler(new DirectoryServerVersionHandler());
149
150    BooleanArgument showUsage;
151    IntegerArgument randomSeed;
152    StringArgument  configFile;
153    StringArgument  templatePath;
154    StringArgument  ldifFile;
155    StringArgument  resourcePath;
156
157    try
158    {
159      configFile =
160              StringArgument.builder("configFile")
161                      .shortIdentifier('c')
162                      .description(INFO_DESCRIPTION_CONFIG_FILE.get())
163                      .hidden()
164                      .required()
165                      .valuePlaceholder(INFO_CONFIGFILE_PLACEHOLDER.get())
166                      .buildAndAddToParser(argParser);
167      resourcePath =
168              StringArgument.builder("resourcePath")
169                      .shortIdentifier('r')
170                      .description(INFO_MAKELDIF_DESCRIPTION_RESOURCE_PATH.get())
171                      .hidden()
172                      .required()
173                      .valuePlaceholder(INFO_PATH_PLACEHOLDER.get())
174                      .buildAndAddToParser(argParser);
175      templatePath =
176              StringArgument.builder("templateFile")
177                      .shortIdentifier('t')
178                      .description(INFO_MAKELDIF_DESCRIPTION_TEMPLATE.get())
179                      .required()
180                      .valuePlaceholder(INFO_FILE_PLACEHOLDER.get())
181                      .buildAndAddToParser(argParser);
182      ldifFile =
183              StringArgument.builder("ldifFile")
184                      .shortIdentifier('o')
185                      .description(INFO_MAKELDIF_DESCRIPTION_LDIF.get())
186                      .required()
187                      .valuePlaceholder(INFO_FILE_PLACEHOLDER.get())
188                      .buildAndAddToParser(argParser);
189      randomSeed =
190              IntegerArgument.builder(OPTION_LONG_RANDOM_SEED)
191                      .shortIdentifier(OPTION_SHORT_RANDOM_SEED)
192                      .description(INFO_MAKELDIF_DESCRIPTION_SEED.get())
193                      .defaultValue(0)
194                      .valuePlaceholder(INFO_SEED_PLACEHOLDER.get())
195                      .buildAndAddToParser(argParser);
196
197      showUsage = showUsageArgument();
198      argParser.addArgument(showUsage);
199      argParser.setUsageArgument(showUsage);
200    }
201    catch (ArgumentException ae)
202    {
203      printWrappedText(err, ERR_CANNOT_INITIALIZE_ARGS.get(ae.getMessage()));
204      return 1;
205    }
206
207
208    // Parse the command-line arguments provided to the program.
209    try
210    {
211      argParser.parseArguments(args);
212    }
213    catch (ArgumentException ae)
214    {
215      argParser.displayMessageAndUsageReference(err, ERR_ERROR_PARSING_ARGS.get(ae.getMessage()));
216      return 1;
217    }
218
219
220    // If we should just display usage or version information,
221    // then print it and exit.
222    if (argParser.usageOrVersionDisplayed())
223    {
224      return 0;
225    }
226
227    // Checks the version - if upgrade required, the tool is unusable
228    try
229    {
230      BuildVersion.checkVersionMismatch();
231    }
232    catch (InitializationException e)
233    {
234      printWrappedText(err, e.getMessage());
235      return 1;
236    }
237
238    if (initializeServer)
239    {
240      // Initialize the Directory Server configuration handler using the
241      // information that was provided.
242      DirectoryServer directoryServer = DirectoryServer.getInstance();
243      DirectoryServer.bootstrapClient();
244
245      try
246      {
247        DirectoryServer.initializeJMX();
248      }
249      catch (Exception e)
250      {
251        printWrappedText(err, ERR_MAKELDIF_CANNOT_INITIALIZE_JMX.get(configFile.getValue(), e.getMessage()));
252        return 1;
253      }
254
255      try
256      {
257        directoryServer.initializeConfiguration(configFile.getValue());
258      }
259      catch (Exception e)
260      {
261        printWrappedText(err, ERR_MAKELDIF_CANNOT_INITIALIZE_CONFIG.get(configFile.getValue(), e.getMessage()));
262        return 1;
263      }
264    }
265
266    if (initializeSchema)
267    {
268      try
269      {
270        DirectoryServer.getInstance().initializeSchema();
271      }
272      catch (Exception e)
273      {
274        printWrappedText(err, ERR_MAKELDIF_CANNOT_INITIALIZE_SCHEMA.get(configFile.getValue(), e.getMessage()));
275        return 1;
276      }
277    }
278
279
280    // Create the random number generator that will be used for the generation
281    // process.
282    Random random;
283    if (randomSeed.isPresent())
284    {
285      try
286      {
287        random = new Random(randomSeed.getIntValue());
288      }
289      catch (Exception e)
290      {
291        random = new Random();
292      }
293    }
294    else
295    {
296      random = new Random();
297    }
298
299
300    // If a resource path was provided, then make sure it's acceptable.
301    File resourceDir = new File(resourcePath.getValue());
302    if (! resourceDir.exists())
303    {
304      printWrappedText(err, ERR_MAKELDIF_NO_SUCH_RESOURCE_DIRECTORY.get(resourcePath.getValue()));
305      return 1;
306    }
307
308
309    // Load and parse the template file.
310    LinkedList<LocalizableMessage> warnings = new LinkedList<>();
311    TemplateFile templateFile = new TemplateFile(resourcePath.getValue(), random);
312    try
313    {
314      templateFile.parse(templatePath.getValue(), warnings);
315    }
316    catch (IOException ioe)
317    {
318      printWrappedText(err, ERR_MAKELDIF_IOEXCEPTION_DURING_PARSE.get(ioe.getMessage()));
319      return 1;
320    }
321    catch (Exception e)
322    {
323      printWrappedText(err, ERR_MAKELDIF_EXCEPTION_DURING_PARSE.get(e.getMessage()));
324      return 1;
325    }
326
327
328    // If there were any warnings, then print them.
329    if (! warnings.isEmpty())
330    {
331      for (LocalizableMessage s : warnings)
332      {
333        printWrappedText(err, s);
334      }
335    }
336
337
338    // Create the LDIF writer that will be used to actually write the LDIF.
339    LDIFExportConfig exportConfig =
340         new LDIFExportConfig(ldifFile.getValue(),
341                              ExistingFileBehavior.OVERWRITE);
342    try
343    {
344      ldifWriter = new LDIFWriter(exportConfig);
345    }
346    catch (IOException ioe)
347    {
348      printWrappedText(err, ERR_MAKELDIF_UNABLE_TO_CREATE_LDIF.get(ldifFile.getValue(), ioe));
349      return 1;
350    }
351
352
353    // Generate the LDIF content.
354    try
355    {
356      templateFile.generateLDIF(this);
357    }
358    catch (Exception e)
359    {
360      printWrappedText(err, ERR_MAKELDIF_ERROR_WRITING_LDIF.get(ldifFile.getValue(), stackTraceToSingleLineString(e)));
361      return 1;
362    }
363    finally
364    {
365      close(ldifWriter);
366    }
367
368
369    // If we've gotten here, then everything was successful.
370    return 0;
371  }
372
373
374  /**
375   * Processes the provided set of command-line arguments and begins generating
376   * the LDIF content.
377   *
378   * @param  args  The command-line arguments provided for this program.
379   *
380   * @return  A result code of zero if all processing completed properly, or
381   *          a nonzero result if a problem occurred.
382   */
383  public int makeLDIFMain(String[] args)
384  {
385     return makeLDIFMain(args, true, true, System.out, System.err);
386  }
387
388
389
390  /**
391   * Writes the provided entry to the appropriate target.
392   *
393   * @param  entry  The entry to be written.
394   *
395   * @return  <CODE>true</CODE> if the entry writer will accept more entries, or
396   *          <CODE>false</CODE> if not.
397   *
398   * @throws  IOException  If a problem occurs while writing the entry to its
399   *                       intended destination.
400   *
401   * @throws  MakeLDIFException  If some other problem occurs.
402   */
403  @Override
404  public boolean writeEntry(TemplateEntry entry)
405         throws IOException, MakeLDIFException
406  {
407    try
408    {
409      if (entry.getDN() != null)
410      {
411        ldifWriter.writeTemplateEntry(entry);
412
413        if ((++entriesWritten % 1000) == 0)
414        {
415          printWrappedText(out, INFO_MAKELDIF_PROCESSED_N_ENTRIES.get(entriesWritten));
416        }
417      }
418      else
419      {
420        AttributeType[] rdnAttrs = entry.getTemplate().getRDNAttributes();
421        String nullRdn = "";
422        for (AttributeType att : rdnAttrs)
423        {
424          if (entry.getValue(att) == null)
425          {
426            nullRdn = att.getNameOrOID();
427            break ;
428          }
429        }
430        printWrappedText(err, ERR_MAKELDIF_CANNOT_WRITE_ENTRY_WITHOUT_DN.get(nullRdn));
431        return true;
432      }
433
434      return true;
435    }
436    catch (IOException ioe)
437    {
438      throw ioe;
439    }
440    catch (Exception e)
441    {
442      LocalizableMessage message = ERR_MAKELDIF_CANNOT_WRITE_ENTRY.get(
443          entry.getDN(), stackTraceToSingleLineString(e));
444      throw new MakeLDIFException(message, e);
445    }
446  }
447
448
449
450  /**
451   * Notifies the entry writer that no more entries will be provided and that
452   * any associated cleanup may be performed.
453   */
454  @Override
455  public void closeEntryWriter()
456  {
457    printWrappedText(out, INFO_MAKELDIF_PROCESSING_COMPLETE.get(entriesWritten));
458  }
459}
460