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