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