001/*
002 * The contents of this file are subject to the terms of the Common Development and
003 * Distribution License (the License). You may not use this file except in compliance with the
004 * License.
005 *
006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
007 * specific language governing permission and limitations under the License.
008 *
009 * When distributing Covered Software, include this CDDL Header Notice in each file and include
010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
011 * Header, with the fields enclosed by brackets [] replaced by your own identifying
012 * information: "Portions Copyright [year] [name of copyright owner]".
013 *
014 * Copyright 2008-2009 Sun Microsystems, Inc.
015 * Portions Copyright 2011-2016 ForgeRock AS.
016 */
017package org.opends.server.plugins;
018
019import java.util.ArrayList;
020import java.util.LinkedHashMap;
021import java.util.LinkedHashSet;
022import java.util.LinkedList;
023import java.util.List;
024import java.util.Map;
025import java.util.Set;
026import java.util.concurrent.ConcurrentHashMap;
027
028import org.forgerock.i18n.LocalizableMessage;
029import org.forgerock.i18n.slf4j.LocalizedLogger;
030import org.forgerock.opendj.config.server.ConfigChangeResult;
031import org.forgerock.opendj.config.server.ConfigException;
032import org.forgerock.opendj.ldap.AVA;
033import org.forgerock.opendj.ldap.ByteString;
034import org.forgerock.opendj.ldap.DN;
035import org.forgerock.opendj.ldap.ResultCode;
036import org.forgerock.opendj.ldap.SearchScope;
037import org.forgerock.opendj.ldap.schema.AttributeType;
038import org.forgerock.opendj.config.server.ConfigurationChangeListener;
039import org.forgerock.opendj.server.config.meta.PluginCfgDefn;
040import org.forgerock.opendj.server.config.server.PluginCfg;
041import org.forgerock.opendj.server.config.server.UniqueAttributePluginCfg;
042import org.opends.server.api.AlertGenerator;
043import org.opends.server.api.Backend;
044import org.opends.server.api.plugin.DirectoryServerPlugin;
045import org.opends.server.api.plugin.PluginResult;
046import org.opends.server.api.plugin.PluginResult.PostOperation;
047import org.opends.server.api.plugin.PluginResult.PreOperation;
048import org.opends.server.api.plugin.PluginType;
049import org.opends.server.core.DirectoryServer;
050import org.opends.server.protocols.internal.InternalClientConnection;
051import org.opends.server.protocols.internal.InternalSearchOperation;
052import org.opends.server.protocols.internal.SearchRequest;
053import org.opends.server.schema.SchemaConstants;
054import org.opends.server.types.Attribute;
055import org.opends.server.types.DirectoryException;
056import org.opends.server.types.Entry;
057import org.opends.server.types.IndexType;
058import org.opends.server.types.Modification;
059import org.opends.server.types.SearchFilter;
060import org.opends.server.types.SearchResultEntry;
061import org.opends.server.types.operation.PluginOperation;
062import org.opends.server.types.operation.PostOperationAddOperation;
063import org.opends.server.types.operation.PostOperationModifyDNOperation;
064import org.opends.server.types.operation.PostOperationModifyOperation;
065import org.opends.server.types.operation.PostSynchronizationAddOperation;
066import org.opends.server.types.operation.PostSynchronizationModifyDNOperation;
067import org.opends.server.types.operation.PostSynchronizationModifyOperation;
068import org.opends.server.types.operation.PreOperationAddOperation;
069import org.opends.server.types.operation.PreOperationModifyDNOperation;
070import org.opends.server.types.operation.PreOperationModifyOperation;
071
072import static org.opends.messages.PluginMessages.*;
073import static org.opends.server.protocols.internal.InternalClientConnection.*;
074import static org.opends.server.protocols.internal.Requests.*;
075import static org.opends.server.util.ServerConstants.*;
076
077/**
078 * This class implements a Directory Server plugin that can be used to ensure
079 * that all values for a given attribute or set of attributes are unique within
080 * the server (or optionally, below a specified set of base DNs).  It will
081 * examine all add, modify, and modify DN operations to determine whether any
082 * new conflicts are introduced.  If a conflict is detected then the operation
083 * will be rejected, unless that operation is being applied through
084 * synchronization in which case an alert will be generated to notify
085 * administrators of the problem.
086 */
087public class UniqueAttributePlugin
088        extends DirectoryServerPlugin<UniqueAttributePluginCfg>
089        implements ConfigurationChangeListener<UniqueAttributePluginCfg>,
090                   AlertGenerator
091{
092  /** The debug log tracer that will be used for this plugin. */
093  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
094
095
096
097  /**
098   * The set of attributes that will be requested when performing internal
099   * search operations.  This indicates that no attributes should be returned.
100   */
101  private static final Set<String> SEARCH_ATTRS = new LinkedHashSet<>(1);
102  static
103  {
104    SEARCH_ATTRS.add(SchemaConstants.NO_ATTRIBUTES);
105  }
106
107
108
109  /** Current plugin configuration. */
110  private UniqueAttributePluginCfg currentConfiguration;
111
112
113
114  /**
115   * The data structure to store the mapping between the attribute value and the
116   * corresponding dn.
117   */
118  private ConcurrentHashMap<ByteString,DN> uniqueAttrValue2Dn;
119
120
121
122  @Override
123  public final void initializePlugin(Set<PluginType> pluginTypes,
124                                     UniqueAttributePluginCfg configuration)
125          throws ConfigException
126  {
127    configuration.addUniqueAttributeChangeListener(this);
128    currentConfiguration = configuration;
129
130    for (PluginType t : pluginTypes)
131    {
132      switch (t)
133      {
134        case PRE_OPERATION_ADD:
135        case PRE_OPERATION_MODIFY:
136        case PRE_OPERATION_MODIFY_DN:
137        case POST_OPERATION_ADD:
138        case POST_OPERATION_MODIFY:
139        case POST_OPERATION_MODIFY_DN:
140        case POST_SYNCHRONIZATION_ADD:
141        case POST_SYNCHRONIZATION_MODIFY:
142        case POST_SYNCHRONIZATION_MODIFY_DN:
143          // These are acceptable.
144          break;
145
146        default:
147          throw new ConfigException(ERR_PLUGIN_UNIQUEATTR_INVALID_PLUGIN_TYPE.get(t));
148      }
149    }
150
151    Set<DN> cfgBaseDNs = configuration.getBaseDN();
152    if (cfgBaseDNs == null || cfgBaseDNs.isEmpty())
153    {
154      cfgBaseDNs = DirectoryServer.getPublicNamingContexts().keySet();
155    }
156
157    for (AttributeType t : configuration.getType())
158    {
159      for (DN baseDN : cfgBaseDNs)
160      {
161        Backend<?> b = DirectoryServer.getBackend(baseDN);
162        if (b != null && ! b.isIndexed(t, IndexType.EQUALITY))
163        {
164          throw new ConfigException(ERR_PLUGIN_UNIQUEATTR_ATTR_UNINDEXED.get(
165              configuration.dn(), t.getNameOrOID(), b.getBackendID()));
166        }
167      }
168    }
169
170    uniqueAttrValue2Dn  = new ConcurrentHashMap<>();
171    DirectoryServer.registerAlertGenerator(this);
172  }
173
174
175
176  @Override
177  public final void finalizePlugin()
178  {
179    currentConfiguration.removeUniqueAttributeChangeListener(this);
180    DirectoryServer.deregisterAlertGenerator(this);
181  }
182
183
184
185  @Override
186  public final PluginResult.PreOperation
187               doPreOperation(PreOperationAddOperation addOperation)
188  {
189    UniqueAttributePluginCfg config = currentConfiguration;
190    Entry entry = addOperation.getEntryToAdd();
191
192    Set<DN> baseDNs = getBaseDNs(config, entry.getName());
193    if (baseDNs == null)
194    {
195      // The entry is outside the scope of this plugin.
196      return PluginResult.PreOperation.continueOperationProcessing();
197    }
198
199    DN entryDN = entry.getName();
200    List<ByteString> recordedValues = new LinkedList<>();
201    for (AttributeType t : config.getType())
202    {
203      for (Attribute a : entry.getAttribute(t))
204      {
205        for (ByteString v : a)
206        {
207          PreOperation stop = checkUniqueness(entryDN, t, v, baseDNs, recordedValues, config);
208          if (stop != null)
209          {
210            return stop;
211          }
212        }
213      }
214    }
215
216    return PluginResult.PreOperation.continueOperationProcessing();
217  }
218
219
220
221  @Override
222  public final PluginResult.PreOperation
223               doPreOperation(PreOperationModifyOperation modifyOperation)
224  {
225    UniqueAttributePluginCfg config = currentConfiguration;
226    DN entryDN = modifyOperation.getEntryDN();
227
228    Set<DN> baseDNs = getBaseDNs(config, entryDN);
229    if (baseDNs == null)
230    {
231      // The entry is outside the scope of this plugin.
232      return PluginResult.PreOperation.continueOperationProcessing();
233    }
234
235    List<ByteString> recordedValues = new LinkedList<>();
236    for (Modification m : modifyOperation.getModifications())
237    {
238      Attribute a = m.getAttribute();
239      AttributeType t = a.getAttributeDescription().getAttributeType();
240      if (!isModifyingUniqueAttribute(t, config))
241      {
242        continue;
243      }
244
245      switch (m.getModificationType().asEnum())
246      {
247        case ADD:
248        case REPLACE:
249          for (ByteString v : a)
250          {
251            PreOperation stop =
252              checkUniqueness(entryDN, t, v, baseDNs, recordedValues, config);
253            if (stop != null)
254            {
255              return stop;
256            }
257          }
258          break;
259
260        case INCREMENT:
261          // We could calculate the new value, but we'll just take it from the updated entry.
262          Attribute updatedAttr = modifyOperation.getModifiedEntry().getExactAttribute(a.getAttributeDescription());
263          if (updatedAttr != null)
264          {
265            for (ByteString v : updatedAttr)
266            {
267              PreOperation stop = checkUniqueness(entryDN, t, v, baseDNs, recordedValues, config);
268              if (stop != null)
269              {
270                return stop;
271              }
272            }
273          }
274          break;
275
276        default:
277          // We don't need to look at this modification because it's not a
278          // modification type of interest.
279          continue;
280      }
281    }
282
283    return PluginResult.PreOperation.continueOperationProcessing();
284  }
285
286
287
288  private PreOperation checkUniqueness(DN entryDN, AttributeType t,
289      ByteString v, Set<DN> baseDNs, List<ByteString> recordedValues,
290      UniqueAttributePluginCfg config)
291  {
292    try
293    {
294      //Raise an exception if a conflicting concurrent operation is
295      //in progress. Otherwise, store this attribute value with its
296      //corresponding DN and proceed.
297      DN conflictDN = uniqueAttrValue2Dn.putIfAbsent(v, entryDN);
298      if (conflictDN == null)
299      {
300        recordedValues.add(v);
301        conflictDN = getConflictingEntryDN(baseDNs, entryDN,
302                                            config, v);
303      }
304      if (conflictDN != null)
305      {
306        // Before returning, we need to remove all values added
307        // in the uniqueAttrValue2Dn map, because PostOperation
308        // plugin does not get called.
309        for (ByteString v2 : recordedValues)
310        {
311          uniqueAttrValue2Dn.remove(v2);
312        }
313        LocalizableMessage msg = ERR_PLUGIN_UNIQUEATTR_ATTR_NOT_UNIQUE.get(
314            t.getNameOrOID(), v, conflictDN);
315        return PluginResult.PreOperation.stopProcessing(
316            ResultCode.CONSTRAINT_VIOLATION, msg);
317      }
318    }
319    catch (DirectoryException de)
320    {
321      logger.traceException(de);
322
323      LocalizableMessage message = ERR_PLUGIN_UNIQUEATTR_INTERNAL_ERROR.get(
324          de.getResultCode(), de.getMessageObject());
325
326      // Try some cleanup before returning, to avoid memory leaks
327      for (ByteString v2 : recordedValues)
328      {
329        uniqueAttrValue2Dn.remove(v2);
330      }
331
332      return PluginResult.PreOperation.stopProcessing(
333          DirectoryServer.getServerErrorResultCode(), message);
334    }
335    return null;
336  }
337
338  @Override
339  public final PluginResult.PreOperation doPreOperation(
340                    PreOperationModifyDNOperation modifyDNOperation)
341  {
342    UniqueAttributePluginCfg config = currentConfiguration;
343
344    Set<DN> baseDNs = getBaseDNs(config,
345                                 modifyDNOperation.getUpdatedEntry().getName());
346    if (baseDNs == null)
347    {
348      // The entry is outside the scope of this plugin.
349      return PluginResult.PreOperation.continueOperationProcessing();
350    }
351
352    List<ByteString> recordedValues = new LinkedList<>();
353    for (AVA ava : modifyDNOperation.getNewRDN())
354    {
355      AttributeType t = ava.getAttributeType();
356      if (!isModifyingUniqueAttribute(t, config))
357      {
358        continue;
359      }
360
361      ByteString v = ava.getAttributeValue();
362      DN entryDN = modifyDNOperation.getEntryDN();
363      PreOperation stop =
364          checkUniqueness(entryDN, t, v, baseDNs, recordedValues, config);
365      if (stop != null)
366      {
367        return stop;
368      }
369    }
370
371    return PluginResult.PreOperation.continueOperationProcessing();
372  }
373
374  private boolean isModifyingUniqueAttribute(AttributeType t, UniqueAttributePluginCfg config)
375  {
376    return config.getType().contains(t);
377  }
378
379  @Override
380  public final void doPostSynchronization(
381                         PostSynchronizationAddOperation addOperation)
382  {
383    UniqueAttributePluginCfg config = currentConfiguration;
384    Entry entry = addOperation.getEntryToAdd();
385
386    Set<DN> baseDNs = getBaseDNs(config, entry.getName());
387    if (baseDNs == null)
388    {
389      // The entry is outside the scope of this plugin.
390      return;
391    }
392
393    DN entryDN = entry.getName();
394    for (AttributeType t : config.getType())
395    {
396      for (Attribute a : entry.getAttribute(t))
397      {
398        for (ByteString v : a)
399        {
400          sendAlertForUnresolvedConflict(addOperation, entryDN, entryDN, t, v, baseDNs, config);
401        }
402      }
403    }
404  }
405
406
407
408  @Override
409  public final void doPostSynchronization(
410                         PostSynchronizationModifyOperation modifyOperation)
411  {
412    UniqueAttributePluginCfg config = currentConfiguration;
413    DN entryDN = modifyOperation.getEntryDN();
414
415    Set<DN> baseDNs = getBaseDNs(config, entryDN);
416    if (baseDNs == null)
417    {
418      // The entry is outside the scope of this plugin.
419      return;
420    }
421
422    for (Modification m : modifyOperation.getModifications())
423    {
424      Attribute a = m.getAttribute();
425      AttributeType t = a.getAttributeDescription().getAttributeType();
426      if (!isModifyingUniqueAttribute(t, config))
427      {
428        continue;
429      }
430
431      switch (m.getModificationType().asEnum())
432      {
433        case ADD:
434        case REPLACE:
435          for (ByteString v : a)
436          {
437            sendAlertForUnresolvedConflict(modifyOperation, entryDN, entryDN, t,
438                v, baseDNs, config);
439          }
440          break;
441
442        case INCREMENT:
443          // We could calculate the new value, but we'll just take it from the updated entry.
444          Attribute updatedAttr = modifyOperation.getModifiedEntry().getExactAttribute(a.getAttributeDescription());
445          if (updatedAttr != null)
446          {
447            for (ByteString v : updatedAttr)
448            {
449              sendAlertForUnresolvedConflict(modifyOperation, entryDN,
450                  entryDN, t, v, baseDNs, config);
451            }
452          }
453          break;
454
455        default:
456          // We don't need to look at this modification because it's not a
457          // modification type of interest.
458          continue;
459      }
460    }
461  }
462
463
464
465  @Override
466  public final void doPostSynchronization(
467                         PostSynchronizationModifyDNOperation modifyDNOperation)
468  {
469    UniqueAttributePluginCfg config = currentConfiguration;
470
471    Set<DN> baseDNs = getBaseDNs(config,
472                                 modifyDNOperation.getUpdatedEntry().getName());
473    if (baseDNs == null)
474    {
475      // The entry is outside the scope of this plugin.
476      return;
477    }
478
479    DN entryDN = modifyDNOperation.getEntryDN();
480    DN updatedEntryDN = modifyDNOperation.getUpdatedEntry().getName();
481    for (AVA ava : modifyDNOperation.getNewRDN())
482    {
483      AttributeType t = ava.getAttributeType();
484      if (isModifyingUniqueAttribute(t, config))
485      {
486        ByteString v = ava.getAttributeValue();
487        sendAlertForUnresolvedConflict(modifyDNOperation, entryDN, updatedEntryDN, t, v, baseDNs, config);
488      }
489    }
490  }
491
492
493
494  private void sendAlertForUnresolvedConflict(PluginOperation operation,
495      DN entryDN, DN updatedEntryDN, AttributeType t, ByteString v,
496      Set<DN> baseDNs, UniqueAttributePluginCfg config)
497  {
498    try
499    {
500      DN conflictDN = uniqueAttrValue2Dn.get(v);
501      if (conflictDN == null)
502      {
503        conflictDN = getConflictingEntryDN(baseDNs, entryDN, config, v);
504      }
505      if (conflictDN != null)
506      {
507        LocalizableMessage message = ERR_PLUGIN_UNIQUEATTR_SYNC_NOT_UNIQUE.get(
508                               t.getNameOrOID(),
509                               operation.getConnectionID(),
510                               operation.getOperationID(),
511                               v,
512                               updatedEntryDN,
513                               conflictDN);
514        DirectoryServer.sendAlertNotification(this,
515                             ALERT_TYPE_UNIQUE_ATTR_SYNC_CONFLICT,
516                             message);
517      }
518    }
519    catch (DirectoryException de)
520    {
521      logger.traceException(de);
522
523      LocalizableMessage message = ERR_PLUGIN_UNIQUEATTR_INTERNAL_ERROR_SYNC.get(
524                            operation.getConnectionID(),
525                            operation.getOperationID(),
526                            updatedEntryDN,
527                            de.getResultCode(),
528                            de.getMessageObject());
529      DirectoryServer.sendAlertNotification(this,
530                           ALERT_TYPE_UNIQUE_ATTR_SYNC_ERROR, message);
531    }
532  }
533
534
535
536  /**
537   * Retrieves the set of base DNs below which uniqueness checks should be
538   * performed.  If no uniqueness checks should be performed for the specified
539   * entry, then {@code null} will be returned.
540   *
541   * @param  config   The plugin configuration to use to make the determination.
542   * @param  entryDN  The DN of the entry for which the checks will be
543   *                  performed.
544   */
545  private Set<DN> getBaseDNs(UniqueAttributePluginCfg config, DN entryDN)
546  {
547    Set<DN> baseDNs = config.getBaseDN();
548    if (baseDNs == null || baseDNs.isEmpty())
549    {
550      baseDNs = DirectoryServer.getPublicNamingContexts().keySet();
551    }
552
553    for (DN baseDN : baseDNs)
554    {
555      if (entryDN.isSubordinateOrEqualTo(baseDN))
556      {
557        return baseDNs;
558      }
559    }
560
561    return null;
562  }
563
564
565
566  /**
567   * Retrieves the DN of the first entry identified that conflicts with the
568   * provided value.
569   *
570   * @param  baseDNs   The set of base DNs below which the search is to be
571   *                   performed.
572   * @param  targetDN  The DN of the entry at which the change is targeted.  If
573   *                   a conflict is found in that entry, then it will be
574   *                   ignored.
575   * @param  config    The plugin configuration to use when making the
576   *                   determination.
577   * @param  value     The value for which to identify any conflicting entries.
578   *
579   * @return  The DN of the first entry identified that contains a conflicting
580   *          value.
581   *
582   * @throws  DirectoryException  If a problem occurred while attempting to
583   *                              make the determination.
584   */
585  private DN getConflictingEntryDN(Set<DN> baseDNs, DN targetDN,
586                                   UniqueAttributePluginCfg config,
587                                   ByteString value)
588          throws DirectoryException
589  {
590    SearchFilter filter;
591    Set<AttributeType> attrTypes = config.getType();
592    if (attrTypes.size() == 1)
593    {
594      filter = SearchFilter.createEqualityFilter(attrTypes.iterator().next(),
595                                                 value);
596    }
597    else
598    {
599      List<SearchFilter> equalityFilters = new ArrayList<>(attrTypes.size());
600      for (AttributeType t : attrTypes)
601      {
602        equalityFilters.add(SearchFilter.createEqualityFilter(t, value));
603      }
604      filter = SearchFilter.createORFilter(equalityFilters);
605    }
606
607    InternalClientConnection conn = getRootConnection();
608    for (DN baseDN : baseDNs)
609    {
610      final SearchRequest request = newSearchRequest(baseDN, SearchScope.WHOLE_SUBTREE, filter)
611          .setSizeLimit(2)
612          .addAttribute(SEARCH_ATTRS);
613      InternalSearchOperation searchOperation = conn.processSearch(request);
614      for (SearchResultEntry e : searchOperation.getSearchEntries())
615      {
616        if (! e.getName().equals(targetDN))
617        {
618          return e.getName();
619        }
620      }
621
622      switch (searchOperation.getResultCode().asEnum())
623      {
624        case SUCCESS:
625        case NO_SUCH_OBJECT:
626          // These are fine.  Either the search was successful or the base DN
627          // didn't exist.
628          break;
629
630        default:
631          // An error occurred that prevented the search from completing
632          // successfully.
633          throw new DirectoryException(searchOperation.getResultCode(),
634                         searchOperation.getErrorMessage().toMessage());
635      }
636    }
637
638    // If we've gotten here, then no conflict was found.
639    return null;
640  }
641
642
643
644  @Override
645  public boolean isConfigurationAcceptable(PluginCfg configuration,
646                                           List<LocalizableMessage> unacceptableReasons)
647  {
648    UniqueAttributePluginCfg cfg = (UniqueAttributePluginCfg) configuration;
649    return isConfigurationChangeAcceptable(cfg, unacceptableReasons);
650  }
651
652
653
654  @Override
655  public boolean isConfigurationChangeAcceptable(
656                      UniqueAttributePluginCfg configuration,
657                      List<LocalizableMessage> unacceptableReasons)
658  {
659    boolean configAcceptable = true;
660
661    for (PluginCfgDefn.PluginType pluginType : configuration.getPluginType())
662    {
663      switch (pluginType)
664      {
665        case PREOPERATIONADD:
666        case PREOPERATIONMODIFY:
667        case PREOPERATIONMODIFYDN:
668        case POSTOPERATIONADD:
669        case POSTOPERATIONMODIFY:
670        case POSTOPERATIONMODIFYDN:
671        case POSTSYNCHRONIZATIONADD:
672        case POSTSYNCHRONIZATIONMODIFY:
673        case POSTSYNCHRONIZATIONMODIFYDN:
674          // These are acceptable.
675          break;
676
677        default:
678          unacceptableReasons.add(ERR_PLUGIN_UNIQUEATTR_INVALID_PLUGIN_TYPE.get(pluginType));
679          configAcceptable = false;
680      }
681    }
682
683    Set<DN> cfgBaseDNs = configuration.getBaseDN();
684    if (cfgBaseDNs == null || cfgBaseDNs.isEmpty())
685    {
686      cfgBaseDNs = DirectoryServer.getPublicNamingContexts().keySet();
687    }
688
689    for (AttributeType t : configuration.getType())
690    {
691      for (DN baseDN : cfgBaseDNs)
692      {
693        Backend<?> b = DirectoryServer.getBackend(baseDN);
694        if (b != null && ! b.isIndexed(t, IndexType.EQUALITY))
695        {
696          unacceptableReasons.add(ERR_PLUGIN_UNIQUEATTR_ATTR_UNINDEXED.get(
697              configuration.dn(), t.getNameOrOID(), b.getBackendID()));
698          configAcceptable = false;
699        }
700      }
701    }
702
703    return configAcceptable;
704  }
705
706
707
708  @Override
709  public ConfigChangeResult applyConfigurationChange(
710                                 UniqueAttributePluginCfg newConfiguration)
711  {
712    currentConfiguration = newConfiguration;
713    return new ConfigChangeResult();
714  }
715
716
717
718  @Override
719  public DN getComponentEntryDN()
720  {
721    return currentConfiguration.dn();
722  }
723
724
725
726  @Override
727  public String getClassName()
728  {
729    return UniqueAttributePlugin.class.getName();
730  }
731
732
733
734  @Override
735  public Map<String,String> getAlerts()
736  {
737    Map<String,String> alerts = new LinkedHashMap<>(2);
738
739    alerts.put(ALERT_TYPE_UNIQUE_ATTR_SYNC_CONFLICT,
740               ALERT_DESCRIPTION_UNIQUE_ATTR_SYNC_CONFLICT);
741    alerts.put(ALERT_TYPE_UNIQUE_ATTR_SYNC_ERROR,
742               ALERT_DESCRIPTION_UNIQUE_ATTR_SYNC_ERROR);
743
744    return alerts;
745  }
746
747
748
749  @Override
750  public final PluginResult.PostOperation
751       doPostOperation(PostOperationAddOperation addOperation)
752  {
753    UniqueAttributePluginCfg config = currentConfiguration;
754    Entry entry = addOperation.getEntryToAdd();
755
756    Set<DN> baseDNs = getBaseDNs(config, entry.getName());
757    if (baseDNs == null)
758    {
759      // The entry is outside the scope of this plugin.
760      return PluginResult.PostOperation.continueOperationProcessing();
761    }
762
763    //Remove the attribute value from the map.
764    for (AttributeType t : config.getType())
765    {
766      for (Attribute a : entry.getAttribute(t))
767      {
768        for (ByteString v : a)
769        {
770          uniqueAttrValue2Dn.remove(v);
771        }
772      }
773    }
774
775    return PluginResult.PostOperation.continueOperationProcessing();
776  }
777
778
779
780
781  @Override
782  public final PluginResult.PostOperation
783       doPostOperation(PostOperationModifyOperation modifyOperation)
784  {
785    UniqueAttributePluginCfg config = currentConfiguration;
786    DN entryDN = modifyOperation.getEntryDN();
787
788    Set<DN> baseDNs = getBaseDNs(config, entryDN);
789    if (baseDNs == null)
790    {
791      // The entry is outside the scope of this plugin.
792      return PluginResult.PostOperation.continueOperationProcessing();
793    }
794
795    for (Modification m : modifyOperation.getModifications())
796    {
797      Attribute a = m.getAttribute();
798      AttributeType t = a.getAttributeDescription().getAttributeType();
799      if (!isModifyingUniqueAttribute(t, config))
800      {
801        continue;
802      }
803
804      switch (m.getModificationType().asEnum())
805      {
806        case ADD:
807        case REPLACE:
808          for (ByteString v : a)
809          {
810            uniqueAttrValue2Dn.remove(v);
811          }
812          break;
813
814        case INCREMENT:
815          // We could calculate the new value, but we'll just take it from the updated entry.
816          Attribute updatedAttr = modifyOperation.getModifiedEntry().getExactAttribute(a.getAttributeDescription());
817          if (updatedAttr != null)
818          {
819            for (ByteString v : updatedAttr)
820            {
821              uniqueAttrValue2Dn.remove(v);
822            }
823          }
824          break;
825
826        default:
827          // We don't need to look at this modification because it's not a
828          // modification type of interest.
829          continue;
830      }
831    }
832
833    return PluginResult.PostOperation.continueOperationProcessing();
834  }
835
836
837
838  @Override
839  public final PluginResult.PostOperation
840       doPostOperation(PostOperationModifyDNOperation modifyDNOperation)
841  {
842    UniqueAttributePluginCfg config = currentConfiguration;
843    Set<DN> baseDNs = getBaseDNs(config,
844                                 modifyDNOperation.getUpdatedEntry().getName());
845    if (baseDNs == null)
846    {
847      // The entry is outside the scope of this plugin.
848      return PostOperation.continueOperationProcessing();
849    }
850
851    for (AVA ava : modifyDNOperation.getNewRDN())
852    {
853      AttributeType t = ava.getAttributeType();
854      if (isModifyingUniqueAttribute(t, config))
855      {
856        uniqueAttrValue2Dn.remove(ava.getAttributeValue());
857      }
858    }
859    return PostOperation.continueOperationProcessing();
860  }
861}
862