From 44e65a90731cfd0dbaff36aaa3993fde064f744d Mon Sep 17 00:00:00 2001
From: dugan <dugan@localhost>
Date: Mon, 03 Sep 2007 02:33:57 +0000
Subject: [PATCH] Commit plugin for maintaining referential integrity. Issue 257.
---
opendj-sdk/opends/src/server/org/opends/server/plugins/ReferentialIntegrityPlugin.java | 829 +++++++++++++++++++++++
opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/core/TestModifyDNOperation.java | 44
opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/plugins/ReferentialIntegrityPluginTestCase.java | 997 +++++++++++++++++++++++++++
opendj-sdk/opends/resource/config/config.ldif | 13
opendj-sdk/opends/src/admin/defn/org/opends/server/admin/std/ReferentialIntegrityPluginConfiguration.xml | 185 +++++
opendj-sdk/opends/tests/unit-tests-testng/resource/config-changes.ldif | 8
opendj-sdk/opends/src/messages/messages/plugin.properties | 42 +
opendj-sdk/opends/resource/schema/02-config.ldif | 23
8 files changed, 2,131 insertions(+), 10 deletions(-)
diff --git a/opendj-sdk/opends/resource/config/config.ldif b/opendj-sdk/opends/resource/config/config.ldif
index e37b617..e042b40 100644
--- a/opendj-sdk/opends/resource/config/config.ldif
+++ b/opendj-sdk/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
diff --git a/opendj-sdk/opends/resource/schema/02-config.ldif b/opendj-sdk/opends/resource/schema/02-config.ldif
index a7834a5..a4aa90b 100644
--- a/opendj-sdk/opends/resource/schema/02-config.ldif
+++ b/opendj-sdk/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' )
diff --git a/opendj-sdk/opends/src/admin/defn/org/opends/server/admin/std/ReferentialIntegrityPluginConfiguration.xml b/opendj-sdk/opends/src/admin/defn/org/opends/server/admin/std/ReferentialIntegrityPluginConfiguration.xml
new file mode 100644
index 0000000..e10d4f5
--- /dev/null
+++ b/opendj-sdk/opends/src/admin/defn/org/opends/server/admin/std/ReferentialIntegrityPluginConfiguration.xml
@@ -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>
+
diff --git a/opendj-sdk/opends/src/messages/messages/plugin.properties b/opendj-sdk/opends/src/messages/messages/plugin.properties
index 4a76304..747d78b 100644
--- a/opendj-sdk/opends/src/messages/messages/plugin.properties
+++ b/opendj-sdk/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
\ No newline at end of file
+ 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
\ No newline at end of file
diff --git a/opendj-sdk/opends/src/server/org/opends/server/plugins/ReferentialIntegrityPlugin.java b/opendj-sdk/opends/src/server/org/opends/server/plugins/ReferentialIntegrityPlugin.java
new file mode 100644
index 0000000..b748629
--- /dev/null
+++ b/opendj-sdk/opends/src/server/org/opends/server/plugins/ReferentialIntegrityPlugin.java
@@ -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();
+ }
+ }
+ }
+}
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/resource/config-changes.ldif b/opendj-sdk/opends/tests/unit-tests-testng/resource/config-changes.ldif
index e260e8c..960b055 100644
--- a/opendj-sdk/opends/tests/unit-tests-testng/resource/config-changes.ldif
+++ b/opendj-sdk/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
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/core/TestModifyDNOperation.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/core/TestModifyDNOperation.java
index 0556382..17af69e 100644
--- a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/core/TestModifyDNOperation.java
+++ b/opendj-sdk/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
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/plugins/ReferentialIntegrityPluginTestCase.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/plugins/ReferentialIntegrityPluginTestCase.java
new file mode 100644
index 0000000..a7c1366
--- /dev/null
+++ b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/plugins/ReferentialIntegrityPluginTestCase.java
@@ -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);
+ }
+}
--
Gitblit v1.10.0