/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. * */ package org.apache.directory.studio.openldap.config.model.io; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Set; import org.apache.directory.api.ldap.model.constants.SchemaConstants; import org.apache.directory.api.ldap.model.entry.Attribute; import org.apache.directory.api.ldap.model.entry.DefaultAttribute; import org.apache.directory.api.ldap.model.exception.LdapException; import org.apache.directory.api.ldap.model.exception.LdapInvalidDnException; import org.apache.directory.api.ldap.model.ldif.LdifEntry; import org.apache.directory.api.ldap.model.name.Dn; import org.apache.directory.api.ldap.model.name.Rdn; import org.apache.directory.api.ldap.model.schema.ObjectClass; import org.apache.directory.api.ldap.model.schema.SchemaManager; import org.apache.directory.studio.ldapbrowser.core.model.IBrowserConnection; import org.apache.directory.studio.openldap.config.OpenLdapConfigurationPlugin; import org.apache.directory.studio.openldap.config.editor.OpenLDAPServerConfigurationEditorUtils; import org.apache.directory.studio.openldap.config.model.AuxiliaryObjectClass; import org.apache.directory.studio.openldap.config.model.ConfigurationElement; import org.apache.directory.studio.openldap.config.model.OlcConfig; import org.apache.directory.studio.openldap.config.model.OlcOverlayConfig; import org.apache.directory.studio.openldap.config.model.OpenLdapConfiguration; import org.apache.directory.studio.openldap.config.model.database.OlcDatabaseConfig; /** * This class implements a configuration reader for OpenLDAP. * * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> */ public class ConfigurationWriter { /** The browserConnection */ private IBrowserConnection browserConnection; /** The configuration */ private OpenLdapConfiguration configuration; /** The list of entries */ private List<LdifEntry> entries; /** * Creates a new instance of ConfigWriter. * * @param browserConnection * the browser connection * @param configuration * the configuration */ public ConfigurationWriter( IBrowserConnection browserConnection, OpenLdapConfiguration configuration ) { this.browserConnection = browserConnection; this.configuration = configuration; } /** * Creates a new instance of ConfigWriter. * * @param configuration * the configuration */ public ConfigurationWriter( OpenLdapConfiguration configuration ) { this.configuration = configuration; } /** * Converts the configuration bean to a list of LDIF entries. */ private void convertConfigurationBeanToLdifEntries( Dn configurationDn ) throws ConfigurationException { try { if ( entries == null ) { entries = new ArrayList<LdifEntry>(); // Adding the global configuration addConfigurationBean( configuration.getGlobal(), Dn.EMPTY_DN ); // Adding databases for ( OlcDatabaseConfig database : configuration.getDatabases() ) { LdifEntry entry = addConfigurationBean( database, configurationDn ); if ( entry != null ) { for ( OlcOverlayConfig overlay : database.getOverlays() ) { addConfigurationBean( overlay, entry.getDn() ); } } } // Adding other elements for ( OlcConfig configurationBean : configuration.getConfigurationElements() ) { addConfigurationBean( configurationBean, configurationDn ); } } } catch ( Exception e ) { throw new ConfigurationException( "Unable to convert the configuration beans to LDIF entries", e ); } } private LdifEntry addConfigurationBean( OlcConfig configurationBean, Dn parentDn ) throws Exception { if ( configurationBean != null ) { // Getting the class of the bean Class<?> beanClass = configurationBean.getClass(); // Creating the entry to hold the bean and adding it to the list LdifEntry entry = new LdifEntry(); entry.setDn( getDn( configurationBean, parentDn ) ); addObjectClassAttribute( entry, getObjectClassNameForBean( beanClass ) ); entries.add( entry ); // Checking auxiliary object classes List<AuxiliaryObjectClass> auxiliaryObjectClassesList = configurationBean .getAuxiliaryObjectClasses(); if ( ( auxiliaryObjectClassesList != null ) && ( auxiliaryObjectClassesList.size() > 0 ) ) { for ( AuxiliaryObjectClass auxiliaryObjectClass : auxiliaryObjectClassesList ) { // Getting the bean class for the auxiliary object class Class<?> auxiliaryObjectClassBeanClass = auxiliaryObjectClass.getClass(); // Updating the objectClass attribute value addAttributeTypeValue( SchemaConstants.OBJECT_CLASS_AT, getObjectClassNameForBean( auxiliaryObjectClassBeanClass ), entry ); // Adding fields of the auxiliary object class to the entry addFieldsToBean( auxiliaryObjectClass, auxiliaryObjectClassBeanClass, entry ); } } // A flag to know when we reached the 'OlcConfig' class when // looping on the class hierarchy of the bean boolean olcConfigBeanClassFound = false; // Looping until the 'OlcConfig' class has been found while ( !olcConfigBeanClassFound ) { // Checking if we reached the 'OlcConfig' class if ( beanClass == OlcConfig.class ) { olcConfigBeanClassFound = true; } // Adding fields of the bean to the entry addFieldsToBean( configurationBean, beanClass, entry ); // Moving to the upper class in the class hierarchy beanClass = beanClass.getSuperclass(); } return entry; } return null; } private void addFieldsToBean( Object configurationBean, Class<?> beanClass, LdifEntry entry ) throws Exception { if ( ( configurationBean != null ) && ( beanClass != null ) && ( entry != null ) ) { // Looping on all fields of the bean Field[] fields = beanClass.getDeclaredFields(); for ( Field field : fields ) { // Making the field accessible (we get an exception if we don't do that) field.setAccessible( true ); // Getting the class of the field Class<?> fieldClass = field.getType(); Object fieldValue = field.get( configurationBean ); if ( fieldValue != null ) { // Looking for the @ConfigurationElement annotation ConfigurationElement configurationElement = field.getAnnotation( ConfigurationElement.class ); if ( configurationElement != null ) { // Checking if we have a value for the attribute type String attributeType = configurationElement.attributeType(); if ( ( attributeType != null ) && ( !"".equals( attributeType ) ) ) { // Checking if the field is optional and if the default value matches if ( configurationElement.isOptional() ) { if ( configurationElement.defaultValue().equalsIgnoreCase( fieldValue.toString() ) ) { // Skipping the addition of the value continue; } } // Adding values to the entry addAttributeTypeValues( configurationElement.attributeType(), fieldValue, entry ); continue; } // Checking if we're dealing with a AdsBaseBean subclass type if ( OlcConfig.class.isAssignableFrom( fieldClass ) ) { addConfigurationBean( ( OlcConfig ) fieldValue, entry.getDn() ); continue; } } } } } } /** * Gets the Dn associated with the configuration bean. * * @param bean * the configuration bean * @param parentDn * the parent dn * @return * the Dn associated with the configuration bean based on the given base Dn. * @throws LdapInvalidDnException * @throws IllegalArgumentException * @throws IllegalAccessException */ private Dn getDn( OlcConfig bean, Dn parentDn ) throws LdapInvalidDnException, IllegalArgumentException, IllegalAccessException { // Getting the class of the bean Class<?> beanClass = bean.getClass(); // A flag to know when we reached the 'AdsBaseBean' class when // looping on the class hierarchy of the bean boolean olcConfigBeanClassFound = false; // Looping until the 'OlcConfig' class has been found while ( !olcConfigBeanClassFound ) { // Checking if we reached the 'OlcConfig' class if ( beanClass == OlcConfig.class ) { olcConfigBeanClassFound = true; } // Looping on all fields of the bean Field[] fields = beanClass.getDeclaredFields(); for ( Field field : fields ) { // Making the field accessible (we get an exception if we don't do that) field.setAccessible( true ); // Looking for the @ConfigurationElement annotation and // if the field is the Rdn ConfigurationElement configurationElement = field.getAnnotation( ConfigurationElement.class ); if ( ( configurationElement != null ) && ( configurationElement.isRdn() ) ) { Object value = field.get( bean ); if ( value == null ) { continue; } // Is the value multiple? if ( isMultiple( value.getClass() ) ) { Collection<?> values = ( Collection<?> ) value; if ( values.size() == 0 ) { continue; } value = values.toArray()[0]; } if ( ( bean.getParentDn() != null ) ) { return bean.getParentDn() .add( new Rdn( configurationElement.attributeType(), value.toString() ) ); } else { return parentDn.add( new Rdn( configurationElement.attributeType(), value.toString() ) ); } } } // Moving to the upper class in the class hierarchy beanClass = beanClass.getSuperclass(); } return Dn.EMPTY_DN; } /** * Gets the name of the object class to use for the given bean class. * * @param c * the bean class * @return * the name of the object class to use for the given bean class */ private String getObjectClassNameForBean( Class<?> c ) { String classNameWithPackage = getClassNameWithoutPackageName( c ); return Character.toLowerCase( classNameWithPackage.charAt( 0 ) ) + classNameWithPackage.substring( 1 ); } /** * Gets the class name of the given class stripped from its package name. * * @param c * the class * @return * the class name of the given class stripped from its package name */ private String getClassNameWithoutPackageName( Class<?> c ) { String className = c.getName(); int firstChar = className.lastIndexOf( '.' ) + 1; if ( firstChar > 0 ) { return className.substring( firstChar ); } return className; } /** * Writes the configuration bean as LDIF to the given file. * * @param path * the output file path * @throws ConfigurationException * if an error occurs during the conversion to LDIF * @throws IOException * if an error occurs when writing the file */ public void writeToPath( String path ) throws ConfigurationException, IOException { writeToFile( new File( path ) ); } /** * Writes the configuration bean as LDIF to the given file. * * @param file * the output file * @throws ConfigurationException * if an error occurs during the conversion to LDIF * @throws IOException * if an error occurs when writing the file */ public void writeToFile( File file ) throws ConfigurationException, IOException { // Writing the file to disk FileWriter writer = new FileWriter( file ); writer.append( writeToString() ); writer.close(); } /** * Writes the configuration to a String object. * * @return * a String containing the LDIF * representation of the configuration * @throws ConfigurationException * if an error occurs during the conversion to LDIF */ public String writeToString() throws ConfigurationException { // Converting the configuration bean to a list of LDIF entries convertConfigurationBeanToLdifEntries( ConfigurationUtils.getConfigurationDn( browserConnection ) ); // Building the StringBuilder StringBuilder sb = new StringBuilder(); sb.append( "version: 1\n" ); for ( LdifEntry entry : entries ) { sb.append( entry.toString() ); } return sb.toString(); } /** * Gets the converted LDIF entries from the configuration bean. * * @param browserConnection the browserConnection * @return * the list of converted LDIF entries * @throws ConfigurationException * if an error occurs during the conversion to LDIF */ /** * TODO getConvertedLdifEntries. * * @return * @throws ConfigurationException */ public List<LdifEntry> getConvertedLdifEntries() throws ConfigurationException { // Converting the configuration bean to a list of LDIF entries convertConfigurationBeanToLdifEntries( ConfigurationUtils.getConfigurationDn( browserConnection ) ); // Returning the list of entries return entries; } /** * Gets the converted LDIF entries from the configuration bean. * * @return * the list of converted LDIF entries * @throws ConfigurationException * if an error occurs during the conversion to LDIF */ public List<LdifEntry> getConvertedLdifEntries( Dn configurationDn ) throws ConfigurationException { // Converting the configuration bean to a list of LDIF entries convertConfigurationBeanToLdifEntries( configurationDn ); // Returning the list of entries return entries; } /** * Adds the computed 'objectClass' attribute for the given entry and object class name. * * @param entry * the entry * @param objectClass * the object class name * @throws LdapException */ private void addObjectClassAttribute( LdifEntry entry, String objectClass ) throws LdapException { try { ObjectClass objectClassObject = OpenLDAPServerConfigurationEditorUtils.getObjectClass( OpenLdapConfigurationPlugin .getDefault().getSchemaManager(), objectClass ); if ( objectClassObject != null ) { // Building the list of 'objectClass' attribute values Set<String> objectClassAttributeValues = new HashSet<String>(); computeObjectClassAttributeValues( objectClassAttributeValues, objectClassObject ); // Adding values to the entry addAttributeTypeValues( SchemaConstants.OBJECT_CLASS_AT, objectClassAttributeValues, entry ); } else { // TODO: throw an exception } } catch ( Exception e ) { throw new LdapException( e ); } } /** * Recursively computes the 'objectClass' attribute values set. * * @param schemaManager * the schema manager * @param objectClassAttributeValues * the set containing the values * @param objectClass * the current object class * @throws LdapException */ private void computeObjectClassAttributeValues( Set<String> objectClassAttributeValues, ObjectClass objectClass ) throws LdapException { try { SchemaManager schemaManager = OpenLdapConfigurationPlugin.getDefault().getSchemaManager(); ObjectClass topObjectClass = OpenLDAPServerConfigurationEditorUtils.getObjectClass( schemaManager, SchemaConstants.TOP_OC ); if ( topObjectClass != null ) { // TODO throw new exception (there should be a top object class } if ( topObjectClass.equals( objectClass ) ) { objectClassAttributeValues.add( objectClass.getName() ); } else { objectClassAttributeValues.add( objectClass.getName() ); List<String> superiors = objectClass.getSuperiorOids(); if ( ( superiors != null ) && ( superiors.size() > 0 ) ) { for ( String superior : superiors ) { ObjectClass superiorObjectClass = OpenLDAPServerConfigurationEditorUtils.getObjectClass( schemaManager, superior ); computeObjectClassAttributeValues( objectClassAttributeValues, superiorObjectClass ); } } else { objectClassAttributeValues.add( topObjectClass.getName() ); } } } catch ( Exception e ) { throw new LdapException( e ); } } /** * Adds values for an attribute type to the given entry. * * @param attributeType * the attribute type * @param value * the value * @param entry * the entry * @throws org.apache.directory.api.ldap.model.exception.LdapException */ private void addAttributeTypeValues( String attributeType, Object o, LdifEntry entry ) throws LdapException { // We don't store a 'null' value if ( o != null ) { // Is the value multiple? if ( isMultiple( o.getClass() ) ) { // Adding each single value separately Collection<?> values = ( Collection<?> ) o; if ( values != null ) { for ( Object value : values ) { addAttributeTypeValue( attributeType, value, entry ); } } } else { // Adding the single value addAttributeTypeValue( attributeType, o, entry ); } } } /** * Adds a value, either byte[] or another type (converted into a String * via the Object.toString() method), to the attribute. * * @param attributeType * the attribute type * @param value * the value * @param entry * the entry */ private void addAttributeTypeValue( String attributeType, Object value, LdifEntry entry ) throws LdapException { // We don't store a 'null' value if ( value != null ) { // Getting the attribute from the entry Attribute attribute = entry.get( attributeType ); // If no attribute has been found, we need to create it and add it to the entry if ( attribute == null ) { attribute = new DefaultAttribute( attributeType ); entry.addAttribute( attribute ); } // Storing the value to the attribute if ( value instanceof byte[] ) { // Value is a byte[] attribute.add( ( byte[] ) value ); } // Storing the boolean value in UPPERCASE (TRUE or FALSE) to the attribute else if ( value instanceof Boolean ) { // Value is a byte[] attribute.add( value.toString().toUpperCase() ); } else { // Value is another type of object that we store as a String // (There will be an automatic translation for primary types like int, long, etc.) attribute.add( value.toString() ); } } } /** * Indicates the given type is multiple. * * @param clazz * the class * @return * <code>true</code> if the given is multiple, * <code>false</code> if not. */ private boolean isMultiple( Class<?> clazz ) { return Collection.class.isAssignableFrom( clazz ); } }