001/* 002 * Copyright 2009-2017 UnboundID Corp. 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2009-2017 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 */ 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.Mutable; 045import com.unboundid.util.ThreadSafety; 046import com.unboundid.util.ThreadSafetyLevel; 047import com.unboundid.util.args.ArgumentException; 048import com.unboundid.util.args.ArgumentParser; 049import com.unboundid.util.args.BooleanArgument; 050import com.unboundid.util.args.FileArgument; 051import com.unboundid.util.args.StringArgument; 052 053import static com.unboundid.ldap.sdk.persist.PersistMessages.*; 054import static com.unboundid.util.Debug.*; 055import 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) 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"); 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}