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