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  configClass;
153    StringArgument  configFile;
154    StringArgument  templatePath;
155    StringArgument  ldifFile;
156    StringArgument  resourcePath;
157
158    try
159    {
160      configFile =
161              StringArgument.builder("configFile")
162                      .shortIdentifier('c')
163                      .description(INFO_DESCRIPTION_CONFIG_FILE.get())
164                      .hidden()
165                      .required()
166                      .valuePlaceholder(INFO_CONFIGFILE_PLACEHOLDER.get())
167                      .buildAndAddToParser(argParser);
168      configClass =
169              StringArgument.builder(OPTION_LONG_CONFIG_CLASS)
170                      .shortIdentifier(OPTION_SHORT_CONFIG_CLASS)
171                      .description(INFO_DESCRIPTION_CONFIG_CLASS.get())
172                      .hidden()
173                      .valuePlaceholder(INFO_CONFIGCLASS_PLACEHOLDER.get())
174                      .buildAndAddToParser(argParser);
175      resourcePath =
176              StringArgument.builder("resourcePath")
177                      .shortIdentifier('r')
178                      .description(INFO_MAKELDIF_DESCRIPTION_RESOURCE_PATH.get())
179                      .hidden()
180                      .required()
181                      .valuePlaceholder(INFO_PATH_PLACEHOLDER.get())
182                      .buildAndAddToParser(argParser);
183      templatePath =
184              StringArgument.builder("templateFile")
185                      .shortIdentifier('t')
186                      .description(INFO_MAKELDIF_DESCRIPTION_TEMPLATE.get())
187                      .required()
188                      .valuePlaceholder(INFO_FILE_PLACEHOLDER.get())
189                      .buildAndAddToParser(argParser);
190      ldifFile =
191              StringArgument.builder("ldifFile")
192                      .shortIdentifier('o')
193                      .description(INFO_MAKELDIF_DESCRIPTION_LDIF.get())
194                      .required()
195                      .valuePlaceholder(INFO_FILE_PLACEHOLDER.get())
196                      .buildAndAddToParser(argParser);
197      randomSeed =
198              IntegerArgument.builder(OPTION_LONG_RANDOM_SEED)
199                      .shortIdentifier(OPTION_SHORT_RANDOM_SEED)
200                      .description(INFO_MAKELDIF_DESCRIPTION_SEED.get())
201                      .defaultValue(0)
202                      .valuePlaceholder(INFO_SEED_PLACEHOLDER.get())
203                      .buildAndAddToParser(argParser);
204
205      showUsage = showUsageArgument();
206      argParser.addArgument(showUsage);
207      argParser.setUsageArgument(showUsage);
208    }
209    catch (ArgumentException ae)
210    {
211      printWrappedText(err, ERR_CANNOT_INITIALIZE_ARGS.get(ae.getMessage()));
212      return 1;
213    }
214
215
216    // Parse the command-line arguments provided to the program.
217    try
218    {
219      argParser.parseArguments(args);
220    }
221    catch (ArgumentException ae)
222    {
223      argParser.displayMessageAndUsageReference(err, ERR_ERROR_PARSING_ARGS.get(ae.getMessage()));
224      return 1;
225    }
226
227
228    // If we should just display usage or version information,
229    // then print it and exit.
230    if (argParser.usageOrVersionDisplayed())
231    {
232      return 0;
233    }
234
235    // Checks the version - if upgrade required, the tool is unusable
236    try
237    {
238      BuildVersion.checkVersionMismatch();
239    }
240    catch (InitializationException e)
241    {
242      printWrappedText(err, e.getMessage());
243      return 1;
244    }
245
246    if (initializeServer)
247    {
248      // Initialize the Directory Server configuration handler using the
249      // information that was provided.
250      DirectoryServer directoryServer = DirectoryServer.getInstance();
251      DirectoryServer.bootstrapClient();
252
253      try
254      {
255        DirectoryServer.initializeJMX();
256      }
257      catch (Exception e)
258      {
259        printWrappedText(err, ERR_MAKELDIF_CANNOT_INITIALIZE_JMX.get(configFile.getValue(), e.getMessage()));
260        return 1;
261      }
262
263      try
264      {
265        directoryServer.initializeConfiguration(configClass.getValue(),
266            configFile.getValue());
267      }
268      catch (Exception e)
269      {
270        printWrappedText(err, ERR_MAKELDIF_CANNOT_INITIALIZE_CONFIG.get(configFile.getValue(), e.getMessage()));
271        return 1;
272      }
273    }
274
275    if (initializeSchema)
276    {
277      try
278      {
279        DirectoryServer.getInstance().initializeSchema();
280      }
281      catch (Exception e)
282      {
283        printWrappedText(err, ERR_MAKELDIF_CANNOT_INITIALIZE_SCHEMA.get(configFile.getValue(), e.getMessage()));
284        return 1;
285      }
286    }
287
288
289    // Create the random number generator that will be used for the generation
290    // process.
291    Random random;
292    if (randomSeed.isPresent())
293    {
294      try
295      {
296        random = new Random(randomSeed.getIntValue());
297      }
298      catch (Exception e)
299      {
300        random = new Random();
301      }
302    }
303    else
304    {
305      random = new Random();
306    }
307
308
309    // If a resource path was provided, then make sure it's acceptable.
310    File resourceDir = new File(resourcePath.getValue());
311    if (! resourceDir.exists())
312    {
313      printWrappedText(err, ERR_MAKELDIF_NO_SUCH_RESOURCE_DIRECTORY.get(resourcePath.getValue()));
314      return 1;
315    }
316
317
318    // Load and parse the template file.
319    LinkedList<LocalizableMessage> warnings = new LinkedList<>();
320    TemplateFile templateFile = new TemplateFile(resourcePath.getValue(), random);
321    try
322    {
323      templateFile.parse(templatePath.getValue(), warnings);
324    }
325    catch (IOException ioe)
326    {
327      printWrappedText(err, ERR_MAKELDIF_IOEXCEPTION_DURING_PARSE.get(ioe.getMessage()));
328      return 1;
329    }
330    catch (Exception e)
331    {
332      printWrappedText(err, ERR_MAKELDIF_EXCEPTION_DURING_PARSE.get(e.getMessage()));
333      return 1;
334    }
335
336
337    // If there were any warnings, then print them.
338    if (! warnings.isEmpty())
339    {
340      for (LocalizableMessage s : warnings)
341      {
342        printWrappedText(err, s);
343      }
344    }
345
346
347    // Create the LDIF writer that will be used to actually write the LDIF.
348    LDIFExportConfig exportConfig =
349         new LDIFExportConfig(ldifFile.getValue(),
350                              ExistingFileBehavior.OVERWRITE);
351    try
352    {
353      ldifWriter = new LDIFWriter(exportConfig);
354    }
355    catch (IOException ioe)
356    {
357      printWrappedText(err, ERR_MAKELDIF_UNABLE_TO_CREATE_LDIF.get(ldifFile.getValue(), ioe));
358      return 1;
359    }
360
361
362    // Generate the LDIF content.
363    try
364    {
365      templateFile.generateLDIF(this);
366    }
367    catch (Exception e)
368    {
369      printWrappedText(err, ERR_MAKELDIF_ERROR_WRITING_LDIF.get(ldifFile.getValue(), stackTraceToSingleLineString(e)));
370      return 1;
371    }
372    finally
373    {
374      close(ldifWriter);
375    }
376
377
378    // If we've gotten here, then everything was successful.
379    return 0;
380  }
381
382
383  /**
384   * Processes the provided set of command-line arguments and begins generating
385   * the LDIF content.
386   *
387   * @param  args  The command-line arguments provided for this program.
388   *
389   * @return  A result code of zero if all processing completed properly, or
390   *          a nonzero result if a problem occurred.
391   */
392  public int makeLDIFMain(String[] args)
393  {
394     return makeLDIFMain(args, true, true, System.out, System.err);
395  }
396
397
398
399  /**
400   * Writes the provided entry to the appropriate target.
401   *
402   * @param  entry  The entry to be written.
403   *
404   * @return  <CODE>true</CODE> if the entry writer will accept more entries, or
405   *          <CODE>false</CODE> if not.
406   *
407   * @throws  IOException  If a problem occurs while writing the entry to its
408   *                       intended destination.
409   *
410   * @throws  MakeLDIFException  If some other problem occurs.
411   */
412  @Override
413  public boolean writeEntry(TemplateEntry entry)
414         throws IOException, MakeLDIFException
415  {
416    try
417    {
418      if (entry.getDN() != null)
419      {
420        ldifWriter.writeTemplateEntry(entry);
421
422        if ((++entriesWritten % 1000) == 0)
423        {
424          printWrappedText(out, INFO_MAKELDIF_PROCESSED_N_ENTRIES.get(entriesWritten));
425        }
426      }
427      else
428      {
429        AttributeType[] rdnAttrs = entry.getTemplate().getRDNAttributes();
430        String nullRdn = "";
431        for (AttributeType att : rdnAttrs)
432        {
433          if (entry.getValue(att) == null)
434          {
435            nullRdn = att.getNameOrOID();
436            break ;
437          }
438        }
439        printWrappedText(err, ERR_MAKELDIF_CANNOT_WRITE_ENTRY_WITHOUT_DN.get(nullRdn));
440        return true;
441      }
442
443      return true;
444    }
445    catch (IOException ioe)
446    {
447      throw ioe;
448    }
449    catch (Exception e)
450    {
451      LocalizableMessage message = ERR_MAKELDIF_CANNOT_WRITE_ENTRY.get(
452          entry.getDN(), stackTraceToSingleLineString(e));
453      throw new MakeLDIFException(message, e);
454    }
455  }
456
457
458
459  /**
460   * Notifies the entry writer that no more entries will be provided and that
461   * any associated cleanup may be performed.
462   */
463  @Override
464  public void closeEntryWriter()
465  {
466    printWrappedText(out, INFO_MAKELDIF_PROCESSING_COMPLETE.get(entriesWritten));
467  }
468}
469