| /* |
| * 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 java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| |
| import org.apache.directory.server.core.DirectoryService; |
| import org.apache.directory.server.core.entry.ServerEntry; |
| import org.apache.directory.server.core.interceptor.BaseInterceptor; |
| import org.apache.directory.server.core.interceptor.NextInterceptor; |
| import org.apache.directory.server.core.interceptor.context.AddOperationContext; |
| import org.apache.directory.server.core.interceptor.context.DeleteOperationContext; |
| import org.apache.directory.server.core.interceptor.context.LookupOperationContext; |
| import org.apache.directory.server.core.interceptor.context.ModifyOperationContext; |
| import org.apache.directory.server.core.interceptor.context.MoveAndRenameOperationContext; |
| import org.apache.directory.server.core.interceptor.context.MoveOperationContext; |
| import org.apache.directory.server.core.interceptor.context.RenameOperationContext; |
| 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.schema.ConcreteNameComponentNormalizer; |
| import org.apache.directory.server.schema.registries.AttributeTypeRegistry; |
| import org.apache.directory.server.schema.registries.OidRegistry; |
| import org.apache.directory.server.schema.registries.Registries; |
| import org.apache.directory.shared.ldap.entry.Modification; |
| import org.apache.directory.shared.ldap.filter.AndNode; |
| 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.NotNode; |
| import org.apache.directory.shared.ldap.filter.ScopeNode; |
| import org.apache.directory.shared.ldap.message.AliasDerefMode; |
| 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.Binding; |
| import javax.naming.NamingException; |
| import javax.naming.directory.SearchControls; |
| import javax.naming.event.EventContext; |
| import javax.naming.event.NamespaceChangeListener; |
| import javax.naming.event.NamingEvent; |
| import javax.naming.event.NamingListener; |
| import javax.naming.event.ObjectChangeListener; |
| |
| |
| /** |
| * An interceptor based serivice for notifying NamingListeners of EventContext |
| * and EventDirContext changes. |
| * |
| * @org.apache.xbean.XBean |
| * |
| * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> |
| * @version $Rev$ |
| */ |
| public class EventInterceptor extends BaseInterceptor |
| { |
| private static Logger log = LoggerFactory.getLogger( EventInterceptor.class ); |
| |
| private PartitionNexus nexus; |
| private Map<NamingListener, Object> sources = new HashMap<NamingListener, Object>(); |
| private Evaluator evaluator; |
| private AttributeTypeRegistry attributeRegistry; |
| private NormalizingVisitor visitor; |
| |
| |
| public void init( DirectoryService directoryService ) throws NamingException |
| { |
| super.init( directoryService ); |
| |
| OidRegistry oidRegistry = directoryService.getRegistries().getOidRegistry(); |
| attributeRegistry = directoryService.getRegistries().getAttributeTypeRegistry(); |
| evaluator = new ExpressionEvaluator( oidRegistry, attributeRegistry ); |
| nexus = directoryService.getPartitionNexus(); |
| NameComponentNormalizer ncn = new ConcreteNameComponentNormalizer( attributeRegistry, oidRegistry ); |
| visitor = new NormalizingVisitor( ncn, directoryService.getRegistries() ); |
| } |
| |
| |
| /** |
| * 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 |
| * @throws NamingException if there are failures adding the naming listener |
| */ |
| public void addNamingListener( EventContext ctx, LdapDN 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 instanceof NotNode ) ) |
| { |
| filter = child.getFirstChild(); |
| } |
| } |
| |
| ScopeNode scope = new ScopeNode( AliasDerefMode.NEVER_DEREF_ALIASES, normalizedBaseDn.toNormName(), |
| searchControls.getSearchScope() ); |
| |
| if ( filter != null ) |
| { |
| BranchNode and = new AndNode(); |
| 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<Object> list = new ArrayList<Object>(); |
| list.add( obj ); |
| list.add( rec ); |
| sources.put( namingListener, list ); |
| } |
| else if ( obj instanceof List ) |
| { |
| //noinspection unchecked |
| List<Object> list = ( List<Object> ) 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<EventSourceRecord> list = ( List<EventSourceRecord> ) obj; |
| |
| for ( int ii = 0; ii < list.size(); ii++ ) |
| { |
| EventSourceRecord rec = list.get( ii ); |
| if ( rec.getEventContext() == ctx ) |
| { |
| list.remove( ii ); |
| } |
| } |
| |
| if ( list.isEmpty() ) |
| { |
| sources.remove( namingListener ); |
| } |
| } |
| |
| } |
| |
| |
| public void add( NextInterceptor next, AddOperationContext opContext ) throws NamingException |
| { |
| next.add( opContext ); |
| //super.add( next, opContext ); |
| |
| LdapDN name = opContext.getDn(); |
| ServerEntry entry = opContext.getEntry(); |
| |
| Set<EventSourceRecord> selecting = getSelectingSources( name, entry ); |
| |
| if ( selecting.isEmpty() ) |
| { |
| return; |
| } |
| |
| Iterator<EventSourceRecord> list = selecting.iterator(); |
| |
| while ( list.hasNext() ) |
| { |
| EventSourceRecord rec = list.next(); |
| NamingListener listener = rec.getNamingListener(); |
| |
| if ( listener instanceof NamespaceChangeListener ) |
| { |
| NamespaceChangeListener nclistener = ( NamespaceChangeListener ) listener; |
| Binding binding = new Binding( name.getUpName(), entry, false ); |
| nclistener.objectAdded( new NamingEvent( rec.getEventContext(), NamingEvent.OBJECT_ADDED, binding, |
| null, entry ) ); |
| } |
| } |
| } |
| |
| |
| public void delete( NextInterceptor next, DeleteOperationContext opContext ) throws NamingException |
| { |
| LdapDN name = opContext.getDn(); |
| ServerEntry entry = nexus.lookup( new LookupOperationContext( opContext.getRegistries(), name ) ); |
| |
| next.delete( opContext ); |
| //super.delete( next, opContext ); |
| |
| Set<EventSourceRecord> selecting = getSelectingSources( name, entry ); |
| |
| if ( selecting.isEmpty() ) |
| { |
| return; |
| } |
| |
| Iterator<EventSourceRecord> 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( Registries registries, LdapDN name, List<Modification> mods, ServerEntry oriEntry ) |
| throws NamingException |
| { |
| ServerEntry entry = nexus.lookup( new LookupOperationContext( registries, name ) ); |
| Set<EventSourceRecord> selecting = getSelectingSources( name, entry ); |
| |
| if ( selecting.isEmpty() ) |
| { |
| return; |
| } |
| |
| Iterator<EventSourceRecord> list = selecting.iterator(); |
| |
| while ( list.hasNext() ) |
| { |
| EventSourceRecord rec = 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, ModifyOperationContext opContext ) throws NamingException |
| { |
| Invocation invocation = InvocationStack.getInstance().peek(); |
| PartitionNexusProxy proxy = invocation.getProxy(); |
| ServerEntry oriEntry = proxy.lookup( |
| new LookupOperationContext( opContext.getRegistries(), opContext.getDn() ), |
| PartitionNexusProxy.LOOKUP_BYPASS ); |
| |
| next.modify( opContext ); |
| |
| notifyOnModify( opContext.getRegistries(), opContext.getDn(), opContext.getModItems(), oriEntry ); |
| } |
| |
| |
| private void notifyOnNameChange( Registries registries, LdapDN oldName, LdapDN newName ) throws NamingException |
| { |
| ServerEntry entry = nexus.lookup( new LookupOperationContext( registries, newName ) ); |
| Set<EventSourceRecord> selecting = getSelectingSources( oldName, entry ); |
| |
| if ( selecting.isEmpty() ) |
| { |
| return; |
| } |
| |
| Iterator<EventSourceRecord> list = selecting.iterator(); |
| |
| while ( list.hasNext() ) |
| { |
| EventSourceRecord rec = 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 rename( NextInterceptor next, RenameOperationContext opContext ) throws NamingException |
| { |
| next.rename( opContext ); |
| //super.rename( next, opContext ); |
| |
| LdapDN newName = ( LdapDN ) opContext.getDn().clone(); |
| newName.remove( newName.size() - 1 ); |
| newName.add( opContext.getNewRdn() ); |
| newName.normalize( attributeRegistry.getNormalizerMapping() ); |
| notifyOnNameChange( opContext.getRegistries(), opContext.getDn(), newName ); |
| } |
| |
| |
| public void moveAndRename( NextInterceptor next, MoveAndRenameOperationContext opContext ) throws NamingException |
| { |
| next.moveAndRename( opContext ); |
| //super.moveAndRename( next, opContext ); |
| |
| LdapDN newName = ( LdapDN ) opContext.getParent().clone(); |
| newName.add( opContext.getNewRdn() ); |
| notifyOnNameChange( opContext.getRegistries(), opContext.getDn(), newName ); |
| } |
| |
| |
| public void move( NextInterceptor next, MoveOperationContext opContext ) throws NamingException |
| { |
| next.move( opContext ); |
| //super.move( next, opContext ); |
| |
| LdapDN oriChildName = opContext.getDn(); |
| |
| LdapDN newName = ( LdapDN ) opContext.getParent().clone(); |
| newName.add( oriChildName.get( oriChildName.size() - 1 ) ); |
| notifyOnNameChange( opContext.getRegistries(), oriChildName, newName ); |
| } |
| |
| |
| Set<EventSourceRecord> getSelectingSources( LdapDN name, ServerEntry entry ) throws NamingException |
| { |
| if ( sources.isEmpty() ) |
| { |
| return Collections.EMPTY_SET; |
| } |
| |
| Set<EventSourceRecord> selecting = new HashSet<EventSourceRecord>(); |
| Iterator<Object> 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( rec ); |
| } |
| } |
| else if ( obj instanceof List ) |
| { |
| List<EventSourceRecord> records = ( List<EventSourceRecord> ) obj; |
| |
| for ( EventSourceRecord rec : records ) |
| { |
| if ( evaluator.evaluate( rec.getFilter(), name.toNormName(), entry ) ) |
| { |
| selecting.add( rec ); |
| } |
| } |
| } |
| else |
| { |
| throw new IllegalStateException( "Unexpected class type of " + obj.getClass() ); |
| } |
| } |
| |
| return selecting; |
| } |
| |
| class EventSourceRecord |
| { |
| private LdapDN base; |
| private SearchControls controls; |
| private ExprNode filter; |
| private EventContext context; |
| private NamingListener listener; |
| |
| |
| public EventSourceRecord( LdapDN 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 LdapDN getBase() |
| { |
| return base; |
| } |
| |
| |
| public SearchControls getSearchControls() |
| { |
| return controls; |
| } |
| } |
| } |