From 85ba0a0b7c41216411fe99c1e714415e53fa5629 Mon Sep 17 00:00:00 2001
From: Nemanja Lukic <nemanja.lukic@forgerock.com>
Date: Thu, 24 Nov 2011 14:35:30 +0000
Subject: [PATCH] Patch for OPENDJ-221
---
opends/src/server/org/opends/server/plugins/ReferentialIntegrityPlugin.java | 505 ++++++++++++++++++++++++++++++++++++++++++++------------
1 files changed, 396 insertions(+), 109 deletions(-)
diff --git a/opends/src/server/org/opends/server/plugins/ReferentialIntegrityPlugin.java b/opends/src/server/org/opends/server/plugins/ReferentialIntegrityPlugin.java
index aab71ce..b83976f 100644
--- a/opends/src/server/org/opends/server/plugins/ReferentialIntegrityPlugin.java
+++ b/opends/src/server/org/opends/server/plugins/ReferentialIntegrityPlugin.java
@@ -24,11 +24,15 @@
*
* Copyright 2008-2010 Sun Microsystems, Inc.
* Portions copyright 2011 ForgeRock AS.
+ * Portions copyright 2011 profiq s.r.o.
*/
package org.opends.server.plugins;
+import java.util.Iterator;
+import org.opends.server.types.operation.PreOperationAddOperation;
+import org.opends.server.types.operation.PreOperationModifyOperation;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
@@ -50,6 +54,8 @@
import org.opends.server.admin.std.server.PluginCfg;
import org.opends.server.admin.std.meta.PluginCfgDefn;
import org.opends.server.admin.server.ConfigurationChangeListener;
+import org.opends.server.admin.std.meta.ReferentialIntegrityPluginCfgDefn
+ .CheckReferencesScopeCriteria;
import org.opends.server.api.Backend;
import org.opends.server.api.DirectoryThread;
import org.opends.server.api.ServerShutdownListener;
@@ -153,6 +159,12 @@
//when the plugin is in background processing mode.
private BufferedWriter writer;
+ /* Specifies the mapping between the attribute type (specified in the
+ * attributeTypes list) and the filter which the plugin should use
+ * to verify the integrity of the value of the given attribute.
+ */
+ private LinkedHashMap<AttributeType, SearchFilter> attrFiltMap =
+ new LinkedHashMap<AttributeType, SearchFilter>();
/**
@@ -163,64 +175,14 @@
throws ConfigException
{
pluginCfg.addReferentialIntegrityChangeListener(this);
- currentConfiguration = pluginCfg;
+ LinkedList<Message> unacceptableReasons = new LinkedList<Message>();
- for (PluginType t : pluginTypes)
+ if (!isConfigurationAcceptable(pluginCfg, unacceptableReasons))
{
- switch (t)
- {
- case POST_OPERATION_DELETE:
- case POST_OPERATION_MODIFY_DN:
- case SUBORDINATE_MODIFY_DN:
- case SUBORDINATE_DELETE:
- // These are acceptable.
- break;
-
- default:
- throw new
- ConfigException(ERR_PLUGIN_REFERENT_INVALID_PLUGIN_TYPE.get(
- t.toString()));
- }
+ throw new ConfigException(unacceptableReasons.getFirst());
}
- Set<DN> cfgBaseDNs = pluginCfg.getBaseDN();
- if ((cfgBaseDNs == null) || cfgBaseDNs.isEmpty())
- {
- cfgBaseDNs = DirectoryServer.getPublicNamingContexts().keySet();
- }
- else
- {
- baseDNs.addAll(cfgBaseDNs);
- }
-
- // Iterate through all of the defined attribute types and ensure that they
- // have acceptable syntaxes and that they are indexed for equality below all
- // base DNs.
- for (AttributeType type : pluginCfg.getAttributeType())
- {
- if (! isAttributeSyntaxValid(type))
- {
- throw new ConfigException(
- ERR_PLUGIN_REFERENT_INVALID_ATTRIBUTE_SYNTAX.get(
- type.getNameOrOID(),
- type.getSyntax().getSyntaxName()));
- }
-
- for (DN baseDN : cfgBaseDNs)
- {
- Backend b = DirectoryServer.getBackend(baseDN);
- if ((b != null) && (! b.isIndexed(type, IndexType.EQUALITY)))
- {
- throw new ConfigException(ERR_PLUGIN_REFERENT_ATTR_UNINDEXED.get(
- pluginCfg.dn().toString(),
- type.getNameOrOID(),
- b.getBackendID()));
- }
- }
-
- attributeTypes.add(type);
- }
-
+ applyConfigurationChange(pluginCfg);
// Set up log file. Note: it is not allowed to change once the plugin is
// active.
@@ -261,10 +223,42 @@
newAttributeTypes.add(type);
}
+ // Load the attribute-filter mapping
+
+ LinkedHashMap<AttributeType, SearchFilter> newAttrFiltMap =
+ new LinkedHashMap<AttributeType, SearchFilter>();
+
+ for (String attrFilt : newConfiguration.getCheckReferencesFilterCriteria())
+ {
+ int sepInd = attrFilt.lastIndexOf(":");
+ String attr = attrFilt.substring(0, sepInd);
+ String filtStr = attrFilt.substring(sepInd + 1);
+
+ AttributeType attrType =
+ DirectoryServer.getAttributeType(attr.toLowerCase());
+
+ try
+ {
+ SearchFilter filter =
+ SearchFilter.createFilterFromString(filtStr);
+ newAttrFiltMap.put(attrType, filter);
+ }
+ catch (DirectoryException de)
+ {
+ /* This should never happen because the filter has already
+ * been verified.
+ */
+ logError(de.getMessageObject());
+ }
+ }
+
//User is not allowed to change the logfile name, append a message that the
//server needs restarting for change to take effect.
+ // The first time the plugin is initialised the 'logFileName' is
+ // not initialised, so in order to verify if it is equal to the new
+ // log file name, we have to make sure the variable is not null.
String newLogFileName=newConfiguration.getLogFile();
- if(!logFileName.equals(newLogFileName))
+ if(logFileName != null && !logFileName.equals(newLogFileName))
{
adminActionRequired=true;
messages.add(
@@ -275,6 +269,7 @@
//Switch to the new lists.
baseDNs = newConfiguredBaseDNs;
attributeTypes = newAttributeTypes;
+ attrFiltMap = newAttrFiltMap;
//If the plugin is enabled and the interval has changed, process that
//change. The change might start or stop the background processing thread.
@@ -294,9 +289,109 @@
public boolean isConfigurationAcceptable(PluginCfg configuration,
List<Message> unacceptableReasons)
{
- ReferentialIntegrityPluginCfg cfg =
+ boolean isAcceptable = true;
+ ReferentialIntegrityPluginCfg pluginCfg =
(ReferentialIntegrityPluginCfg) configuration;
- return isConfigurationChangeAcceptable(cfg, unacceptableReasons);
+
+ for (PluginCfgDefn.PluginType t : pluginCfg.getPluginType())
+ {
+ switch (t)
+ {
+ case POSTOPERATIONDELETE:
+ case POSTOPERATIONMODIFYDN:
+ case SUBORDINATEMODIFYDN:
+ case SUBORDINATEDELETE:
+ case PREOPERATIONMODIFY:
+ case PREOPERATIONADD:
+ // These are acceptable.
+ break;
+
+ default:
+ isAcceptable = false;
+ unacceptableReasons.add(ERR_PLUGIN_REFERENT_INVALID_PLUGIN_TYPE.get(
+ t.toString()));
+ }
+ }
+
+ Set<DN> cfgBaseDNs = pluginCfg.getBaseDN();
+ if ((cfgBaseDNs == null) || cfgBaseDNs.isEmpty())
+ {
+ cfgBaseDNs = DirectoryServer.getPublicNamingContexts().keySet();
+ }
+
+ // Iterate through all of the defined attribute types and ensure that they
+ // have acceptable syntaxes and that they are indexed for equality below all
+ // base DNs.
+ Set<AttributeType> attributeTypes = pluginCfg.getAttributeType();
+ for (AttributeType type : attributeTypes)
+ {
+ if (! isAttributeSyntaxValid(type))
+ {
+ isAcceptable = false;
+ unacceptableReasons.add(
+ ERR_PLUGIN_REFERENT_INVALID_ATTRIBUTE_SYNTAX.get(
+ type.getNameOrOID(),
+ type.getSyntax().getSyntaxName()));
+ }
+
+ for (DN baseDN : cfgBaseDNs)
+ {
+ Backend b = DirectoryServer.getBackend(baseDN);
+ if ((b != null) && (!b.isIndexed(type, IndexType.EQUALITY)))
+ {
+ isAcceptable = false;
+ unacceptableReasons.add(ERR_PLUGIN_REFERENT_ATTR_UNINDEXED.get(
+ pluginCfg.dn().toString(),
+ type.getNameOrOID(),
+ b.getBackendID()));
+ }
+ }
+ }
+
+ /* Iterate through the attribute-filter mapping and verify that the
+ * map contains attributes listed in the attribute-type parameter
+ * and that the filter is valid.
+ */
+
+ for (String attrFilt : pluginCfg.getCheckReferencesFilterCriteria())
+ {
+ int sepInd = attrFilt.lastIndexOf(":");
+ String attr = attrFilt.substring(0, sepInd).trim();
+ String filtStr = attrFilt.substring(sepInd + 1).trim();
+
+ /* TODO: strip the ;options part? */
+
+ /* Get the attribute type for the given attribute. The attribute
+ * type has to be present in the attributeType list.
+ */
+
+ AttributeType attrType =
+ DirectoryServer.getAttributeType(attr.toLowerCase());
+
+ if (attrType == null || !attributeTypes.contains(attrType))
+ {
+ isAcceptable = false;
+ unacceptableReasons.add(
+ ERR_PLUGIN_REFERENT_ATTR_NOT_LISTED.get(attr));
+ }
+
+ /* Verify the filter.
+ */
+
+ try
+ {
+ SearchFilter.createFilterFromString(filtStr);
+ }
+ catch (DirectoryException de)
+ {
+ isAcceptable = false;
+ unacceptableReasons.add(
+ ERR_PLUGIN_REFERENT_BAD_FILTER.get(filtStr, de.getMessage()));
+ }
+
+ }
+
+ return isAcceptable;
}
@@ -307,61 +402,10 @@
ReferentialIntegrityPluginCfg configuration,
List<Message> unacceptableReasons)
{
- boolean configAcceptable = true;
- for (PluginCfgDefn.PluginType pluginType : configuration.getPluginType())
- {
- switch (pluginType)
- {
- case POSTOPERATIONDELETE:
- case POSTOPERATIONMODIFYDN:
- case SUBORDINATEMODIFYDN:
- case SUBORDINATEDELETE:
- // These are acceptable.
- break;
- default:
- unacceptableReasons.add(ERR_PLUGIN_REFERENT_INVALID_PLUGIN_TYPE.
- get(pluginType.toString()));
- configAcceptable = false;
- }
- }
-
- // Iterate through the set of base DNs that we will check and ensure that
- // the corresponding backend is indexed appropriately.
- Set<DN> cfgBaseDNs = configuration.getBaseDN();
- if ((cfgBaseDNs == null) || cfgBaseDNs.isEmpty())
- {
- cfgBaseDNs = DirectoryServer.getPublicNamingContexts().keySet();
- }
-
- //Iterate through attributes and check that each has a valid syntax
- for (AttributeType type : configuration.getAttributeType())
- {
- if (!isAttributeSyntaxValid(type))
- {
- unacceptableReasons.add(
- ERR_PLUGIN_REFERENT_INVALID_ATTRIBUTE_SYNTAX.get(
- type.getNameOrOID(), type.getSyntax().getSyntaxName()));
- configAcceptable = false;
- }
-
- for (DN baseDN : cfgBaseDNs)
- {
- Backend b = DirectoryServer.getBackend(baseDN);
- if ((b != null) && (! b.isIndexed(type, IndexType.EQUALITY)))
- {
- unacceptableReasons.add(ERR_PLUGIN_REFERENT_ATTR_UNINDEXED.get(
- configuration.dn().toString(),
- type.getNameOrOID(), b.getBackendID()));
- configAcceptable = false;
- }
- }
- }
-
- return configAcceptable;
+ return isConfigurationAcceptable(configuration, unacceptableReasons);
}
-
/**
* {@inheritDoc}
*/
@@ -1057,4 +1101,247 @@
}
}
}
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public PluginResult.PreOperation doPreOperation(
+ PreOperationModifyOperation modifyOperation)
+ {
+ /* Skip the integrity checks if the enforcing is not enabled
+ */
+
+ if (!currentConfiguration.isCheckReferences())
+ {
+ return PluginResult.PreOperation.continueOperationProcessing();
+ }
+
+ final List<Modification> mods = modifyOperation.getModifications();
+ final Entry entry = modifyOperation.getModifiedEntry();
+
+ for (Modification mod : mods)
+ {
+ final ModificationType modType = mod.getModificationType();
+
+ /* Process only ADD and REPLACE modification types.
+ */
+ if ((modType != ModificationType.ADD)
+ && (modType != ModificationType.REPLACE))
+ {
+ break;
+ }
+
+ AttributeType attrType = mod.getAttribute().getAttributeType();
+ Set<String> attrOptions = mod.getAttribute().getOptions();
+ Attribute modifiedAttribute = entry.getExactAttribute(attrType,
+ attrOptions);
+ if (modifiedAttribute != null)
+ {
+ PluginResult.PreOperation result =
+ isIntegrityMaintained(modifiedAttribute, entry.getDN());
+ if (result.getResultCode() != ResultCode.SUCCESS)
+ {
+ return result;
+ }
+ }
+ }
+
+ /* At this point, everything is fine.
+ */
+ return PluginResult.PreOperation.continueOperationProcessing();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public PluginResult.PreOperation doPreOperation(
+ PreOperationAddOperation addOperation)
+ {
+ /* Skip the integrity checks if the enforcing is not enabled.
+ */
+
+ if (!currentConfiguration.isCheckReferences())
+ {
+ return PluginResult.PreOperation.continueOperationProcessing();
+ }
+
+ final Entry entry = addOperation.getEntryToAdd();
+
+ for (AttributeType attrType : attributeTypes)
+ {
+ final List<Attribute> attrs = entry.getAttribute(attrType, false);
+
+ if (attrs != null)
+ {
+ PluginResult.PreOperation result =
+ isIntegrityMaintained(attrs, entry.getDN());
+ if (result.getResultCode() != ResultCode.SUCCESS)
+ {
+ return result;
+ }
+ }
+ }
+
+ /* If we reahed this point, everything is fine.
+ */
+ return PluginResult.PreOperation.continueOperationProcessing();
+ }
+
+ /**
+ * Verifies that the integrity of values is maintained.
+ * @param attrs Attribute list which refers to another entry in the
+ * directory.
+ * @param entryDN DN of the entry which contains the <CODE>attr</CODE>
+ * attribute.
+ * @return The SUCCESS if the integrity is maintained or
+ * CONSTRAINT_VIOLATION oherwise
+ */
+ private PluginResult.PreOperation
+ isIntegrityMaintained(List<Attribute> attrs, DN entryDN)
+ {
+ PluginResult.PreOperation result = null;
+ for(Attribute attr : attrs)
+ {
+ result = isIntegrityMaintained(attr, entryDN);
+ if (result != PluginResult.PreOperation.continueOperationProcessing())
+ {
+ return result;
+ }
+ }
+
+ return PluginResult.PreOperation.continueOperationProcessing();
+ }
+
+ /**
+ * Verifies that the integrity of values is maintained.
+ * @param attr Attribute which refers to another entry in the
+ * directory.
+ * @param entryDN DN of the entry which contains the <CODE>attr</CODE>
+ * attribute.
+ * @return The SUCCESS if the integrity is maintained or
+ * CONSTRAINT_VIOLATION oherwise
+ */
+ private PluginResult.PreOperation isIntegrityMaintained(Attribute attr,
+ DN entryDN)
+ {
+ /* Verify that the entry belongs to one of the configured naming
+ * contexts.
+ */
+
+ boolean isLocal = false;
+
+ if (baseDNs.isEmpty())
+ {
+ baseDNs = DirectoryServer.getPublicNamingContexts().keySet();
+ }
+
+ for (DN baseDN : baseDNs)
+ {
+ if (entryDN.matchesBaseAndScope(baseDN, SearchScope.SUBORDINATE_SUBTREE))
+ {
+ isLocal = true;
+ break;
+ }
+ }
+
+ /* If the entry does not belong to any of the configured naming
+ * contexts continue further operation processing without checking
+ * the integrity.
+ */
+
+ if (!isLocal)
+ {
+ return PluginResult.PreOperation.continueOperationProcessing();
+ }
+
+ /* Iterate over the list of attributes */
+
+ Iterator<AttributeValue> attrValIt = attr.iterator();
+
+ try
+ {
+ while (attrValIt.hasNext())
+ {
+ AttributeValue attrVal = attrValIt.next();
+ DN valueEntryDN = null;
+ Entry valueEntry = null;
+
+ valueEntryDN = DN.decode(attrVal.getNormalizedValue());
+
+ if (currentConfiguration.getCheckReferencesScopeCriteria()
+ == CheckReferencesScopeCriteria.NAMING_CONTEXT)
+ {
+ boolean matches = false;
+
+ for (DN baseDN : baseDNs)
+ {
+ if (entryDN.matchesBaseAndScope(baseDN,
+ SearchScope.SUBORDINATE_SUBTREE))
+ {
+ matches = true;
+ break;
+ }
+ }
+
+ if (!matches)
+ {
+ return PluginResult.PreOperation.stopProcessing(
+ ResultCode.CONSTRAINT_VIOLATION,
+ ERR_PLUGIN_REFERENT_NAMINGCONTEXT_MISMATCH.get(
+ valueEntryDN.toString(),
+ attr.getName(),
+ entryDN.toString()
+ )
+ );
+ }
+
+ valueEntry = DirectoryServer.getEntry(valueEntryDN);
+ }
+ else
+ {
+ valueEntry = DirectoryServer.getEntry(valueEntryDN);
+ }
+
+ /* Verify that the value entry exists in the backend.
+ */
+
+ if (valueEntry == null)
+ {
+ return PluginResult.PreOperation.stopProcessing(
+ ResultCode.CONSTRAINT_VIOLATION,
+ ERR_PLUGIN_REFERENT_ENTRY_MISSING.get(
+ valueEntryDN.toString(),
+ attr.getName(),
+ entryDN.toString()
+ ));
+ }
+
+ /* Verify that the value entry conforms to the filter.
+ */
+
+ SearchFilter filter = attrFiltMap.get(attr.getAttributeType());
+ if (filter != null && !filter.matchesEntry(valueEntry))
+ {
+ return PluginResult.PreOperation.stopProcessing(
+ ResultCode.CONSTRAINT_VIOLATION,
+ ERR_PLUGIN_REFERENT_FILTER_MISMATCH.get(
+ valueEntry.getDN().toString(),
+ attr.getName(),
+ entryDN.toString(),
+ filter.toString())
+ );
+ }
+ }
+ }
+ catch (DirectoryException de)
+ {
+ return PluginResult.PreOperation.stopProcessing(
+ ResultCode.OTHER,
+ ERR_PLUGIN_REFERENT_EXCEPTION.get(de.getLocalizedMessage()));
+ }
+
+ return PluginResult.PreOperation.continueOperationProcessing();
+ }
}
--
Gitblit v1.10.0