001/*
002 * Copyright 2009-2018 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2009-2018 Ping Identity Corporation
007 *
008 * This program is free software; you can redistribute it and/or modify
009 * it under the terms of the GNU General Public License (GPLv2 only)
010 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
011 * as published by the Free Software Foundation.
012 *
013 * This program is distributed in the hope that it will be useful,
014 * but WITHOUT ANY WARRANTY; without even the implied warranty of
015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
016 * GNU General Public License for more details.
017 *
018 * You should have received a copy of the GNU General Public License
019 * along with this program; if not, see <http://www.gnu.org/licenses>.
020 */
021package com.unboundid.ldap.sdk.persist;
022
023
024
025import java.io.File;
026import java.io.OutputStream;
027import java.io.Serializable;
028import java.util.LinkedHashMap;
029import java.util.List;
030
031import com.unboundid.asn1.ASN1OctetString;
032import com.unboundid.ldap.sdk.Attribute;
033import com.unboundid.ldap.sdk.Entry;
034import com.unboundid.ldap.sdk.Modification;
035import com.unboundid.ldap.sdk.ModificationType;
036import com.unboundid.ldap.sdk.ResultCode;
037import com.unboundid.ldap.sdk.Version;
038import com.unboundid.ldap.sdk.schema.AttributeTypeDefinition;
039import com.unboundid.ldap.sdk.schema.ObjectClassDefinition;
040import com.unboundid.ldif.LDIFModifyChangeRecord;
041import com.unboundid.ldif.LDIFRecord;
042import com.unboundid.ldif.LDIFWriter;
043import com.unboundid.util.CommandLineTool;
044import com.unboundid.util.Debug;
045import com.unboundid.util.Mutable;
046import com.unboundid.util.StaticUtils;
047import com.unboundid.util.ThreadSafety;
048import com.unboundid.util.ThreadSafetyLevel;
049import com.unboundid.util.args.ArgumentException;
050import com.unboundid.util.args.ArgumentParser;
051import com.unboundid.util.args.BooleanArgument;
052import com.unboundid.util.args.FileArgument;
053import com.unboundid.util.args.StringArgument;
054
055import static com.unboundid.ldap.sdk.persist.PersistMessages.*;
056
057
058
059/**
060 * This class provides a tool which can be used to generate LDAP attribute
061 * type and object class definitions which may be used to store objects
062 * created from a specified Java class.  The given class must be included in the
063 * classpath of the JVM used to invoke the tool, and must be marked with the
064 * {@link LDAPObject} annotation.
065 */
066@Mutable()
067@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE)
068public final class GenerateSchemaFromSource
069       extends CommandLineTool
070       implements Serializable
071{
072  /**
073   * The serial version UID for this serializable class.
074   */
075  private static final long serialVersionUID = 1029934829295836935L;
076
077
078
079  // Arguments used by this tool.
080  private BooleanArgument modifyFormatArg;
081  private FileArgument    outputFileArg;
082  private StringArgument  classNameArg;
083
084
085
086  /**
087   * Parse the provided command line arguments and perform the appropriate
088   * processing.
089   *
090   * @param  args  The command line arguments provided to this program.
091   */
092  public static void main(final String[] args)
093  {
094    final ResultCode resultCode = main(args, System.out, System.err);
095    if (resultCode != ResultCode.SUCCESS)
096    {
097      System.exit(resultCode.intValue());
098    }
099  }
100
101
102
103  /**
104   * Parse the provided command line arguments and perform the appropriate
105   * processing.
106   *
107   * @param  args       The command line arguments provided to this program.
108   * @param  outStream  The output stream to which standard out should be
109   *                    written.  It may be {@code null} if output should be
110   *                    suppressed.
111   * @param  errStream  The output stream to which standard error should be
112   *                    written.  It may be {@code null} if error messages
113   *                    should be suppressed.
114   *
115   * @return  A result code indicating whether the processing was successful.
116   */
117  public static ResultCode main(final String[] args,
118                                final OutputStream outStream,
119                                final OutputStream errStream)
120  {
121    final GenerateSchemaFromSource tool =
122         new GenerateSchemaFromSource(outStream, errStream);
123    return tool.runTool(args);
124  }
125
126
127
128  /**
129   * Creates a new instance of this tool.
130   *
131   * @param  outStream  The output stream to which standard out should be
132   *                    written.  It may be {@code null} if output should be
133   *                    suppressed.
134   * @param  errStream  The output stream to which standard error should be
135   *                    written.  It may be {@code null} if error messages
136   *                    should be suppressed.
137   */
138  public GenerateSchemaFromSource(final OutputStream outStream,
139                                  final OutputStream errStream)
140  {
141    super(outStream, errStream);
142  }
143
144
145
146  /**
147   * {@inheritDoc}
148   */
149  @Override()
150  public String getToolName()
151  {
152    return "generate-schema-from-source";
153  }
154
155
156
157  /**
158   * {@inheritDoc}
159   */
160  @Override()
161  public String getToolDescription()
162  {
163    return INFO_GEN_SCHEMA_TOOL_DESCRIPTION.get();
164  }
165
166
167
168  /**
169   * Retrieves the version string for this tool.
170   *
171   * @return  The version string for this tool.
172   */
173  @Override()
174  public String getToolVersion()
175  {
176    return Version.NUMERIC_VERSION_STRING;
177  }
178
179
180
181  /**
182   * Indicates whether this tool should provide support for an interactive mode,
183   * in which the tool offers a mode in which the arguments can be provided in
184   * a text-driven menu rather than requiring them to be given on the command
185   * line.  If interactive mode is supported, it may be invoked using the
186   * "--interactive" argument.  Alternately, if interactive mode is supported
187   * and {@link #defaultsToInteractiveMode()} returns {@code true}, then
188   * interactive mode may be invoked by simply launching the tool without any
189   * arguments.
190   *
191   * @return  {@code true} if this tool supports interactive mode, or
192   *          {@code false} if not.
193   */
194  @Override()
195  public boolean supportsInteractiveMode()
196  {
197    return true;
198  }
199
200
201
202  /**
203   * Indicates whether this tool defaults to launching in interactive mode if
204   * the tool is invoked without any command-line arguments.  This will only be
205   * used if {@link #supportsInteractiveMode()} returns {@code true}.
206   *
207   * @return  {@code true} if this tool defaults to using interactive mode if
208   *          launched without any command-line arguments, or {@code false} if
209   *          not.
210   */
211  @Override()
212  public boolean defaultsToInteractiveMode()
213  {
214    return true;
215  }
216
217
218
219  /**
220   * Indicates whether this tool supports the use of a properties file for
221   * specifying default values for arguments that aren't specified on the
222   * command line.
223   *
224   * @return  {@code true} if this tool supports the use of a properties file
225   *          for specifying default values for arguments that aren't specified
226   *          on the command line, or {@code false} if not.
227   */
228  @Override()
229  public boolean supportsPropertiesFile()
230  {
231    return true;
232  }
233
234
235
236  /**
237   * {@inheritDoc}
238   */
239  @Override()
240  public void addToolArguments(final ArgumentParser parser)
241         throws ArgumentException
242  {
243    classNameArg = new StringArgument('c', "javaClass", true, 1,
244         INFO_GEN_SCHEMA_VALUE_PLACEHOLDER_CLASS.get(),
245         INFO_GEN_SCHEMA_ARG_DESCRIPTION_JAVA_CLASS.get());
246    classNameArg.addLongIdentifier("java-class", true);
247    parser.addArgument(classNameArg);
248
249    outputFileArg = new FileArgument('f', "outputFile", true, 1,
250         INFO_GEN_SCHEMA_VALUE_PLACEHOLDER_PATH.get(),
251         INFO_GEN_SCHEMA_ARG_DESCRIPTION_OUTPUT_FILE.get(), false, true, true,
252         false);
253    outputFileArg.addLongIdentifier("output-file", true);
254    parser.addArgument(outputFileArg);
255
256    modifyFormatArg = new BooleanArgument('m', "modifyFormat",
257         INFO_GEN_SCHEMA_ARG_DESCRIPTION_MODIFY_FORMAT.get());
258    modifyFormatArg.addLongIdentifier("modify-format", true);
259    parser.addArgument(modifyFormatArg);
260  }
261
262
263
264  /**
265   * {@inheritDoc}
266   */
267  @Override()
268  public ResultCode doToolProcessing()
269  {
270    // Load the specified Java class.
271    final String className = classNameArg.getValue();
272    final Class<?> targetClass;
273    try
274    {
275      targetClass = Class.forName(className);
276    }
277    catch (final Exception e)
278    {
279      Debug.debugException(e);
280      err(ERR_GEN_SCHEMA_CANNOT_LOAD_CLASS.get(className));
281      return ResultCode.PARAM_ERROR;
282    }
283
284
285    // Create an LDAP persister for the class and use it to ensure that the
286    // class is valid.
287    final LDAPPersister<?> persister;
288    try
289    {
290      persister = LDAPPersister.getInstance(targetClass);
291    }
292    catch (final Exception e)
293    {
294      Debug.debugException(e);
295      err(ERR_GEN_SCHEMA_INVALID_CLASS.get(className,
296           StaticUtils.getExceptionMessage(e)));
297      return ResultCode.LOCAL_ERROR;
298    }
299
300
301    // Use the persister to generate the attribute type and object class
302    // definitions.
303    final List<AttributeTypeDefinition> attrTypes;
304    try
305    {
306      attrTypes = persister.constructAttributeTypes();
307    }
308    catch (final Exception e)
309    {
310      Debug.debugException(e);
311      err(ERR_GEN_SCHEMA_ERROR_CONSTRUCTING_ATTRS.get(className,
312           StaticUtils.getExceptionMessage(e)));
313      return ResultCode.LOCAL_ERROR;
314    }
315
316    final List<ObjectClassDefinition> objectClasses;
317    try
318    {
319      objectClasses = persister.constructObjectClasses();
320    }
321    catch (final Exception e)
322    {
323      Debug.debugException(e);
324      err(ERR_GEN_SCHEMA_ERROR_CONSTRUCTING_OCS.get(className,
325           StaticUtils.getExceptionMessage(e)));
326      return ResultCode.LOCAL_ERROR;
327    }
328
329
330    // Convert the attribute type and object class definitions into their
331    // appropriate string representations.
332    int i=0;
333    final ASN1OctetString[] attrTypeValues =
334         new ASN1OctetString[attrTypes.size()];
335    for (final AttributeTypeDefinition d : attrTypes)
336    {
337      attrTypeValues[i++] = new ASN1OctetString(d.toString());
338    }
339
340    i=0;
341    final ASN1OctetString[] ocValues =
342         new ASN1OctetString[objectClasses.size()];
343    for (final ObjectClassDefinition d : objectClasses)
344    {
345      ocValues[i++] = new ASN1OctetString(d.toString());
346    }
347
348
349    // Construct the LDIF record to be written.
350    final LDIFRecord schemaRecord;
351    if (modifyFormatArg.isPresent())
352    {
353      schemaRecord = new LDIFModifyChangeRecord("cn=schema",
354           new Modification(ModificationType.ADD, "attributeTypes",
355                attrTypeValues),
356           new Modification(ModificationType.ADD, "objectClasses", ocValues));
357    }
358    else
359    {
360      schemaRecord = new Entry("cn=schema",
361           new Attribute("objectClass", "top", "ldapSubentry", "subschema"),
362           new Attribute("cn", "schema"),
363           new Attribute("attributeTypes", attrTypeValues),
364           new Attribute("objectClasses", ocValues));
365    }
366
367
368    // Write the schema entry to the specified file.
369    final File outputFile = outputFileArg.getValue();
370    try
371    {
372      final LDIFWriter ldifWriter = new LDIFWriter(outputFile);
373      ldifWriter.writeLDIFRecord(schemaRecord);
374      ldifWriter.close();
375    }
376    catch (final Exception e)
377    {
378      Debug.debugException(e);
379      err(ERR_GEN_SCHEMA_CANNOT_WRITE_SCHEMA.get(outputFile.getAbsolutePath(),
380           StaticUtils.getExceptionMessage(e)));
381      return ResultCode.LOCAL_ERROR;
382    }
383
384
385    return ResultCode.SUCCESS;
386  }
387
388
389
390  /**
391   * {@inheritDoc}
392   */
393  @Override()
394  public LinkedHashMap<String[],String> getExampleUsages()
395  {
396    final LinkedHashMap<String[],String> examples = new LinkedHashMap<>(1);
397
398    final String[] args =
399    {
400      "--javaClass", "com.example.MyClass",
401      "--outputFile", "MyClass-schema.ldif"
402    };
403    examples.put(args, INFO_GEN_SCHEMA_EXAMPLE_1.get());
404
405    return examples;
406  }
407}