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