001    /*
002     * Copyright 2009-2016 UnboundID Corp.
003     * All Rights Reserved.
004     */
005    /*
006     * Copyright (C) 2009-2016 UnboundID Corp.
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     */
021    package com.unboundid.ldap.sdk.persist;
022    
023    
024    
025    import java.io.File;
026    import java.io.OutputStream;
027    import java.io.Serializable;
028    import java.util.LinkedHashMap;
029    import java.util.List;
030    
031    import com.unboundid.asn1.ASN1OctetString;
032    import com.unboundid.ldap.sdk.Attribute;
033    import com.unboundid.ldap.sdk.Entry;
034    import com.unboundid.ldap.sdk.Modification;
035    import com.unboundid.ldap.sdk.ModificationType;
036    import com.unboundid.ldap.sdk.ResultCode;
037    import com.unboundid.ldap.sdk.Version;
038    import com.unboundid.ldap.sdk.schema.AttributeTypeDefinition;
039    import com.unboundid.ldap.sdk.schema.ObjectClassDefinition;
040    import com.unboundid.ldif.LDIFModifyChangeRecord;
041    import com.unboundid.ldif.LDIFRecord;
042    import com.unboundid.ldif.LDIFWriter;
043    import com.unboundid.util.CommandLineTool;
044    import com.unboundid.util.Mutable;
045    import com.unboundid.util.ThreadSafety;
046    import com.unboundid.util.ThreadSafetyLevel;
047    import com.unboundid.util.args.ArgumentException;
048    import com.unboundid.util.args.ArgumentParser;
049    import com.unboundid.util.args.BooleanArgument;
050    import com.unboundid.util.args.FileArgument;
051    import com.unboundid.util.args.StringArgument;
052    
053    import static com.unboundid.ldap.sdk.persist.PersistMessages.*;
054    import static com.unboundid.util.Debug.*;
055    import static com.unboundid.util.StaticUtils.*;
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)
068    public 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");
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");
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");
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 (Exception e)
278        {
279          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 (Exception e)
293        {
294          debugException(e);
295          err(ERR_GEN_SCHEMA_INVALID_CLASS.get(className, getExceptionMessage(e)));
296          return ResultCode.LOCAL_ERROR;
297        }
298    
299    
300        // Use the persister to generate the attribute type and object class
301        // definitions.
302        final List<AttributeTypeDefinition> attrTypes;
303        try
304        {
305          attrTypes = persister.constructAttributeTypes();
306        }
307        catch (Exception e)
308        {
309          debugException(e);
310          err(ERR_GEN_SCHEMA_ERROR_CONSTRUCTING_ATTRS.get(className,
311               getExceptionMessage(e)));
312          return ResultCode.LOCAL_ERROR;
313        }
314    
315        final List<ObjectClassDefinition> objectClasses;
316        try
317        {
318          objectClasses = persister.constructObjectClasses();
319        }
320        catch (Exception e)
321        {
322          debugException(e);
323          err(ERR_GEN_SCHEMA_ERROR_CONSTRUCTING_OCS.get(className,
324               getExceptionMessage(e)));
325          return ResultCode.LOCAL_ERROR;
326        }
327    
328    
329        // Convert the attribute type and object class definitions into their
330        // appropriate string representations.
331        int i=0;
332        final ASN1OctetString[] attrTypeValues =
333             new ASN1OctetString[attrTypes.size()];
334        for (final AttributeTypeDefinition d : attrTypes)
335        {
336          attrTypeValues[i++] = new ASN1OctetString(d.toString());
337        }
338    
339        i=0;
340        final ASN1OctetString[] ocValues =
341             new ASN1OctetString[objectClasses.size()];
342        for (final ObjectClassDefinition d : objectClasses)
343        {
344          ocValues[i++] = new ASN1OctetString(d.toString());
345        }
346    
347    
348        // Construct the LDIF record to be written.
349        final LDIFRecord schemaRecord;
350        if (modifyFormatArg.isPresent())
351        {
352          schemaRecord = new LDIFModifyChangeRecord("cn=schema",
353               new Modification(ModificationType.ADD, "attributeTypes",
354                    attrTypeValues),
355               new Modification(ModificationType.ADD, "objectClasses", ocValues));
356        }
357        else
358        {
359          schemaRecord = new Entry("cn=schema",
360               new Attribute("objectClass", "top", "ldapSubentry", "subschema"),
361               new Attribute("cn", "schema"),
362               new Attribute("attributeTypes", attrTypeValues),
363               new Attribute("objectClasses", ocValues));
364        }
365    
366    
367        // Write the schema entry to the specified file.
368        final File outputFile = outputFileArg.getValue();
369        try
370        {
371          final LDIFWriter ldifWriter = new LDIFWriter(outputFile);
372          ldifWriter.writeLDIFRecord(schemaRecord);
373          ldifWriter.close();
374        }
375        catch (final Exception e)
376        {
377          debugException(e);
378          err(ERR_GEN_SCHEMA_CANNOT_WRITE_SCHEMA.get(outputFile.getAbsolutePath(),
379               getExceptionMessage(e)));
380          return ResultCode.LOCAL_ERROR;
381        }
382    
383    
384        return ResultCode.SUCCESS;
385      }
386    
387    
388    
389      /**
390       * {@inheritDoc}
391       */
392      @Override()
393      public LinkedHashMap<String[],String> getExampleUsages()
394      {
395        final LinkedHashMap<String[],String> examples =
396             new LinkedHashMap<String[],String>(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    }