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