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