blob: 8cd03afcc6c31c50dc0fa1d180d539ca6506017d [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
package org.apache.directory.server.core.event;
import org.apache.directory.server.core.DirectoryServiceConfiguration;
import org.apache.directory.server.core.configuration.InterceptorConfiguration;
import org.apache.directory.server.core.interceptor.BaseInterceptor;
import org.apache.directory.server.core.interceptor.NextInterceptor;
import org.apache.directory.server.core.invocation.Invocation;
import org.apache.directory.server.core.invocation.InvocationStack;
import org.apache.directory.server.core.normalization.NormalizingVisitor;
import org.apache.directory.server.core.partition.PartitionNexus;
import org.apache.directory.server.core.partition.PartitionNexusProxy;
import org.apache.directory.server.core.schema.AttributeTypeRegistry;
import org.apache.directory.server.core.schema.ConcreteNameComponentNormalizer;
import org.apache.directory.server.core.schema.OidRegistry;
import org.apache.directory.shared.ldap.filter.BranchNode;
import org.apache.directory.shared.ldap.filter.ExprNode;
import org.apache.directory.shared.ldap.filter.LeafNode;
import org.apache.directory.shared.ldap.filter.ScopeNode;
import org.apache.directory.shared.ldap.message.DerefAliasesEnum;
import org.apache.directory.shared.ldap.message.ModificationItemImpl;
import org.apache.directory.shared.ldap.name.LdapDN;
import org.apache.directory.shared.ldap.name.NameComponentNormalizer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.naming.Name;
import javax.naming.NamingException;
import javax.naming.Binding;
import javax.naming.NamingEnumeration;
import javax.naming.event.*;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.SearchControls;
import java.util.*;
/**
* An interceptor based serivice for notifying NamingListeners of EventContext
* and EventDirContext changes.
*
* @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
* @version $Rev$
*/
public class EventService extends BaseInterceptor
{
private static Logger log = LoggerFactory.getLogger( EventService.class );
private PartitionNexus nexus;
private Map sources = new HashMap();
private Evaluator evaluator = null;
private AttributeTypeRegistry attributeRegistry;
private NormalizingVisitor visitor;
public void init( DirectoryServiceConfiguration factoryCfg, InterceptorConfiguration cfg ) throws NamingException
{
super.init( factoryCfg, cfg );
OidRegistry oidRegistry = factoryCfg.getGlobalRegistries().getOidRegistry();
attributeRegistry = factoryCfg.getGlobalRegistries().getAttributeTypeRegistry();
evaluator = new ExpressionEvaluator( oidRegistry, attributeRegistry );
nexus = factoryCfg.getPartitionNexus();
NameComponentNormalizer ncn = new ConcreteNameComponentNormalizer( attributeRegistry, oidRegistry );
visitor = new NormalizingVisitor( ncn, factoryCfg.getGlobalRegistries().getOidRegistry() );
}
/**
* Registers a NamingListener with this service for notification of change.
*
* @param ctx the context used to register on (the source)
* @param name the name of the base/target
* @param filter the filter to use for evaluating event triggering
* @param searchControls the search controls to use when evaluating triggering
* @param namingListener the naming listener to register
*/
public void addNamingListener( EventContext ctx, Name name, ExprNode filter, SearchControls searchControls,
NamingListener namingListener ) throws NamingException
{
LdapDN normalizedBaseDn = new LdapDN( name );
normalizedBaseDn.normalize( attributeRegistry.getNormalizerMapping() );
// -------------------------------------------------------------------
// must normalize the filter here: need to handle special cases
// -------------------------------------------------------------------
if ( filter.isLeaf() )
{
LeafNode ln = ( LeafNode ) filter;
if ( !attributeRegistry.hasAttributeType( ln.getAttribute() ) )
{
StringBuffer buf = new StringBuffer();
buf.append( "undefined filter based on undefined attributeType '" );
buf.append( ln.getAttribute() );
buf.append( "' not evaluted at all. Only using scope node." );
log.warn( buf.toString() );
filter = null;
}
else
{
filter.accept( visitor );
}
}
else
{
filter.accept( visitor );
// Check that after pruning/normalization we have a valid branch node at the top
BranchNode child = ( BranchNode ) filter;
// If the remaining filter branch node has no children set filter to null
if ( child.getChildren().size() == 0 )
{
log.warn( "Undefined branchnode filter without child nodes not evaluted at all. " +
"Only using scope node." );
filter = null;
}
// Now for AND & OR nodes with a single child left replace them with their child
if ( child.getChildren().size() == 1 && child.getOperator() != BranchNode.NOT )
{
filter = child.getChild();
}
}
ScopeNode scope = new ScopeNode( DerefAliasesEnum.NEVERDEREFALIASES, normalizedBaseDn.toNormName(),
searchControls.getSearchScope() );
if ( filter != null )
{
BranchNode and = new BranchNode( BranchNode.AND );
and.addNode( scope );
and.addNode( filter );
filter = and;
}
else
{
filter = scope;
}
EventSourceRecord rec = new EventSourceRecord( name, filter, ctx, searchControls, namingListener );
Object obj = sources.get( namingListener );
if ( obj == null )
{
sources.put( namingListener, rec );
}
else if ( obj instanceof EventSourceRecord )
{
List list = new ArrayList();
list.add( obj );
list.add( rec );
sources.put( namingListener, list );
}
else if ( obj instanceof List )
{
List list = ( List ) obj;
list.add( rec );
}
}
public void removeNamingListener( EventContext ctx, NamingListener namingListener )
{
Object obj = sources.get( namingListener );
if ( obj == null )
{
return;
}
if ( obj instanceof EventSourceRecord )
{
sources.remove( namingListener );
}
else if ( obj instanceof List )
{
List list = ( List ) obj;
for ( int ii = 0; ii < list.size(); ii++ )
{
EventSourceRecord rec = ( EventSourceRecord ) list.get( ii );
if ( rec.getEventContext() == ctx )
{
list.remove( ii );
}
}
if ( list.isEmpty() )
{
sources.remove( namingListener );
}
}
}
public void add( NextInterceptor next, LdapDN normName, Attributes entry ) throws NamingException
{
super.add( next, normName, entry );
Set selecting = getSelectingSources( normName, entry );
if ( selecting.isEmpty() )
{
return;
}
Iterator list = selecting.iterator();
while ( list.hasNext() )
{
EventSourceRecord rec = ( EventSourceRecord ) list.next();
NamingListener listener = rec.getNamingListener();
if ( listener instanceof NamespaceChangeListener )
{
NamespaceChangeListener nclistener = ( NamespaceChangeListener ) listener;
Binding binding = new Binding( normName.getUpName(), entry, false );
nclistener.objectAdded( new NamingEvent( rec.getEventContext(), NamingEvent.OBJECT_ADDED, binding,
null, entry ) );
}
}
}
public void delete( NextInterceptor next, LdapDN name ) throws NamingException
{
Attributes entry = nexus.lookup( name );
super.delete( next, name );
Set selecting = getSelectingSources( name, entry );
if ( selecting.isEmpty() )
{
return;
}
Iterator list = selecting.iterator();
while ( list.hasNext() )
{
EventSourceRecord rec = ( EventSourceRecord ) list.next();
NamingListener listener = rec.getNamingListener();
if ( listener instanceof NamespaceChangeListener )
{
NamespaceChangeListener nclistener = ( NamespaceChangeListener ) listener;
Binding binding = new Binding( name.getUpName(), entry, false );
nclistener.objectRemoved( new NamingEvent( rec.getEventContext(), NamingEvent.OBJECT_REMOVED, null,
binding, entry ) );
}
}
}
private void notifyOnModify( LdapDN name, ModificationItemImpl[] mods, Attributes oriEntry ) throws NamingException
{
Attributes entry = nexus.lookup( name );
Set selecting = getSelectingSources( name, entry );
if ( selecting.isEmpty() )
{
return;
}
Iterator list = selecting.iterator();
while ( list.hasNext() )
{
EventSourceRecord rec = ( EventSourceRecord ) list.next();
NamingListener listener = rec.getNamingListener();
if ( listener instanceof ObjectChangeListener )
{
ObjectChangeListener oclistener = ( ObjectChangeListener ) listener;
Binding before = new Binding( name.getUpName(), oriEntry, false );
Binding after = new Binding( name.getUpName(), entry, false );
oclistener.objectChanged( new NamingEvent( rec.getEventContext(), NamingEvent.OBJECT_CHANGED, after,
before, mods ) );
}
}
}
public void modify( NextInterceptor next, LdapDN name, int modOp, Attributes mods ) throws NamingException
{
Invocation invocation = InvocationStack.getInstance().peek();
PartitionNexusProxy proxy = invocation.getProxy();
Attributes oriEntry = proxy.lookup( name, PartitionNexusProxy.LOOKUP_BYPASS );
super.modify( next, name, modOp, mods );
// package modifications in ModItem format for event delivery
ModificationItemImpl[] modItems = new ModificationItemImpl[mods.size()];
NamingEnumeration list = mods.getAll();
for ( int ii = 0; ii < modItems.length; ii++ )
{
modItems[ii] = new ModificationItemImpl( modOp, ( Attribute ) list.next() );
}
notifyOnModify( name, modItems, oriEntry );
}
public void modify( NextInterceptor next, LdapDN name, ModificationItemImpl[] mods ) throws NamingException
{
Invocation invocation = InvocationStack.getInstance().peek();
PartitionNexusProxy proxy = invocation.getProxy();
Attributes oriEntry = proxy.lookup( name, PartitionNexusProxy.LOOKUP_BYPASS );
super.modify( next, name, mods );
notifyOnModify( name, mods, oriEntry );
}
private void notifyOnNameChange( LdapDN oldName, LdapDN newName ) throws NamingException
{
Attributes entry = nexus.lookup( newName );
Set selecting = getSelectingSources( oldName, entry );
if ( selecting.isEmpty() )
{
return;
}
Iterator list = selecting.iterator();
while ( list.hasNext() )
{
EventSourceRecord rec = ( EventSourceRecord ) list.next();
NamingListener listener = rec.getNamingListener();
if ( listener instanceof NamespaceChangeListener )
{
NamespaceChangeListener nclistener = ( NamespaceChangeListener ) listener;
Binding oldBinding = new Binding( oldName.getUpName(), entry, false );
Binding newBinding = new Binding( newName.getUpName(), entry, false );
nclistener.objectRenamed( new NamingEvent( rec.getEventContext(), NamingEvent.OBJECT_RENAMED,
newBinding, oldBinding, entry ) );
}
}
}
public void modifyRn( NextInterceptor next, LdapDN name, String newRn, boolean deleteOldRn ) throws NamingException
{
super.modifyRn( next, name, newRn, deleteOldRn );
LdapDN newName = ( LdapDN ) name.clone();
newName.remove( newName.size() - 1 );
newName.add( newRn );
newName.normalize( attributeRegistry.getNormalizerMapping() );
notifyOnNameChange( name, newName );
}
public void move( NextInterceptor next, LdapDN oriChildName, LdapDN newParentName, String newRn, boolean deleteOldRn )
throws NamingException
{
super.move( next, oriChildName, newParentName, newRn, deleteOldRn );
LdapDN newName = ( LdapDN ) newParentName.clone();
newName.add( newRn );
notifyOnNameChange( oriChildName, newName );
}
public void move( NextInterceptor next, LdapDN oriChildName, LdapDN newParentName ) throws NamingException
{
super.move( next, oriChildName, newParentName );
LdapDN newName = ( LdapDN ) newParentName.clone();
newName.add( oriChildName.get( oriChildName.size() - 1 ) );
notifyOnNameChange( oriChildName, newName );
}
Set getSelectingSources( LdapDN name, Attributes entry ) throws NamingException
{
if ( sources.isEmpty() )
{
return Collections.EMPTY_SET;
}
Set selecting = new HashSet();
Iterator list = sources.values().iterator();
while ( list.hasNext() )
{
Object obj = list.next();
if ( obj instanceof EventSourceRecord )
{
EventSourceRecord rec = ( EventSourceRecord ) obj;
if ( evaluator.evaluate( rec.getFilter(), name.toNormName(), entry ) )
{
selecting.add( obj );
}
}
else if ( obj instanceof List )
{
List records = ( List ) obj;
for ( int ii = 0; ii < records.size(); ii++ )
{
EventSourceRecord rec = ( EventSourceRecord ) records.get( ii );
if ( evaluator.evaluate( rec.getFilter(), name.toNormName(), entry ) )
{
selecting.add( obj );
}
}
}
else
{
throw new IllegalStateException( "Unexpected class type of " + obj.getClass() );
}
}
return selecting;
}
class EventSourceRecord
{
private Name base;
private SearchControls controls;
private ExprNode filter;
private EventContext context;
private NamingListener listener;
public EventSourceRecord(Name base, ExprNode filter, EventContext context, SearchControls controls,
NamingListener listener)
{
this.filter = filter;
this.context = context;
this.base = base;
this.controls = controls;
this.listener = listener;
}
public NamingListener getNamingListener()
{
return listener;
}
public ExprNode getFilter()
{
return filter;
}
public EventContext getEventContext()
{
return context;
}
public Name getBase()
{
return base;
}
public SearchControls getSearchControls()
{
return controls;
}
}
}