001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 * 019 */ 020package org.apache.directory.server.config; 021 022 023import java.io.File; 024import java.io.FileWriter; 025import java.io.IOException; 026import java.lang.reflect.Field; 027import java.util.ArrayList; 028import java.util.Collection; 029import java.util.HashSet; 030import java.util.List; 031import java.util.Set; 032 033import org.apache.directory.api.ldap.model.constants.SchemaConstants; 034import org.apache.directory.api.ldap.model.entry.Attribute; 035import org.apache.directory.api.ldap.model.entry.DefaultAttribute; 036import org.apache.directory.api.ldap.model.exception.LdapException; 037import org.apache.directory.api.ldap.model.exception.LdapInvalidAttributeValueException; 038import org.apache.directory.api.ldap.model.exception.LdapInvalidDnException; 039import org.apache.directory.api.ldap.model.ldif.LdifEntry; 040import org.apache.directory.api.ldap.model.name.Dn; 041import org.apache.directory.api.ldap.model.name.Rdn; 042import org.apache.directory.api.ldap.model.schema.ObjectClass; 043import org.apache.directory.api.ldap.model.schema.SchemaManager; 044import org.apache.directory.api.util.Strings; 045import org.apache.directory.server.config.beans.AdsBaseBean; 046import org.apache.directory.server.config.beans.ConfigBean; 047 048 049/** 050 * This class implements a writer for ApacheDS Configuration. 051 * <p> 052 * It can be used either: 053 * <ul> 054 * <li>write the configuration to an LDIF</li> 055 * <li>get the list of LDIF entries from the configuration</li> 056 * </ul> 057 * 058 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> 059 */ 060public class ConfigWriter 061{ 062 /** The schema manager */ 063 private SchemaManager schemaManager; 064 065 /** The configuration bean */ 066 private ConfigBean configBean; 067 068 /** The list of entries */ 069 private List<LdifEntry> entries; 070 071 072 /** 073 * Creates a new instance of ConfigWriter. 074 * 075 * @param schemaManager 076 * the schema manager 077 * @param configBean 078 * the configuration bean 079 */ 080 public ConfigWriter( SchemaManager schemaManager, ConfigBean configBean ) 081 { 082 this.schemaManager = schemaManager; 083 this.configBean = configBean; 084 } 085 086 087 /** 088 * Converts the configuration bean to a list of LDIF entries. 089 */ 090 private void convertConfigurationBeanToLdifEntries() throws ConfigurationException 091 { 092 try 093 { 094 if ( entries == null ) 095 { 096 entries = new ArrayList<LdifEntry>(); 097 098 // Building the default config root entry 'ou=config' 099 LdifEntry configRootEntry = new LdifEntry(); 100 configRootEntry.setDn( new Dn( SchemaConstants.OU_AT + "=" + "config" ) ); 101 addObjectClassAttribute( schemaManager, configRootEntry, "organizationalUnit" ); 102 addAttributeTypeValues( SchemaConstants.OU_AT, "config", configRootEntry ); 103 entries.add( configRootEntry ); 104 105 // Building entries from the directory service beans 106 List<AdsBaseBean> directoryServiceBeans = configBean.getDirectoryServiceBeans(); 107 for ( AdsBaseBean adsBaseBean : directoryServiceBeans ) 108 { 109 addBean( configRootEntry.getDn(), schemaManager, adsBaseBean, entries ); 110 } 111 } 112 } 113 catch ( Exception e ) 114 { 115 throw new ConfigurationException( "Unable to convert the configuration bean to LDIF entries", e ); 116 } 117 } 118 119 120 /** 121 * Writes the configuration bean as LDIF to the given file. 122 * 123 * @param path 124 * the output file path 125 * @throws ConfigurationException 126 * if an error occurs during the conversion to LDIF 127 * @throws IOException 128 * if an error occurs when writing the file 129 */ 130 public void writeToPath( String path ) throws ConfigurationException, IOException 131 { 132 writeToFile( new File( path ) ); 133 } 134 135 136 /** 137 * Writes the configuration bean as LDIF to the given file. 138 * 139 * @param file 140 * the output file 141 * @throws ConfigurationException 142 * if an error occurs during the conversion to LDIF 143 * @throws IOException 144 * if an error occurs when writing the file 145 */ 146 public void writeToFile( File file ) throws ConfigurationException, IOException 147 { 148 // Writing the file to disk 149 FileWriter writer = new FileWriter( file ); 150 writer.append( writeToString() ); 151 writer.close(); 152 } 153 154 155 /** 156 * Writes the configuration to a String object. 157 * 158 * @return 159 * a String containing the LDIF 160 * representation of the configuration 161 * @throws ConfigurationException 162 * if an error occurs during the conversion to LDIF 163 */ 164 public String writeToString() throws ConfigurationException 165 { 166 // Converting the configuration bean to a list of LDIF entries 167 convertConfigurationBeanToLdifEntries(); 168 169 // Building the StringBuilder 170 StringBuilder sb = new StringBuilder(); 171 sb.append( "version: 1\n" ); 172 for ( LdifEntry entry : entries ) 173 { 174 sb.append( entry.toString() ); 175 } 176 177 return sb.toString(); 178 } 179 180 181 /** 182 * Gets the converted LDIF entries from the configuration bean. 183 * 184 * @return 185 * the list of converted LDIF entries 186 * @throws ConfigurationException 187 * if an error occurs during the conversion to LDIF 188 */ 189 public List<LdifEntry> getConvertedLdifEntries() throws ConfigurationException 190 { 191 // Converting the configuration bean to a list of LDIF entries 192 convertConfigurationBeanToLdifEntries(); 193 194 // Returning the list of entries 195 return entries; 196 } 197 198 199 /** 200 * Adds the computed 'objectClass' attribute for the given entry and object class name. 201 * 202 * @param schemaManager 203 * the schema manager 204 * @param entry 205 * the entry 206 * @param objectClass 207 * the object class name 208 * @throws LdapException 209 */ 210 private void addObjectClassAttribute( SchemaManager schemaManager, LdifEntry entry, String objectClass ) 211 throws LdapException 212 { 213 ObjectClass objectClassObject = schemaManager.lookupObjectClassRegistry( objectClass ); 214 if ( objectClassObject != null ) 215 { 216 // Building the list of 'objectClass' attribute values 217 Set<String> objectClassAttributeValues = new HashSet<String>(); 218 computeObjectClassAttributeValues( schemaManager, objectClassAttributeValues, objectClassObject ); 219 220 // Adding values to the entry 221 addAttributeTypeValues( SchemaConstants.OBJECT_CLASS_AT, objectClassAttributeValues, entry ); 222 } 223 else 224 { 225 throw new IllegalStateException( "Missing object class " + objectClass ); 226 } 227 } 228 229 230 /** 231 * Recursively computes the 'objectClass' attribute values set. 232 * 233 * @param schemaManager 234 * the schema manager 235 * @param objectClassAttributeValues 236 * the set containing the values 237 * @param objectClass 238 * the current object class 239 * @throws LdapException 240 */ 241 private void computeObjectClassAttributeValues( SchemaManager schemaManager, 242 Set<String> objectClassAttributeValues, 243 ObjectClass objectClass ) throws LdapException 244 { 245 ObjectClass topObjectClass = schemaManager.lookupObjectClassRegistry( SchemaConstants.TOP_OC ); 246 if ( topObjectClass == null ) 247 { 248 throw new IllegalStateException( "Missing top object class." ); 249 } 250 251 if ( topObjectClass.equals( objectClass ) ) 252 { 253 objectClassAttributeValues.add( objectClass.getName() ); 254 } 255 else 256 { 257 objectClassAttributeValues.add( objectClass.getName() ); 258 259 List<ObjectClass> superiors = objectClass.getSuperiors(); 260 if ( ( superiors != null ) && ( superiors.size() > 0 ) ) 261 { 262 for ( ObjectClass superior : superiors ) 263 { 264 computeObjectClassAttributeValues( schemaManager, objectClassAttributeValues, superior ); 265 } 266 } 267 else 268 { 269 objectClassAttributeValues.add( topObjectClass.getName() ); 270 } 271 } 272 } 273 274 275 /** 276 * Adds a configuration bean to the list of entries. 277 * 278 * @param rootDn 279 * the current root Dn 280 * @param schemaManager 281 * the schema manager 282 * @param bean 283 * the configuration bean 284 * @param entries 285 * the list of the entries 286 * @throws Exception 287 */ 288 private void addBean( Dn rootDn, SchemaManager schemaManager, AdsBaseBean bean, List<LdifEntry> entries ) 289 throws Exception 290 { 291 addBean( rootDn, schemaManager, bean, entries, null, null ); 292 } 293 294 295 /** 296 * Adds a configuration bean to the list of entries. 297 * 298 * @param rootDn 299 * the current root Dn 300 * @param schemaManager 301 * the schema manager 302 * @param bean 303 * the configuration bean 304 * @param entries 305 * the list of the entries 306 * @param parentEntry 307 * the parent entry 308 * @param attributeTypeForParentEntry 309 * the attribute type to use when adding the value of 310 * the Rdn to the parent entry 311 * @throws Exception 312 */ 313 private void addBean( Dn rootDn, SchemaManager schemaManager, AdsBaseBean bean, List<LdifEntry> entries, 314 LdifEntry parentEntry, String attributeTypeForParentEntry ) 315 throws Exception 316 { 317 if ( bean != null ) 318 { 319 // Getting the class of the bean 320 Class<?> beanClass = bean.getClass(); 321 322 // Creating the entry to hold the bean and adding it to the list 323 LdifEntry entry = new LdifEntry(); 324 entry.setDn( getDn( rootDn, bean ) ); 325 addObjectClassAttribute( schemaManager, entry, getObjectClassNameForBean( beanClass ) ); 326 entries.add( entry ); 327 328 // A flag to know when we reached the 'AdsBaseBean' class when 329 // looping on the class hierarchy of the bean 330 boolean adsBaseBeanClassFound = false; 331 332 // Looping until the 'AdsBaseBean' class has been found 333 while ( !adsBaseBeanClassFound ) 334 { 335 // Checking if we reached the 'AdsBaseBean' class 336 if ( beanClass == AdsBaseBean.class ) 337 { 338 adsBaseBeanClassFound = true; 339 } 340 341 // Looping on all fields of the bean 342 Field[] fields = beanClass.getDeclaredFields(); 343 for ( Field field : fields ) 344 { 345 // Making the field accessible (we get an exception if we don't do that) 346 field.setAccessible( true ); 347 348 // Getting the class of the field 349 Class<?> fieldClass = field.getType(); 350 Object fieldValue = field.get( bean ); 351 352 // Looking for the @ConfigurationElement annotation 353 ConfigurationElement configurationElement = field.getAnnotation( ConfigurationElement.class ); 354 if ( configurationElement != null ) 355 { 356 // Getting the annotation's values 357 String attributeType = configurationElement.attributeType(); 358 String objectClass = configurationElement.objectClass(); 359 String container = configurationElement.container(); 360 boolean isOptional = configurationElement.isOptional(); 361 String defaultValue = configurationElement.defaultValue(); 362 363 // Checking if we have a value for the attribute type 364 if ( ( attributeType != null ) && ( !"".equals( attributeType ) ) ) 365 { 366 // Checking if the field is optional and if the default value matches 367 if ( isOptional ) 368 { 369 if ( ( defaultValue != null ) && ( fieldValue != null ) 370 && ( defaultValue.equalsIgnoreCase( fieldValue.toString() ) ) ) 371 { 372 // Skipping the addition of the value 373 continue; 374 } 375 } 376 377 // Adding values to the entry 378 addAttributeTypeValues( configurationElement.attributeType(), fieldValue, entry ); 379 380 continue; 381 } 382 // Checking if we have a value for the object class 383 else if ( ( objectClass != null ) && ( !"".equals( objectClass ) ) ) 384 { 385 // Checking if we're dealing with a container 386 if ( ( container != null ) && ( !"".equals( container ) ) ) 387 { 388 // Creating the entry for the container and adding it to the list 389 LdifEntry containerEntry = new LdifEntry(); 390 containerEntry.setDn( entry.getDn().add( new Rdn( SchemaConstants.OU_AT, container ) ) ); 391 addObjectClassAttribute( schemaManager, containerEntry, 392 SchemaConstants.ORGANIZATIONAL_UNIT_OC ); 393 addAttributeTypeValues( SchemaConstants.OU_AT, container, containerEntry ); 394 entries.add( containerEntry ); 395 396 if ( Collection.class.isAssignableFrom( fieldClass ) ) 397 { 398 // Looping on the Collection's objects 399 @SuppressWarnings("unchecked") 400 Collection<Object> collection = ( Collection<Object> ) fieldValue; 401 if ( collection != null ) 402 { 403 for ( Object object : collection ) 404 { 405 if ( object instanceof AdsBaseBean ) 406 { 407 // Adding the bean 408 addBean( containerEntry.getDn(), schemaManager, ( AdsBaseBean ) object, 409 entries, entry, attributeType ); 410 411 continue; 412 } 413 else 414 { 415 // TODO throw an error, if we have a container, the type must be a subtype of AdsBaseBean 416 throw new Exception(); 417 } 418 } 419 } 420 } 421 else 422 { 423 // TODO throw an error, if we have a container, the type must be a subtype of Collection 424 throw new Exception(); 425 } 426 } 427 else 428 { 429 // Adding the bean 430 addBean( entry.getDn(), schemaManager, ( AdsBaseBean ) fieldValue, entries, entry, 431 attributeType ); 432 } 433 } 434 } 435 } 436 437 // Moving to the upper class in the class hierarchy 438 beanClass = beanClass.getSuperclass(); 439 } 440 } 441 } 442 443 444 /** 445 * Gets the name of the object class to use for the given bean class. 446 * 447 * @param c 448 * the bean class 449 * @return 450 * the name of the object class to use for the given bean class 451 */ 452 private String getObjectClassNameForBean( Class<?> c ) 453 { 454 String classNameWithPackage = getClassNameWithoutPackageName( c ); 455 return "ads-" + classNameWithPackage.substring( 0, classNameWithPackage.length() - 4 ); 456 } 457 458 459 /** 460 * Gets the class name of the given class stripped from its package name. 461 * 462 * @param c 463 * the class 464 * @return 465 * the class name of the given class stripped from its package name 466 */ 467 private String getClassNameWithoutPackageName( Class<?> c ) 468 { 469 String className = c.getName(); 470 471 int firstChar = className.lastIndexOf( '.' ) + 1; 472 if ( firstChar > 0 ) 473 { 474 return className.substring( firstChar ); 475 } 476 477 return className; 478 } 479 480 481 /** 482 * Indicates the given type is multiple. 483 * 484 * @param clazz 485 * the class 486 * @return 487 * <code>true</code> if the given is multiple, 488 * <code>false</code> if not. 489 */ 490 private boolean isMultiple( Class<?> clazz ) 491 { 492 return Collection.class.isAssignableFrom( clazz ); 493 } 494 495 496 /** 497 * Gets the Dn associated with the configuration bean based on the given base Dn. 498 * 499 * @param baseDn 500 * the base Dn 501 * @param bean 502 * the configuration bean 503 * @return 504 * the Dn associated with the configuration bean based on the given base Dn. 505 * @throws LdapInvalidDnException 506 * @throws IllegalArgumentException 507 * @throws IllegalAccessException 508 * @throws LdapInvalidAttributeValueException 509 */ 510 private Dn getDn( Dn baseDn, AdsBaseBean bean ) throws LdapInvalidDnException, IllegalArgumentException, 511 IllegalAccessException, LdapInvalidAttributeValueException 512 { 513 // Getting the class of the bean 514 Class<?> beanClass = bean.getClass(); 515 516 // A flag to know when we reached the 'AdsBaseBean' class when 517 // looping on the class hierarchy of the bean 518 boolean adsBaseBeanClassFound = false; 519 520 // Looping until the 'AdsBaseBean' class has been found 521 while ( !adsBaseBeanClassFound ) 522 { 523 // Checking if we reached the 'AdsBaseBean' class 524 if ( beanClass == AdsBaseBean.class ) 525 { 526 adsBaseBeanClassFound = true; 527 } 528 529 // Looping on all fields of the bean 530 Field[] fields = beanClass.getDeclaredFields(); 531 for ( Field field : fields ) 532 { 533 // Making the field accessible (we get an exception if we don't do that) 534 field.setAccessible( true ); 535 536 // Looking for the @ConfigurationElement annotation and 537 // if the field is the Rdn 538 ConfigurationElement configurationElement = field.getAnnotation( ConfigurationElement.class ); 539 if ( ( configurationElement != null ) && ( configurationElement.isRdn() ) ) 540 { 541 return baseDn.add( new Rdn( configurationElement.attributeType(), field.get( bean ).toString() ) ); 542 } 543 } 544 545 // Moving to the upper class in the class hierarchy 546 beanClass = beanClass.getSuperclass(); 547 } 548 549 return Dn.EMPTY_DN; // TODO Throw an error when we reach that point 550 } 551 552 553 /** 554 * Adds values for an attribute type to the given entry. 555 * 556 * @param attributeType 557 * the attribute type 558 * @param value 559 * the value 560 * @param entry 561 * the entry 562 * @throws org.apache.directory.api.ldap.model.exception.LdapException 563 */ 564 private void addAttributeTypeValues( String attributeType, Object o, LdifEntry entry ) 565 throws LdapException 566 { 567 // We don't store a 'null' value 568 if ( o != null ) 569 { 570 // Is the value multiple? 571 if ( isMultiple( o.getClass() ) ) 572 { 573 // Adding each single value separately 574 Collection<?> values = ( Collection<?> ) o; 575 if ( values != null ) 576 { 577 for ( Object value : values ) 578 { 579 addAttributeTypeValue( attributeType, value, entry ); 580 } 581 } 582 } 583 else 584 { 585 // Adding the single value 586 addAttributeTypeValue( attributeType, o, entry ); 587 } 588 } 589 } 590 591 592 /** 593 * Adds a value, either byte[] or another type (converted into a String 594 * via the Object.toString() method), to the attribute. 595 * 596 * @param attributeType 597 * the attribute type 598 * @param value 599 * the value 600 * @param entry 601 * the entry 602 */ 603 private void addAttributeTypeValue( String attributeType, Object value, LdifEntry entry ) throws LdapException 604 { 605 // We don't store a 'null' value 606 if ( value != null ) 607 { 608 // Getting the attribute from the entry 609 Attribute attribute = entry.get( attributeType ); 610 611 // If no attribute has been found, we need to create it and add it to the entry 612 if ( attribute == null ) 613 { 614 attribute = new DefaultAttribute( attributeType ); 615 entry.addAttribute( attribute ); 616 } 617 618 // Storing the value to the attribute 619 if ( value instanceof byte[] ) 620 { 621 // Value is a byte[] 622 attribute.add( ( byte[] ) value ); 623 } 624 // Storing the boolean value in UPPERCASE (TRUE or FALSE) to the attribute 625 else if ( value instanceof Boolean ) 626 { 627 // Value is a byte[] 628 attribute.add( Strings.toUpperCase( value.toString() ) ); 629 } 630 else 631 { 632 // Value is another type of object that we store as a String 633 // (There will be an automatic translation for primary types like int, long, etc.) 634 attribute.add( value.toString() ); 635 } 636 } 637 } 638}