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}