| | |
| | | */ |
| | | package org.opends.server.plugins; |
| | | |
| | | import org.opends.server.admin.std.server.UniqueAttributePluginCfg; |
| | | import org.opends.server.admin.std.meta.PluginCfgDefn; |
| | | |
| | | |
| | | import java.util.ArrayList; |
| | | import java.util.LinkedHashMap; |
| | | import java.util.LinkedHashSet; |
| | | import java.util.List; |
| | | import java.util.Set; |
| | | |
| | | import org.opends.messages.Message; |
| | | import org.opends.server.admin.server.ConfigurationChangeListener; |
| | | import org.opends.server.admin.std.meta.PluginCfgDefn; |
| | | import org.opends.server.admin.std.server.PluginCfg; |
| | | import org.opends.server.admin.std.server.UniqueAttributePluginCfg; |
| | | import org.opends.server.api.AlertGenerator; |
| | | import org.opends.server.api.plugin.DirectoryServerPlugin; |
| | | import org.opends.server.api.plugin.PluginType; |
| | | import org.opends.server.api.plugin.PreOperationPluginResult; |
| | | import org.opends.server.config.ConfigException; |
| | | import org.opends.server.types.*; |
| | | import org.opends.server.core.DirectoryServer; |
| | | import org.opends.server.loggers.debug.DebugTracer; |
| | | import org.opends.server.protocols.internal.InternalClientConnection; |
| | | import org.opends.server.protocols.internal.InternalSearchOperation; |
| | | import org.opends.server.types.Attribute; |
| | | import org.opends.server.types.AttributeType; |
| | | import org.opends.server.types.AttributeValue; |
| | | import org.opends.server.types.ConfigChangeResult; |
| | | import org.opends.server.types.DebugLogLevel; |
| | | import org.opends.server.types.DereferencePolicy; |
| | | import org.opends.server.types.DirectoryException; |
| | | import org.opends.server.types.DN; |
| | | import org.opends.server.types.Entry; |
| | | import org.opends.server.types.Modification; |
| | | import org.opends.server.types.RDN; |
| | | import org.opends.server.types.ResultCode; |
| | | import org.opends.server.types.SearchFilter; |
| | | import org.opends.server.types.SearchResultEntry; |
| | | import org.opends.server.types.SearchScope; |
| | | import org.opends.server.types.operation.PostSynchronizationAddOperation; |
| | | import org.opends.server.types.operation.PostSynchronizationModifyDNOperation; |
| | | import org.opends.server.types.operation.PostSynchronizationModifyOperation; |
| | | import org.opends.server.types.operation.PreOperationAddOperation; |
| | | import org.opends.server.types.operation.PreOperationModifyDNOperation; |
| | | import org.opends.server.types.operation.PreOperationModifyOperation; |
| | | import org.opends.server.types.operation.PreOperationOperation; |
| | | import org.opends.server.core.DirectoryServer; |
| | | import org.opends.server.protocols.internal.InternalClientConnection; |
| | | import org.opends.server.protocols.internal.InternalSearchOperation; |
| | | import org.opends.messages.Message; |
| | | import static org.opends.messages.PluginMessages.*; |
| | | |
| | | import java.util.*; |
| | | import static org.opends.messages.PluginMessages.*; |
| | | import static org.opends.server.loggers.debug.DebugLogger.*; |
| | | import static org.opends.server.util.ServerConstants.*; |
| | | |
| | | |
| | | |
| | | /** |
| | | * This class implements a Directory Server plugin that performs attribute |
| | | * uniqueness checking on the add, modify and modifyDN operations. If the |
| | | * operation is eligible for checking based on a set of configuration criteria, |
| | | * then the operation's attribute values will be checked, using that |
| | | * configuration criteria, for uniqueness against the server's values to |
| | | * determine if the operation can proceed. |
| | | * This class implements a Directory Server plugin that can be used to ensure |
| | | * that all values for a given attribute or set of attributes are unique within |
| | | * the server (or optionally, below a specified set of base DNs). It will |
| | | * examine all add, modify, and modify DN operations to determine whether any |
| | | * new conflicts are introduced. If a conflict is detected then the operation |
| | | * will be rejected, unless that operation is being applied through |
| | | * synchronization in which case an alert will be generated to notify |
| | | * administrators of the problem. |
| | | */ |
| | | public class UniqueAttributePlugin |
| | | extends DirectoryServerPlugin<UniqueAttributePluginCfg> |
| | | implements ConfigurationChangeListener<UniqueAttributePluginCfg> { |
| | | implements ConfigurationChangeListener<UniqueAttributePluginCfg>, |
| | | AlertGenerator |
| | | { |
| | | /** |
| | | * The debug log tracer that will be used for this plugin. |
| | | */ |
| | | private static final DebugTracer TRACER = getTracer(); |
| | | |
| | | |
| | | |
| | | /** |
| | | * The pre-operation plugin result that should be returned if an operation |
| | | * would have resulted in a unique attribute conflict. |
| | | */ |
| | | private static final PreOperationPluginResult FAILED_PREOP_RESULT = |
| | | new PreOperationPluginResult(false, false, false, true); |
| | | |
| | | |
| | | |
| | | /** |
| | | * The set of attributes that will be requested when performing internal |
| | | * search operations. This indicates that no attributes should be returned. |
| | | */ |
| | | private static final LinkedHashSet<String> SEARCH_ATTRS = |
| | | new LinkedHashSet<String>(1); |
| | | static |
| | | { |
| | | SEARCH_ATTRS.add("1.1"); |
| | | } |
| | | |
| | | |
| | | |
| | | //Current plugin configuration. |
| | | private UniqueAttributePluginCfg currentConfiguration; |
| | | |
| | | //List of attribute types that must be unique. |
| | | private LinkedHashSet<AttributeType> uniqueAttributeTypes = |
| | | new LinkedHashSet<AttributeType>(); |
| | | |
| | | //List of base DNs that limit the scope of the uniqueness checking. |
| | | private LinkedHashSet<DN> baseDNs = new LinkedHashSet<DN>(); |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | |
| | | @Override() |
| | | public final void initializePlugin(Set<PluginType> pluginTypes, |
| | | UniqueAttributePluginCfg configuration) |
| | | throws ConfigException { |
| | | throws ConfigException |
| | | { |
| | | configuration.addUniqueAttributeChangeListener(this); |
| | | currentConfiguration = configuration; |
| | | DirectoryServer.registerAlertGenerator(this); |
| | | |
| | | for (PluginType t : pluginTypes) |
| | | switch (t) { |
| | | { |
| | | switch (t) |
| | | { |
| | | case PRE_OPERATION_ADD: |
| | | case PRE_OPERATION_MODIFY: |
| | | case PRE_OPERATION_MODIFY_DN: |
| | | case POST_SYNCHRONIZATION_ADD: |
| | | case POST_SYNCHRONIZATION_MODIFY: |
| | | case POST_SYNCHRONIZATION_MODIFY_DN: |
| | | // These are acceptable. |
| | | break; |
| | | |
| | | default: |
| | | Message message = |
| | | ERR_PLUGIN_UNIQUEATTR_INVALID_PLUGIN_TYPE.get(t.toString()); |
| | | throw new ConfigException(message); |
| | | |
| | | } |
| | | //Load base DNs if any. |
| | | for(DN baseDN : configuration.getUniqueAttributeBaseDN()) |
| | | baseDNs.add(baseDN); |
| | | //Load attribute types if any. |
| | | for(AttributeType attributeType : configuration.getUniqueAttributeType()) |
| | | uniqueAttributeTypes.add(attributeType); |
| | | } |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override() |
| | | public final void finalizePlugin() |
| | | { |
| | | currentConfiguration.removeUniqueAttributeChangeListener(this); |
| | | DirectoryServer.deregisterAlertGenerator(this); |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override() |
| | | public final PreOperationPluginResult |
| | | doPreOperation(PreOperationAddOperation addOperation) |
| | | { |
| | | UniqueAttributePluginCfg config = currentConfiguration; |
| | | Entry entry = addOperation.getEntryToAdd(); |
| | | |
| | | Set<DN> baseDNs = getBaseDNs(config, entry.getDN()); |
| | | if (baseDNs == null) |
| | | { |
| | | // The entry is outside the scope of this plugin. |
| | | return PreOperationPluginResult.SUCCESS; |
| | | } |
| | | |
| | | for (AttributeType t : config.getUniqueAttributeType()) |
| | | { |
| | | List<Attribute> attrList = entry.getAttribute(t); |
| | | if (attrList != null) |
| | | { |
| | | for (Attribute a : attrList) |
| | | { |
| | | for (AttributeValue v : a.getValues()) |
| | | { |
| | | try |
| | | { |
| | | DN conflictDN = getConflictingEntryDN(baseDNs, entry.getDN(), |
| | | config, v); |
| | | if (conflictDN != null) |
| | | { |
| | | addOperation.appendErrorMessage( |
| | | ERR_PLUGIN_UNIQUEATTR_ATTR_NOT_UNIQUE.get(t.getNameOrOID(), |
| | | v.getStringValue(), conflictDN.toString())); |
| | | addOperation.setResultCode(ResultCode.CONSTRAINT_VIOLATION); |
| | | return FAILED_PREOP_RESULT; |
| | | } |
| | | } |
| | | catch (DirectoryException de) |
| | | { |
| | | if (debugEnabled()) |
| | | { |
| | | TRACER.debugCaught(DebugLogLevel.ERROR, de); |
| | | } |
| | | |
| | | Message m = ERR_PLUGIN_UNIQUEATTR_INTERNAL_ERROR.get( |
| | | de.getResultCode().toString(), |
| | | de.getMessageObject()); |
| | | |
| | | addOperation.setResultCode( |
| | | DirectoryServer.getServerErrorResultCode()); |
| | | addOperation.appendErrorMessage(m); |
| | | return FAILED_PREOP_RESULT; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | return PreOperationPluginResult.SUCCESS; |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override() |
| | | public final PreOperationPluginResult |
| | | doPreOperation(PreOperationModifyOperation modifyOperation) |
| | | { |
| | | UniqueAttributePluginCfg config = currentConfiguration; |
| | | DN entryDN = modifyOperation.getEntryDN(); |
| | | |
| | | Set<DN> baseDNs = getBaseDNs(config, entryDN); |
| | | if (baseDNs == null) |
| | | { |
| | | // The entry is outside the scope of this plugin. |
| | | return PreOperationPluginResult.SUCCESS; |
| | | } |
| | | |
| | | for (Modification m : modifyOperation.getModifications()) |
| | | { |
| | | Attribute a = m.getAttribute(); |
| | | AttributeType t = a.getAttributeType(); |
| | | if (! config.getUniqueAttributeType().contains(t)) |
| | | { |
| | | // This modification isn't for a unique attribute. |
| | | continue; |
| | | } |
| | | |
| | | switch (m.getModificationType()) |
| | | { |
| | | case ADD: |
| | | case REPLACE: |
| | | for (AttributeValue v : a.getValues()) |
| | | { |
| | | try |
| | | { |
| | | DN conflictDN = getConflictingEntryDN(baseDNs, entryDN, config, |
| | | v); |
| | | if (conflictDN != null) |
| | | { |
| | | modifyOperation.appendErrorMessage( |
| | | ERR_PLUGIN_UNIQUEATTR_ATTR_NOT_UNIQUE.get(t.getNameOrOID(), |
| | | v.getStringValue(), conflictDN.toString())); |
| | | modifyOperation.setResultCode(ResultCode.CONSTRAINT_VIOLATION); |
| | | return FAILED_PREOP_RESULT; |
| | | } |
| | | } |
| | | catch (DirectoryException de) |
| | | { |
| | | if (debugEnabled()) |
| | | { |
| | | TRACER.debugCaught(DebugLogLevel.ERROR, de); |
| | | } |
| | | |
| | | Message message = ERR_PLUGIN_UNIQUEATTR_INTERNAL_ERROR.get( |
| | | de.getResultCode().toString(), |
| | | de.getMessageObject()); |
| | | |
| | | modifyOperation.setResultCode( |
| | | DirectoryServer.getServerErrorResultCode()); |
| | | modifyOperation.appendErrorMessage(message); |
| | | return FAILED_PREOP_RESULT; |
| | | } |
| | | } |
| | | break; |
| | | |
| | | case INCREMENT: |
| | | // We could calculate the new value, but we'll just take it from the |
| | | // updated entry. |
| | | List<Attribute> attrList = |
| | | modifyOperation.getModifiedEntry().getAttribute(t, |
| | | a.getOptions()); |
| | | if (attrList != null) |
| | | { |
| | | for (Attribute updatedAttr : attrList) |
| | | { |
| | | if (! updatedAttr.optionsEqual(a.getOptions())) |
| | | { |
| | | continue; |
| | | } |
| | | |
| | | for (AttributeValue v : updatedAttr.getValues()) |
| | | { |
| | | try |
| | | { |
| | | DN conflictDN = getConflictingEntryDN(baseDNs, entryDN, |
| | | config, v); |
| | | if (conflictDN != null) |
| | | { |
| | | modifyOperation.appendErrorMessage( |
| | | ERR_PLUGIN_UNIQUEATTR_ATTR_NOT_UNIQUE.get( |
| | | t.getNameOrOID(), v.getStringValue(), |
| | | conflictDN.toString())); |
| | | modifyOperation.setResultCode( |
| | | ResultCode.CONSTRAINT_VIOLATION); |
| | | return FAILED_PREOP_RESULT; |
| | | } |
| | | } |
| | | catch (DirectoryException de) |
| | | { |
| | | if (debugEnabled()) |
| | | { |
| | | TRACER.debugCaught(DebugLogLevel.ERROR, de); |
| | | } |
| | | |
| | | Message message = ERR_PLUGIN_UNIQUEATTR_INTERNAL_ERROR.get( |
| | | de.getResultCode().toString(), |
| | | de.getMessageObject()); |
| | | |
| | | modifyOperation.setResultCode( |
| | | DirectoryServer.getServerErrorResultCode()); |
| | | modifyOperation.appendErrorMessage(message); |
| | | return FAILED_PREOP_RESULT; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | break; |
| | | |
| | | default: |
| | | // We don't need to look at this modification because it's not a |
| | | // modification type of interest. |
| | | continue; |
| | | } |
| | | } |
| | | |
| | | return PreOperationPluginResult.SUCCESS; |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override() |
| | | public final PreOperationPluginResult doPreOperation( |
| | | PreOperationModifyDNOperation modifyDNOperation) |
| | | { |
| | | UniqueAttributePluginCfg config = currentConfiguration; |
| | | |
| | | Set<DN> baseDNs = getBaseDNs(config, |
| | | modifyDNOperation.getUpdatedEntry().getDN()); |
| | | if (baseDNs == null) |
| | | { |
| | | // The entry is outside the scope of this plugin. |
| | | return PreOperationPluginResult.SUCCESS; |
| | | } |
| | | |
| | | RDN newRDN = modifyDNOperation.getNewRDN(); |
| | | for (int i=0; i < newRDN.getNumValues(); i++) |
| | | { |
| | | AttributeType t = newRDN.getAttributeType(i); |
| | | if (! config.getUniqueAttributeType().contains(t)) |
| | | { |
| | | // We aren't interested in this attribute type. |
| | | continue; |
| | | } |
| | | |
| | | try |
| | | { |
| | | AttributeValue v = newRDN.getAttributeValue(i); |
| | | DN conflictDN = getConflictingEntryDN(baseDNs, |
| | | modifyDNOperation.getEntryDN(), config, v); |
| | | if (conflictDN != null) |
| | | { |
| | | modifyDNOperation.appendErrorMessage( |
| | | ERR_PLUGIN_UNIQUEATTR_ATTR_NOT_UNIQUE.get(t.getNameOrOID(), |
| | | v.getStringValue(), conflictDN.toString())); |
| | | modifyDNOperation.setResultCode(ResultCode.CONSTRAINT_VIOLATION); |
| | | return FAILED_PREOP_RESULT; |
| | | } |
| | | } |
| | | catch (DirectoryException de) |
| | | { |
| | | if (debugEnabled()) |
| | | { |
| | | TRACER.debugCaught(DebugLogLevel.ERROR, de); |
| | | } |
| | | |
| | | Message m = ERR_PLUGIN_UNIQUEATTR_INTERNAL_ERROR.get( |
| | | de.getResultCode().toString(), |
| | | de.getMessageObject()); |
| | | |
| | | modifyDNOperation.setResultCode( |
| | | DirectoryServer.getServerErrorResultCode()); |
| | | modifyDNOperation.appendErrorMessage(m); |
| | | return FAILED_PREOP_RESULT; |
| | | } |
| | | } |
| | | |
| | | return PreOperationPluginResult.SUCCESS; |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override() |
| | | public final void doPostSynchronization( |
| | | PostSynchronizationAddOperation addOperation) |
| | | { |
| | | UniqueAttributePluginCfg config = currentConfiguration; |
| | | Entry entry = addOperation.getEntryToAdd(); |
| | | |
| | | Set<DN> baseDNs = getBaseDNs(config, entry.getDN()); |
| | | if (baseDNs == null) |
| | | { |
| | | // The entry is outside the scope of this plugin. |
| | | return; |
| | | } |
| | | |
| | | for (AttributeType t : config.getUniqueAttributeType()) |
| | | { |
| | | List<Attribute> attrList = entry.getAttribute(t); |
| | | if (attrList != null) |
| | | { |
| | | for (Attribute a : attrList) |
| | | { |
| | | for (AttributeValue v : a.getValues()) |
| | | { |
| | | try |
| | | { |
| | | DN conflictDN = getConflictingEntryDN(baseDNs, entry.getDN(), |
| | | config, v); |
| | | if (conflictDN != null) |
| | | { |
| | | Message m = ERR_PLUGIN_UNIQUEATTR_SYNC_NOT_UNIQUE.get( |
| | | t.getNameOrOID(), |
| | | addOperation.getConnectionID(), |
| | | addOperation.getOperationID(), |
| | | v.getStringValue(), |
| | | entry.getDN().toString(), |
| | | conflictDN.toString()); |
| | | DirectoryServer.sendAlertNotification(this, |
| | | ALERT_TYPE_UNIQUE_ATTR_SYNC_CONFLICT, m); |
| | | } |
| | | } |
| | | catch (DirectoryException de) |
| | | { |
| | | if (debugEnabled()) |
| | | { |
| | | TRACER.debugCaught(DebugLogLevel.ERROR, de); |
| | | } |
| | | |
| | | Message m = ERR_PLUGIN_UNIQUEATTR_INTERNAL_ERROR_SYNC.get( |
| | | addOperation.getConnectionID(), |
| | | addOperation.getOperationID(), |
| | | entry.getDN().toString(), |
| | | de.getResultCode().toString(), |
| | | de.getMessageObject()); |
| | | DirectoryServer.sendAlertNotification(this, |
| | | ALERT_TYPE_UNIQUE_ATTR_SYNC_ERROR, m); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override() |
| | | public final void doPostSynchronization( |
| | | PostSynchronizationModifyOperation modifyOperation) |
| | | { |
| | | UniqueAttributePluginCfg config = currentConfiguration; |
| | | DN entryDN = modifyOperation.getEntryDN(); |
| | | |
| | | Set<DN> baseDNs = getBaseDNs(config, entryDN); |
| | | if (baseDNs == null) |
| | | { |
| | | // The entry is outside the scope of this plugin. |
| | | return; |
| | | } |
| | | |
| | | for (Modification m : modifyOperation.getModifications()) |
| | | { |
| | | Attribute a = m.getAttribute(); |
| | | AttributeType t = a.getAttributeType(); |
| | | if (! config.getUniqueAttributeType().contains(t)) |
| | | { |
| | | // This modification isn't for a unique attribute. |
| | | continue; |
| | | } |
| | | |
| | | switch (m.getModificationType()) |
| | | { |
| | | case ADD: |
| | | case REPLACE: |
| | | for (AttributeValue v : a.getValues()) |
| | | { |
| | | try |
| | | { |
| | | DN conflictDN = getConflictingEntryDN(baseDNs, entryDN, config, |
| | | v); |
| | | if (conflictDN != null) |
| | | { |
| | | Message message = ERR_PLUGIN_UNIQUEATTR_SYNC_NOT_UNIQUE.get( |
| | | t.getNameOrOID(), |
| | | modifyOperation.getConnectionID(), |
| | | modifyOperation.getOperationID(), |
| | | v.getStringValue(), |
| | | entryDN.toString(), |
| | | conflictDN.toString()); |
| | | DirectoryServer.sendAlertNotification(this, |
| | | ALERT_TYPE_UNIQUE_ATTR_SYNC_CONFLICT, |
| | | message); |
| | | } |
| | | } |
| | | catch (DirectoryException de) |
| | | { |
| | | if (debugEnabled()) |
| | | { |
| | | TRACER.debugCaught(DebugLogLevel.ERROR, de); |
| | | } |
| | | |
| | | Message message = ERR_PLUGIN_UNIQUEATTR_INTERNAL_ERROR_SYNC.get( |
| | | modifyOperation.getConnectionID(), |
| | | modifyOperation.getOperationID(), |
| | | entryDN.toString(), |
| | | de.getResultCode().toString(), |
| | | de.getMessageObject()); |
| | | DirectoryServer.sendAlertNotification(this, |
| | | ALERT_TYPE_UNIQUE_ATTR_SYNC_ERROR, message); |
| | | } |
| | | } |
| | | break; |
| | | |
| | | case INCREMENT: |
| | | // We could calculate the new value, but we'll just take it from the |
| | | // updated entry. |
| | | List<Attribute> attrList = |
| | | modifyOperation.getModifiedEntry().getAttribute(t, |
| | | a.getOptions()); |
| | | if (attrList != null) |
| | | { |
| | | for (Attribute updatedAttr : attrList) |
| | | { |
| | | if (! updatedAttr.optionsEqual(a.getOptions())) |
| | | { |
| | | continue; |
| | | } |
| | | |
| | | for (AttributeValue v : updatedAttr.getValues()) |
| | | { |
| | | try |
| | | { |
| | | DN conflictDN = getConflictingEntryDN(baseDNs, entryDN, |
| | | config, v); |
| | | if (conflictDN != null) |
| | | { |
| | | Message message = ERR_PLUGIN_UNIQUEATTR_SYNC_NOT_UNIQUE.get( |
| | | t.getNameOrOID(), |
| | | modifyOperation.getConnectionID(), |
| | | modifyOperation.getOperationID(), |
| | | v.getStringValue(), |
| | | entryDN.toString(), |
| | | conflictDN.toString()); |
| | | DirectoryServer.sendAlertNotification(this, |
| | | ALERT_TYPE_UNIQUE_ATTR_SYNC_CONFLICT, |
| | | message); |
| | | } |
| | | } |
| | | catch (DirectoryException de) |
| | | { |
| | | if (debugEnabled()) |
| | | { |
| | | TRACER.debugCaught(DebugLogLevel.ERROR, de); |
| | | } |
| | | |
| | | Message message = |
| | | ERR_PLUGIN_UNIQUEATTR_INTERNAL_ERROR_SYNC.get( |
| | | modifyOperation.getConnectionID(), |
| | | modifyOperation.getOperationID(), |
| | | entryDN.toString(), |
| | | de.getResultCode().toString(), |
| | | de.getMessageObject()); |
| | | DirectoryServer.sendAlertNotification(this, |
| | | ALERT_TYPE_UNIQUE_ATTR_SYNC_ERROR, |
| | | message); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | break; |
| | | |
| | | default: |
| | | // We don't need to look at this modification because it's not a |
| | | // modification type of interest. |
| | | continue; |
| | | } |
| | | } |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override() |
| | | public final void doPostSynchronization( |
| | | PostSynchronizationModifyDNOperation modifyDNOperation) |
| | | { |
| | | UniqueAttributePluginCfg config = currentConfiguration; |
| | | |
| | | Set<DN> baseDNs = getBaseDNs(config, |
| | | modifyDNOperation.getUpdatedEntry().getDN()); |
| | | if (baseDNs == null) |
| | | { |
| | | // The entry is outside the scope of this plugin. |
| | | return; |
| | | } |
| | | |
| | | RDN newRDN = modifyDNOperation.getNewRDN(); |
| | | for (int i=0; i < newRDN.getNumValues(); i++) |
| | | { |
| | | AttributeType t = newRDN.getAttributeType(i); |
| | | if (! config.getUniqueAttributeType().contains(t)) |
| | | { |
| | | // We aren't interested in this attribute type. |
| | | continue; |
| | | } |
| | | |
| | | try |
| | | { |
| | | AttributeValue v = newRDN.getAttributeValue(i); |
| | | DN conflictDN = getConflictingEntryDN(baseDNs, |
| | | modifyDNOperation.getEntryDN(), config, v); |
| | | if (conflictDN != null) |
| | | { |
| | | Message m = |
| | | ERR_PLUGIN_UNIQUEATTR_SYNC_NOT_UNIQUE.get( |
| | | t.getNameOrOID(), |
| | | modifyDNOperation.getConnectionID(), |
| | | modifyDNOperation.getOperationID(), |
| | | v.getStringValue(), |
| | | modifyDNOperation.getUpdatedEntry().getDN().toString(), |
| | | conflictDN.toString()); |
| | | DirectoryServer.sendAlertNotification(this, |
| | | ALERT_TYPE_UNIQUE_ATTR_SYNC_CONFLICT, m); |
| | | } |
| | | } |
| | | catch (DirectoryException de) |
| | | { |
| | | if (debugEnabled()) |
| | | { |
| | | TRACER.debugCaught(DebugLogLevel.ERROR, de); |
| | | } |
| | | |
| | | Message m = ERR_PLUGIN_UNIQUEATTR_INTERNAL_ERROR_SYNC.get( |
| | | modifyDNOperation.getConnectionID(), |
| | | modifyDNOperation.getOperationID(), |
| | | modifyDNOperation.getUpdatedEntry().getDN().toString(), |
| | | de.getResultCode().toString(), |
| | | de.getMessageObject()); |
| | | DirectoryServer.sendAlertNotification(this, |
| | | ALERT_TYPE_UNIQUE_ATTR_SYNC_ERROR, m); |
| | | } |
| | | } |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * Retrieves the set of base DNs below which uniqueness checks should be |
| | | * performed. If no uniqueness checks should be performed for the specified |
| | | * entry, then {@code null} will be returned. |
| | | * |
| | | * @param config The plugin configuration to use to make the determination. |
| | | * @param entryDN The DN of the entry for which the checks will be |
| | | * performed. |
| | | */ |
| | | private Set<DN> getBaseDNs(UniqueAttributePluginCfg config, DN entryDN) |
| | | { |
| | | Set<DN> baseDNs = config.getUniqueAttributeBaseDN(); |
| | | if ((baseDNs == null) || baseDNs.isEmpty()) |
| | | { |
| | | baseDNs = DirectoryServer.getPublicNamingContexts().keySet(); |
| | | } |
| | | |
| | | for (DN baseDN : baseDNs) |
| | | { |
| | | if (entryDN.isDescendantOf(baseDN)) |
| | | { |
| | | return baseDNs; |
| | | } |
| | | } |
| | | |
| | | return null; |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * Retrieves the DN of the first entry identified that conflicts with the |
| | | * provided value. |
| | | * |
| | | * @param baseDNs The set of base DNs below which the search is to be |
| | | * performed. |
| | | * @param targetDN The DN of the entry at which the change is targeted. If |
| | | * a conflict is found in that entry, then it will be |
| | | * ignored. |
| | | * @param config The plugin configuration to use when making the |
| | | * determination. |
| | | * @param value The value for which to identify any conflicting entries. |
| | | * |
| | | * @return The DN of the first entry identified that contains a conflicting |
| | | * value. |
| | | * |
| | | * @throws DirectoryException If a problem occurred while attempting to |
| | | * make the determination. |
| | | */ |
| | | private DN getConflictingEntryDN(Set<DN> baseDNs, DN targetDN, |
| | | UniqueAttributePluginCfg config, |
| | | AttributeValue value) |
| | | throws DirectoryException |
| | | { |
| | | SearchFilter filter; |
| | | Set<AttributeType> attrTypes = config.getUniqueAttributeType(); |
| | | if (attrTypes.size() == 1) |
| | | { |
| | | filter = SearchFilter.createEqualityFilter(attrTypes.iterator().next(), |
| | | value); |
| | | } |
| | | else |
| | | { |
| | | ArrayList<SearchFilter> equalityFilters = |
| | | new ArrayList<SearchFilter>(attrTypes.size()); |
| | | for (AttributeType t : attrTypes) |
| | | { |
| | | equalityFilters.add(SearchFilter.createEqualityFilter(t, value)); |
| | | } |
| | | filter = SearchFilter.createORFilter(equalityFilters); |
| | | } |
| | | |
| | | InternalClientConnection conn = |
| | | InternalClientConnection.getRootConnection(); |
| | | |
| | | for (DN baseDN : baseDNs) |
| | | { |
| | | InternalSearchOperation searchOperation = |
| | | conn.processSearch(baseDN, SearchScope.WHOLE_SUBTREE, |
| | | DereferencePolicy.NEVER_DEREF_ALIASES, 2, 0, |
| | | false, filter, SEARCH_ATTRS); |
| | | for (SearchResultEntry e : searchOperation.getSearchEntries()) |
| | | { |
| | | if (! e.getDN().equals(targetDN)) |
| | | { |
| | | return e.getDN(); |
| | | } |
| | | } |
| | | |
| | | switch (searchOperation.getResultCode()) |
| | | { |
| | | case SUCCESS: |
| | | case NO_SUCH_OBJECT: |
| | | // These are fine. Either the search was successful or the base DN |
| | | // didn't exist. |
| | | break; |
| | | |
| | | default: |
| | | // An error occurred that prevented the search from completing |
| | | // successfully. |
| | | throw new DirectoryException(searchOperation.getResultCode(), |
| | | searchOperation.getErrorMessage().toMessage()); |
| | | } |
| | | } |
| | | |
| | | // If we've gotten here, then no conflict was found. |
| | | return null; |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override() |
| | | public boolean isConfigurationAcceptable(PluginCfg configuration, |
| | | List<Message> unacceptableReasons) |
| | | { |
| | | UniqueAttributePluginCfg cfg = (UniqueAttributePluginCfg) configuration; |
| | | return isConfigurationChangeAcceptable(cfg, unacceptableReasons); |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | public boolean isConfigurationChangeAcceptable( |
| | | UniqueAttributePluginCfg configuration, |
| | | List<Message> unacceptableReasons) { |
| | | UniqueAttributePluginCfg configuration, |
| | | List<Message> unacceptableReasons) |
| | | { |
| | | boolean configAcceptable = true; |
| | | for (PluginCfgDefn.PluginType pluginType : configuration.getPluginType()) |
| | | { |
| | |
| | | case PREOPERATIONADD: |
| | | case PREOPERATIONMODIFY: |
| | | case PREOPERATIONMODIFYDN: |
| | | case POSTSYNCHRONIZATIONADD: |
| | | case POSTSYNCHRONIZATIONMODIFY: |
| | | case POSTSYNCHRONIZATIONMODIFYDN: |
| | | // These are acceptable. |
| | | break; |
| | | |
| | | default: |
| | | Message message = |
| | | ERR_PLUGIN_UNIQUEATTR_INVALID_PLUGIN_TYPE.get(pluginType.toString()); |
| | | Message message = ERR_PLUGIN_UNIQUEATTR_INVALID_PLUGIN_TYPE.get( |
| | | pluginType.toString()); |
| | | unacceptableReasons.add(message); |
| | | configAcceptable = false; |
| | | } |
| | |
| | | return configAcceptable; |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | public ConfigChangeResult applyConfigurationChange( |
| | | UniqueAttributePluginCfg newConfiguration) { |
| | | ResultCode resultCode = ResultCode.SUCCESS; |
| | | boolean adminActionRequired = false; |
| | | ArrayList<Message> messages = new ArrayList<Message>(); |
| | | LinkedHashSet<AttributeType> newUniqueattributeTypes= |
| | | new LinkedHashSet<AttributeType>(); |
| | | LinkedHashSet<DN> newConfiguredBaseDNs = new LinkedHashSet<DN>(); |
| | | //Load base DNs from new configuration. |
| | | for(DN baseDN : newConfiguration.getUniqueAttributeBaseDN()) |
| | | newConfiguredBaseDNs.add(baseDN); |
| | | //Load attribute types from new configuration. |
| | | for(AttributeType attributeType : newConfiguration.getUniqueAttributeType()) |
| | | newUniqueattributeTypes.add(attributeType); |
| | | //Switch to the new lists and configurations. |
| | | baseDNs = newConfiguredBaseDNs; |
| | | uniqueAttributeTypes = newUniqueattributeTypes; |
| | | UniqueAttributePluginCfg newConfiguration) |
| | | { |
| | | currentConfiguration = newConfiguration; |
| | | return new ConfigChangeResult(resultCode, adminActionRequired, messages); |
| | | return new ConfigChangeResult(ResultCode.SUCCESS, false); |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override() |
| | | public final PreOperationPluginResult |
| | | doPreOperation(PreOperationAddOperation addOperation) { |
| | | PreOperationPluginResult pluginResult=PreOperationPluginResult.SUCCESS; |
| | | DN entryDN=addOperation.getEntryDN(); |
| | | if(isEntryUniquenessCandidate(entryDN)) { |
| | | List<AttributeValue> valueList = |
| | | getEntryAttributeValues(addOperation.getEntryToAdd()); |
| | | if(!searchAllBaseDNs(valueList, entryDN)) |
| | | pluginResult = getPluginErrorResult(addOperation, |
| | | ERR_PLUGIN_UNIQUEATTR_ADD_NOT_UNIQUE.get(entryDN.toString())); |
| | | } |
| | | return pluginResult; |
| | | public DN getComponentEntryDN() |
| | | { |
| | | return currentConfiguration.dn(); |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override() |
| | | public final PreOperationPluginResult |
| | | doPreOperation(PreOperationModifyOperation modifyOperation) { |
| | | PreOperationPluginResult pluginResult=PreOperationPluginResult.SUCCESS; |
| | | DN entryDN = modifyOperation.getEntryDN(); |
| | | if(isEntryUniquenessCandidate(entryDN)) { |
| | | List<AttributeValue> valueList = |
| | | getModificationAttributeValues(modifyOperation.getModifications(), |
| | | modifyOperation.getModifiedEntry()); |
| | | if(!searchAllBaseDNs(valueList, entryDN)) |
| | | pluginResult = getPluginErrorResult(modifyOperation, |
| | | ERR_PLUGIN_UNIQUEATTR_MOD_NOT_UNIQUE.get(entryDN.toString())); |
| | | } |
| | | return pluginResult; |
| | | public String getClassName() |
| | | { |
| | | return UniqueAttributePlugin.class.getName(); |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override() |
| | | public final PreOperationPluginResult |
| | | doPreOperation(PreOperationModifyDNOperation modifyDNOperation) { |
| | | PreOperationPluginResult pluginResult=PreOperationPluginResult.SUCCESS; |
| | | DN entryDN=modifyDNOperation.getOriginalEntry().getDN(); |
| | | //If the operation has a new superior DN then use that, since any moves |
| | | //need to make sure there are no conflicts in the new superior base DN. |
| | | if(modifyDNOperation.getNewSuperior() != null) |
| | | entryDN = modifyDNOperation.getNewSuperior(); |
| | | if(isEntryUniquenessCandidate(entryDN)) { |
| | | List<AttributeValue> valueList = |
| | | getRDNAttributeValues(modifyDNOperation.getNewRDN()); |
| | | if(!searchAllBaseDNs(valueList, entryDN)) |
| | | pluginResult = getPluginErrorResult(modifyDNOperation, |
| | | ERR_PLUGIN_UNIQUEATTR_MODDN_NOT_UNIQUE.get(entryDN.toString())); |
| | | } |
| | | return pluginResult; |
| | | } |
| | | public LinkedHashMap<String,String> getAlerts() |
| | | { |
| | | LinkedHashMap<String,String> alerts = new LinkedHashMap<String,String>(2); |
| | | |
| | | /** |
| | | * Determine if the specified DN is a candidate for attribute uniqueness |
| | | * checking. Checking is skipped if the the unique attribute type list is |
| | | * empty or if there are base DNS configured and the specified DN is not a |
| | | * descendant of any of them. Checking is performed for all other cases. |
| | | * |
| | | * @param dn The DN to check. |
| | | * |
| | | * @return Returns <code>true</code> if the operation needs uniqueness |
| | | * checking performed. |
| | | */ |
| | | private boolean |
| | | isEntryUniquenessCandidate(DN dn) { |
| | | if(uniqueAttributeTypes.isEmpty()) |
| | | return false; |
| | | else if(baseDNs.isEmpty()) |
| | | return true; |
| | | else { |
| | | for(DN baseDN : baseDNs) |
| | | if(baseDN.isAncestorOf(dn)) |
| | | return true; |
| | | } |
| | | return false; |
| | | } |
| | | alerts.put(ALERT_TYPE_UNIQUE_ATTR_SYNC_CONFLICT, |
| | | ALERT_DESCRIPTION_UNIQUE_ATTR_SYNC_CONFLICT); |
| | | alerts.put(ALERT_TYPE_UNIQUE_ATTR_SYNC_ERROR, |
| | | ALERT_DESCRIPTION_UNIQUE_ATTR_SYNC_ERROR); |
| | | |
| | | /** |
| | | * Returns a plugin result instance indicating that the operation should be |
| | | * terminated; that no further pre-operation processing should be performed |
| | | * and that the server should send the response immediately. It also adds |
| | | * a CONSTRAINT_VIOLATION result code and the specified error message to |
| | | * the specified operation. |
| | | * |
| | | * @param operation The operation to add the result code and message to. |
| | | * |
| | | * @param message The message to add to the operation. |
| | | * |
| | | * @return Returns a plugin result instance that halts further processing |
| | | * on this operation. |
| | | */ |
| | | private PreOperationPluginResult |
| | | getPluginErrorResult(PreOperationOperation operation, Message message) { |
| | | operation.appendErrorMessage(message); |
| | | operation.setResultCode(ResultCode.CONSTRAINT_VIOLATION); |
| | | return new PreOperationPluginResult(false, false, true); |
| | | } |
| | | |
| | | /** |
| | | * Searches all of the the attribute types of the specified RDN for matches |
| | | * in the unique attribute type list. If matches are found, then the |
| | | * corresponding values are added to a list of values that will be eventually |
| | | * searched for uniqueness. |
| | | |
| | | * @param rdn The RDN to examine. |
| | | * |
| | | * @return Returns a list of attribute values from the RDN that matches the |
| | | * unique attribute type list. |
| | | */ |
| | | private List<AttributeValue> getRDNAttributeValues(RDN rdn) { |
| | | LinkedList<AttributeValue> valueList= |
| | | new LinkedList<AttributeValue>(); |
| | | int numAVAs = rdn.getNumValues(); |
| | | for (int i = 0; i < numAVAs; i++) { |
| | | if(uniqueAttributeTypes.contains(rdn.getAttributeType(i))) |
| | | valueList.add(rdn.getAttributeValue(i)); |
| | | } |
| | | return valueList; |
| | | } |
| | | |
| | | /** |
| | | * Searches all of the attribute types of the specified entry for matches |
| | | * in the unique attribute type list. Ff matches are found, then the |
| | | * corresponding values are added to a list of values that will eventually |
| | | * be searched for uniqueness. |
| | | * |
| | | * @param entry The entry to examine. |
| | | * |
| | | * @return Returns a list of attribute values from the entry that matches the |
| | | * unique attribute type list. |
| | | */ |
| | | private List<AttributeValue> getEntryAttributeValues(Entry entry) { |
| | | LinkedList<AttributeValue> valueList=new LinkedList<AttributeValue>(); |
| | | for(AttributeType attributeType : uniqueAttributeTypes) { |
| | | if(entry.hasAttribute(attributeType)) { |
| | | List<Attribute> attrList=entry.getAttribute(attributeType); |
| | | for (Attribute a : attrList) |
| | | valueList.addAll(a.getValues()); |
| | | } |
| | | } |
| | | return valueList; |
| | | } |
| | | |
| | | /** |
| | | * Iterate over the unique attribute type list calling a method that will |
| | | * search the specified modification list for each attribute type and add |
| | | * the corresponding values to a list of values. |
| | | * |
| | | * @param modificationList The modification list to search over. |
| | | * |
| | | * @param modifedEntry The copy of the entry with modifications applied. |
| | | * |
| | | * @return Returns a list of attribute values from the modification list |
| | | * that matches the unique attribute type list. |
| | | */ |
| | | private List<AttributeValue> |
| | | getModificationAttributeValues(List<Modification> modificationList, |
| | | Entry modifedEntry) { |
| | | LinkedList<AttributeValue> valueList = |
| | | new LinkedList<AttributeValue>(); |
| | | for(AttributeType attributeType : uniqueAttributeTypes) |
| | | getModValuesForAttribute(modificationList, attributeType, valueList, |
| | | modifedEntry); |
| | | return valueList; |
| | | } |
| | | |
| | | /** |
| | | * Searches the specified modification list for the provided attribute type. |
| | | * If a match is found than the attribute value is added to a list of |
| | | * attribute values that will be eventually searched for uniqueness. |
| | | * |
| | | * @param modificationList The modification list to search over. |
| | | * |
| | | * @param attributeType The attribute type to search for. |
| | | * |
| | | * @param valueList A list of attribute values to put the values in. |
| | | * |
| | | * @param modifiedEntry A copy of the entry with modifications applied. |
| | | */ |
| | | private void |
| | | getModValuesForAttribute(List<Modification> modificationList, |
| | | AttributeType attributeType, |
| | | LinkedList<AttributeValue> valueList, |
| | | Entry modifiedEntry) { |
| | | |
| | | for(Modification modification : modificationList) { |
| | | ModificationType modType=modification.getModificationType(); |
| | | //Skip delete modifications or modifications on attribute types not |
| | | //matching the specified type. |
| | | if(modType == ModificationType.DELETE || |
| | | !modification.getAttribute().getAttributeType().equals(attributeType)) |
| | | continue; |
| | | //Increment uses modified entry to get value for the attribute type. |
| | | if(modType == ModificationType.INCREMENT) { |
| | | List<Attribute> modifiedAttrs = |
| | | modifiedEntry.getAttribute(attributeType, |
| | | modification.getAttribute().getOptions()); |
| | | if (modifiedAttrs != null) { |
| | | for (Attribute a : modifiedAttrs) |
| | | valueList.addAll(a.getValues()); |
| | | } |
| | | } else { |
| | | Attribute modifiedAttribute=modification.getAttribute(); |
| | | if(modifiedAttribute.hasValue()) |
| | | valueList.addAll(modifiedAttribute.getValues()); |
| | | } |
| | | } |
| | | } |
| | | |
| | | |
| | | /** |
| | | * Iterates over the base DNs configured by the plugin entry searching for |
| | | * value matches. If the base DN list is empty then the public naming |
| | | * contexts are used instead. |
| | | * |
| | | * @param valueList The list of values to search for. |
| | | * |
| | | * @param entryDN The DN of the entry related to the operation. |
| | | * |
| | | * @return Returns <code>true</code> if a value is unique. |
| | | */ |
| | | private boolean |
| | | searchAllBaseDNs(List<AttributeValue> valueList, DN entryDN) { |
| | | if(valueList.isEmpty()) |
| | | return true; |
| | | if(baseDNs.isEmpty()) { |
| | | for(DN baseDN : DirectoryServer.getPublicNamingContexts().keySet()) { |
| | | if(searchBaseDN(valueList, baseDN, entryDN)) |
| | | return false; |
| | | } |
| | | } else { |
| | | for(DN baseDN : baseDNs) { |
| | | if(searchBaseDN(valueList, baseDN, entryDN)) |
| | | return false; |
| | | } |
| | | } |
| | | return true; |
| | | } |
| | | |
| | | /** |
| | | * Search a single base DN for all the values in a specified value list. |
| | | * A filter is created to search all the attribute at once for each |
| | | * value in the list. |
| | | * |
| | | * @param valueList The list of values to search for. |
| | | * |
| | | * @param baseDN The base DN to base the search at. |
| | | * |
| | | * @param entryDN The DN of the entry related to the operation. |
| | | * |
| | | * @return Returns <code>true</code> if the values are not unique under the |
| | | * under the base DN. |
| | | */ |
| | | private boolean |
| | | searchBaseDN(List<AttributeValue> valueList, DN baseDN, |
| | | DN entryDN) { |
| | | //Filter set to hold component filters. |
| | | HashSet<SearchFilter> componentFilters=new HashSet<SearchFilter>(); |
| | | for(AttributeValue value : valueList) { |
| | | //Iterate over the unique attribute list and build a equality filter |
| | | //using each attribute type in the list and the current value being |
| | | //matched. |
| | | for(AttributeType attributeType : uniqueAttributeTypes) |
| | | componentFilters.add(SearchFilter.createEqualityFilter(attributeType, |
| | | value)); |
| | | //Perform the search using the OR filter created from the filter |
| | | //components created above. |
| | | InternalClientConnection conn = |
| | | InternalClientConnection.getRootConnection(); |
| | | InternalSearchOperation operation = conn.processSearch(baseDN, |
| | | SearchScope.WHOLE_SUBTREE, |
| | | DereferencePolicy.NEVER_DEREF_ALIASES, 0, 0, true, |
| | | SearchFilter.createORFilter(componentFilters), |
| | | null); |
| | | switch (operation.getResultCode()) { |
| | | case SUCCESS: |
| | | break; |
| | | |
| | | case NO_SUCH_OBJECT: |
| | | //This base DN doesn't exist, return false. |
| | | return false; |
| | | |
| | | case SIZE_LIMIT_EXCEEDED: |
| | | case TIME_LIMIT_EXCEEDED: |
| | | case ADMIN_LIMIT_EXCEEDED: |
| | | default: |
| | | //Couldn't determine if the attribute is unique because an |
| | | //administrative limit was reached during the search. Fail the |
| | | //operation by returning true. Possibly log an error here? |
| | | return true; |
| | | } |
| | | for (SearchResultEntry entry : operation.getSearchEntries()) { |
| | | //Only allow the entry DN to exist. The user might be modifying |
| | | //the attribute values and putting the same value back. Any other entry |
| | | //means the value is not unique. |
| | | if(!entry.getDN().equals(entryDN)) |
| | | return true; |
| | | } |
| | | componentFilters.clear(); |
| | | } |
| | | return false; |
| | | return alerts; |
| | | } |
| | | } |
| | | |