/* * 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 * * * Copyright 2008-2009 Sun Microsystems, Inc. * Portions Copyright 2011-2013 ForgeRock AS */ package org.opends.server.plugins; import java.util.LinkedList; import java.util.ArrayList; import java.util.concurrent.ConcurrentHashMap; 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.Backend; import org.opends.server.api.plugin.DirectoryServerPlugin; import org.opends.server.api.plugin.PluginType; import org.opends.server.api.plugin.PluginResult; import org.opends.server.api.plugin.PluginResult.PostOperation; import org.opends.server.config.ConfigException; 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.schema.SchemaConstants; 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.IndexType; 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.PostOperationAddOperation; import org.opends.server.types.operation.PostOperationModifyDNOperation; import org.opends.server.types.operation.PostOperationModifyOperation; 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 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 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>, AlertGenerator { /** * The debug log tracer that will be used for this plugin. */ private static final DebugTracer TRACER = getTracer(); /** * 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(SchemaConstants.NO_ATTRIBUTES); } //Current plugin configuration. private UniqueAttributePluginCfg currentConfiguration; // The data structure to store the mapping between the attribute value // and the corresponding dn. private ConcurrentHashMap<AttributeValue,DN> uniqueAttrValue2Dn; /** * {@inheritDoc} */ @Override() public final void initializePlugin(Set<PluginType> pluginTypes, UniqueAttributePluginCfg configuration) throws ConfigException { configuration.addUniqueAttributeChangeListener(this); currentConfiguration = configuration; for (PluginType t : pluginTypes) { switch (t) { case PRE_OPERATION_ADD: case PRE_OPERATION_MODIFY: case PRE_OPERATION_MODIFY_DN: case POST_OPERATION_ADD: case POST_OPERATION_MODIFY: case POST_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); } } Set<DN> cfgBaseDNs = configuration.getBaseDN(); if ((cfgBaseDNs == null) || cfgBaseDNs.isEmpty()) { cfgBaseDNs = DirectoryServer.getPublicNamingContexts().keySet(); } for (AttributeType t : configuration.getType()) { for (DN baseDN : cfgBaseDNs) { Backend b = DirectoryServer.getBackend(baseDN); if ((b != null) && (! b.isIndexed(t, IndexType.EQUALITY))) { throw new ConfigException(ERR_PLUGIN_UNIQUEATTR_ATTR_UNINDEXED.get( configuration.dn().toString(), t.getNameOrOID(), b.getBackendID())); } } } uniqueAttrValue2Dn = new ConcurrentHashMap<AttributeValue,DN>(); DirectoryServer.registerAlertGenerator(this); } /** * {@inheritDoc} */ @Override() public final void finalizePlugin() { currentConfiguration.removeUniqueAttributeChangeListener(this); DirectoryServer.deregisterAlertGenerator(this); } /** * {@inheritDoc} */ @Override() public final PluginResult.PreOperation 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 PluginResult.PreOperation.continueOperationProcessing(); } LinkedList<AttributeValue> recordedValues = new LinkedList<AttributeValue>(); for (AttributeType t : config.getType()) { List<Attribute> attrList = entry.getAttribute(t); if (attrList != null) { for (Attribute a : attrList) { for (AttributeValue v : a) { try { DN conflictDN = null; //Raise an exception if a conflicting concurrent operation is in //progress. Otherwise, store this attribute value with its //corresponding DN and proceed. if((conflictDN= uniqueAttrValue2Dn.putIfAbsent(v, entry.getDN()))==null) { recordedValues.add(v); conflictDN = getConflictingEntryDN(baseDNs, entry.getDN(), config, v); } if (conflictDN != null) { // Before returning, we need to remove all values added // in the uniqueAttrValue2Dn map, because PostOperation // plugin does not get called. for (AttributeValue v2 : recordedValues) { uniqueAttrValue2Dn.remove(v2); } Message msg = ERR_PLUGIN_UNIQUEATTR_ATTR_NOT_UNIQUE.get( t.getNameOrOID(), v.getValue().toString(), conflictDN.toString()); return PluginResult.PreOperation.stopProcessing( ResultCode.CONSTRAINT_VIOLATION, msg); } } catch (DirectoryException de) { if (debugEnabled()) { TRACER.debugCaught(DebugLogLevel.ERROR, de); } Message m = ERR_PLUGIN_UNIQUEATTR_INTERNAL_ERROR.get( de.getResultCode().toString(), de.getMessageObject()); // Try some cleanup before returning, to avoid memory leaks for (AttributeValue v2 : recordedValues) { uniqueAttrValue2Dn.remove(v2); } return PluginResult.PreOperation.stopProcessing( DirectoryServer.getServerErrorResultCode(), m); } } } } } return PluginResult.PreOperation.continueOperationProcessing(); } /** * {@inheritDoc} */ @Override() public final PluginResult.PreOperation 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 PluginResult.PreOperation.continueOperationProcessing(); } LinkedList<AttributeValue> recordedValues = new LinkedList<AttributeValue>(); for (Modification m : modifyOperation.getModifications()) { Attribute a = m.getAttribute(); AttributeType t = a.getAttributeType(); if (! config.getType().contains(t)) { // This modification isn't for a unique attribute. continue; } switch (m.getModificationType()) { case ADD: case REPLACE: for (AttributeValue v : a) { try { DN conflictDN = null; //Raise an exception if a conflicting concurrent operation is in //progress. Otherwise, store this attribute value with its //corresponding DN and proceed. if((conflictDN= uniqueAttrValue2Dn.putIfAbsent(v, entryDN))==null) { recordedValues.add(v); conflictDN = getConflictingEntryDN(baseDNs, entryDN, config, v); } if (conflictDN != null) { // Before returning, we need to remove all values added // in the uniqueAttrValue2Dn map, because PostOperation // plugin does not get called. for (AttributeValue v2 : recordedValues) { uniqueAttrValue2Dn.remove(v2); } Message msg = ERR_PLUGIN_UNIQUEATTR_ATTR_NOT_UNIQUE.get( t.getNameOrOID(), v.getValue().toString(), conflictDN.toString()); return PluginResult.PreOperation.stopProcessing( ResultCode.CONSTRAINT_VIOLATION, msg); } } catch (DirectoryException de) { if (debugEnabled()) { TRACER.debugCaught(DebugLogLevel.ERROR, de); } Message message = ERR_PLUGIN_UNIQUEATTR_INTERNAL_ERROR.get( de.getResultCode().toString(), de.getMessageObject()); return PluginResult.PreOperation.stopProcessing( DirectoryServer.getServerErrorResultCode(), 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) { try { DN conflictDN = null; //Raise an exception if a conflicting concurrent operation is //in progress. Otherwise, store this attribute value with its //corresponding DN and proceed. if((conflictDN= uniqueAttrValue2Dn.putIfAbsent(v, entryDN))==null) { recordedValues.add(v); conflictDN = getConflictingEntryDN(baseDNs, entryDN, config, v); } if (conflictDN != null) { // Before returning, we need to remove all values added // in the uniqueAttrValue2Dn map, because PostOperation // plugin does not get called. for (AttributeValue v2 : recordedValues) { uniqueAttrValue2Dn.remove(v2); } Message msg = ERR_PLUGIN_UNIQUEATTR_ATTR_NOT_UNIQUE.get( t.getNameOrOID(), v.getValue().toString(), conflictDN.toString()); return PluginResult.PreOperation.stopProcessing( ResultCode.CONSTRAINT_VIOLATION, msg); } } catch (DirectoryException de) { if (debugEnabled()) { TRACER.debugCaught(DebugLogLevel.ERROR, de); } Message message = ERR_PLUGIN_UNIQUEATTR_INTERNAL_ERROR.get( de.getResultCode().toString(), de.getMessageObject()); // Try some cleanup before returning, to avoid memory leaks for (AttributeValue v2 : recordedValues) { uniqueAttrValue2Dn.remove(v2); } return PluginResult.PreOperation.stopProcessing( DirectoryServer.getServerErrorResultCode(), message); } } } } break; default: // We don't need to look at this modification because it's not a // modification type of interest. continue; } } return PluginResult.PreOperation.continueOperationProcessing(); } /** * {@inheritDoc} */ @Override() public final PluginResult.PreOperation 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 PluginResult.PreOperation.continueOperationProcessing(); } LinkedList<AttributeValue> recordedValues = new LinkedList<AttributeValue>(); RDN newRDN = modifyDNOperation.getNewRDN(); for (int i=0; i < newRDN.getNumValues(); i++) { AttributeType t = newRDN.getAttributeType(i); if (! config.getType().contains(t)) { // We aren't interested in this attribute type. continue; } try { AttributeValue v = newRDN.getAttributeValue(i); DN conflictDN = null; //Raise an exception if a conflicting concurrent operation is in //progress. Otherwise, store this attribute value with its //corresponding DN and proceed. if((conflictDN=uniqueAttrValue2Dn.putIfAbsent( v, modifyDNOperation.getEntryDN()))==null) { recordedValues.add(v); conflictDN = getConflictingEntryDN(baseDNs, modifyDNOperation.getEntryDN(), config, v); } if (conflictDN != null) { // Before returning, we need to remove all values added // in the uniqueAttrValue2Dn map, because PostOperation // plugin does not get called. for (AttributeValue v2 : recordedValues) { uniqueAttrValue2Dn.remove(v2); } Message msg = ERR_PLUGIN_UNIQUEATTR_ATTR_NOT_UNIQUE.get( t.getNameOrOID(), v.getValue().toString(), conflictDN.toString()); return PluginResult.PreOperation.stopProcessing( ResultCode.CONSTRAINT_VIOLATION, msg); } } catch (DirectoryException de) { if (debugEnabled()) { TRACER.debugCaught(DebugLogLevel.ERROR, de); } Message m = ERR_PLUGIN_UNIQUEATTR_INTERNAL_ERROR.get( de.getResultCode().toString(), de.getMessageObject()); // Try some cleanup before returning, to avoid memory leaks for (AttributeValue v2 : recordedValues) { uniqueAttrValue2Dn.remove(v2); } return PluginResult.PreOperation.stopProcessing( DirectoryServer.getServerErrorResultCode(), m); } } return PluginResult.PreOperation.continueOperationProcessing(); } /** * {@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.getType()) { List<Attribute> attrList = entry.getAttribute(t); if (attrList != null) { for (Attribute a : attrList) { for (AttributeValue v : a) { try { DN conflictDN = null; if((conflictDN=uniqueAttrValue2Dn.get(v)) == null) { 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.getValue().toString(), 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.getType().contains(t)) { // This modification isn't for a unique attribute. continue; } switch (m.getModificationType()) { case ADD: case REPLACE: for (AttributeValue v : a) { try { DN conflictDN = null; if((conflictDN=uniqueAttrValue2Dn.get(v)) == null) { 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.getValue().toString(), 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) { try { DN conflictDN = null; if((conflictDN=uniqueAttrValue2Dn.get(v)) == null) { 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.getValue().toString(), 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.getType().contains(t)) { // We aren't interested in this attribute type. continue; } try { AttributeValue v = newRDN.getAttributeValue(i); DN conflictDN = null; if((conflictDN=uniqueAttrValue2Dn.get(v)) == null) { 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.getValue().toString(), 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.getBaseDN(); 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.getType(); 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) { boolean configAcceptable = true; for (PluginCfgDefn.PluginType pluginType : configuration.getPluginType()) { switch (pluginType) { case PREOPERATIONADD: case PREOPERATIONMODIFY: case PREOPERATIONMODIFYDN: case POSTOPERATIONADD: case POSTOPERATIONMODIFY: case POSTOPERATIONMODIFYDN: case POSTSYNCHRONIZATIONADD: case POSTSYNCHRONIZATIONMODIFY: case POSTSYNCHRONIZATIONMODIFYDN: // These are acceptable. break; default: Message message = ERR_PLUGIN_UNIQUEATTR_INVALID_PLUGIN_TYPE.get( pluginType.toString()); unacceptableReasons.add(message); configAcceptable = false; } } Set<DN> cfgBaseDNs = configuration.getBaseDN(); if ((cfgBaseDNs == null) || cfgBaseDNs.isEmpty()) { cfgBaseDNs = DirectoryServer.getPublicNamingContexts().keySet(); } for (AttributeType t : configuration.getType()) { for (DN baseDN : cfgBaseDNs) { Backend b = DirectoryServer.getBackend(baseDN); if ((b != null) && (! b.isIndexed(t, IndexType.EQUALITY))) { unacceptableReasons.add(ERR_PLUGIN_UNIQUEATTR_ATTR_UNINDEXED.get( configuration.dn().toString(), t.getNameOrOID(), b.getBackendID())); configAcceptable = false; } } } return configAcceptable; } /** * {@inheritDoc} */ public ConfigChangeResult applyConfigurationChange( UniqueAttributePluginCfg newConfiguration) { currentConfiguration = newConfiguration; return new ConfigChangeResult(ResultCode.SUCCESS, false); } /** * {@inheritDoc} */ public DN getComponentEntryDN() { return currentConfiguration.dn(); } /** * {@inheritDoc} */ public String getClassName() { return UniqueAttributePlugin.class.getName(); } /** * {@inheritDoc} */ public LinkedHashMap<String,String> getAlerts() { LinkedHashMap<String,String> alerts = new LinkedHashMap<String,String>(2); 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); return alerts; } /** * {@inheritDoc} */ @Override() public final PluginResult.PostOperation doPostOperation(PostOperationAddOperation 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 PluginResult.PostOperation.continueOperationProcessing(); } //Remove the attribute value from the map. for (AttributeType t : config.getType()) { List<Attribute> attrList = entry.getAttribute(t); if (attrList != null) { for (Attribute a : attrList) { for (AttributeValue v : a) { uniqueAttrValue2Dn.remove(v); } } } } return PluginResult.PostOperation.continueOperationProcessing(); } /** * {@inheritDoc} */ @Override() public final PluginResult.PostOperation doPostOperation(PostOperationModifyOperation 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 PluginResult.PostOperation.continueOperationProcessing(); } for (Modification m : modifyOperation.getModifications()) { Attribute a = m.getAttribute(); AttributeType t = a.getAttributeType(); if (! config.getType().contains(t)) { // This modification isn't for a unique attribute. continue; } switch (m.getModificationType()) { case ADD: case REPLACE: for (AttributeValue v : a) { uniqueAttrValue2Dn.remove(v); } 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) { uniqueAttrValue2Dn.remove(v); } } } break; default: // We don't need to look at this modification because it's not a // modification type of interest. continue; } } return PluginResult.PostOperation.continueOperationProcessing(); } /** * {@inheritDoc} */ @Override() public final PluginResult.PostOperation doPostOperation(PostOperationModifyDNOperation 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 PostOperation.continueOperationProcessing(); } RDN newRDN = modifyDNOperation.getNewRDN(); for (int i=0; i < newRDN.getNumValues(); i++) { AttributeType t = newRDN.getAttributeType(i); if (! config.getType().contains(t)) { // We aren't interested in this attribute type. continue; } AttributeValue v = newRDN.getAttributeValue(i); uniqueAttrValue2Dn.remove(v); } return PostOperation.continueOperationProcessing(); } }