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