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}