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