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 }