| opends/resource/config/config.ldif | ●●●●● patch | view | raw | blame | history | |
| opends/resource/schema/02-config.ldif | ●●●●● patch | view | raw | blame | history | |
| opends/src/admin/defn/org/opends/server/admin/std/ReferentialIntegrityPluginConfiguration.xml | ●●●●● patch | view | raw | blame | history | |
| opends/src/messages/messages/plugin.properties | ●●●●● patch | view | raw | blame | history | |
| opends/src/server/org/opends/server/plugins/ReferentialIntegrityPlugin.java | ●●●●● patch | view | raw | blame | history | |
| opends/tests/unit-tests-testng/resource/config-changes.ldif | ●●●●● patch | view | raw | blame | history | |
| opends/tests/unit-tests-testng/src/server/org/opends/server/core/TestModifyDNOperation.java | ●●●●● patch | view | raw | blame | history | |
| opends/tests/unit-tests-testng/src/server/org/opends/server/plugins/ReferentialIntegrityPluginTestCase.java | ●●●●● patch | view | raw | blame | history |
opends/resource/config/config.ldif
@@ -1480,6 +1480,19 @@ ds-cfg-plugin-type: preOperationModifyDN ds-cfg-unique-attribute-type: uid dn: cn=Referential Integrity,cn=Plugins,cn=config objectClass: top objectClass: ds-cfg-plugin objectClass: ds-cfg-referential-integrity-plugin cn: Referential Integrity ds-cfg-plugin-class: org.opends.server.plugins.ReferentialIntegrityPlugin ds-cfg-plugin-enabled: false ds-cfg-plugin-type: postOperationDelete ds-cfg-plugin-type: postOperationModifyDN ds-cfg-plugin-type: subordinateModifyDN ds-cfg-referential-integrity-attribute-type: member ds-cfg-referential-integrity-attribute-type: uniqueMember dn: cn=Root DNs,cn=config objectClass: top objectClass: ds-cfg-root-dn-base opends/resource/schema/02-config.ldif
@@ -1626,6 +1626,22 @@ NAME 'ds-cfg-save-config-on-successful-startup' SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE X-ORIGIN 'OpenDS Directory Server' ) attributeTypes: ( 1.3.6.1.4.1.26027.1.1.474 NAME 'ds-cfg-referential-integrity-log-file' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'OpenDS Directory Server' ) attributeTypes: ( 1.3.6.1.4.1.26027.1.1.475 NAME 'ds-cfg-referential-integrity-update-interval' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'OpenDS Directory Server' ) attributeTypes: ( 1.3.6.1.4.1.26027.1.1.472 NAME 'ds-cfg-referential-integrity-attribute-type' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'OpenDS Directory Server' ) attributeTypes: ( 1.3.6.1.4.1.26027.1.1.473 NAME 'ds-cfg-referential-integrity-base-dn' SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 X-ORIGIN 'OpenDS Directory Server' ) objectClasses: ( 1.3.6.1.4.1.26027.1.2.1 NAME 'ds-cfg-access-control-handler' SUP top STRUCTURAL MUST ( cn $ ds-cfg-acl-handler-class $ ds-cfg-acl-handler-enabled ) @@ -2431,4 +2447,11 @@ objectClasses: ( 1.3.6.1.4.1.26027.1.2.162 NAME 'ds-cfg-subject-equals-dn-certificate-mapper' SUP ds-cfg-certificate-mapper STRUCTURAL X-ORIGIN 'OpenDS Directory Server' ) objectClasses: ( 1.3.6.1.4.1.26027.1.2.122 NAME 'ds-cfg-referential-integrity-plugin' SUP ds-cfg-plugin STRUCTURAL MUST ds-cfg-referential-integrity-attribute-type MAY ( ds-cfg-referential-integrity-base-dn $ ds-cfg-referential-integrity-update-interval $ ds-cfg-referential-integrity-log-file ) X-ORIGIN 'OpenDS Directory Server' ) opends/src/admin/defn/org/opends/server/admin/std/ReferentialIntegrityPluginConfiguration.xml
New file @@ -0,0 +1,185 @@ <?xml version="1.0" encoding="UTF-8"?> <!-- ! CDDL HEADER START ! ! The contents of this file are subject to the terms of the ! Common Development and Distribution License, Version 1.0 only ! (the "License"). You may not use this file except in compliance ! with the License. ! ! You can obtain a copy of the license at ! trunk/opends/resource/legal-notices/OpenDS.LICENSE ! or https://OpenDS.dev.java.net/OpenDS.LICENSE. ! See the License for the specific language governing permissions ! and limitations under the License. ! ! When distributing Covered Code, include this CDDL HEADER in each ! file and include the License file at ! trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable, ! add the following below this CDDL HEADER, with the fields enclosed ! by brackets "[]" replaced with your own identifying information: ! Portions Copyright [yyyy] [name of copyright owner] ! ! CDDL HEADER END ! ! ! Portions Copyright 2007 Sun Microsystems, Inc. ! --> <adm:managed-object name="referential-integrity-plugin" plural-name="referential-integrity-plugins" package="org.opends.server.admin.std" extends="plugin" xmlns:adm="http://www.opends.org/admin" xmlns:ldap="http://www.opends.org/admin-ldap"> <adm:synopsis> The <adm:user-friendly-name /> is used to enforce referential integrity on DN attribute types specified in the plugin configuration. The values of these attribute types, may reference entries that have been deleted by a delete operation or renamed by a modify DN operation. The plugin will either remove stale references to deleted entries or remove an old references and add a new references to a renamed entries. The plugin allows the scope of this referential check to be limited to a set of base DNs if desired. It also can be configured to perform the referential checking in background mode at specified intervals. </adm:synopsis> <adm:profile name="ldap"> <ldap:object-class> <ldap:oid>1.3.6.1.4.1.26027.1.2.122</ldap:oid> <ldap:name>ds-cfg-referential-integrity-plugin</ldap:name> <ldap:superior>ds-cfg-plugin</ldap:superior> </ldap:object-class> </adm:profile> <adm:property-override name="plugin-class"> <adm:default-behavior> <adm:defined> <adm:value> org.opends.server.plugins.ReferentialIntegrityPlugin </adm:value> </adm:defined> </adm:default-behavior> </adm:property-override> <adm:property name="referential-integrity-attribute-type" mandatory="true" multi-valued="true"> <adm:synopsis> Specifies an attribute type to process referential integrity checking on. There must be at least one of these specified in the plugin configuration and the syntax of the type must either be distinguished name (1.3.6.1.4.1.1466.115.121.1.12) or name and optional uid (1.3.6.1.4.1.1466.115.121.1.34). </adm:synopsis> <adm:description> Specifies an attribute type to process referential integrity. </adm:description> <adm:default-behavior> <adm:alias> <adm:synopsis>T The ds-cfg-referential-integrity attribute must be specified at least once. </adm:synopsis> </adm:alias> </adm:default-behavior> <adm:syntax> <adm:attribute-type /> </adm:syntax> <adm:profile name="ldap"> <ldap:attribute> <ldap:oid>1.3.6.1.4.1.26027.1.1.472</ldap:oid> <ldap:name>ds-cfg-referential-integrity-attribute-type</ldap:name> </ldap:attribute> </adm:profile> </adm:property> <adm:property name="referential-integrity-base-dn" mandatory="false" multi-valued="true"> <adm:synopsis> Specifies a base DN to restrict the referential integrity processing scope. If none of these are specified in the plugin configuration, then the server's public naming contexts are used. </adm:synopsis> <adm:description> Specifies the a base DN to restrict the referential integrity processing scope. </adm:description> <adm:default-behavior> <adm:alias> <adm:synopsis> The scope will be to use all of the public naming contexts. </adm:synopsis> </adm:alias> </adm:default-behavior> <adm:syntax> <adm:dn /> </adm:syntax> <adm:profile name="ldap"> <ldap:attribute> <ldap:oid>1.3.6.1.4.1.26027.1.1.473</ldap:oid> <ldap:name>ds-cfg-referential-integrity-base-dn</ldap:name> </ldap:attribute> </adm:profile> </adm:property> <adm:property name="referential-integrity-log-file" mandatory="false" multi-valued="false"> <adm:synopsis> Specifies the log file location where the update records will be written when the plugin is in background mode processing. The default location is in the logs directory of the server instance, using the file name "referint". </adm:synopsis> <adm:description> Specifies the log file location where the update records will be written. </adm:description> <adm:default-behavior> <adm:defined> <adm:value>logs/referint</adm:value> </adm:defined> </adm:default-behavior> <adm:syntax> <adm:string /> </adm:syntax> <adm:profile name="ldap"> <ldap:attribute> <ldap:oid>1.3.6.1.4.1.26027.1.1.474</ldap:oid> <ldap:name>ds-cfg-referential-integrity-log-file</ldap:name> </ldap:attribute> </adm:profile> </adm:property> <adm:property name="referential-integrity-update-interval" mandatory="false" multi-valued="false"> <adm:synopsis> Specifies the interval, in seconds, when the referential integrity background thread will wakeup and process new update records. If this value is 0, then the updates are processed in foreground. </adm:synopsis> <adm:description> Specifies the interval, in seconds, when the referential integrity update thread will wakeup and process new update records. </adm:description> <adm:default-behavior> <adm:defined> <adm:value> 0 seconds </adm:value> </adm:defined> </adm:default-behavior> <adm:syntax> <adm:duration base-unit="s" allow-unlimited="false" /> </adm:syntax> <adm:profile name="ldap"> <ldap:attribute> <ldap:oid>1.3.6.1.4.1.26027.1.1.475</ldap:oid> <ldap:name>ds-cfg-referential-integrity-update-interval</ldap:name> </ldap:attribute> </adm:profile> </adm:property> </adm:managed-object> opends/src/messages/messages/plugin.properties
@@ -317,4 +317,44 @@ changes failed the attribute value uniqueness check SEVERE_ERR_PLUGIN_REFERINT_INVALID_PLUGIN_TYPE_81=An attempt was made to \ register the Referential Integrity plugin to be invoked as a %s plugin. This \ plugin type is not allowed for this plugin plugin type is not allowed for this plugin SEVERE_ERR_PLUGIN_REFERENT_CREATE_LOGFILE_82=An error occured during \ Referential Integity plugin initialization because log file creation \ failed: %s SEVERE_ERR_PLUGIN_REFERENT_CLOSE_LOGFILE_83=An error occured closing the \ Referential Integrity plugin update logile: %s SEVERE_ERR_PLUGIN_REFERENT_REPLACE_LOGFILE_84=An error occured replacing the \ Referential Integrity plugin update logile: %s INFO_PLUGIN_REFERENT_LOGFILE_CHANGE_REQUIRES_RESTART_85=The file name that \ the Referential Integrity plugin logs changes to during background \ processing has been changed from %s to %s, but this change will not take \ effect until the server is restarted INFO_PLUGIN_REFERENT_BACKGROUND_PROCESSING_UPDATE_INTERVAL_CHANGED_86=The \ Referential Integrity plugin background processing update interval has \ been changed from %s to %s, the new value will now be during background \ processing INFO_PLUGIN_REFERENT_BACKGROUND_PROCESSING_STOPPING_87=The Referential \ Integrity plugin background processing has been stopped INFO_PLUGIN_REFERENT_BACKGROUND_PROCESSING_STARTING_88=The Referential \ Integrity plugin has started background processing using the update \ interval %s SEVERE_ERR_PLUGIN_REFERENT_SEARCH_FAILED_89=The Referential \ Integrity plugin failed when performaing an internal search: %s SEVERE_ERR_PLUGIN_REFERENT_MODIFY_FAILED_90=The Referential \ Integrity plugin failed when performing an internal modify on entry %s: %s MILD_ERR_PLUGIN_REFERENT_CANNOT_DECODE_STRING_AS_DN_91=The Referential \ Integrity plugin failed to decode a entry DN from the update log: %s INFO_PLUGIN_REFERENT_SEARCH_NO_SUCH_OBJECT_92=The Referential Integrity \ plugin failed when performing a search because the base DN %s does not exist SEVERE_ERR_PLUGIN_REFERENT_INVALID_ATTRIBUTE_SYNTAX_93=An error occured \ in the Referential Integrity plugin while attempting to configure the \ attribute type %s which has a syntax OID of %s. A Referential Integrity \ attribute type must have a syntax OID of either \ 1.3.6.1.4.1.1466.115.121.1.12 (for the distinguished name syntax) or \ 1.3.6.1.4.1.1466.115.121.1.34 (for the name and optional uid syntax) SEVERE_ERR_PLUGIN_REFERENT_SKIP_DELETE_PROCESSING_94=The Referential Integrity \ plugin will not process a post delete operation on entry %s because the core \ operation failed SEVERE_ERR_PLUGIN_REFERENT_SKIP_MODIFY_DN_PROCESSING_95=The Referential \ Integrity plugin will not process a post modify DN operation on entry %s \ because the core operation failed opends/src/server/org/opends/server/plugins/ReferentialIntegrityPlugin.java
New file @@ -0,0 +1,829 @@ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at * trunk/opends/resource/legal-notices/OpenDS.LICENSE * or https://OpenDS.dev.java.net/OpenDS.LICENSE. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable, * add the following below this CDDL HEADER, with the fields enclosed * by brackets "[]" replaced with your own identifying information: * Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END * * * Portions Copyright 2007 Sun Microsystems, Inc. */ package org.opends.server.plugins; import org.opends.server.admin.std.server.ReferentialIntegrityPluginCfg; import org.opends.server.admin.std.meta.PluginCfgDefn; import org.opends.server.admin.server.ConfigurationChangeListener; import org.opends.server.api.plugin.*; import org.opends.server.api.DirectoryThread; import org.opends.server.api.ServerShutdownListener; import org.opends.server.config.ConfigException; import org.opends.server.types.*; import org.opends.server.types.operation.SubordinateModifyDNOperation; import org.opends.server.types.operation.PostOperationModifyDNOperation; import org.opends.server.types.operation.PostOperationDeleteOperation; import org.opends.messages.Message; import static org.opends.messages.PluginMessages.*; import static org.opends.server.util.StaticUtils.*; import org.opends.server.core.DirectoryServer; import org.opends.server.core.ModifyOperation; import org.opends.server.protocols.internal.InternalClientConnection; import org.opends.server.protocols.internal.InternalSearchOperation; import org.opends.server.loggers.debug.DebugTracer; import static org.opends.server.loggers.debug.DebugLogger.getTracer; import static org.opends.server.loggers.debug.DebugLogger.debugEnabled; import static org.opends.server.loggers.ErrorLogger.*; import static org.opends.server.schema.SchemaConstants.*; import java.util.*; import java.io.*; /** * This class implements a Directory Server post operation plugin that performs * Referential Integrity processing on successful delete and modify DN * operations. The plugin uses a set of configuration criteria to determine * what attribute types to check referential integrity on, and, the set of * base DNs to search for entries that might need referential integrity * processing. If none of these base DNs are specified in the configuration, * then the public naming contexts are used as the base DNs by default. * * The plugin also has an option to process changes in background using * a thread that wakes up periodically looking for change records in a log * file. * **/ public class ReferentialIntegrityPlugin extends DirectoryServerPlugin<ReferentialIntegrityPluginCfg> implements ConfigurationChangeListener<ReferentialIntegrityPluginCfg>, ServerShutdownListener { /** * The tracer object for the debug logger. */ private static final DebugTracer TRACER = getTracer(); //Current plugin configuration. private ReferentialIntegrityPluginCfg currentConfiguration; //List of attribute types that will be checked during referential integrity //processing. private LinkedHashSet<AttributeType> attributeTypes = new LinkedHashSet<AttributeType>(); //List of base DNs that limit the scope of the referential integrity checking. private Set<DN> baseDNs = new LinkedHashSet<DN>(); //The update interval the background thread uses. If it is 0, then //the changes are processed in foreground. private long interval; //The flag used by the background thread to check if it should exit. private boolean stopRequested=false; //The thread name. private final String name="Referential Integrity Background Update Thread"; //The name of the logfile that the update thread uses to process change //records. Defaults to "logs/referint", but can be changed in the //configuration. private String logFileName; //The File class that logfile corresponds to. private File logFile; //The Thread class that the background thread corresponds to. private Thread backGroundThread=null; /** * Used to save a map in the modifyDN operation attachment map that holds * the old entry DNs and the new entry DNs related to a modify DN rename to * new superior operation. */ public static final String MODIFYDN_DNS="modifyDNs"; //The buffered reader that is used to read the log file by the background //thread. private BufferedReader reader; //The buffered writer that is used to write update records in the log //when the plugin is in background processing mode. private BufferedWriter writer; /** * {@inheritDoc} */ public final void initializePlugin(Set<PluginType> pluginTypes, ReferentialIntegrityPluginCfg pluginCfg) throws ConfigException { pluginCfg.addReferentialIntegrityChangeListener(this); currentConfiguration = pluginCfg; for (PluginType t : pluginTypes) switch (t) { case POST_OPERATION_DELETE: case POST_OPERATION_MODIFY_DN: case SUBORDINATE_MODIFY_DN: // These are acceptable. break; default: throw new ConfigException(ERR_PLUGIN_REFERINT_INVALID_PLUGIN_TYPE. get(t.toString())); } for(DN baseDN : pluginCfg.getReferentialIntegrityBaseDN()) baseDNs.add(baseDN); //Iterate through attributes and check that each has a valid syntax //before adding it to the list. for(AttributeType type : pluginCfg.getReferentialIntegrityAttributeType()) { if(!isAttributeSyntaxValid(type)) throw new ConfigException(ERR_PLUGIN_REFERENT_INVALID_ATTRIBUTE_SYNTAX. get(type.getNameOrOID(), type.getSyntax().getSyntaxName())); attributeTypes.add(type); } //Set up log file. Note: t is not allowed to change once the plugin //is active. setUpLogFile(pluginCfg.getReferentialIntegrityLogFile()); interval=pluginCfg.getReferentialIntegrityUpdateInterval(); //Set up background processing if interval > 0. if(interval > 0) setUpBackGroundProcessing(); } /** * {@inheritDoc} */ public ConfigChangeResult applyConfigurationChange( ReferentialIntegrityPluginCfg newConfiguration) { ResultCode resultCode = ResultCode.SUCCESS; boolean adminActionRequired = false; ArrayList<Message> messages = new ArrayList<Message>(); LinkedHashSet<DN> newConfiguredBaseDNs = new LinkedHashSet<DN>(); //Load base DNs from new configuration. for(DN baseDN : newConfiguration.getReferentialIntegrityBaseDN()) newConfiguredBaseDNs.add(baseDN); LinkedHashSet<AttributeType> newAttributeTypes = new LinkedHashSet<AttributeType>(); //Load attribute types from new configuration. for(AttributeType type : newConfiguration.getReferentialIntegrityAttributeType()) newAttributeTypes.add(type); String newLogFileName=newConfiguration.getReferentialIntegrityLogFile(); //User is not allowed to change the logfile name, append a message that the //server needs restarting for change to take effect. if(!logFileName.equals(newLogFileName)) { adminActionRequired=true; messages.add(INFO_PLUGIN_REFERENT_LOGFILE_CHANGE_REQUIRES_RESTART. get(logFileName, newLogFileName)); } //Switch to the new lists. baseDNs = newConfiguredBaseDNs; attributeTypes = newAttributeTypes; long newInterval=newConfiguration.getReferentialIntegrityUpdateInterval(); //If the interval has changed, process that change. The change might start //or stop the background processing thread. if(newInterval != interval) processIntervalChange(newInterval, messages); currentConfiguration = newConfiguration; return new ConfigChangeResult(resultCode, adminActionRequired, messages); } /** * {@inheritDoc} */ public boolean isConfigurationChangeAcceptable( ReferentialIntegrityPluginCfg configuration, List<Message> unacceptableReasons) { boolean configAcceptable = true; for (PluginCfgDefn.PluginType pluginType : configuration.getPluginType()) { switch (pluginType) { case POSTOPERATIONDELETE: case POSTOPERATIONMODIFYDN: case SUBORDINATEMODIFYDN: // These are acceptable. break; default: unacceptableReasons.add(ERR_PLUGIN_REFERINT_INVALID_PLUGIN_TYPE. get(pluginType.toString())); configAcceptable = false; } if(!configAcceptable) break; } //Iterate through attributes and check that each has a valid syntax for(AttributeType type : configuration.getReferentialIntegrityAttributeType()) if(!isAttributeSyntaxValid(type)) { unacceptableReasons.add(ERR_PLUGIN_REFERENT_INVALID_ATTRIBUTE_SYNTAX. get(type.getNameOrOID(), type.getSyntax().getSyntaxName())); configAcceptable = false; break; } return configAcceptable; } /** * {@inheritDoc} */ @SuppressWarnings("unchecked") public PostOperationPluginResult doPostOperation(PostOperationModifyDNOperation modifyDNOperation) { //If the core operation failed, then append an error message and skip //processing. if(modifyDNOperation.getResultCode() != ResultCode.SUCCESS) return PostOperationPluginResult.SUCCESS; // modifyDNOperation.appendErrorMessage( //// ERR_PLUGIN_REFERENT_SKIP_MODIFY_DN_PROCESSING. // get(modifyDNOperation.getEntryDN().toString())); else if(modifyDNOperation.getNewSuperior() == null) { //Core operation was a rename entry. DN oldEntryDN=modifyDNOperation.getOriginalEntry().getDN(); DN newEntryDN=modifyDNOperation.getUpdatedEntry().getDN(); Map<DN,DN> modDNmap=new LinkedHashMap<DN,DN>(); modDNmap.put(oldEntryDN, newEntryDN); processModifyDN(modDNmap,(interval != 0)); } else { //Core operation is a move to a new superior, use the save map of //old DNs and new DNs from the operation attachment. // //This cast causes an unchecked cast warning, suppress it since the //cast is ok. Map<DN,DN> modDNmap = (Map<DN, DN>) modifyDNOperation.getAttachment(MODIFYDN_DNS); processModifyDN(modDNmap, (interval != 0)); } return PostOperationPluginResult.SUCCESS; } /** * {@inheritDoc} */ public PostOperationPluginResult doPostOperation(PostOperationDeleteOperation deleteOperation) { //If the core operation failed, then append an error message and skip //processing. if(deleteOperation.getResultCode() != ResultCode.SUCCESS) return PostOperationPluginResult.SUCCESS; // deleteOperation.appendErrorMessage( // ERR_PLUGIN_REFERENT_SKIP_DELETE_PROCESSING. // get(deleteOperation.getEntryDN().toString())); else processDelete(deleteOperation.getEntryDN(), (interval != 0)); return PostOperationPluginResult.SUCCESS; } /** * {@inheritDoc} */ @SuppressWarnings("unchecked") public SubordinateModifyDNPluginResult processSubordinateModifyDN( SubordinateModifyDNOperation modifyDNOperation, Entry oldEntry, Entry newEntry, List<Modification> modifications) { //This cast gives an unchecked cast warning, suppress it since the cast //is ok. Map<DN,DN>modDNmap= (Map<DN, DN>) modifyDNOperation.getAttachment(MODIFYDN_DNS); if(modDNmap == null) { //First time through, create the map and set it in the operation //attachment. modDNmap=new LinkedHashMap<DN,DN>(); modifyDNOperation.setAttachment(MODIFYDN_DNS, modDNmap); } modDNmap.put(oldEntry.getDN(), newEntry.getDN()); return SubordinateModifyDNPluginResult.SUCCESS; } /** * Verify that the specified attribute has either a distinguished name syntax * or "name and optional UID" syntax. * * @param attribute The attribute to check the syntax of. * * @return Returns <code>true</code> if the attribute has a valid syntax. * */ private boolean isAttributeSyntaxValid(AttributeType attribute) { return (attribute.getSyntaxOID().equals(SYNTAX_DN_OID) || attribute.getSyntaxOID().equals(SYNTAX_NAME_AND_OPTIONAL_UID_OID)); } /** * Process the specifed new interval value. This processing depends on what * the current interval value is and new value will be. The values have been * checked for equality at this point and are not equal. * * If the old interval is 0, then the server is in foreground mode and * the background thread needs to be started using the new interval value. * * If the new interval value is 0, the the server is in background mode * and the the background thread needs to be stopped. * * If the user just wants to change the interval value, the background thread * needs to be interrupted so that it can use the new interval value. * * @param newInterval The new interval value to use. * * @param msgs An array list of messages that thread stop and start messages * can be added to. * */ private void processIntervalChange(long newInterval, ArrayList<Message> msgs) { if(interval == 0) { DirectoryServer.registerShutdownListener(this); interval=newInterval; msgs.add(INFO_PLUGIN_REFERENT_BACKGROUND_PROCESSING_STARTING. get(Long.toString(interval))); setUpBackGroundProcessing(); } else if(newInterval == 0) { Message message= INFO_PLUGIN_REFERENT_BACKGROUND_PROCESSING_STOPPING.get(); msgs.add(message); processServerShutdown(message); interval=newInterval; } else { interval=newInterval; backGroundThread.interrupt(); msgs.add( INFO_PLUGIN_REFERENT_BACKGROUND_PROCESSING_UPDATE_INTERVAL_CHANGED. get(Long.toString(interval),Long.toString(newInterval))); } } /** * Process a modify DN post operation using the specified map of old and new * entry DNs. The boolean "log" is used to determine if the map * is written to the log file for the background thread to pick up. If the * map is to be processed in foreground, than each base DN or public * naming context (if the base DN configuration is empty) is processed. * * @param modDNMap The map of old entry and new entry DNs from the modify * DN operation. * * @param log Set to <code>true</code> if the map should be written to a log * file so that the background thread can process the changes at * a later time. * */ private void processModifyDN(Map<DN, DN> modDNMap, boolean log) { if(modDNMap != null) { if(log) writeLog(modDNMap); else for(DN baseDN : getBaseDNsToSearch()) doBaseDN(baseDN, modDNMap); } } /** * Used by both the background thread and the delete post operation to * process a delete operation on the specified entry DN. The * boolean "log" is used to determine if the DN is written to the log file * for the background thread to pick up. This value is set to false if the * background thread is processing changes. If this method is being called * by a delete post operation, then setting the "log" value to false will * cause the DN to be processed in foreground * * If the DN is to be processed, than each base DN or public naming * context (if the base DN configuration is empty) is is checked to see if * entries under it contain references to the deleted entry DN that need * to be removed. * * @param entryDN The DN of the deleted entry. * * @param log Set to <code>true</code> if the DN should be written to a log * file so that the background thread can process the change at * a later time. * */ private void processDelete(DN entryDN, boolean log) { if(log) writeLog(entryDN); else for(DN baseDN : getBaseDNsToSearch()) searchBaseDN(baseDN, entryDN, null); } /** * Used by the background thread to process the specified old entry DN and * new entry DN. Each base DN or public naming context (if the base DN * configuration is empty) is checked to see if they contain entries with * references to the old entry DN that need to be changed to the new entry DN. * * @param oldEntryDN The entry DN before the modify DN operation. * * @param newEntryDN The entry DN after the modify DN operation. * */ private void processModifyDN(DN oldEntryDN, DN newEntryDN) { for(DN baseDN : getBaseDNsToSearch()) searchBaseDN(baseDN, oldEntryDN, newEntryDN); } /** * Return a set of DNs that are used to search for references under. If the * base DN configuration set is empty, then the public naming contexts * are used. * * @return A set of DNs to use in the reference searches. * */ private Set<DN> getBaseDNsToSearch() { if(baseDNs.isEmpty()) return DirectoryServer.getPublicNamingContexts().keySet(); else return baseDNs; } /** * Search a base DN using a filter built from the configured attribute * types and the specified old entry DN. For each entry that is found from * the search, delete the old entry DN from the entry. If the new entry * DN is not null, then add it to the entry. * * @param baseDN The DN to base the search at. * * @param oldEntryDN The old entry DN that needs to be deleted or replaced. * * @param newEntryDN The new entry DN that needs to be added. May be null * if the original operation was a delete. * */ private void searchBaseDN(DN baseDN, DN oldEntryDN, DN newEntryDN) { HashSet<SearchFilter> componentFilters=new HashSet<SearchFilter>(); //Build an equality search with all of the configured attribute types //and the old entry DN. for(AttributeType attributeType : attributeTypes) componentFilters.add(SearchFilter.createEqualityFilter(attributeType, new AttributeValue(attributeType, oldEntryDN.toString()))); InternalClientConnection conn = InternalClientConnection.getRootConnection(); InternalSearchOperation operation = conn.processSearch(baseDN, SearchScope.WHOLE_SUBTREE, DereferencePolicy.NEVER_DEREF_ALIASES, 0, 0, false, SearchFilter.createORFilter(componentFilters), null); switch (operation.getResultCode()) { case SUCCESS: break; case NO_SUCH_OBJECT: Message message= INFO_PLUGIN_REFERENT_SEARCH_NO_SUCH_OBJECT. get(baseDN.toString()); logError(message); return; default: Message message1 = ERR_PLUGIN_REFERENT_SEARCH_FAILED. get(String.valueOf(operation.getErrorMessage())); logError(message1); return; } for (SearchResultEntry entry : operation.getSearchEntries()) deleteAddAttributesEntry(entry, oldEntryDN, newEntryDN); } /** * This method is used in foreground processing of a modify DN operation. * It uses the specified map to perform base DN searching for each map * entry. The key is the old entry DN and the value is the * new entry DN. * * @param baseDN The DN to base the search at. * * @param modifyDNmap The map containing the modify DN old and new entry DNs. * */ private void doBaseDN(DN baseDN, Map<DN,DN> modifyDNmap) { for(Map.Entry<DN,DN> mapEntry: modifyDNmap.entrySet()) searchBaseDN(baseDN, mapEntry.getKey(), mapEntry.getValue()); } /** * For each attribute type, delete the specified old entry DN and * optionally add the specified new entry DN if the DN is not null. * The specified entry is used to see if it contains each attribute type so * those types that the entry contains can be modified. An internal modify * is performed to change the entry. * * @param e The entry that contains the old references. * * @param oldEntryDN The old entry DN to remove references to. * * @param newEntryDN The new entry DN to add a reference to, if it is not * null. * */ private void deleteAddAttributesEntry(Entry e, DN oldEntryDN, DN newEntryDN) { LinkedList<Modification> mods = new LinkedList<Modification>(); DN entryDN=e.getDN(); for(AttributeType type : attributeTypes) { if(e.hasAttribute(type)) { AttributeValue deleteValue= new AttributeValue(type, oldEntryDN.toString()); LinkedHashSet<AttributeValue> deleteValues= new LinkedHashSet<AttributeValue>(); deleteValues.add(deleteValue); mods.add(new Modification(ModificationType.DELETE, new Attribute(type, type.getNameOrOID(), deleteValues))); //If the new entry DN exists, create an ADD modification for it. if(newEntryDN != null) { LinkedHashSet<AttributeValue> addValues= new LinkedHashSet<AttributeValue>(); AttributeValue addValue= new AttributeValue(type, newEntryDN.toString()); addValues.add(addValue); mods.add(new Modification(ModificationType.ADD, new Attribute(type, type.getNameOrOID(), addValues))); } } } InternalClientConnection conn = InternalClientConnection.getRootConnection(); ModifyOperation modifyOperation = conn.processModify(entryDN, mods); if(modifyOperation.getResultCode() != ResultCode.SUCCESS) logError(ERR_PLUGIN_REFERENT_MODIFY_FAILED.get(entryDN.toString(), String.valueOf(modifyOperation.getErrorMessage()))); } /** * Sets up the log file that the plugin can write update recored to and * the background thread can use to read update records from. The specifed * log file name is the name to use for the file. If the file exists from * a previous run, use it. * * @param logFileName The name of the file to use, may be absolute. * * @throws ConfigException If a new file cannot be created if needed. * */ private void setUpLogFile(String logFileName) throws ConfigException { this.logFileName=logFileName; logFile=getFileForPath(logFileName); try { if(!logFile.exists()) logFile.createNewFile(); } catch (IOException io) { Message message= ERR_PLUGIN_REFERENT_CREATE_LOGFILE.get(io.getMessage()); throw new ConfigException(message); } } /** * Sets up a buffered writer that the plugin can use to write update records * with. * * @throws IOException If a new file writer cannot be created. * */ private void setupWriter() throws IOException { writer=new BufferedWriter(new FileWriter(logFile, true)); } /** * Sets up a buffered reader that the background thread can use to read * update records with. * * @throws IOException If a new file reader cannot be created. * */ private void setupReader() throws IOException { reader=new BufferedReader(new FileReader(logFile)); } /** * Write the specified map of old entry and new entry DNs to the log * file. Each entry of the map is a line in the file, the key is the old * entry normalized DN and the value is the new entry normalized DN. * The DNs are separated by the tab character. This map is related to a * modify DN operation. * * @param modDNmap The map of old entry and new entry DNs. * */ private void writeLog(Map<DN,DN> modDNmap) { synchronized(logFile) { try { setupWriter(); for(Map.Entry<DN,DN> mapEntry : modDNmap.entrySet()) { writer.write(mapEntry.getKey().toNormalizedString() + "\t" + mapEntry.getValue().toNormalizedString()); writer.newLine(); } writer.flush(); writer.close(); } catch (IOException io) { Message message = ERR_PLUGIN_REFERENT_CLOSE_LOGFILE.get(io.getMessage()); logError(message); } } } /** * Write the specified entry DN to the log file. This entry DN is related to * a delete operation. * * @param deletedEntryDN The DN of the deleted entry. * */ private void writeLog(DN deletedEntryDN) { synchronized(logFile) { try { setupWriter(); writer.write(deletedEntryDN.toNormalizedString()); writer.newLine(); writer.flush(); writer.close(); } catch (IOException io) { Message message = ERR_PLUGIN_REFERENT_CLOSE_LOGFILE.get(io.getMessage()); logError(message); } } } /** * Process all of the records in the log file. Each line of the file is read * and parsed to determine if it was a delete operation (a single normalized * DN) or a modify DN operation (two normalized DNs separated by a tab). The * corresponding operation method is called to perform the referential * integrity processing as though the operation was just processed. After * all of the records in log file have been processed, the log file is * cleared so that new records can be added. * */ private void processLog() { synchronized(logFile) { try { if(logFile.length() == 0) return; setupReader(); String line; while((line=reader.readLine()) != null) { try { String[] a=line.split("[\t]"); DN origDn = DN.decode(a[0]); //If there is only a single DN string than it must be a delete. if(a.length == 1) { processDelete(origDn, false); } else { DN movedDN=DN.decode(a[1]); processModifyDN(origDn, movedDN); } } catch (DirectoryException ex) { //This exception should rarely happen since the plugin wrote the DN //strings originally. Message message= ERR_PLUGIN_REFERENT_CANNOT_DECODE_STRING_AS_DN. get(ex.getMessage()); logError(message); } } reader.close(); logFile.delete(); logFile.createNewFile(); } catch (IOException io) { Message message = ERR_PLUGIN_REFERENT_REPLACE_LOGFILE.get(io.getMessage()); logError(message); } } } /** * Return the listener name. * * @return The name of the listener. * */ public String getShutdownListenerName() { return name; } /** * Process a server shutdown. If the background thread is running it needs * to be interrupted so it can read the stop request variable and exit. * * @param reason The reason message for the shutdown. * */ public void processServerShutdown(Message reason) { stopRequested = true; // Wait for back ground thread to terminate while (backGroundThread != null && backGroundThread.isAlive()) { try { // Interrupt if its sleeping backGroundThread.interrupt(); backGroundThread.join(); } catch (InterruptedException ex) { //Expected. } } DirectoryServer.deregisterShutdownListener(this); backGroundThread=null; } /** * Returns the interval time converted to milliseconds. * * @return The interval time for the background thread. */ private long getInterval() { return interval * 1000; } /** * Sets up background processing of referential integrity by creating a * new background thread to process updates. * */ private void setUpBackGroundProcessing() { if(backGroundThread == null) { DirectoryServer.registerShutdownListener(this); stopRequested = false; backGroundThread = new BackGroundThread(); backGroundThread.start(); } } /** * Used by the background thread to determine if it should exit. * * @return Returns <code>true</code> if the background thread should exit. * */ private boolean isShuttingDown() { return stopRequested; } /** * The background referential integrity processing thread. Wakes up after * sleeping for a configurable interval and checks the log file for update * records. * */ private class BackGroundThread extends DirectoryThread { /** * Constructor for the background thread. */ public BackGroundThread() { super(name); } /** * Run method for the background thread. */ public void run() { while(!isShuttingDown()) { try { sleep(getInterval()); } catch(InterruptedException e) { continue; } catch(Exception e) { if (debugEnabled()) { TRACER.debugCaught(DebugLogLevel.ERROR, e); } } processLog(); } } } } opends/tests/unit-tests-testng/resource/config-changes.ldif
@@ -309,6 +309,14 @@ replace: ds-cfg-unique-attribute-type ds-cfg-unique-attribute-type: oncRpcNumber dn: cn=Referential Integrity,cn=Plugins,cn=config changeType: modify replace: ds-cfg-plugin-enabled ds-cfg-plugin-enabled: true - add: ds-cfg-referential-integrity-base-dn ds-cfg-referential-integrity-base-dn: dc=does,dc=not,dc=exist dn: cn=Test Unique Attribute,cn=Plugins,cn=config changeType: add objectClass: top opends/tests/unit-tests-testng/src/server/org/opends/server/core/TestModifyDNOperation.java
@@ -157,13 +157,17 @@ new InternalClientConnection(DN.decode("uid=proxy.user,o=test")); } /** * Invokes a number of operation methods on the provided modify operation * for which all processing has been completed. * for which all processing has been completed. This method is used for * tests that bypass the referential integrity plugin for whatever reason. * * @param modifyDNOperation The operation to be tested. */ private void examineCompletedOperation(ModifyDNOperation modifyDNOperation) private void examineCompletedOPNoExtraPluginCounts(ModifyDNOperation modifyDNOperation) { assertTrue(modifyDNOperation.getProcessingStartTime() > 0); assertTrue(modifyDNOperation.getProcessingStopTime() > 0); @@ -177,6 +181,29 @@ assertEquals(InvocationCounterPlugin.getPostResponseCount(), 1); } /** * Invokes a number of operation methods on the provided modify operation * for which all processing has been completed. The counters * postResponseCount and preParseCount are incremented twice when * referential integrity plugin is enabled. * * @param modifyDNOperation The operation to be tested. */ private void examineCompletedOperation(ModifyDNOperation modifyDNOperation) { assertTrue(modifyDNOperation.getProcessingStartTime() > 0); assertTrue(modifyDNOperation.getProcessingStopTime() > 0); assertTrue(modifyDNOperation.getProcessingTime() >= 0); assertNotNull(modifyDNOperation.getResponseLogElements()); assertEquals(InvocationCounterPlugin.getPreParseCount(), 2); assertEquals(InvocationCounterPlugin.getPreOperationCount(), 1); assertEquals(InvocationCounterPlugin.getPostOperationCount(), 1); ensurePostReponseHasRun(); assertEquals(InvocationCounterPlugin.getPostResponseCount(), 2); } /** * Invokes a number of operation methods on the provided modify operation * for which the pre-operation plugin was not called. @@ -512,7 +539,7 @@ assertTrue(newEntry.hasValue(attribute, null, new AttributeValue(attribute, "user.test0"))); } examineCompletedOperation(modifyDNOperation); examineCompletedOPNoExtraPluginCounts(modifyDNOperation); InvocationCounterPlugin.resetAllCounters(); modifyDNOperation = @@ -537,7 +564,7 @@ assertFalse(newEntry.hasValue(attribute, null, new AttributeValue(attribute, "user.test0"))); } examineCompletedOperation(modifyDNOperation); examineCompletedOPNoExtraPluginCounts(modifyDNOperation); } @Test @@ -571,7 +598,7 @@ assertTrue(newEntry.hasValue(attribute, null, new AttributeValue(attribute, "user.test0"))); } examineCompletedOperation(modifyDNOperation); examineCompletedOPNoExtraPluginCounts(modifyDNOperation); InvocationCounterPlugin.resetAllCounters(); modifyDNOperation = @@ -596,7 +623,7 @@ assertFalse(newEntry.hasValue(attribute, null, new AttributeValue(attribute, "user.test0"))); } examineCompletedOperation(modifyDNOperation); examineCompletedOPNoExtraPluginCounts(modifyDNOperation); } @Test @@ -631,7 +658,7 @@ assertTrue(attribute.hasValue(new AttributeValue(attribute.getAttributeType(), "Aaccf Amar"))); } examineCompletedOperation(modifyDNOperation); examineCompletedOPNoExtraPluginCounts(modifyDNOperation); InvocationCounterPlugin.resetAllCounters(); modifyDNOperation = @@ -658,8 +685,7 @@ { assertTrue(attribute.hasValue(new AttributeValue(attribute.getAttributeType(), "user.0"))); } examineCompletedOperation(modifyDNOperation); examineCompletedOPNoExtraPluginCounts(modifyDNOperation); } @Test opends/tests/unit-tests-testng/src/server/org/opends/server/plugins/ReferentialIntegrityPluginTestCase.java
New file @@ -0,0 +1,997 @@ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at * trunk/opends/resource/legal-notices/OpenDS.LICENSE * or https://OpenDS.dev.java.net/OpenDS.LICENSE. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable, * add the following below this CDDL HEADER, with the fields enclosed * by brackets "[]" replaced with your own identifying information: * Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END * * * Portions Copyright 2007 Sun Microsystems, Inc. */ package org.opends.server.plugins; import org.testng.annotations.*; import org.opends.server.TestCaseUtils; import org.opends.server.config.ConfigException; import org.opends.server.admin.std.server.ReferentialIntegrityPluginCfg; import org.opends.server.admin.std.meta.ReferentialIntegrityPluginCfgDefn; import org.opends.server.admin.server.AdminTestCaseUtils; import org.opends.server.api.plugin.PluginType; import org.opends.server.api.Group; import static org.testng.Assert.assertEquals; import org.opends.server.core.*; import org.opends.server.protocols.internal.InternalClientConnection; import org.opends.server.protocols.internal.InternalSearchOperation; import org.opends.server.types.*; import java.util.LinkedList; import java.util.LinkedHashSet; import java.util.List; import java.util.HashSet; /** * Unit test to test Referential Integrity plugin. */ public class ReferentialIntegrityPluginTestCase extends PluginTestCase { //Config DNs and attributes. private DN configDN; private String dsConfigAttrType="ds-cfg-referential-integrity-attribute-type"; private String dsConfigBaseDN="ds-cfg-referential-integrity-base-dn"; private String dsConfigUpdateInterval= "ds-cfg-referential-integrity-update-interval"; //Suffixes to use for non-public naming context tests. private String exSuffix="dc=example,dc=com"; private String testSuffix="o=test"; //dc=example,dc=com entries. private String user1="uid=user.1, ou=People, ou=dept," + exSuffix; private String user2="uid=user.2, ou=People, ou=dept," + exSuffix; private String user3="uid=user.3, ou=People, ou=dept," + exSuffix; //Test entry to use for rename tests. private String tuser1="uid=user.1, ou=People, ou=dept," + testSuffix; //Old superior, new superior and new RDN for move tree tests. private String newSuperior="ou=moved dept," + exSuffix; private String oldSuperior="ou=people, ou=dept," + exSuffix; private String newRdn="ou=moved people"; //DNs to verfiy that the moved tree test worked. private String user1_moved= "uid=user.1," + newRdn + ',' +newSuperior; private String user2_moved= "uid=user.2," + newRdn + ',' + newSuperior; private String user3_moved= "uid=user.3," + newRdn + ',' + newSuperior; //DN to test that the rename test worked. private String tuser1_rename= "cn=new user.1, ou=People, ou=dept," + testSuffix; private String tuser1_rdn="cn=new user.1"; //Test DNs to add to various groups. private String tuser2="uid=user.2, ou=People, ou=dept," + testSuffix; private String tuser3="uid=user.3, ou=People, ou=dept," + testSuffix; //Groups to use for member and uniquemember attrbutes in dc=example, dc=com //suffix. private String group = "cn=group, ou=groups," + exSuffix; private String ugroup = "cn=group, ou=unique groups," + exSuffix; //DN to use for seeAlso attrbutes. private String spPerson = "cn=special person, ou=Special People," + exSuffix; //Same as above but for o=test suffix. private String tgroup = "cn=group, ou=groups," + testSuffix; private String tugroup = "cn=group, ou=unique groups," + testSuffix; private String tspPerson = "cn=special person, ou=Special People," + testSuffix; /** * Test that a move to a new superior changes the correct entries under * the correct suffixes. * * @throws Exception If an unexpected result is returned. * */ @Test() public void testModDNMoveTree() throws Exception { //Add attributes interested in: member, uniquemember, seealso. replaceAttrEntry(configDN, dsConfigAttrType,"member"); addAttrEntry(configDN, dsConfigAttrType,"uniquemember", "seealso"); //Add suffixes to make referential changes under: //o=test, and o=group, ou=unique groups, dc=example, dc=com replaceAttrEntry(configDN, dsConfigBaseDN, testSuffix); addAttrEntry(configDN, dsConfigBaseDN, ugroup); //Add DNs to groups and special entries addAttrEntry(DN.decode(tgroup), "member", user1, user2, user3); addAttrEntry(DN.decode(tugroup), "uniquemember", user1, user2, user3); addAttrEntry(DN.decode(ugroup), "uniquemember", user1, user2, user3); addAttrEntry(DN.decode(spPerson), "seealso", user1, user2, user3); //Perform the move. doModDN(oldSuperior, newRdn, newSuperior); //This group under the suffix all DNs should be moved. isMember(tgroup, true, user1_moved, user2_moved, user3_moved); //This group under a suffix all DNs should be moved. isAttributeValueEntry(tugroup, true, "uniquemember", user1_moved, user2_moved, user3_moved); //This group under a suffix all DNs should be moved. isAttributeValueEntry(ugroup, true, "uniquemember", user1_moved, user2_moved, user3_moved); //This group not under a suffix, old entries should exist. isAttributeValueEntry(spPerson, true,"seealso", user1, user2, user3); } /** * Test that a rename changes the correct entries under * the correct suffixes. * * @throws Exception If an unexpected result is returned. * */ @Test() public void testModDNMoveEntry() throws Exception { //Add attributes interested in: member, uniquemember, seealso. replaceAttrEntry(configDN, dsConfigAttrType,"member"); addAttrEntry(configDN, dsConfigAttrType,"uniquemember", "seealso"); //Add suffixes to make referential changes under: //dc=example,dc=com and o=group, ou=unique groups, o=test replaceAttrEntry(configDN, dsConfigBaseDN, exSuffix); addAttrEntry(configDN, dsConfigBaseDN, tugroup); //Add DNs to groups and special entry addAttrEntry(DN.decode(group), "member", tuser1, tuser2, tuser3); addAttrEntry(DN.decode(ugroup), "uniquemember", tuser1, tuser2, tuser3); addAttrEntry(DN.decode(tugroup), "uniquemember", tuser1, tuser2, tuser3); addAttrEntry(DN.decode(tspPerson), "seealso", tuser1, tuser2, tuser3); //Perform rename. doModDN(tuser1,tuser1_rdn, null); //Verify that the changes were made. isMember(group, true, tuser1_rename, tuser2, tuser3); isAttributeValueEntry(ugroup, true, "uniquemember", tuser1_rename, tuser2, tuser3); isAttributeValueEntry(tugroup, true, "uniquemember", tuser1_rename, tuser2, tuser3); isAttributeValueEntry(tspPerson, true,"seealso", tuser1, tuser2, tuser3); } /** * Test a delete using public naming contexts as base DNs. * * @throws Exception If an unexpected result is returned. * */ @Test() public void testReferentialDelete() throws Exception { replaceAttrEntry(configDN, dsConfigAttrType,"member"); addAttrEntry(DN.decode(tgroup), "member", tuser1, tuser2, tuser3); deleteEntries(tuser1, tuser2, tuser3); isMember(tgroup, false, tuser1, tuser2, tuser3); } /** * Test that delete using public naming context works in both background * processing (set interval to 1 and wait 2 seconds) and forground. The * changes are made without restarting the server. * * @throws Exception If an unexpected result happens. * */ @Test() public void testReferentialDeleteBackGround() throws Exception { replaceAttrEntry(configDN, dsConfigAttrType,"member"); //Set interval to 1 second, this should start the background thread //and put the plugin in background mode. replaceAttrEntry(configDN, dsConfigUpdateInterval,"1 seconds"); addAttrEntry(DN.decode(tgroup), "member", tuser1, tuser2, tuser3); deleteEntries(tuser1, tuser2, tuser3); //Wait two seconds and then check the group. Thread.sleep(2000); isMember(tgroup, false, tuser1, tuser2, tuser3); //Change the interval to zero seconds, this should stop the background //thread. replaceAttrEntry(configDN, dsConfigUpdateInterval,"0 seconds"); addEntries(tuser1, tuser2, tuser3); addAttrEntry(DN.decode(tgroup), "member", tuser1, tuser2, tuser3); deleteEntries(tuser1, tuser2, tuser3); //Don't wait, the changes should be there. isMember(tgroup, false, tuser1, tuser2, tuser3); } /** * Test delete using multiple attribute types and public naming contexts. * * @throws Exception If an unexpected result happened. * */ @Test() public void testReferentialDeleteAttrs() throws Exception { replaceAttrEntry(configDN, dsConfigAttrType,"member"); addAttrEntry(configDN, dsConfigAttrType,"uniquemember", "seealso"); addAttrEntry(DN.decode(tgroup), "member", tuser1, tuser2, tuser3); addAttrEntry(DN.decode(tugroup), "uniquemember", tuser1, tuser2, tuser3); addAttrEntry(DN.decode(tspPerson), "seealso", tuser1, tuser2, tuser3); deleteEntries(tuser1, tuser2, tuser3); isMember(tgroup, false, tuser1, tuser2, tuser3); isAttributeValueEntry(tugroup, false, "uniquemember", tuser1, tuser2, tuser3); isAttributeValueEntry(tspPerson, false,"seealso", tuser1, tuser2, tuser3); } /** * Check delete with multiple attribute types and multiple suffixes. * * @throws Exception If an unexpected result happened. * */ @Test() public void testReferentialDeleteAttrsSuffix() throws Exception { replaceAttrEntry(configDN, dsConfigAttrType,"member"); addAttrEntry(configDN, dsConfigAttrType,"uniquemember", "seealso"); replaceAttrEntry(configDN, dsConfigBaseDN, exSuffix); addAttrEntry(configDN, dsConfigBaseDN, tugroup); addAttrEntry(DN.decode(group), "member", tuser1, tuser2, tuser3); addAttrEntry(DN.decode(ugroup), "uniquemember", tuser1, tuser2, tuser3); addAttrEntry(DN.decode(tugroup), "uniquemember", tuser1, tuser2, tuser3); addAttrEntry(DN.decode(tspPerson), "seealso", tuser1, tuser2, tuser3); deleteEntries(tuser1, tuser2, tuser3); isMember(group, false, tuser1, tuser2, tuser3); isAttributeValueEntry(ugroup, true, "uniquemember", tuser1, tuser2, tuser3); isAttributeValueEntry(tugroup, false, "uniquemember", tuser1, tuser2, tuser3); isAttributeValueEntry(tspPerson, true,"seealso", tuser1, tuser2, tuser3); } /** * Retrieves a set of valid configuration entries that may be used to * initialize the plugin. * * @return An array of config entries. * * @throws Exception If an unexpected problem occurs. * */ @DataProvider(name = "validConfigs") public Object[][] getValidConfigs() throws Exception { List<Entry> entries = TestCaseUtils.makeEntries( "dn: cn=Referential Integrity,cn=Plugins,cn=config", "objectClass: top", "objectClass: ds-cfg-plugin", "objectClass: ds-cfg-referential-integrity-plugin", "cn: Referential Integrity", "ds-cfg-plugin-class: org.opends.server.plugins.ReferentialIntegrityPlugin", "ds-cfg-plugin-enabled: true", "ds-cfg-plugin-type: postOperationDelete", "ds-cfg-plugin-type: postOperationModifyDN", "ds-cfg-plugin-type: subordinateModifyDN", "ds-cfg-referential-integrity-attribute-type: member", "", "dn: cn=Referential Integrity,cn=Plugins,cn=config", "objectClass: top", "objectClass: ds-cfg-plugin", "objectClass: ds-cfg-referential-integrity-plugin", "cn: Referential Integrity", "ds-cfg-plugin-class: org.opends.server.plugins.ReferentialIntegrityPlugin", "ds-cfg-plugin-enabled: true", "ds-cfg-plugin-type: postOperationDelete", "ds-cfg-plugin-type: postOperationModifyDN", "ds-cfg-plugin-type: subordinateModifyDN", "ds-cfg-referential-integrity-attribute-type: member", "ds-cfg-referential-integrity-attribute-type: uniqueMember", "ds-cfg-referential-integrity-attribute-type: seeAlso", "", "dn: cn=Referential Integrity,cn=Plugins,cn=config", "objectClass: top", "objectClass: ds-cfg-plugin", "objectClass: ds-cfg-referential-integrity-plugin", "cn: Referential Integrity", "ds-cfg-plugin-class: org.opends.server.plugins.ReferentialIntegrityPlugin", "ds-cfg-plugin-enabled: true", "ds-cfg-plugin-type: postOperationDelete", "ds-cfg-plugin-type: postOperationModifyDN", "ds-cfg-plugin-type: subordinateModifyDN", "ds-cfg-referential-integrity-attribute-type: member", "ds-cfg-referential-integrity-attribute-type: uniqueMember", "ds-cfg-referential-integrity-attribute-type: seeAlso", "ds-cfg-referential-integrity-base-dn: ou=people, dc=example,dc=com", "ds-cfg-referential-integrity-base-dn: ou=dept, dc=example,dc=com", "ds-cfg-referential-integrity-base-dn: ou=people, o=test", "", "dn: cn=Referential Integrity,cn=Plugins,cn=config", "objectClass: top", "objectClass: ds-cfg-plugin", "objectClass: ds-cfg-referential-integrity-plugin", "cn: Referential Integrity", "ds-cfg-plugin-class: org.opends.server.plugins.ReferentialIntegrityPlugin", "ds-cfg-plugin-enabled: true", "ds-cfg-plugin-type: postOperationDelete", "ds-cfg-plugin-type: postOperationModifyDN", "ds-cfg-plugin-type: subordinateModifyDN", "ds-cfg-referential-integrity-attribute-type: member", "ds-cfg-referential-integrity-attribute-type: uniqueMember", "ds-cfg-referential-integrity-attribute-type: seeAlso", "ds-cfg-referential-integrity-base-dn: ou=people, dc=example,dc=com", "ds-cfg-referential-integrity-base-dn: ou=dept, dc=example,dc=com", "ds-cfg-referential-integrity-base-dn: ou=people, o=test", "ds-cfg-referential-integrity-update-interval: 300 seconds", "", "dn: cn=Referential Integrity,cn=Plugins,cn=config", "objectClass: top", "objectClass: ds-cfg-plugin", "objectClass: ds-cfg-referential-integrity-plugin", "cn: Referential Integrity", "ds-cfg-plugin-class: org.opends.server.plugins.ReferentialIntegrityPlugin", "ds-cfg-plugin-enabled: true", "ds-cfg-plugin-type: postOperationDelete", "ds-cfg-plugin-type: postOperationModifyDN", "ds-cfg-plugin-type: subordinateModifyDN", "ds-cfg-referential-integrity-attribute-type: member", "ds-cfg-referential-integrity-attribute-type: uniqueMember", "ds-cfg-referential-integrity-attribute-type: seeAlso", "ds-cfg-referential-integrity-base-dn: ou=people, dc=example,dc=com", "ds-cfg-referential-integrity-base-dn: ou=dept, dc=example,dc=com", "ds-cfg-referential-integrity-base-dn: ou=people, o=test", "ds-cfg-referential-integrity-update-interval: 300 seconds", "ds-cfg-referential-integrity-log-file: logs/test", "", "dn: cn=Referential Integrity,cn=Plugins,cn=config", "objectClass: top", "objectClass: ds-cfg-plugin", "objectClass: ds-cfg-referential-integrity-plugin", "cn: Referential Integrity", "ds-cfg-plugin-class: org.opends.server.plugins.ReferentialIntegrityPlugin", "ds-cfg-plugin-enabled: true", "ds-cfg-plugin-type: postOperationModifyDN", "ds-cfg-plugin-type: subordinateModifyDN", "ds-cfg-referential-integrity-attribute-type: member", "ds-cfg-referential-integrity-attribute-type: uniqueMember", "ds-cfg-referential-integrity-attribute-type: seeAlso", "ds-cfg-referential-integrity-base-dn: ou=people, dc=example,dc=com", "ds-cfg-referential-integrity-base-dn: ou=dept, dc=example,dc=com", "ds-cfg-referential-integrity-base-dn: ou=people, o=test", "ds-cfg-referential-integrity-update-interval: 300 seconds", "ds-cfg-referential-integrity-log-file: logs/test" ); Object[][] array = new Object[entries.size()][1]; for (int i=0; i < array.length; i++) { array[i] = new Object[] { entries.get(i) }; } return array; } /** * Tests the process of initializing the server with valid configurations. * * @param e The configuration entry to use for the initialization. * * @throws Exception If an unexpected problem occurs. * */ @Test(dataProvider = "validConfigs") public void testInitializeWithValidConfigs(Entry e) throws Exception { HashSet<PluginType> pluginTypes = new HashSet<PluginType>(); List<Attribute> attrList = e.getAttribute("ds-cfg-plugin-type"); for (Attribute a : attrList){ for (AttributeValue v : a.getValues()) pluginTypes.add(PluginType.forName(v.getStringValue().toLowerCase())); } ReferentialIntegrityPluginCfg configuration = AdminTestCaseUtils.getConfiguration( ReferentialIntegrityPluginCfgDefn.getInstance(), e); ReferentialIntegrityPlugin plugin = new ReferentialIntegrityPlugin(); plugin.initializePlugin(pluginTypes, configuration); plugin.finalizePlugin(); } /** * Retrieves a set of invalid configuration entries that may be used to * initialize the plugin. * * @return An array of config entries. * * @throws Exception If an unexpected problem occurs. * */ @DataProvider(name = "invalidConfigs") public Object[][] getInValidConfigs() throws Exception { List<Entry> entries = TestCaseUtils.makeEntries( "dn: cn=Referential Integrity,cn=Plugins,cn=config", "objectClass: top", "objectClass: ds-cfg-plugin", "objectClass: ds-cfg-referential-integrity-plugin", "cn: Referential Integrity", "ds-cfg-plugin-class: org.opends.server.plugins.ReferentialIntegrityPlugin", "ds-cfg-plugin-enabled: true", "ds-cfg-plugin-type: postOperationDelete", "ds-cfg-plugin-type: postOperationModifyDN", "ds-cfg-plugin-type: subordinateModifyDN", "ds-cfg-referential-integrity-attribute-type: cn", "", "dn: cn=Referential Integrity,cn=Plugins,cn=config", "objectClass: top", "objectClass: ds-cfg-plugin", "objectClass: ds-cfg-referential-integrity-plugin", "cn: Referential Integrity", "ds-cfg-plugin-class: org.opends.server.plugins.ReferentialIntegrityPlugin", "ds-cfg-plugin-enabled: true", "ds-cfg-plugin-type: postOperationDelete", "ds-cfg-plugin-type: postOperationModifyDN", "ds-cfg-plugin-type: subordinateModifyDN", "ds-cfg-referential-integrity-attribute-type: member", "ds-cfg-referential-integrity-attribute-type: uniqueMember", "ds-cfg-referential-integrity-attribute-type: sn", "", "dn: cn=Referential Integrity,cn=Plugins,cn=config", "objectClass: top", "objectClass: ds-cfg-plugin", "objectClass: ds-cfg-referential-integrity-plugin", "cn: Referential Integrity", "ds-cfg-plugin-class: org.opends.server.plugins.ReferentialIntegrityPlugin", "ds-cfg-plugin-enabled: true", "ds-cfg-plugin-type: postOperationDelete", "ds-cfg-plugin-type: preOperationModifyDN", "ds-cfg-plugin-type: subordinateModifyDN", "ds-cfg-referential-integrity-attribute-type: member", "ds-cfg-referential-integrity-attribute-type: uniqueMember", "ds-cfg-referential-integrity-attribute-type: seeAlso", "ds-cfg-referential-integrity-base-dn: ou=people, dc=example,dc=com", "ds-cfg-referential-integrity-base-dn: ou=dept, dc=example,dc=com", "ds-cfg-referential-integrity-base-dn: baddn", "", "dn: cn=Referential Integrity,cn=Plugins,cn=config", "objectClass: top", "objectClass: ds-cfg-plugin", "objectClass: ds-cfg-referential-integrity-plugin", "cn: Referential Integrity", "ds-cfg-plugin-class: org.opends.server.plugins.ReferentialIntegrityPlugin", "ds-cfg-plugin-enabled: true", "ds-cfg-plugin-type: postOperationDelete", "ds-cfg-plugin-type: postOperationModifyDN", "ds-cfg-plugin-type: subordinateModifyDN", "ds-cfg-referential-integrity-attribute-type: member", "ds-cfg-referential-integrity-attribute-type: uniqueMember", "ds-cfg-referential-integrity-attribute-type: seeAlso", "ds-cfg-referential-integrity-base-dn: ou=people, dc=example,dc=com", "ds-cfg-referential-integrity-base-dn: ou=dept, dc=example,dc=com", "ds-cfg-referential-integrity-base-dn: ou=people, o=test", "ds-cfg-referential-integrity-update-interval: -5 seconds", "", "dn: cn=Referential Integrity,cn=Plugins,cn=config", "objectClass: top", "objectClass: ds-cfg-plugin", "objectClass: ds-cfg-referential-integrity-plugin", "cn: Referential Integrity", "ds-cfg-plugin-class: org.opends.server.plugins.ReferentialIntegrityPlugin", "ds-cfg-plugin-enabled: true", "ds-cfg-plugin-type: postOperationDelete", "ds-cfg-plugin-type: postOperationModifyDN", "ds-cfg-plugin-type: subordinateModifyDN", "ds-cfg-referential-integrity-attribute-type: member", "ds-cfg-referential-integrity-attribute-type: uniqueMember", "ds-cfg-referential-integrity-attribute-type: notanattribute", "ds-cfg-referential-integrity-base-dn: ou=people, dc=example,dc=com", "ds-cfg-referential-integrity-base-dn: ou=dept, dc=example,dc=com", "ds-cfg-referential-integrity-base-dn: ou=people, o=test", "ds-cfg-referential-integrity-update-interval: 300 seconds", "ds-cfg-referential-integrity-log-file: logs/test", "", "dn: cn=Referential Integrity,cn=Plugins,cn=config", "objectClass: top", "objectClass: ds-cfg-plugin", "objectClass: ds-cfg-referential-integrity-plugin", "cn: Referential Integrity", "ds-cfg-plugin-class: org.opends.server.plugins.ReferentialIntegrityPlugin", "ds-cfg-plugin-enabled: true", "ds-cfg-plugin-type: postOperationModifyDN", "ds-cfg-plugin-type: subordinateModifyDN", "ds-cfg-referential-integrity-attribute-type: member", "ds-cfg-referential-integrity-attribute-type: uniqueMember", "ds-cfg-referential-integrity-attribute-type: seeAlso", "ds-cfg-referential-integrity-base-dn: ou=people, dc=example,dc=com", "ds-cfg-referential-integrity-base-dn: ou=dept, dc=example,dc=com", "ds-cfg-referential-integrity-base-dn: ou=people, o=test", "ds-cfg-referential-integrity-update-interval: 300 seconds", "ds-cfg-referential-integrity-log-file: /hopefully/doesn't/file/exist" ); Object[][] array = new Object[entries.size()][1]; for (int i=0; i < array.length; i++) { array[i] = new Object[] { entries.get(i) }; } return array; } /** * Tests the process of initializing the server with inValid configurations. * * @param e The configuration entry to use for the initialization. * * @throws Exception If an unexpected problem occurs. * */ @Test(dataProvider = "invalidConfigs", expectedExceptions = { ConfigException.class }) public void testInitializeWithInValidConfigs(Entry e) throws Exception { HashSet<PluginType> pluginTypes = new HashSet<PluginType>(); List<Attribute> attrList = e.getAttribute("ds-cfg-plugin-type"); for (Attribute a : attrList){ for (AttributeValue v : a.getValues()) pluginTypes.add(PluginType.forName(v.getStringValue().toLowerCase())); } ReferentialIntegrityPluginCfg configuration = AdminTestCaseUtils.getConfiguration( ReferentialIntegrityPluginCfgDefn.getInstance(), e); ReferentialIntegrityPlugin plugin = new ReferentialIntegrityPlugin(); plugin.initializePlugin(pluginTypes, configuration); plugin.finalizePlugin(); } /** * Ensures that the Directory Server is running. * * @throws Exception If an unexpected problem occurs. * */ @BeforeClass() public void startServer() throws Exception { TestCaseUtils.startServer(); configDN= DN.decode("cn=Referential Integrity ,cn=Plugins,cn=config"); } /** * Clears configuration information before each method run and re-adds * entries. * * @throws Exception If an unexpected problem occurs. * */ @BeforeMethod public void clearConfigEntries() throws Exception { deleteAttrsEntry(configDN, dsConfigBaseDN); //Hopefully put an attribute type there that won't impact the rest of the //unit tests. replaceAttrEntry(configDN, dsConfigAttrType,"seeAlso"); replaceAttrEntry(configDN, dsConfigUpdateInterval,"0 seconds"); TestCaseUtils.initializeTestBackend(true); addTestEntries("o=test"); TestCaseUtils.clearJEBackend(true,"userRoot", "dc=example,dc=com"); addTestEntries("dc=example,dc=com"); } /** * Clears things up after the unit test is completed. * * @throws Exception If an unexpected problem occurs. * */ @AfterClass public void tearDown() throws Exception { deleteAttrsEntry(configDN, dsConfigBaseDN); //Hopefully put an attribute type there that won't impact the rest of the //unit tests. replaceAttrEntry(configDN, dsConfigAttrType,"seeAlso"); replaceAttrEntry(configDN, dsConfigUpdateInterval,"0 seconds"); TestCaseUtils.clearJEBackend(false,"userRoot", "dc=example,dc=com"); } /** * Create entries under the specified suffix and add them to the server. * The character argument is used to make the mail attribute unique. * * @param suffix The suffix to use in building the entries. * * @throws Exception If a problem occurs. * */ private void addTestEntries(String suffix) throws Exception { TestCaseUtils.addEntries( "dn: ou=dept," + suffix, "objectClass: top", "objectClass: organizationalUnit", "ou: dept", "aci: (targetattr= \"*\")" + "(version 3.0; acl \"allow all\";" + "allow(all) userdn=\"ldap:///anyone\";)", "", "dn: ou=moved dept," + suffix, "objectClass: top", "objectClass: organizationalUnit", "ou: moved dept", "aci: (targetattr= \"*\")" + "(version 3.0; acl \"allow all\";" + "allow(all) userdn=\"ldap:///anyone\";)", "", "dn: ou=groups," + suffix, "objectClass: top", "objectClass: organizationalUnit", "ou: groups", "aci: (targetattr= \"*\")" + "(version 3.0; acl \"allow all\";" + "allow(all) userdn=\"ldap:///anyone\";)", "", "dn: ou=unique Groups," + suffix, "objectClass: top", "objectClass: organizationalUnit", "ou: unique Groups", "aci: (targetattr= \"*\")" + "(version 3.0; acl \"allow all\";" + "allow(all) userdn=\"ldap:///anyone\";)", "", "dn: ou=People, ou=dept," + suffix, "objectClass: top", "objectClass: organizationalUnit", "ou: People", "", "dn: ou=Special People," + suffix, "objectClass: top", "objectClass: organizationalUnit", "ou: Special People", "aci: (targetattr= \"*\")" + "(version 3.0; acl \"allow all\";" + "allow(all) userdn=\"ldap:///anyone\";)", "", "dn: cn=special person, ou=Special People," + suffix, "objectClass: top", "objectClass: person", "objectClass: organizationalPerson", "objectClass: inetOrgPerson", "uid: 1", "givenName: User", "sn: 1", "cn: special person", "userPassword: password", "mail: user1" +"@test", "employeeNumber: 1", "mobile: 1-111-1234", "pager: 1-111-5678", "description: Use for seeAlso attribute", "", "dn: cn=group, ou=groups," + suffix, "objectClass: top", "objectClass: groupOfNames", "cn: group", "aci: (targetattr= \"*\")" + "(version 3.0; acl \"allow all\";" + "allow(all) userdn=\"ldap:///anyone\";)", "", "dn: cn=group, ou=unique groups," + suffix, "objectClass: top", "objectClass: groupOfUniqueNames", "cn: group", "aci: (targetattr= \"*\")" + "(version 3.0; acl \"allow all\";" + "allow(all) userdn=\"ldap:///anyone\";)", "", "dn: uid=user.1, ou=People, ou=dept," + suffix, "objectClass: top", "objectClass: person", "objectClass: organizationalPerson", "objectClass: inetOrgPerson", "uid: 1", "givenName: User", "sn: 1", "cn: User 1", "userPassword: password", "mail: user1" +"@test", "employeeNumber: 1", "mobile: 1-111-1234", "pager: 1-111-5678", "telephoneNumber: 1-111-9012", "", "dn: uid=user.2, ou=People, ou=dept," + suffix, "objectClass: top", "objectClass: person", "objectClass: organizationalPerson", "objectClass: inetOrgPerson", "uid: 2", "givenName: User", "sn: 2", "cn: User 2", "mail: user2" + "@test", "userPassword: password", "employeeNumber: 2", "mobile: 1-222-1234", "pager: 1-222-5678", "telephoneNumber: 1-222-9012", "", "dn: uid=user.3, ou=People, ou=dept," + suffix, "objectClass: top", "objectClass: person", "objectClass: organizationalPerson", "objectClass: inetOrgPerson", "uid: 3", "givenName: User", "sn: 3", "cn: User 3", "mail: user3" + "@test", "userPassword: password", "employeeNumber: 3", "mobile: 1-333-1234", "pager: 1-333-5678", "telephoneNumber: 1-333-9012", "", "dn: uid=user.4, ou=People, ou=dept," + suffix, "objectClass: top", "objectClass: person", "objectClass: organizationalPerson", "objectClass: inetOrgPerson", "uid: 4", "givenName: User", "sn: 4", "cn: User 4", "mail: user4" + "@test", "userPassword: password", "employeeNumber: 4", "mobile: 1-444-1234", "pager: 1-444-5678", "telephoneNumber: 1-444-9012", "", "dn: uid=user.5, ou=People, ou=dept," + suffix, "objectClass: top", "objectClass: person", "objectClass: organizationalPerson", "objectClass: inetOrgPerson", "uid: 5", "givenName: User", "sn: 5", "cn: User 5", "mail: user5" + "@test", "userPassword: password", "employeeNumber: 5", "mobile: 1-555-1234", "pager: 1-555-5678", "telephoneNumber: 1-555-9012" ); } /** * Add specified attr type and type values to the entry specified by dn. * * @param dn The dn of the entry to add the attribute and values to. * * @param attrTypeString The attribute type to add the values to. * * @param attrValStrings The values to add to the entry. * */ private void addAttrEntry(DN dn, String attrTypeString, String... attrValStrings) { LinkedList<Modification> mods = new LinkedList<Modification>(); LinkedHashSet<AttributeValue> attrValues = new LinkedHashSet<AttributeValue>(); AttributeType attrType = getAttrType(attrTypeString); for(String valString : attrValStrings) attrValues.add(new AttributeValue(attrType, valString)); Attribute attr = new Attribute(attrType, attrTypeString, attrValues); mods.add(new Modification(ModificationType.ADD, attr)); InternalClientConnection conn = InternalClientConnection.getRootConnection(); conn.processModify(dn, mods); } /** * Replace specified attr type and type values to the entry specified by dn. * * @param dn The dn of the entry to replace the attribute and values to. * * @param attrTypeString The attribute type to replace the values in. * * @param attrValStrings The values to replace in the the entry. * */ private void replaceAttrEntry(DN dn, String attrTypeString, String... attrValStrings) { LinkedList<Modification> mods = new LinkedList<Modification>(); LinkedHashSet<AttributeValue> attrValues = new LinkedHashSet<AttributeValue>(); AttributeType attrType = getAttrType(attrTypeString); for(String valString : attrValStrings) attrValues.add(new AttributeValue(attrType, valString)); Attribute attr = new Attribute(attrType, attrTypeString, attrValues); mods.add(new Modification(ModificationType.REPLACE, attr)); InternalClientConnection conn = InternalClientConnection.getRootConnection(); conn.processModify(dn, mods); } /** * Remove the attributes specified by the attribute type strings from the * entry corresponding to the dn argument. * * @param dn The entry to remove the attributes from. * * @param attrTypeStrings The attribute type string list to remove from the * entry. * * @throws Exception If an error occurs. * */ private void deleteAttrsEntry(DN dn, String... attrTypeStrings) throws Exception { LinkedList<Modification> mods = new LinkedList<Modification>(); for(String attrTypeString : attrTypeStrings) { AttributeType attrType = getAttrType(attrTypeString); mods.add(new Modification(ModificationType.DELETE, new Attribute(attrType))); } InternalClientConnection conn = InternalClientConnection.getRootConnection(); conn.processModify(dn, mods); } /** * Return the attribute type corresponding to the attribute type string. * * @param attrTypeString The attribute type string name. * * @return An attribute type object pertaining to the string. * */ private AttributeType getAttrType(String attrTypeString) { AttributeType attrType = DirectoryServer.getAttributeType(attrTypeString); if (attrType == null) attrType = DirectoryServer.getDefaultAttributeType(attrTypeString); return attrType; } private void deleteEntries(String... dns) throws Exception{ InternalClientConnection conn = InternalClientConnection.getRootConnection(); for(String dn : dns) { DeleteOperation op=conn.processDelete(DN.decode(dn)); assertEquals(op.getResultCode(), ResultCode.SUCCESS); } } /** * Check membership in a static group of the specified dns. The expected * boolean is used to check if the dns are expected or not expected in the * groups. * * @param group The group to check membership in. * * @param expected Set to <code>true</code> if the dns are expected in the * groups. * * @param dns The dns to check membership for. * * @throws Exception If an unexpected membership occurs. * */ private void isMember(String group, boolean expected, String... dns) throws Exception { GroupManager groupManager=DirectoryServer.getGroupManager(); Group instance=groupManager.getGroupInstance(DN.decode(group)); for(String dn : dns) assertEquals(instance.isMember(DN.decode(dn)), expected); } private void isAttributeValueEntry(String entryDN, boolean expected, String attr, String... dns) throws Exception { AttributeType type= getAttrType(attr); String filterStr="(" + attr + "=*)"; InternalClientConnection conn = InternalClientConnection.getRootConnection(); InternalSearchOperation operation = conn.processSearch(DN.decode(entryDN), SearchScope.BASE_OBJECT, DereferencePolicy.NEVER_DEREF_ALIASES, 0, 0, false, SearchFilter.createFilterFromString(filterStr), null); for (SearchResultEntry entry : operation.getSearchEntries()) { for(String dn : dns) { AttributeValue value = new AttributeValue(type, dn); assertEquals(entry.hasValue(type, null, value), expected); } } } /** * Add the entries created using the specified DNs to the server. * * @param dns The dns to use in entry creation. * * @throws Exception If an unexpected result happens. * */ private void addEntries(String... dns) throws Exception { InternalClientConnection conn = InternalClientConnection.getRootConnection(); for(String dn : dns) { Entry e=makeEntry(dn); AddOperation op=conn.processAdd(e); assertEquals(op.getResultCode(), ResultCode.SUCCESS); } } /** * Make a entry with the specified dn. * * @param dn The dn of the entry. * @return The created entry. * @throws Exception If the entry can't be created. */ private Entry makeEntry(String dn) throws Exception { return TestCaseUtils.makeEntry( "dn: " + dn, "objectClass: top", "objectClass: person", "objectClass: organizationalPerson", "objectClass: inetOrgPerson", "uid: 1", "givenName: test", "sn: 1", "cn: test" ); } /** * Perform modify DN operation. * * @param dn The DN to renmame or move. * * @param rdn RDN value. * * @param newSuperior New superior to move to. * * @throws Exception If the operation can't be performed. * */ private void doModDN(String dn, String rdn, String newSuperior) throws Exception { InternalClientConnection conn = InternalClientConnection.getRootConnection(); ModifyDNOperation modDNop; if(newSuperior != null) modDNop = conn.processModifyDN(DN.decode(dn), RDN.decode(rdn), true, DN.decode(newSuperior)); else modDNop = conn.processModifyDN(DN.decode(dn), RDN.decode(rdn), false, null); assertEquals(modDNop.getResultCode(), ResultCode.SUCCESS); } }