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