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 performBackendInitializationProcessing(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 performBackendFinalizationProcessing(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  private void doPostAdd(Entry entry)
698  {
699    if (entry.isSubentry() || entry.isLDAPSubentry())
700    {
701      lock.writeLock().lock();
702      try
703      {
704        try
705        {
706          addSubentry(entry);
707
708          // Notify change listeners.
709          for (SubentryChangeListener changeListener :
710            changeListeners)
711          {
712            try
713            {
714              changeListener.handleSubentryAdd(entry);
715            }
716            catch (Exception e)
717            {
718              logger.traceException(e);
719            }
720          }
721        }
722        catch (Exception e)
723        {
724          logger.traceException(e);
725
726          // FIXME -- Handle this.
727        }
728      }
729      finally
730      {
731        lock.writeLock().unlock();
732      }
733    }
734  }
735
736  private void doPostDelete(Entry entry)
737  {
738    lock.writeLock().lock();
739    try
740    {
741      for (SubEntry subEntry : dit2SubEntry.getSubtree(entry.getName()))
742      {
743        removeSubentry(subEntry.getEntry());
744
745        // Notify change listeners.
746        for (SubentryChangeListener changeListener :
747                changeListeners)
748        {
749          try
750          {
751            changeListener.handleSubentryDelete(subEntry.getEntry());
752          }
753          catch (Exception e)
754          {
755            logger.traceException(e);
756          }
757        }
758      }
759    }
760    finally
761    {
762      lock.writeLock().unlock();
763    }
764  }
765
766  private void doPostModify(Entry oldEntry, Entry newEntry)
767  {
768    boolean notify = false;
769
770    lock.writeLock().lock();
771    try
772    {
773      if (oldEntry.isSubentry() || oldEntry.isLDAPSubentry())
774      {
775        removeSubentry(oldEntry);
776        notify = true;
777      }
778      if (newEntry.isSubentry() || newEntry.isLDAPSubentry())
779      {
780        try
781        {
782          addSubentry(newEntry);
783          notify = true;
784        }
785        catch (Exception e)
786        {
787          logger.traceException(e);
788
789          // FIXME -- Handle this.
790        }
791      }
792
793      if (notify)
794      {
795        // Notify change listeners.
796        for (SubentryChangeListener changeListener :
797          changeListeners)
798        {
799          try
800          {
801            changeListener.handleSubentryModify(
802                    oldEntry, newEntry);
803          }
804          catch (Exception e)
805          {
806            logger.traceException(e);
807          }
808        }
809      }
810    }
811    finally
812    {
813      lock.writeLock().unlock();
814    }
815  }
816
817  private void doPostModifyDN(final Entry oldEntry, final Entry newEntry)
818  {
819    lock.writeLock().lock();
820    try
821    {
822      Collection<SubEntry> setToDelete = dit2SubEntry.getSubtree(oldEntry.getName());
823      for (SubEntry subentry : setToDelete)
824      {
825        final Entry currentSubentry = subentry.getEntry();
826        removeSubentry(currentSubentry);
827
828        Entry renamedSubentry = null;
829        try
830        {
831          renamedSubentry = currentSubentry.duplicate(false);
832          final DN renamedDN = currentSubentry.getName().rename(oldEntry.getName(), newEntry.getName());
833          renamedSubentry.setDN(renamedDN);
834          addSubentry(renamedSubentry);
835        }
836        catch (Exception e)
837        {
838          // Shouldnt happen.
839          logger.traceException(e);
840        }
841
842        // Notify change listeners.
843        for (SubentryChangeListener changeListener :
844          changeListeners)
845        {
846          try
847          {
848            changeListener.handleSubentryModify(currentSubentry, renamedSubentry);
849          }
850          catch (Exception e)
851          {
852            logger.traceException(e);
853          }
854        }
855      }
856    }
857    finally
858    {
859      lock.writeLock().unlock();
860    }
861  }
862
863  /** {@inheritDoc} */
864  @Override
865  public PreOperation doPreOperation(
866          PreOperationAddOperation addOperation)
867  {
868    Entry entry = addOperation.getEntryToAdd();
869
870    if (entry.isSubentry() || entry.isLDAPSubentry())
871    {
872      ClientConnection conn = addOperation.getClientConnection();
873      if (!conn.hasPrivilege(Privilege.SUBENTRY_WRITE,
874           conn.getOperationInProgress(
875             addOperation.getMessageID())))
876      {
877        return PluginResult.PreOperation.stopProcessing(
878                ResultCode.INSUFFICIENT_ACCESS_RIGHTS,
879                ERR_SUBENTRY_WRITE_INSUFFICIENT_PRIVILEGES.get());
880      }
881      for (SubentryChangeListener changeListener :
882              changeListeners)
883      {
884        try
885        {
886          changeListener.checkSubentryAddAcceptable(entry);
887        }
888        catch (DirectoryException de)
889        {
890          logger.traceException(de);
891
892          return PluginResult.PreOperation.stopProcessing(
893                  de.getResultCode(), de.getMessageObject());
894        }
895      }
896    }
897
898    return PluginResult.PreOperation.continueOperationProcessing();
899  }
900
901  /** {@inheritDoc} */
902  @Override
903  public PreOperation doPreOperation(
904          PreOperationDeleteOperation deleteOperation)
905  {
906    Entry entry = deleteOperation.getEntryToDelete();
907    boolean hasSubentryWritePrivilege = false;
908
909    lock.readLock().lock();
910    try
911    {
912      for (SubEntry subEntry : dit2SubEntry.getSubtree(entry.getName()))
913      {
914        if (!hasSubentryWritePrivilege)
915        {
916          ClientConnection conn = deleteOperation.getClientConnection();
917          if (!conn.hasPrivilege(Privilege.SUBENTRY_WRITE,
918               conn.getOperationInProgress(
919                 deleteOperation.getMessageID())))
920          {
921            return PluginResult.PreOperation.stopProcessing(
922                    ResultCode.INSUFFICIENT_ACCESS_RIGHTS,
923                    ERR_SUBENTRY_WRITE_INSUFFICIENT_PRIVILEGES.get());
924          }
925          hasSubentryWritePrivilege = true;
926        }
927        for (SubentryChangeListener changeListener :
928                changeListeners)
929        {
930          try
931          {
932            changeListener.checkSubentryDeleteAcceptable(
933                    subEntry.getEntry());
934          }
935          catch (DirectoryException de)
936          {
937            logger.traceException(de);
938
939            return PluginResult.PreOperation.stopProcessing(
940                    de.getResultCode(), de.getMessageObject());
941          }
942        }
943      }
944    }
945    finally
946    {
947      lock.readLock().unlock();
948    }
949
950    return PluginResult.PreOperation.continueOperationProcessing();
951  }
952
953  /** {@inheritDoc} */
954  @Override
955  public PreOperation doPreOperation(
956          PreOperationModifyOperation modifyOperation)
957  {
958    Entry oldEntry = modifyOperation.getCurrentEntry();
959    Entry newEntry = modifyOperation.getModifiedEntry();
960
961    if (newEntry.isSubentry() || newEntry.isLDAPSubentry() ||
962        oldEntry.isSubentry() || oldEntry.isLDAPSubentry())
963    {
964      ClientConnection conn = modifyOperation.getClientConnection();
965      if (!conn.hasPrivilege(Privilege.SUBENTRY_WRITE,
966           conn.getOperationInProgress(
967             modifyOperation.getMessageID())))
968      {
969        return PluginResult.PreOperation.stopProcessing(
970                ResultCode.INSUFFICIENT_ACCESS_RIGHTS,
971                ERR_SUBENTRY_WRITE_INSUFFICIENT_PRIVILEGES.get());
972      }
973      for (SubentryChangeListener changeListener :
974              changeListeners)
975      {
976        try
977        {
978          changeListener.checkSubentryModifyAcceptable(
979                  oldEntry, newEntry);
980        }
981        catch (DirectoryException de)
982        {
983          logger.traceException(de);
984
985          return PluginResult.PreOperation.stopProcessing(
986                  de.getResultCode(), de.getMessageObject());
987        }
988      }
989    }
990
991    return PluginResult.PreOperation.continueOperationProcessing();
992  }
993
994  /** {@inheritDoc} */
995  @Override
996  public PreOperation doPreOperation(PreOperationModifyDNOperation modifyDNOperation)
997  {
998    boolean hasSubentryWritePrivilege = false;
999
1000    lock.readLock().lock();
1001    try
1002    {
1003      final Entry oldEntry = modifyDNOperation.getOriginalEntry();
1004      Collection<SubEntry> setToDelete =
1005              dit2SubEntry.getSubtree(oldEntry.getName());
1006      for (SubEntry subentry : setToDelete)
1007      {
1008        if (!hasSubentryWritePrivilege)
1009        {
1010          ClientConnection conn = modifyDNOperation.getClientConnection();
1011          if (!conn.hasPrivilege(Privilege.SUBENTRY_WRITE,
1012               conn.getOperationInProgress(
1013                 modifyDNOperation.getMessageID())))
1014          {
1015            return PluginResult.PreOperation.stopProcessing(
1016                    ResultCode.INSUFFICIENT_ACCESS_RIGHTS,
1017                    ERR_SUBENTRY_WRITE_INSUFFICIENT_PRIVILEGES.get());
1018          }
1019          hasSubentryWritePrivilege = true;
1020        }
1021
1022        final Entry newEntry = modifyDNOperation.getUpdatedEntry();
1023        final Entry currentSubentry = subentry.getEntry();
1024        final Entry renamedSubentry = currentSubentry.duplicate(false);
1025        final DN renamedDN = currentSubentry.getName().rename(oldEntry.getName(), newEntry.getName());
1026        renamedSubentry.setDN(renamedDN);
1027
1028        for (SubentryChangeListener changeListener : changeListeners)
1029        {
1030          try
1031          {
1032            changeListener.checkSubentryModifyAcceptable(currentSubentry, renamedSubentry);
1033          }
1034          catch (DirectoryException de)
1035          {
1036            logger.traceException(de);
1037
1038            return PluginResult.PreOperation.stopProcessing(
1039                    de.getResultCode(), de.getMessageObject());
1040          }
1041        }
1042      }
1043    }
1044    finally
1045    {
1046      lock.readLock().unlock();
1047    }
1048
1049    return PluginResult.PreOperation.continueOperationProcessing();
1050  }
1051
1052  /** {@inheritDoc} */
1053  @Override
1054  public PostOperation doPostOperation(
1055          PostOperationAddOperation addOperation)
1056  {
1057    // Only do something if the operation is successful, meaning there
1058    // has been a change.
1059    if (addOperation.getResultCode() == ResultCode.SUCCESS)
1060    {
1061      doPostAdd(addOperation.getEntryToAdd());
1062    }
1063
1064    // If we've gotten here, then everything is acceptable.
1065    return PluginResult.PostOperation.continueOperationProcessing();
1066  }
1067
1068  /** {@inheritDoc} */
1069  @Override
1070  public PostOperation doPostOperation(
1071          PostOperationDeleteOperation deleteOperation)
1072  {
1073    // Only do something if the operation is successful, meaning there
1074    // has been a change.
1075    if (deleteOperation.getResultCode() == ResultCode.SUCCESS)
1076    {
1077      doPostDelete(deleteOperation.getEntryToDelete());
1078    }
1079
1080    // If we've gotten here, then everything is acceptable.
1081    return PluginResult.PostOperation.continueOperationProcessing();
1082  }
1083
1084  /** {@inheritDoc} */
1085  @Override
1086  public PostOperation doPostOperation(
1087          PostOperationModifyOperation modifyOperation)
1088  {
1089    // Only do something if the operation is successful, meaning there
1090    // has been a change.
1091    if (modifyOperation.getResultCode() == ResultCode.SUCCESS)
1092    {
1093      doPostModify(modifyOperation.getCurrentEntry(),
1094            modifyOperation.getModifiedEntry());
1095    }
1096
1097    // If we've gotten here, then everything is acceptable.
1098    return PluginResult.PostOperation.continueOperationProcessing();
1099  }
1100
1101  /** {@inheritDoc} */
1102  @Override
1103  public PostOperation doPostOperation(
1104          PostOperationModifyDNOperation modifyDNOperation)
1105  {
1106    // Only do something if the operation is successful, meaning there
1107    // has been a change.
1108    if (modifyDNOperation.getResultCode() == ResultCode.SUCCESS)
1109    {
1110      doPostModifyDN(modifyDNOperation.getOriginalEntry(),
1111            modifyDNOperation.getUpdatedEntry());
1112    }
1113
1114    // If we've gotten here, then everything is acceptable.
1115    return PluginResult.PostOperation.continueOperationProcessing();
1116  }
1117
1118  /** {@inheritDoc} */
1119  @Override
1120  public void doPostSynchronization(
1121      PostSynchronizationAddOperation addOperation)
1122  {
1123    Entry entry = addOperation.getEntryToAdd();
1124    if (entry != null)
1125    {
1126      doPostAdd(entry);
1127    }
1128  }
1129
1130  /** {@inheritDoc} */
1131  @Override
1132  public void doPostSynchronization(
1133      PostSynchronizationDeleteOperation deleteOperation)
1134  {
1135    Entry entry = deleteOperation.getEntryToDelete();
1136    if (entry != null)
1137    {
1138      doPostDelete(entry);
1139    }
1140  }
1141
1142  /** {@inheritDoc} */
1143  @Override
1144  public void doPostSynchronization(
1145      PostSynchronizationModifyOperation modifyOperation)
1146  {
1147    Entry entry = modifyOperation.getCurrentEntry();
1148    Entry modEntry = modifyOperation.getModifiedEntry();
1149    if (entry != null && modEntry != null)
1150    {
1151      doPostModify(entry, modEntry);
1152    }
1153  }
1154
1155  /** {@inheritDoc} */
1156  @Override
1157  public void doPostSynchronization(
1158      PostSynchronizationModifyDNOperation modifyDNOperation)
1159  {
1160    Entry oldEntry = modifyDNOperation.getOriginalEntry();
1161    Entry newEntry = modifyDNOperation.getUpdatedEntry();
1162    if (oldEntry != null && newEntry != null)
1163    {
1164      doPostModifyDN(oldEntry, newEntry);
1165    }
1166  }
1167}