/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (the "License"). You may not use this file except in compliance
* with the License.
*
* You can obtain a copy of the license at
* trunk/opends/resource/legal-notices/OpenDS.LICENSE
* or https://OpenDS.dev.java.net/OpenDS.LICENSE.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at
* trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
* add the following below this CDDL HEADER, with the fields enclosed
* by brackets "[]" replaced with your own identifying information:
* Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*
*
* Copyright 2009 Sun Microsystems, Inc.
*/
package org.opends.server.core;
import java.util.*;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import org.opends.server.api.Backend;
import org.opends.server.api.BackendInitializationListener;
import org.opends.server.api.ChangeNotificationListener;
import org.opends.server.controls.SubentriesControl;
import org.opends.server.loggers.debug.DebugTracer;
import org.opends.server.protocols.internal.InternalClientConnection;
import org.opends.server.protocols.internal.InternalSearchOperation;
import org.opends.server.types.Control;
import org.opends.server.types.DebugLogLevel;
import org.opends.server.types.DereferencePolicy;
import org.opends.server.types.DN;
import org.opends.server.types.DirectoryException;
import org.opends.server.types.Entry;
import org.opends.server.types.SearchResultEntry;
import org.opends.server.types.SearchScope;
import org.opends.server.types.SearchFilter;
import org.opends.server.types.SubEntry;
import org.opends.server.types.operation.PostResponseAddOperation;
import org.opends.server.types.operation.PostResponseDeleteOperation;
import org.opends.server.types.operation.PostResponseModifyOperation;
import org.opends.server.types.operation.PostResponseModifyDNOperation;
import org.opends.server.workflowelement.localbackend.
LocalBackendSearchOperation;
import static org.opends.messages.CoreMessages.*;
import static org.opends.server.loggers.debug.DebugLogger.*;
import static org.opends.server.loggers.ErrorLogger.*;
import static org.opends.server.util.ServerConstants.*;
import static org.opends.server.config.ConfigConstants.*;
/**
* This class provides a mechanism for interacting with subentries defined in
* the Directory Server. It will handle all necessary processing at server
* startup to identify and load subentries within the server.
*
* FIXME: At the present time, it assumes that all of the necessary
* information about subentries defined in the server can be held in
* memory. If it is determined that this approach is not workable
* in all cases, then we will need an alternate strategy.
*/
public class SubentryManager
implements BackendInitializationListener, ChangeNotificationListener
{
/**
* The tracer object for the debug logger.
*/
private static final DebugTracer TRACER = getTracer();
// A mapping between the DNs and applicable subentries.
private HashMap> dn2SubEntry;
// A mapping between the DNs and applicable collective subentries.
private HashMap> dn2CollectiveSubEntry;
// Internal search all operational attributes.
private LinkedHashSet requestAttrs;
// Lock to protect internal data structures.
private final ReentrantReadWriteLock lock;
/**
* Creates a new instance of this group manager.
*/
public SubentryManager()
{
lock = new ReentrantReadWriteLock();
dn2SubEntry = new HashMap>();
dn2CollectiveSubEntry = new HashMap>();
requestAttrs = new LinkedHashSet();
requestAttrs.add("subtreespecification");
requestAttrs.add("*");
DirectoryServer.registerBackendInitializationListener(this);
DirectoryServer.registerChangeNotificationListener(this);
}
/**
* Add a given entry to this subentry manager.
* @param entry to add.
*/
private void addSubEntry(Entry entry) throws DirectoryException
{
SubEntry subEntry = new SubEntry(entry);
RFC3672SubtreeSpecification subSpec =
subEntry.getSubTreeSpecification();
DN subDN = subSpec.getBaseDN();
List subList = null;
lock.writeLock().lock();
try
{
if (subEntry.isCollective())
{
subList = dn2CollectiveSubEntry.get(subDN);
}
else
{
subList = dn2SubEntry.get(subDN);
}
if (subList == null)
{
subList = new ArrayList();
if (subEntry.isCollective())
{
dn2CollectiveSubEntry.put(subDN, subList);
}
else
{
dn2SubEntry.put(subDN, subList);
}
}
subList.add(subEntry);
}
finally
{
lock.writeLock().unlock();
}
}
/**
* Remove a given entry from this subentry manager.
* @param entry to remove.
*/
private void removeSubEntry(Entry entry)
{
lock.writeLock().lock();
try
{
boolean removed = false;
Iterator>> iterator =
dn2SubEntry.entrySet().iterator();
while (iterator.hasNext())
{
Map.Entry> mapEntry = iterator.next();
List subList = mapEntry.getValue();
for (SubEntry subEntry : subList)
{
if (subEntry.getDN().equals(entry.getDN()))
{
removed = subList.remove(subEntry);
break;
}
}
if (subList.isEmpty())
{
iterator.remove();
}
if (removed)
{
return;
}
}
iterator = dn2CollectiveSubEntry.entrySet().iterator();
while (iterator.hasNext())
{
Map.Entry> mapEntry = iterator.next();
List subList = mapEntry.getValue();
for (SubEntry subEntry : subList)
{
if (subEntry.getDN().equals(entry.getDN()))
{
removed = subList.remove(subEntry);
break;
}
}
if (subList.isEmpty())
{
iterator.remove();
}
if (removed)
{
return;
}
}
}
finally
{
lock.writeLock().unlock();
}
}
/**
* {@inheritDoc} In this case, the server will search the backend to find
* all subentries that it may contain and register them with this manager.
*/
public void performBackendInitializationProcessing(Backend backend)
{
InternalClientConnection conn =
InternalClientConnection.getRootConnection();
LinkedList requestControls = new LinkedList();
requestControls.add(new SubentriesControl(true, true));
SearchFilter filter = null;
try
{
filter = SearchFilter.createFilterFromString("(" +
ATTR_OBJECTCLASS + "=" + OC_SUBENTRY + ")");
if (backend.getEntryCount() > 0 && ! backend.isIndexed(filter))
{
logError(WARN_SUBENTRY_FILTER_NOT_INDEXED.get(
String.valueOf(filter), backend.getBackendID()));
}
}
catch (Exception e)
{
if (debugEnabled())
{
TRACER.debugCaught(DebugLogLevel.ERROR, e);
}
}
for (DN baseDN : backend.getBaseDNs())
{
try
{
if (! backend.entryExists(baseDN))
{
continue;
}
}
catch (Exception e)
{
if (debugEnabled())
{
TRACER.debugCaught(DebugLogLevel.ERROR, e);
}
// FIXME -- Is there anything that we need to do here?
continue;
}
InternalSearchOperation internalSearch = new InternalSearchOperation(
conn, InternalClientConnection.nextOperationID(),
InternalClientConnection.nextMessageID(),
requestControls, baseDN, SearchScope.WHOLE_SUBTREE,
DereferencePolicy.NEVER_DEREF_ALIASES, 0, 0, false,
filter, requestAttrs, null);
LocalBackendSearchOperation localSearch =
new LocalBackendSearchOperation(internalSearch);
try
{
backend.search(localSearch);
}
catch (Exception e)
{
if (debugEnabled())
{
TRACER.debugCaught(DebugLogLevel.ERROR, e);
}
// FIXME -- Is there anything that we need to do here?
continue;
}
for (SearchResultEntry entry : internalSearch.getSearchEntries())
{
if (entry.isSubentry())
{
try
{
addSubEntry(entry);
}
catch (Exception e)
{
if (debugEnabled())
{
TRACER.debugCaught(DebugLogLevel.ERROR, e);
}
// FIXME -- Handle this.
continue;
}
}
}
}
}
/**
* Return subentries applicable to specific DN.
* Note that this getter will skip any collective subentries,
* returning only applicable regular subentries.
* @param dn for which to retrieve applicable
* subentries.
* @return applicable subentries.
*/
public List getSubentries(DN dn)
{
if (dn2SubEntry.isEmpty())
{
return Collections.emptyList();
}
List subentries = new ArrayList();
lock.readLock().lock();
try
{
for (DN subDN = dn; subDN != null;
subDN = subDN.getParent())
{
List subList = dn2SubEntry.get(subDN);
if (subList != null)
{
for (SubEntry subEntry : subList)
{
RFC3672SubtreeSpecification subSpec =
subEntry.getSubTreeSpecification();
if (subSpec.isDNWithinScope(dn))
{
subentries.add(subEntry);
}
}
}
}
}
finally
{
lock.readLock().unlock();
}
return subentries;
}
/**
* Return subentries applicable to specific entry.
* Note that this getter will skip any collective subentries,
* returning only applicable regular subentries.
* @param entry for which to retrieve applicable
* subentries.
* @return applicable subentries.
*/
public List getSubentries(Entry entry)
{
if (dn2SubEntry.isEmpty())
{
return Collections.emptyList();
}
List subentries = new ArrayList();
lock.readLock().lock();
try
{
for (DN subDN = entry.getDN(); subDN != null;
subDN = subDN.getParent())
{
List subList = dn2SubEntry.get(subDN);
if (subList != null)
{
for (SubEntry subEntry : subList)
{
RFC3672SubtreeSpecification subSpec =
subEntry.getSubTreeSpecification();
if (subSpec.isWithinScope(entry))
{
subentries.add(subEntry);
}
}
}
}
}
finally
{
lock.readLock().unlock();
}
return subentries;
}
/**
* Return collective subentries applicable to specific DN.
* Note that this getter will skip any regular subentries,
* returning only applicable collective subentries.
* @param dn for which to retrieve applicable
* subentries.
* @return applicable subentries.
*/
public List getCollectiveSubentries(DN dn)
{
if (dn2CollectiveSubEntry.isEmpty())
{
return Collections.emptyList();
}
List subentries = new ArrayList();
lock.readLock().lock();
try
{
for (DN subDN = dn; subDN != null;
subDN = subDN.getParent())
{
List subList = dn2CollectiveSubEntry.get(subDN);
if (subList != null)
{
for (SubEntry subEntry : subList)
{
RFC3672SubtreeSpecification subSpec =
subEntry.getSubTreeSpecification();
if (subSpec.isDNWithinScope(dn))
{
subentries.add(subEntry);
}
}
}
}
}
finally
{
lock.readLock().unlock();
}
return subentries;
}
/**
* Return collective subentries applicable to specific entry.
* Note that this getter will skip any regular subentries,
* returning only applicable collective subentries.
* @param entry for which to retrieve applicable
* subentries.
* @return applicable subentries.
*/
public List getCollectiveSubentries(Entry entry)
{
if (dn2CollectiveSubEntry.isEmpty())
{
return Collections.emptyList();
}
List subentries = new ArrayList();
lock.readLock().lock();
try
{
for (DN subDN = entry.getDN(); subDN != null;
subDN = subDN.getParent())
{
List subList = dn2CollectiveSubEntry.get(subDN);
if (subList != null)
{
for (SubEntry subEntry : subList)
{
RFC3672SubtreeSpecification subSpec =
subEntry.getSubTreeSpecification();
if (subSpec.isWithinScope(entry))
{
subentries.add(subEntry);
}
}
}
}
}
finally
{
lock.readLock().unlock();
}
return subentries;
}
/**
* {@inheritDoc} In this case, the server will de-register
* all subentries associated with the provided backend.
*/
public void performBackendFinalizationProcessing(Backend backend)
{
lock.writeLock().lock();
try
{
Iterator>> iterator =
dn2SubEntry.entrySet().iterator();
while (iterator.hasNext())
{
Map.Entry> mapEntry = iterator.next();
List subList = mapEntry.getValue();
for (SubEntry subEntry : subList)
{
if (backend.handlesEntry(subEntry.getDN()))
{
subList.remove(subEntry);
}
}
if (subList.isEmpty())
{
iterator.remove();
}
}
iterator = dn2CollectiveSubEntry.entrySet().iterator();
while (iterator.hasNext())
{
Map.Entry> mapEntry = iterator.next();
List subList = mapEntry.getValue();
for (SubEntry subEntry : subList)
{
if (backend.handlesEntry(subEntry.getDN()))
{
subList.remove(subEntry);
}
}
if (subList.isEmpty())
{
iterator.remove();
}
}
}
finally
{
lock.writeLock().unlock();
}
}
/**
* {@inheritDoc} In this case, each entry is checked to see if it is
* a subentry, and if so it will be registered with this manager.
*/
public void handleAddOperation(PostResponseAddOperation addOperation,
Entry entry)
{
if (entry.isSubentry())
{
try
{
addSubEntry(entry);
}
catch (Exception e)
{
if (debugEnabled())
{
TRACER.debugCaught(DebugLogLevel.ERROR, e);
}
// FIXME -- Handle this.
}
}
}
/**
* {@inheritDoc} In this case, each entry is checked to see if it is
* a subentry, and if so it will be deregistered with this manager.
*/
public void handleDeleteOperation(PostResponseDeleteOperation deleteOperation,
Entry entry)
{
if (entry.isSubentry())
{
removeSubEntry(entry);
}
}
/**
* {@inheritDoc} In this case, if the entry is a registered subentry
* then it will be recreated from the contents of the provided entry
* and re-registered with this manager.
*/
public void handleModifyOperation(PostResponseModifyOperation modifyOperation,
Entry oldEntry, Entry newEntry)
{
if (oldEntry.isSubentry())
{
removeSubEntry(oldEntry);
}
if (newEntry.isSubentry())
{
try
{
addSubEntry(newEntry);
}
catch (Exception e)
{
if (debugEnabled())
{
TRACER.debugCaught(DebugLogLevel.ERROR, e);
}
// FIXME -- Handle this.
}
}
}
/**
* {@inheritDoc} In this case, if the subentry is registered then it
* will be recreated from the contents of the provided entry and re-
* registered with this manager under the new DN and the old instance
* will be deregistered.
*/
public void handleModifyDNOperation(
PostResponseModifyDNOperation modifyDNOperation,
Entry oldEntry, Entry newEntry)
{
if (oldEntry.isSubentry())
{
removeSubEntry(oldEntry);
try
{
addSubEntry(newEntry);
}
catch (Exception e)
{
if (debugEnabled())
{
TRACER.debugCaught(DebugLogLevel.ERROR, e);
}
// FIXME -- Handle this.
}
}
}
}