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