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 2009-2010 Sun Microsystems, Inc.
015 * Portions Copyright 2011-2016 ForgeRock AS.
016 */
017package org.opends.server.core;
018
019import java.util.ArrayList;
020import java.util.Collection;
021import java.util.Collections;
022import java.util.EnumSet;
023import java.util.HashMap;
024import java.util.Iterator;
025import java.util.List;
026import java.util.Map;
027import java.util.Set;
028import java.util.concurrent.CopyOnWriteArrayList;
029import java.util.concurrent.locks.ReadWriteLock;
030import java.util.concurrent.locks.ReentrantReadWriteLock;
031
032import org.forgerock.i18n.slf4j.LocalizedLogger;
033import org.forgerock.opendj.ldap.DN;
034import org.forgerock.opendj.ldap.ResultCode;
035import org.forgerock.opendj.ldap.SearchScope;
036import org.opends.server.api.Backend;
037import org.opends.server.api.BackendInitializationListener;
038import org.opends.server.api.ClientConnection;
039import org.opends.server.api.DITCacheMap;
040import org.opends.server.api.SubentryChangeListener;
041import org.opends.server.api.plugin.InternalDirectoryServerPlugin;
042import org.opends.server.api.plugin.PluginResult;
043import org.opends.server.api.plugin.PluginResult.PostOperation;
044import org.opends.server.api.plugin.PluginResult.PreOperation;
045import org.opends.server.api.plugin.PluginType;
046import org.opends.server.controls.SubentriesControl;
047import org.opends.server.protocols.internal.InternalClientConnection;
048import org.opends.server.protocols.internal.InternalSearchOperation;
049import org.opends.server.protocols.internal.SearchRequest;
050import org.opends.server.types.DirectoryException;
051import org.opends.server.types.Entry;
052import org.opends.server.types.Privilege;
053import org.opends.server.types.SearchFilter;
054import org.opends.server.types.SearchResultEntry;
055import org.opends.server.types.SubEntry;
056import org.opends.server.types.SubtreeSpecification;
057import org.opends.server.types.operation.PostOperationAddOperation;
058import org.opends.server.types.operation.PostOperationDeleteOperation;
059import org.opends.server.types.operation.PostOperationModifyDNOperation;
060import org.opends.server.types.operation.PostOperationModifyOperation;
061import org.opends.server.types.operation.PostSynchronizationAddOperation;
062import org.opends.server.types.operation.PostSynchronizationDeleteOperation;
063import org.opends.server.types.operation.PostSynchronizationModifyDNOperation;
064import org.opends.server.types.operation.PostSynchronizationModifyOperation;
065import org.opends.server.types.operation.PreOperationAddOperation;
066import org.opends.server.types.operation.PreOperationDeleteOperation;
067import org.opends.server.types.operation.PreOperationModifyDNOperation;
068import org.opends.server.types.operation.PreOperationModifyOperation;
069import org.opends.server.workflowelement.localbackend.LocalBackendSearchOperation;
070
071import static org.opends.messages.CoreMessages.*;
072import static org.opends.server.config.ConfigConstants.*;
073import static org.opends.server.protocols.internal.InternalClientConnection.*;
074import static org.opends.server.protocols.internal.Requests.*;
075import static org.opends.server.util.CollectionUtils.*;
076import static org.opends.server.util.ServerConstants.*;
077
078/**
079 * This class provides a mechanism for interacting with subentries defined in
080 * the Directory Server.  It will handle all necessary processing at server
081 * startup to identify and load subentries within the server.
082 * <BR><BR>
083 * FIXME:  At the present time, it assumes that all of the necessary
084 * information about subentries defined in the server can be held in
085 * memory.  If it is determined that this approach is not workable
086 * in all cases, then we will need an alternate strategy.
087 */
088public class SubentryManager extends InternalDirectoryServerPlugin
089        implements BackendInitializationListener
090{
091  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
092
093  /** Dummy configuration DN for Subentry Manager. */
094  private static final String CONFIG_DN = "cn=Subentry Manager,cn=config";
095
096  /** A mapping between the DNs and applicable subentries. */
097  private final Map<DN, List<SubEntry>> dn2SubEntry = new HashMap<>();
098  /** A mapping between the DNs and applicable collective subentries. */
099  private final Map<DN, List<SubEntry>> dn2CollectiveSubEntry = new HashMap<>();
100  /** A mapping between subentry DNs and subentry objects. */
101  private final DITCacheMap<SubEntry> dit2SubEntry = new DITCacheMap<>();
102  /** Internal search all operational attributes. */
103  private final Set<String> requestAttrs = newLinkedHashSet("*", "+");
104  /** Lock to protect internal data structures. */
105  private final ReadWriteLock lock = new ReentrantReadWriteLock();
106  /** The set of change notification listeners. */
107  private final List<SubentryChangeListener> changeListeners = new CopyOnWriteArrayList<>();
108
109  /**
110   * Creates a new instance of this subentry manager.
111   *
112   * @throws DirectoryException If a problem occurs while
113   *                            creating an instance of
114   *                            the subentry manager.
115   */
116  public SubentryManager() throws DirectoryException
117  {
118    super(DN.valueOf(CONFIG_DN), EnumSet.of(
119          PluginType.PRE_OPERATION_ADD,
120          PluginType.PRE_OPERATION_DELETE,
121          PluginType.PRE_OPERATION_MODIFY,
122          PluginType.PRE_OPERATION_MODIFY_DN,
123          PluginType.POST_OPERATION_ADD,
124          PluginType.POST_OPERATION_DELETE,
125          PluginType.POST_OPERATION_MODIFY,
126          PluginType.POST_OPERATION_MODIFY_DN,
127          PluginType.POST_SYNCHRONIZATION_ADD,
128          PluginType.POST_SYNCHRONIZATION_DELETE,
129          PluginType.POST_SYNCHRONIZATION_MODIFY,
130          PluginType.POST_SYNCHRONIZATION_MODIFY_DN),
131          true);
132
133    DirectoryServer.registerInternalPlugin(this);
134    DirectoryServer.registerBackendInitializationListener(this);
135  }
136
137  /**
138   * Perform any required finalization tasks for Subentry Manager.
139   * This should only be called at Directory Server shutdown.
140   */
141  public void finalizeSubentryManager()
142  {
143    // Deregister as internal plugin and
144    // backend initialization listener.
145    DirectoryServer.deregisterInternalPlugin(this);
146    DirectoryServer.deregisterBackendInitializationListener(this);
147  }
148
149  /**
150   * Registers the provided change notification listener with this manager
151   * so that it will be notified of any add, delete, modify, or modify DN
152   * operations that are performed.
153   *
154   * @param  changeListener  The change notification listener to register
155   *                         with this manager.
156   */
157  public void registerChangeListener(SubentryChangeListener changeListener)
158  {
159    changeListeners.add(changeListener);
160  }
161
162  /**
163   * Deregisters the provided change notification listener with this manager
164   * so that it will no longer be notified of any add, delete, modify, or
165   * modify DN operations that are performed.
166   *
167   * @param  changeListener  The change notification listener to deregister
168   *                         with this manager.
169   */
170  public void deregisterChangeListener(SubentryChangeListener changeListener)
171  {
172    changeListeners.remove(changeListener);
173  }
174
175  /**
176   * Add a given entry to this subentry manager.
177   * @param entry to add.
178   */
179  private void addSubentry(Entry entry) throws DirectoryException
180  {
181    SubEntry subEntry = new SubEntry(entry);
182    SubtreeSpecification subSpec = subEntry.getSubTreeSpecification();
183    DN subDN = subSpec.getBaseDN();
184    lock.writeLock().lock();
185    try
186    {
187      Map<DN, List<SubEntry>> subEntryMap = getSubEntryMap(subEntry);
188      List<SubEntry> subList = subEntryMap.get(subDN);
189      if (subList == null)
190      {
191        subList = new ArrayList<>();
192        subEntryMap.put(subDN, subList);
193      }
194      dit2SubEntry.put(entry.getName(), subEntry);
195      subList.add(subEntry);
196    }
197    finally
198    {
199      lock.writeLock().unlock();
200    }
201  }
202
203  private Map<DN, List<SubEntry>> getSubEntryMap(SubEntry subEntry)
204  {
205    return (subEntry.isCollective() || subEntry.isInheritedCollective()) ? dn2CollectiveSubEntry : dn2SubEntry;
206  }
207
208  /**
209   * Remove a given entry from this subentry manager.
210   *
211   * @param entry
212   *          to remove.
213   */
214  private void removeSubentry(Entry entry)
215  {
216    lock.writeLock().lock();
217    try
218    {
219      if (!removeSubEntry(dn2SubEntry, entry))
220      {
221        removeSubEntry(dn2CollectiveSubEntry, entry);
222      }
223    }
224    finally
225    {
226      lock.writeLock().unlock();
227    }
228  }
229
230  private boolean removeSubEntry(Map<DN, List<SubEntry>> subEntryMap, Entry entry)
231  {
232    Iterator<List<SubEntry>> subEntryListsIt = subEntryMap.values().iterator();
233    while (subEntryListsIt.hasNext())
234    {
235      List<SubEntry> subEntries = subEntryListsIt.next();
236      Iterator<SubEntry> subEntriesIt = subEntries.iterator();
237      while (subEntriesIt.hasNext())
238      {
239        SubEntry subEntry = subEntriesIt.next();
240        if (subEntry.getDN().equals(entry.getName()))
241        {
242          dit2SubEntry.remove(entry.getName());
243          subEntriesIt.remove();
244          if (subEntries.isEmpty())
245          {
246            subEntryListsIt.remove();
247          }
248          return true;
249        }
250      }
251    }
252    return false;
253  }
254
255  /**
256   * {@inheritDoc}  In this case, the server will search the backend to find
257   * all subentries that it may contain and register them with this manager.
258   */
259  @Override
260  public void performBackendPreInitializationProcessing(Backend<?> backend)
261  {
262    InternalClientConnection conn = getRootConnection();
263    SubentriesControl control = new SubentriesControl(true, true);
264
265    SearchFilter filter = null;
266    try
267    {
268      filter = SearchFilter.createFilterFromString("(|" +
269            "(" + ATTR_OBJECTCLASS + "=" + OC_SUBENTRY + ")" +
270            "(" + ATTR_OBJECTCLASS + "=" + OC_LDAP_SUBENTRY + ")" +
271            ")");
272      if (backend.getEntryCount() > 0 && ! backend.isIndexed(filter))
273      {
274        logger.warn(WARN_SUBENTRY_FILTER_NOT_INDEXED, filter, backend.getBackendID());
275      }
276    }
277    catch (Exception e)
278    {
279      logger.traceException(e);
280    }
281
282    for (DN baseDN : backend.getBaseDNs())
283    {
284      try
285      {
286        if (! backend.entryExists(baseDN))
287        {
288          continue;
289        }
290      }
291      catch (Exception e)
292      {
293        logger.traceException(e);
294
295        // FIXME -- Is there anything that we need to do here?
296        continue;
297      }
298
299      SearchRequest request = newSearchRequest(baseDN, SearchScope.WHOLE_SUBTREE, filter)
300          .addAttribute(requestAttrs)
301          .addControl(control);
302      InternalSearchOperation internalSearch =
303          new InternalSearchOperation(conn, nextOperationID(), nextMessageID(), request);
304      LocalBackendSearchOperation localSearch = new LocalBackendSearchOperation(internalSearch);
305
306      try
307      {
308        backend.search(localSearch);
309      }
310      catch (Exception e)
311      {
312        logger.traceException(e);
313
314        // FIXME -- Is there anything that we need to do here?
315        continue;
316      }
317
318      for (SearchResultEntry entry : internalSearch.getSearchEntries())
319      {
320        if (isSubEntry(entry))
321        {
322          try
323          {
324            addSubentry(entry);
325            notifySubentryAdded(entry);
326          }
327          catch (Exception e)
328          {
329            logger.traceException(e);
330          }
331        }
332      }
333    }
334  }
335
336  private void notifySubentryAdded(final Entry entry)
337  {
338    for (SubentryChangeListener changeListener : changeListeners)
339    {
340      try
341      {
342        changeListener.handleSubentryAdd(entry);
343      }
344      catch (Exception e)
345      {
346        logger.traceException(e);
347      }
348    }
349  }
350
351  private void notifySubentryDeleted(final Entry entry)
352  {
353    for (SubentryChangeListener changeListener : changeListeners)
354    {
355      try
356      {
357        changeListener.handleSubentryDelete(entry);
358      }
359      catch (Exception e)
360      {
361        logger.traceException(e);
362      }
363    }
364  }
365
366  private void notifySubentryModified(final Entry oldEntry, final Entry newEntry)
367  {
368    for (SubentryChangeListener changeListener : changeListeners)
369    {
370      try
371      {
372        changeListener.handleSubentryModify(oldEntry, newEntry);
373      }
374      catch (Exception e)
375      {
376        logger.traceException(e);
377      }
378    }
379  }
380
381  /**
382   * Return all subentries for this manager.
383   * Note that this getter will skip any collective subentries,
384   * returning only applicable regular subentries.
385   * @return all subentries for this manager.
386   */
387  public List<SubEntry> getSubentries()
388  {
389    if (dn2SubEntry.isEmpty())
390    {
391      return Collections.emptyList();
392    }
393
394    List<SubEntry> subentries = new ArrayList<>();
395
396    lock.readLock().lock();
397    try
398    {
399      for (List<SubEntry> subList : dn2SubEntry.values())
400      {
401        subentries.addAll(subList);
402      }
403    }
404    finally
405    {
406      lock.readLock().unlock();
407    }
408
409    return subentries;
410  }
411
412  /**
413   * Return subentries applicable to specific DN.
414   * Note that this getter will skip any collective subentries,
415   * returning only applicable regular subentries.
416   * @param  dn for which to retrieve applicable subentries.
417   * @return applicable subentries.
418   */
419  public List<SubEntry> getSubentries(DN dn)
420  {
421    return getSubentries(dn2SubEntry, dn);
422  }
423
424  private List<SubEntry> getSubentries(Map<DN, List<SubEntry>> subEntryMap, DN dn)
425  {
426    if (subEntryMap.isEmpty())
427    {
428      return Collections.emptyList();
429    }
430
431    lock.readLock().lock();
432    try
433    {
434      List<SubEntry> subentries = new ArrayList<>();
435      for (DN subDN = dn; subDN != null && !subDN.isRootDN(); subDN = subDN.parent())
436      {
437        List<SubEntry> subList = subEntryMap.get(subDN);
438        if (subList != null)
439        {
440          for (SubEntry subEntry : subList)
441          {
442            SubtreeSpecification subSpec = subEntry.getSubTreeSpecification();
443            if (subSpec.isDNWithinScope(dn))
444            {
445              subentries.add(subEntry);
446            }
447          }
448        }
449      }
450      return subentries;
451    }
452    finally
453    {
454      lock.readLock().unlock();
455    }
456  }
457
458  /**
459   * Return subentries applicable to specific entry.
460   * Note that this getter will skip any collective subentries,
461   * returning only applicable regular subentries.
462   * @param  entry for which to retrieve applicable
463   *         subentries.
464   * @return applicable subentries.
465   */
466  public List<SubEntry> getSubentries(Entry entry)
467  {
468    return getSubentries(dn2SubEntry, entry);
469  }
470
471  private List<SubEntry> getSubentries(Map<DN, List<SubEntry>> subEntryMap, Entry entry)
472  {
473    if (subEntryMap.isEmpty())
474    {
475      return Collections.emptyList();
476    }
477
478    lock.readLock().lock();
479    try
480    {
481      List<SubEntry> subentries = new ArrayList<>();
482      for (DN subDN = entry.getName(); subDN != null && !subDN.isRootDN(); subDN = subDN.parent())
483      {
484        List<SubEntry> subList = subEntryMap.get(subDN);
485        if (subList != null)
486        {
487          for (SubEntry subEntry : subList)
488          {
489            SubtreeSpecification subSpec = subEntry.getSubTreeSpecification();
490            if (subSpec.isWithinScope(entry))
491            {
492              subentries.add(subEntry);
493            }
494          }
495        }
496      }
497      return subentries;
498    }
499    finally
500    {
501      lock.readLock().unlock();
502    }
503  }
504
505  /**
506   * Return collective subentries applicable to specific DN.
507   * Note that this getter will skip any regular subentries,
508   * returning only applicable collective subentries.
509   * @param  dn for which to retrieve applicable
510   *         subentries.
511   * @return applicable subentries.
512   */
513  public List<SubEntry> getCollectiveSubentries(DN dn)
514  {
515    return getSubentries(dn2CollectiveSubEntry, dn);
516  }
517
518  /**
519   * Return collective subentries applicable to specific entry.
520   * Note that this getter will skip any regular subentries,
521   * returning only applicable collective subentries.
522   * @param  entry for which to retrieve applicable
523   *         subentries.
524   * @return applicable subentries.
525   */
526  public List<SubEntry> getCollectiveSubentries(Entry entry)
527  {
528    return getSubentries(dn2CollectiveSubEntry, entry);
529  }
530
531  /**
532   * {@inheritDoc}  In this case, the server will de-register
533   * all subentries associated with the provided backend.
534   */
535  @Override
536  public void performBackendPostFinalizationProcessing(Backend<?> backend)
537  {
538    lock.writeLock().lock();
539    try
540    {
541      performBackendPostFinalizationProcessing(dn2SubEntry, backend);
542      performBackendPostFinalizationProcessing(dn2CollectiveSubEntry, backend);
543    }
544    finally
545    {
546      lock.writeLock().unlock();
547    }
548  }
549
550  private void performBackendPostFinalizationProcessing(Map<DN, List<SubEntry>> subEntryMap, Backend<?> backend)
551  {
552    Iterator<List<SubEntry>> subEntryListsIt = subEntryMap.values().iterator();
553    while (subEntryListsIt.hasNext())
554    {
555      List<SubEntry> subEntryList = subEntryListsIt.next();
556      Iterator<SubEntry> subEntriesIt = subEntryList.iterator();
557      while (subEntriesIt.hasNext())
558      {
559        SubEntry subEntry = subEntriesIt.next();
560        if (backend.handlesEntry(subEntry.getDN()))
561        {
562          dit2SubEntry.remove(subEntry.getDN());
563          subEntriesIt.remove();
564          notifySubentryDeleted(subEntry.getEntry());
565        }
566      }
567      if (subEntryList.isEmpty())
568      {
569        subEntryListsIt.remove();
570      }
571    }
572  }
573
574  @Override
575  public void performBackendPostInitializationProcessing(Backend<?> backend) {
576    // Nothing to do.
577  }
578
579  @Override
580  public void performBackendPreFinalizationProcessing(Backend<?> backend) {
581    // Nothing to do.
582  }
583
584  private void doPostAdd(Entry entry)
585  {
586    if (isSubEntry(entry))
587    {
588      lock.writeLock().lock();
589      try
590      {
591        try
592        {
593          addSubentry(entry);
594          notifySubentryAdded(entry);
595        }
596        catch (Exception e)
597        {
598          logger.traceException(e);
599        }
600      }
601      finally
602      {
603        lock.writeLock().unlock();
604      }
605    }
606  }
607
608  private void doPostDelete(Entry entry)
609  {
610    // Fast-path for deleted entries which do not have subordinate sub-entries.
611    lock.readLock().lock();
612    try
613    {
614      final Collection<SubEntry> subtree = dit2SubEntry.getSubtree(entry.getName());
615      if (subtree.isEmpty())
616      {
617        return;
618      }
619    }
620    finally
621    {
622      lock.readLock().unlock();
623    }
624
625    // Slow-path.
626    lock.writeLock().lock();
627    try
628    {
629      for (SubEntry subEntry : dit2SubEntry.getSubtree(entry.getName()))
630      {
631        removeSubentry(subEntry.getEntry());
632        notifySubentryDeleted(subEntry.getEntry());
633      }
634    }
635    finally
636    {
637      lock.writeLock().unlock();
638    }
639  }
640
641  private void doPostModify(Entry oldEntry, Entry newEntry)
642  {
643    final boolean oldEntryIsSubentry = isSubEntry(oldEntry);
644    final boolean newEntryIsSubentry = isSubEntry(newEntry);
645    if (!oldEntryIsSubentry && !newEntryIsSubentry)
646    {
647      return; // Nothing to do.
648    }
649
650    boolean notify = false;
651    lock.writeLock().lock();
652    try
653    {
654      if (oldEntryIsSubentry)
655      {
656        removeSubentry(oldEntry);
657        notify = true;
658      }
659      if (newEntryIsSubentry)
660      {
661        try
662        {
663          addSubentry(newEntry);
664          notify = true;
665        }
666        catch (Exception e)
667        {
668          logger.traceException(e);
669
670          // FIXME -- Handle this.
671        }
672      }
673
674      if (notify)
675      {
676        notifySubentryModified(oldEntry, newEntry);
677      }
678    }
679    finally
680    {
681      lock.writeLock().unlock();
682    }
683  }
684
685  private boolean isSubEntry(final Entry e)
686  {
687    return e.isSubentry() || e.isLDAPSubentry();
688  }
689
690  private void doPostModifyDN(final Entry oldEntry, final Entry newEntry)
691  {
692    lock.writeLock().lock();
693    try
694    {
695      Collection<SubEntry> setToDelete = dit2SubEntry.getSubtree(oldEntry.getName());
696      for (SubEntry subentry : setToDelete)
697      {
698        final Entry currentSubentry = subentry.getEntry();
699        removeSubentry(currentSubentry);
700
701        Entry renamedSubentry = null;
702        try
703        {
704          renamedSubentry = currentSubentry.duplicate(false);
705          final DN renamedDN = currentSubentry.getName().rename(oldEntry.getName(), newEntry.getName());
706          renamedSubentry.setDN(renamedDN);
707          addSubentry(renamedSubentry);
708        }
709        catch (Exception e)
710        {
711          // Shouldnt happen.
712          logger.traceException(e);
713        }
714
715        notifySubentryModified(currentSubentry, renamedSubentry);
716      }
717    }
718    finally
719    {
720      lock.writeLock().unlock();
721    }
722  }
723
724  @Override
725  public PreOperation doPreOperation(PreOperationAddOperation addOperation)
726  {
727    Entry entry = addOperation.getEntryToAdd();
728
729    if (isSubEntry(entry))
730    {
731      ClientConnection conn = addOperation.getClientConnection();
732      if (!conn.hasPrivilege(Privilege.SUBENTRY_WRITE, conn.getOperationInProgress(addOperation.getMessageID())))
733      {
734        return PluginResult.PreOperation.stopProcessing(
735                ResultCode.INSUFFICIENT_ACCESS_RIGHTS,
736                ERR_SUBENTRY_WRITE_INSUFFICIENT_PRIVILEGES.get());
737      }
738      for (SubentryChangeListener changeListener : changeListeners)
739      {
740        try
741        {
742          changeListener.checkSubentryAddAcceptable(entry);
743        }
744        catch (DirectoryException de)
745        {
746          logger.traceException(de);
747          return PluginResult.PreOperation.stopProcessing(de.getResultCode(), de.getMessageObject());
748        }
749      }
750    }
751
752    return PluginResult.PreOperation.continueOperationProcessing();
753  }
754
755  @Override
756  public PreOperation doPreOperation(PreOperationDeleteOperation deleteOperation)
757  {
758    Entry entry = deleteOperation.getEntryToDelete();
759    boolean hasSubentryWritePrivilege = false;
760
761    lock.readLock().lock();
762    try
763    {
764      for (SubEntry subEntry : dit2SubEntry.getSubtree(entry.getName()))
765      {
766        if (!hasSubentryWritePrivilege)
767        {
768          ClientConnection conn = deleteOperation.getClientConnection();
769          if (!conn.hasPrivilege(Privilege.SUBENTRY_WRITE,
770                                 conn.getOperationInProgress(deleteOperation.getMessageID())))
771          {
772            return PluginResult.PreOperation.stopProcessing(
773                    ResultCode.INSUFFICIENT_ACCESS_RIGHTS,
774                    ERR_SUBENTRY_WRITE_INSUFFICIENT_PRIVILEGES.get());
775          }
776          hasSubentryWritePrivilege = true;
777        }
778        for (SubentryChangeListener changeListener : changeListeners)
779        {
780          try
781          {
782            changeListener.checkSubentryDeleteAcceptable(subEntry.getEntry());
783          }
784          catch (DirectoryException de)
785          {
786            logger.traceException(de);
787            return PluginResult.PreOperation.stopProcessing(de.getResultCode(), de.getMessageObject());
788          }
789        }
790      }
791    }
792    finally
793    {
794      lock.readLock().unlock();
795    }
796
797    return PluginResult.PreOperation.continueOperationProcessing();
798  }
799
800  @Override
801  public PreOperation doPreOperation(PreOperationModifyOperation modifyOperation)
802  {
803    Entry oldEntry = modifyOperation.getCurrentEntry();
804    Entry newEntry = modifyOperation.getModifiedEntry();
805
806    if (isSubEntry(newEntry) || isSubEntry(oldEntry))
807    {
808      ClientConnection conn = modifyOperation.getClientConnection();
809      if (!conn.hasPrivilege(Privilege.SUBENTRY_WRITE,
810                             conn.getOperationInProgress(modifyOperation.getMessageID())))
811      {
812        return PluginResult.PreOperation.stopProcessing(
813                ResultCode.INSUFFICIENT_ACCESS_RIGHTS,
814                ERR_SUBENTRY_WRITE_INSUFFICIENT_PRIVILEGES.get());
815      }
816      for (SubentryChangeListener changeListener : changeListeners)
817      {
818        try
819        {
820          changeListener.checkSubentryModifyAcceptable(oldEntry, newEntry);
821        }
822        catch (DirectoryException de)
823        {
824          logger.traceException(de);
825          return PluginResult.PreOperation.stopProcessing(de.getResultCode(), de.getMessageObject());
826        }
827      }
828    }
829
830    return PluginResult.PreOperation.continueOperationProcessing();
831  }
832
833  @Override
834  public PreOperation doPreOperation(PreOperationModifyDNOperation modifyDNOperation)
835  {
836    boolean hasSubentryWritePrivilege = false;
837
838    lock.readLock().lock();
839    try
840    {
841      final Entry oldEntry = modifyDNOperation.getOriginalEntry();
842      Collection<SubEntry> setToDelete = dit2SubEntry.getSubtree(oldEntry.getName());
843      for (SubEntry subentry : setToDelete)
844      {
845        if (!hasSubentryWritePrivilege)
846        {
847          ClientConnection conn = modifyDNOperation.getClientConnection();
848          if (!conn.hasPrivilege(Privilege.SUBENTRY_WRITE,
849                                 conn.getOperationInProgress(modifyDNOperation.getMessageID())))
850          {
851            return PluginResult.PreOperation.stopProcessing(
852                    ResultCode.INSUFFICIENT_ACCESS_RIGHTS,
853                    ERR_SUBENTRY_WRITE_INSUFFICIENT_PRIVILEGES.get());
854          }
855          hasSubentryWritePrivilege = true;
856        }
857
858        final Entry newEntry = modifyDNOperation.getUpdatedEntry();
859        final Entry currentSubentry = subentry.getEntry();
860        final Entry renamedSubentry = currentSubentry.duplicate(false);
861        final DN renamedDN = currentSubentry.getName().rename(oldEntry.getName(), newEntry.getName());
862        renamedSubentry.setDN(renamedDN);
863
864        for (SubentryChangeListener changeListener : changeListeners)
865        {
866          try
867          {
868            changeListener.checkSubentryModifyAcceptable(currentSubentry, renamedSubentry);
869          }
870          catch (DirectoryException de)
871          {
872            logger.traceException(de);
873            return PluginResult.PreOperation.stopProcessing(de.getResultCode(), de.getMessageObject());
874          }
875        }
876      }
877    }
878    finally
879    {
880      lock.readLock().unlock();
881    }
882
883    return PluginResult.PreOperation.continueOperationProcessing();
884  }
885
886  @Override
887  public PostOperation doPostOperation(PostOperationAddOperation addOperation)
888  {
889    // Only do something if the operation is successful, meaning there
890    // has been a change.
891    if (addOperation.getResultCode() == ResultCode.SUCCESS)
892    {
893      doPostAdd(addOperation.getEntryToAdd());
894    }
895
896    // If we've gotten here, then everything is acceptable.
897    return PluginResult.PostOperation.continueOperationProcessing();
898  }
899
900  @Override
901  public PostOperation doPostOperation(PostOperationDeleteOperation deleteOperation)
902  {
903    // Only do something if the operation is successful, meaning there
904    // has been a change.
905    if (deleteOperation.getResultCode() == ResultCode.SUCCESS)
906    {
907      doPostDelete(deleteOperation.getEntryToDelete());
908    }
909
910    // If we've gotten here, then everything is acceptable.
911    return PluginResult.PostOperation.continueOperationProcessing();
912  }
913
914  @Override
915  public PostOperation doPostOperation(PostOperationModifyOperation modifyOperation)
916  {
917    // Only do something if the operation is successful, meaning there
918    // has been a change.
919    if (modifyOperation.getResultCode() == ResultCode.SUCCESS)
920    {
921      doPostModify(modifyOperation.getCurrentEntry(), modifyOperation.getModifiedEntry());
922    }
923
924    // If we've gotten here, then everything is acceptable.
925    return PluginResult.PostOperation.continueOperationProcessing();
926  }
927
928  @Override
929  public PostOperation doPostOperation(PostOperationModifyDNOperation modifyDNOperation)
930  {
931    // Only do something if the operation is successful, meaning there
932    // has been a change.
933    if (modifyDNOperation.getResultCode() == ResultCode.SUCCESS)
934    {
935      doPostModifyDN(modifyDNOperation.getOriginalEntry(), modifyDNOperation.getUpdatedEntry());
936    }
937
938    // If we've gotten here, then everything is acceptable.
939    return PluginResult.PostOperation.continueOperationProcessing();
940  }
941
942  @Override
943  public void doPostSynchronization(PostSynchronizationAddOperation addOperation)
944  {
945    Entry entry = addOperation.getEntryToAdd();
946    if (entry != null)
947    {
948      doPostAdd(entry);
949    }
950  }
951
952  @Override
953  public void doPostSynchronization(PostSynchronizationDeleteOperation deleteOperation)
954  {
955    Entry entry = deleteOperation.getEntryToDelete();
956    if (entry != null)
957    {
958      doPostDelete(entry);
959    }
960  }
961
962  @Override
963  public void doPostSynchronization(PostSynchronizationModifyOperation modifyOperation)
964  {
965    Entry entry = modifyOperation.getCurrentEntry();
966    Entry modEntry = modifyOperation.getModifiedEntry();
967    if (entry != null && modEntry != null)
968    {
969      doPostModify(entry, modEntry);
970    }
971  }
972
973  @Override
974  public void doPostSynchronization(PostSynchronizationModifyDNOperation modifyDNOperation)
975  {
976    Entry oldEntry = modifyDNOperation.getOriginalEntry();
977    Entry newEntry = modifyDNOperation.getUpdatedEntry();
978    if (oldEntry != null && newEntry != null)
979    {
980      doPostModifyDN(oldEntry, newEntry);
981    }
982  }
983}