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