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