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}