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-2010 Sun Microsystems, Inc. 015 * Portions Copyright 2011-2016 ForgeRock AS. 016 */ 017package org.opends.server.workflowelement.localbackend; 018 019import java.util.LinkedList; 020import java.util.List; 021import java.util.ListIterator; 022import java.util.concurrent.atomic.AtomicBoolean; 023 024import org.forgerock.i18n.LocalizableMessage; 025import org.forgerock.i18n.LocalizableMessageBuilder; 026import org.forgerock.i18n.slf4j.LocalizedLogger; 027import org.forgerock.opendj.ldap.AVA; 028import org.forgerock.opendj.ldap.ByteString; 029import org.forgerock.opendj.ldap.ModificationType; 030import org.forgerock.opendj.ldap.ResultCode; 031import org.forgerock.opendj.ldap.schema.AttributeType; 032import org.opends.server.api.AccessControlHandler; 033import org.opends.server.api.Backend; 034import org.opends.server.api.ClientConnection; 035import org.opends.server.api.SynchronizationProvider; 036import org.opends.server.controls.LDAPAssertionRequestControl; 037import org.opends.server.controls.LDAPPostReadRequestControl; 038import org.opends.server.controls.LDAPPreReadRequestControl; 039import org.opends.server.core.AccessControlConfigManager; 040import org.opends.server.core.DirectoryServer; 041import org.opends.server.core.ModifyDNOperation; 042import org.opends.server.core.ModifyDNOperationWrapper; 043import org.opends.server.core.PersistentSearch; 044import org.opends.server.types.Attribute; 045import org.opends.server.types.Attributes; 046import org.opends.server.types.CanceledOperationException; 047import org.opends.server.types.Control; 048import org.forgerock.opendj.ldap.DN; 049import org.opends.server.types.DirectoryException; 050import org.opends.server.types.Entry; 051import org.opends.server.types.LockManager.DNLock; 052import org.opends.server.types.Modification; 053import org.forgerock.opendj.ldap.RDN; 054import org.opends.server.types.SearchFilter; 055import org.opends.server.types.operation.PostOperationModifyDNOperation; 056import org.opends.server.types.operation.PostResponseModifyDNOperation; 057import org.opends.server.types.operation.PostSynchronizationModifyDNOperation; 058import org.opends.server.types.operation.PreOperationModifyDNOperation; 059 060import static org.opends.messages.CoreMessages.*; 061import static org.opends.server.core.DirectoryServer.*; 062import static org.opends.server.types.AbstractOperation.*; 063import static org.opends.server.util.ServerConstants.*; 064import static org.opends.server.util.StaticUtils.*; 065import static org.opends.server.workflowelement.localbackend.LocalBackendWorkflowElement.*; 066 067/** 068 * This class defines an operation used to move an entry in a local backend 069 * of the Directory Server. 070 */ 071public class LocalBackendModifyDNOperation 072 extends ModifyDNOperationWrapper 073 implements PreOperationModifyDNOperation, 074 PostOperationModifyDNOperation, 075 PostResponseModifyDNOperation, 076 PostSynchronizationModifyDNOperation 077{ 078 private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); 079 080 /** The backend in which the operation is to be processed. */ 081 private Backend<?> backend; 082 083 /** Indicates whether the no-op control was included in the request. */ 084 private boolean noOp; 085 086 /** The client connection on which this operation was requested. */ 087 private ClientConnection clientConnection; 088 089 /** The original DN of the entry. */ 090 private DN entryDN; 091 092 /** The current entry, before it is renamed. */ 093 private Entry currentEntry; 094 095 /** The new entry, as it will appear after it has been renamed. */ 096 private Entry newEntry; 097 098 /** The LDAP post-read request control, if present in the request. */ 099 private LDAPPostReadRequestControl postReadRequest; 100 101 /** The LDAP pre-read request control, if present in the request. */ 102 private LDAPPreReadRequestControl preReadRequest; 103 104 /** The new RDN for the entry. */ 105 private RDN newRDN; 106 107 108 109 /** 110 * Creates a new operation that may be used to move an entry in a 111 * local backend of the Directory Server. 112 * 113 * @param operation The operation to enhance. 114 */ 115 public LocalBackendModifyDNOperation (ModifyDNOperation operation) 116 { 117 super(operation); 118 LocalBackendWorkflowElement.attachLocalOperation (operation, this); 119 } 120 121 122 123 /** 124 * Retrieves the current entry, before it is renamed. This will not be 125 * available to pre-parse plugins or during the conflict resolution portion of 126 * the synchronization processing. 127 * 128 * @return The current entry, or <CODE>null</CODE> if it is not yet 129 * available. 130 */ 131 @Override 132 public final Entry getOriginalEntry() 133 { 134 return currentEntry; 135 } 136 137 138 139 /** 140 * Retrieves the new entry, as it will appear after it is renamed. This will 141 * not be available to pre-parse plugins or during the conflict resolution 142 * portion of the synchronization processing. 143 * 144 * @return The updated entry, or <CODE>null</CODE> if it is not yet 145 * available. 146 */ 147 @Override 148 public final Entry getUpdatedEntry() 149 { 150 return newEntry; 151 } 152 153 154 155 /** 156 * Process this modify DN operation in a local backend. 157 * 158 * @param wfe 159 * The local backend work-flow element. 160 * @throws CanceledOperationException 161 * if this operation should be cancelled 162 */ 163 public void processLocalModifyDN(final LocalBackendWorkflowElement wfe) 164 throws CanceledOperationException 165 { 166 this.backend = wfe.getBackend(); 167 168 clientConnection = getClientConnection(); 169 170 // Check for a request to cancel this operation. 171 checkIfCanceled(false); 172 173 try 174 { 175 AtomicBoolean executePostOpPlugins = new AtomicBoolean(false); 176 processModifyDN(executePostOpPlugins); 177 178 // Invoke the post-operation or post-synchronization modify DN plugins. 179 if (isSynchronizationOperation()) 180 { 181 if (getResultCode() == ResultCode.SUCCESS) 182 { 183 getPluginConfigManager().invokePostSynchronizationModifyDNPlugins(this); 184 } 185 } 186 else if (executePostOpPlugins.get()) 187 { 188 if (!processOperationResult(this, getPluginConfigManager().invokePostOperationModifyDNPlugins(this))) 189 { 190 return; 191 } 192 } 193 } 194 finally 195 { 196 LocalBackendWorkflowElement.filterNonDisclosableMatchedDN(this); 197 } 198 199 // Register a post-response call-back which will notify persistent 200 // searches and change listeners. 201 if (getResultCode() == ResultCode.SUCCESS) 202 { 203 registerPostResponseCallback(new Runnable() 204 { 205 @Override 206 public void run() 207 { 208 for (PersistentSearch psearch : backend.getPersistentSearches()) 209 { 210 psearch.processModifyDN(newEntry, currentEntry.getName()); 211 } 212 } 213 }); 214 } 215 } 216 217 private void processModifyDN(AtomicBoolean executePostOpPlugins) 218 throws CanceledOperationException 219 { 220 // Process the entry DN, newRDN, and newSuperior elements from their raw 221 // forms as provided by the client to the forms required for the rest of 222 // the modify DN processing. 223 entryDN = getEntryDN(); 224 225 newRDN = getNewRDN(); 226 if (newRDN == null) 227 { 228 return; 229 } 230 231 DN newSuperior = getNewSuperior(); 232 if (newSuperior == null && getRawNewSuperior() != null) 233 { 234 return; 235 } 236 237 // Construct the new DN to use for the entry. 238 DN parentDN; 239 if (newSuperior == null) 240 { 241 parentDN = DirectoryServer.getParentDNInSuffix(entryDN); 242 } 243 else 244 { 245 if (newSuperior.isSubordinateOrEqualTo(entryDN)) 246 { 247 setResultCode(ResultCode.UNWILLING_TO_PERFORM); 248 appendErrorMessage(ERR_MODDN_NEW_SUPERIOR_IN_SUBTREE.get(entryDN, newSuperior)); 249 return; 250 } 251 parentDN = newSuperior; 252 } 253 254 if (parentDN == null || parentDN.isRootDN()) 255 { 256 setResultCode(ResultCode.UNWILLING_TO_PERFORM); 257 appendErrorMessage(ERR_MODDN_NO_PARENT.get(entryDN)); 258 return; 259 } 260 261 DN newDN = parentDN.child(newRDN); 262 263 // Get the backend for the current entry, and the backend for the new 264 // entry. If either is null, or if they are different, then fail. 265 Backend<?> currentBackend = backend; 266 if (currentBackend == null) 267 { 268 setResultCode(ResultCode.NO_SUCH_OBJECT); 269 appendErrorMessage(ERR_MODDN_NO_BACKEND_FOR_CURRENT_ENTRY.get(entryDN)); 270 return; 271 } 272 273 Backend<?> newBackend = DirectoryServer.getBackend(newDN); 274 if (newBackend == null) 275 { 276 setResultCode(ResultCode.NO_SUCH_OBJECT); 277 appendErrorMessage(ERR_MODDN_NO_BACKEND_FOR_NEW_ENTRY.get(entryDN, newDN)); 278 return; 279 } 280 else if (!currentBackend.equals(newBackend)) 281 { 282 setResultCode(ResultCode.UNWILLING_TO_PERFORM); 283 appendErrorMessage(ERR_MODDN_DIFFERENT_BACKENDS.get(entryDN, newDN)); 284 return; 285 } 286 287 // Check for a request to cancel this operation. 288 checkIfCanceled(false); 289 290 /* 291 * Acquire subtree write locks for the current and new DN. Be careful to avoid deadlocks by 292 * taking the locks in a well defined order. 293 */ 294 DNLock currentLock = null; 295 DNLock newLock = null; 296 try 297 { 298 if (entryDN.compareTo(newDN) < 0) 299 { 300 currentLock = DirectoryServer.getLockManager().tryWriteLockSubtree(entryDN); 301 newLock = DirectoryServer.getLockManager().tryWriteLockSubtree(newDN); 302 } 303 else 304 { 305 newLock = DirectoryServer.getLockManager().tryWriteLockSubtree(newDN); 306 currentLock = DirectoryServer.getLockManager().tryWriteLockSubtree(entryDN); 307 } 308 309 if (currentLock == null) 310 { 311 setResultCode(ResultCode.BUSY); 312 appendErrorMessage(ERR_MODDN_CANNOT_LOCK_CURRENT_DN.get(entryDN)); 313 return; 314 } 315 316 if (newLock == null) 317 { 318 setResultCode(ResultCode.BUSY); 319 appendErrorMessage(ERR_MODDN_CANNOT_LOCK_NEW_DN.get(entryDN, newDN)); 320 return; 321 } 322 323 // Check for a request to cancel this operation. 324 checkIfCanceled(false); 325 326 // Get the current entry from the appropriate backend. If it doesn't 327 // exist, then fail. 328 currentEntry = currentBackend.getEntry(entryDN); 329 330 if (getOriginalEntry() == null) 331 { 332 // See if one of the entry's ancestors exists. 333 setMatchedDN(findMatchedDN(entryDN)); 334 335 setResultCode(ResultCode.NO_SUCH_OBJECT); 336 appendErrorMessage(ERR_MODDN_NO_CURRENT_ENTRY.get(entryDN)); 337 return; 338 } 339 340 // Check to see if there are any controls in the request. If so, then 341 // see if there is any special processing required. 342 handleRequestControls(); 343 344 // Check to see if the client has permission to perform the 345 // modify DN. 346 347 // FIXME: for now assume that this will check all permission 348 // pertinent to the operation. This includes proxy authorization 349 // and any other controls specified. 350 351 // FIXME: earlier checks to see if the entry or new superior 352 // already exists may have already exposed sensitive information 353 // to the client. 354 try 355 { 356 if (!getAccessControlHandler().isAllowed(this)) 357 { 358 setResultCodeAndMessageNoInfoDisclosure(currentEntry, entryDN, 359 ResultCode.INSUFFICIENT_ACCESS_RIGHTS, 360 ERR_MODDN_AUTHZ_INSUFFICIENT_ACCESS_RIGHTS.get(entryDN)); 361 return; 362 } 363 } 364 catch (DirectoryException e) 365 { 366 setResultCode(e.getResultCode()); 367 appendErrorMessage(e.getMessageObject()); 368 return; 369 } 370 371 // Duplicate the entry and set its new DN. Also, create an empty list 372 // to hold the attribute-level modifications. 373 newEntry = currentEntry.duplicate(false); 374 newEntry.setDN(newDN); 375 376 // init the modifications 377 addModification(null); 378 List<Modification> modifications = getModifications(); 379 380 if (!handleConflictResolution()) 381 { 382 return; 383 } 384 385 // Apply any changes to the entry based on the change in its RDN. 386 // Also perform schema checking on the updated entry. 387 applyRDNChanges(modifications); 388 389 // If the operation is not a synchronization operation, 390 // - Apply the RDN changes. 391 // - Invoke the pre-operation modify DN plugins. 392 // - apply additional modifications provided by the plugins. 393 // If the operation is a synchronization operation 394 // - apply the operation as it was originally done on the master. 395 if (!isSynchronizationOperation()) 396 { 397 // Check for a request to cancel this operation. 398 checkIfCanceled(false); 399 400 // Get a count of the current number of modifications. The 401 // pre-operation plugins may alter this list, and we need to be able 402 // to identify which changes were made after they're done. 403 int modCount = modifications.size(); 404 405 executePostOpPlugins.set(true); 406 if (!processOperationResult(this, getPluginConfigManager().invokePreOperationModifyDNPlugins(this))) 407 { 408 return; 409 } 410 411 // Check to see if any of the pre-operation plugins made any changes 412 // to the entry. If so, then apply them. 413 if (modifications.size() > modCount) 414 { 415 applyPreOpModifications(modifications, modCount, true); 416 } 417 } 418 else 419 { 420 applyPreOpModifications(modifications, 0, false); 421 } 422 423 LocalBackendWorkflowElement.checkIfBackendIsWritable(currentBackend, 424 this, entryDN, ERR_MODDN_SERVER_READONLY, ERR_MODDN_BACKEND_READONLY); 425 426 if (noOp) 427 { 428 appendErrorMessage(INFO_MODDN_NOOP.get()); 429 setResultCode(ResultCode.NO_OPERATION); 430 } 431 else 432 { 433 if (!processPreOperation()) 434 { 435 return; 436 } 437 currentBackend.renameEntry(entryDN, newEntry, this); 438 } 439 440 // Attach the pre-read and/or post-read controls to the response if 441 // appropriate. 442 LocalBackendWorkflowElement.addPreReadResponse(this, preReadRequest, 443 currentEntry); 444 LocalBackendWorkflowElement.addPostReadResponse(this, postReadRequest, 445 newEntry); 446 447 if (!noOp) 448 { 449 setResultCode(ResultCode.SUCCESS); 450 } 451 } 452 catch (DirectoryException de) 453 { 454 logger.traceException(de); 455 456 setResponseData(de); 457 return; 458 } 459 finally 460 { 461 if (currentLock != null) 462 { 463 currentLock.unlock(); 464 } 465 if (newLock != null) 466 { 467 newLock.unlock(); 468 } 469 processSynchPostOperationPlugins(); 470 } 471 } 472 473 private DirectoryException newDirectoryException(Entry entry, 474 ResultCode resultCode, LocalizableMessage message) throws DirectoryException 475 { 476 return LocalBackendWorkflowElement.newDirectoryException(this, entry, null, 477 resultCode, message, ResultCode.NO_SUCH_OBJECT, 478 ERR_MODDN_NO_CURRENT_ENTRY.get(entryDN)); 479 } 480 481 private void setResultCodeAndMessageNoInfoDisclosure(Entry entry, DN entryDN, 482 ResultCode realResultCode, LocalizableMessage realMessage) throws DirectoryException 483 { 484 LocalBackendWorkflowElement.setResultCodeAndMessageNoInfoDisclosure(this, 485 entry, entryDN, realResultCode, realMessage, ResultCode.NO_SUCH_OBJECT, 486 ERR_MODDN_NO_CURRENT_ENTRY.get(entryDN)); 487 } 488 489 /** 490 * Processes the set of controls included in the request. 491 * 492 * @throws DirectoryException If a problem occurs that should cause the 493 * modify DN operation to fail. 494 */ 495 private void handleRequestControls() throws DirectoryException 496 { 497 LocalBackendWorkflowElement.evaluateProxyAuthControls(this); 498 LocalBackendWorkflowElement.removeAllDisallowedControls(entryDN, this); 499 500 for (ListIterator<Control> iter = getRequestControls().listIterator(); iter.hasNext();) 501 { 502 final Control c = iter.next(); 503 final String oid = c.getOID(); 504 505 if (OID_LDAP_ASSERTION.equals(oid)) 506 { 507 LDAPAssertionRequestControl assertControl = getRequestControl(LDAPAssertionRequestControl.DECODER); 508 509 SearchFilter filter; 510 try 511 { 512 filter = assertControl.getSearchFilter(); 513 } 514 catch (DirectoryException de) 515 { 516 logger.traceException(de); 517 518 throw newDirectoryException(currentEntry, de.getResultCode(), 519 ERR_MODDN_CANNOT_PROCESS_ASSERTION_FILTER.get(entryDN, de.getMessageObject())); 520 } 521 522 // Check if the current user has permission to make this determination. 523 if (!getAccessControlHandler().isAllowed(this, currentEntry, filter)) 524 { 525 throw new DirectoryException(ResultCode.INSUFFICIENT_ACCESS_RIGHTS, 526 ERR_CONTROL_INSUFFICIENT_ACCESS_RIGHTS.get(oid)); 527 } 528 529 try 530 { 531 if (!filter.matchesEntry(currentEntry)) 532 { 533 throw newDirectoryException(currentEntry, ResultCode.ASSERTION_FAILED, 534 ERR_MODDN_ASSERTION_FAILED.get(entryDN)); 535 } 536 } 537 catch (DirectoryException de) 538 { 539 if (de.getResultCode() == ResultCode.ASSERTION_FAILED) 540 { 541 throw de; 542 } 543 544 logger.traceException(de); 545 546 throw newDirectoryException(currentEntry, de.getResultCode(), 547 ERR_MODDN_CANNOT_PROCESS_ASSERTION_FILTER.get(entryDN, de.getMessageObject())); 548 } 549 } 550 else if (OID_LDAP_NOOP_OPENLDAP_ASSIGNED.equals(oid)) 551 { 552 noOp = true; 553 } 554 else if (OID_LDAP_READENTRY_PREREAD.equals(oid)) 555 { 556 preReadRequest = getRequestControl(LDAPPreReadRequestControl.DECODER); 557 iter.set(preReadRequest); 558 } 559 else if (OID_LDAP_READENTRY_POSTREAD.equals(oid)) 560 { 561 if (c instanceof LDAPPostReadRequestControl) 562 { 563 postReadRequest = (LDAPPostReadRequestControl) c; 564 } 565 else 566 { 567 postReadRequest = getRequestControl(LDAPPostReadRequestControl.DECODER); 568 iter.set(postReadRequest); 569 } 570 } 571 else if (LocalBackendWorkflowElement.isProxyAuthzControl(oid)) 572 { 573 continue; 574 } 575 else if (c.isCritical() && !backend.supportsControl(oid)) 576 { 577 throw new DirectoryException(ResultCode.UNAVAILABLE_CRITICAL_EXTENSION, 578 ERR_MODDN_UNSUPPORTED_CRITICAL_CONTROL.get(entryDN, oid)); 579 } 580 } 581 } 582 583 private AccessControlHandler<?> getAccessControlHandler() 584 { 585 return AccessControlConfigManager.getInstance().getAccessControlHandler(); 586 } 587 588 /** 589 * Updates the entry so that its attributes are changed to reflect the changes 590 * to the RDN. This also performs schema checking on the updated entry. 591 * 592 * @param modifications A list to hold the modifications made to the entry. 593 * 594 * @throws DirectoryException If a problem occurs that should cause the 595 * modify DN operation to fail. 596 */ 597 private void applyRDNChanges(List<Modification> modifications) 598 throws DirectoryException 599 { 600 // If we should delete the old RDN values from the entry, then do so. 601 if (deleteOldRDN()) 602 { 603 for (AVA ava : entryDN.rdn()) 604 { 605 Attribute a = Attributes.create( 606 ava.getAttributeType(), 607 ava.getAttributeName(), 608 ava.getAttributeValue()); 609 610 // If the associated attribute type is marked NO-USER-MODIFICATION, then 611 // refuse the update. 612 if (a.getAttributeDescription().getAttributeType().isNoUserModification() 613 && !isInternalOperation() 614 && !isSynchronizationOperation()) 615 { 616 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, 617 ERR_MODDN_OLD_RDN_ATTR_IS_NO_USER_MOD.get(entryDN, a.getAttributeDescription())); 618 } 619 620 List<ByteString> missingValues = new LinkedList<>(); 621 newEntry.removeAttribute(a, missingValues); 622 623 if (missingValues.isEmpty()) 624 { 625 modifications.add(new Modification(ModificationType.DELETE, a)); 626 } 627 } 628 } 629 630 631 // Add the new RDN values to the entry. 632 for (AVA ava : newRDN) 633 { 634 Attribute a = Attributes.create( 635 ava.getAttributeType(), 636 ava.getAttributeName(), 637 ava.getAttributeValue()); 638 639 List<ByteString> duplicateValues = new LinkedList<>(); 640 newEntry.addAttribute(a, duplicateValues); 641 642 if (duplicateValues.isEmpty()) 643 { 644 // If the associated attribute type is marked NO-USER-MODIFICATION, then 645 // refuse the update. 646 if (a.getAttributeDescription().getAttributeType().isNoUserModification()) 647 { 648 if (!isInternalOperation() && !isSynchronizationOperation()) 649 { 650 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, 651 ERR_MODDN_NEW_RDN_ATTR_IS_NO_USER_MOD.get(entryDN, a.getAttributeDescription())); 652 } 653 } 654 else 655 { 656 modifications.add(new Modification(ModificationType.ADD, a)); 657 } 658 } 659 } 660 661 // If the server is configured to check the schema and the operation is not 662 // a synchronization operation, make sure that the resulting entry is valid 663 // as per the server schema. 664 if (DirectoryServer.checkSchema() && !isSynchronizationOperation()) 665 { 666 LocalizableMessageBuilder invalidReason = new LocalizableMessageBuilder(); 667 if (! newEntry.conformsToSchema(null, false, true, true, 668 invalidReason)) 669 { 670 throw new DirectoryException(ResultCode.OBJECTCLASS_VIOLATION, 671 ERR_MODDN_VIOLATES_SCHEMA.get(entryDN, invalidReason)); 672 } 673 674 for (AVA ava : newRDN) 675 { 676 AttributeType at = ava.getAttributeType(); 677 if (at.isObsolete()) 678 { 679 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, 680 ERR_MODDN_NEWRDN_ATTR_IS_OBSOLETE.get(entryDN, at.getNameOrOID())); 681 } 682 } 683 } 684 } 685 686 687 688 /** 689 * Applies any modifications performed during pre-operation plugin processing. 690 * This also performs schema checking for the updated entry. 691 * 692 * @param modifications A list containing the modifications made to the 693 * entry. 694 * @param startPos The position in the list at which the pre-operation 695 * modifications start. 696 * @param checkSchema A boolean allowing to control if schema must be 697 * checked 698 * 699 * @throws DirectoryException If a problem occurs that should cause the 700 * modify DN operation to fail. 701 */ 702 private void applyPreOpModifications(List<Modification> modifications, 703 int startPos, boolean checkSchema) 704 throws DirectoryException 705 { 706 for (int i=startPos; i < modifications.size(); i++) 707 { 708 Modification m = modifications.get(i); 709 Attribute a = m.getAttribute(); 710 711 switch (m.getModificationType().asEnum()) 712 { 713 case ADD: 714 List<ByteString> duplicateValues = new LinkedList<>(); 715 newEntry.addAttribute(a, duplicateValues); 716 break; 717 718 case DELETE: 719 List<ByteString> missingValues = new LinkedList<>(); 720 newEntry.removeAttribute(a, missingValues); 721 break; 722 723 case REPLACE: 724 newEntry.replaceAttribute(a); 725 break; 726 727 case INCREMENT: 728 newEntry.incrementAttribute(a); 729 break; 730 } 731 } 732 733 734 // Make sure that the updated entry still conforms to the server 735 // schema. 736 if (DirectoryServer.checkSchema() && checkSchema) 737 { 738 LocalizableMessageBuilder invalidReason = new LocalizableMessageBuilder(); 739 if (! newEntry.conformsToSchema(null, false, true, true, 740 invalidReason)) 741 { 742 throw new DirectoryException(ResultCode.OBJECTCLASS_VIOLATION, 743 ERR_MODDN_PREOP_VIOLATES_SCHEMA.get(entryDN, invalidReason)); 744 } 745 } 746 } 747 748 749 750 /** 751 * Handle conflict resolution. 752 * @return {@code true} if processing should continue for the operation, or 753 * {@code false} if not. 754 */ 755 private boolean handleConflictResolution() 756 { 757 for (SynchronizationProvider<?> provider : getSynchronizationProviders()) { 758 try { 759 if (!processOperationResult(this, provider.handleConflictResolution(this))) { 760 return false; 761 } 762 } catch (DirectoryException de) { 763 logger.traceException(de); 764 logger.error(ERR_MODDN_SYNCH_CONFLICT_RESOLUTION_FAILED, 765 getConnectionID(), getOperationID(), getExceptionMessage(de)); 766 767 setResponseData(de); 768 return false; 769 } 770 } 771 return true; 772 } 773 774 /** 775 * Process pre operation. 776 * @return {@code true} if processing should continue for the operation, or 777 * {@code false} if not. 778 */ 779 private boolean processPreOperation() 780 { 781 for (SynchronizationProvider<?> provider : getSynchronizationProviders()) { 782 try { 783 if (!processOperationResult(this, provider.doPreOperation(this))) { 784 return false; 785 } 786 } catch (DirectoryException de) { 787 logger.traceException(de); 788 logger.error(ERR_MODDN_SYNCH_PREOP_FAILED, getConnectionID(), 789 getOperationID(), getExceptionMessage(de)); 790 setResponseData(de); 791 return false; 792 } 793 } 794 return true; 795 } 796 797 /** 798 * Invoke post operation synchronization providers. 799 */ 800 private void processSynchPostOperationPlugins() 801 { 802 for (SynchronizationProvider<?> provider : DirectoryServer 803 .getSynchronizationProviders()) { 804 try { 805 provider.doPostOperation(this); 806 } catch (DirectoryException de) { 807 logger.traceException(de); 808 logger.error(ERR_MODDN_SYNCH_POSTOP_FAILED, getConnectionID(), 809 getOperationID(), getExceptionMessage(de)); 810 setResponseData(de); 811 return; 812 } 813 } 814 } 815}