001    /*
002     *  Licensed to the Apache Software Foundation (ASF) under one
003     *  or more contributor license agreements.  See the NOTICE file
004     *  distributed with this work for additional information
005     *  regarding copyright ownership.  The ASF licenses this file
006     *  to you under the Apache License, Version 2.0 (the
007     *  "License"); you may not use this file except in compliance
008     *  with the License.  You may obtain a copy of the License at
009     *  
010     *    http://www.apache.org/licenses/LICENSE-2.0
011     *  
012     *  Unless required by applicable law or agreed to in writing,
013     *  software distributed under the License is distributed on an
014     *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015     *  KIND, either express or implied.  See the License for the
016     *  specific language governing permissions and limitations
017     *  under the License. 
018     *  
019     */
020    package org.apache.directory.server.core.event;
021    
022    
023    import java.util.ArrayList;
024    import java.util.Collections;
025    import java.util.List;
026    import java.util.concurrent.ArrayBlockingQueue;
027    import java.util.concurrent.CopyOnWriteArrayList;
028    import java.util.concurrent.ExecutorService;
029    import java.util.concurrent.ThreadPoolExecutor;
030    import java.util.concurrent.TimeUnit;
031    
032    import org.apache.directory.server.core.DirectoryService;
033    import org.apache.directory.server.core.entry.ClonedServerEntry;
034    import org.apache.directory.server.core.interceptor.BaseInterceptor;
035    import org.apache.directory.server.core.interceptor.NextInterceptor;
036    import org.apache.directory.server.core.interceptor.context.AddOperationContext;
037    import org.apache.directory.server.core.interceptor.context.DeleteOperationContext;
038    import org.apache.directory.server.core.interceptor.context.ModifyOperationContext;
039    import org.apache.directory.server.core.interceptor.context.MoveAndRenameOperationContext;
040    import org.apache.directory.server.core.interceptor.context.MoveOperationContext;
041    import org.apache.directory.server.core.interceptor.context.OperationContext;
042    import org.apache.directory.server.core.interceptor.context.RenameOperationContext;
043    import org.apache.directory.server.core.normalization.FilterNormalizingVisitor;
044    import org.apache.directory.server.core.partition.ByPassConstants;
045    import org.apache.directory.shared.ldap.entry.ServerEntry;
046    import org.apache.directory.shared.ldap.filter.ExprNode;
047    import org.apache.directory.shared.ldap.name.DN;
048    import org.apache.directory.shared.ldap.name.NameComponentNormalizer;
049    import org.apache.directory.shared.ldap.schema.SchemaManager;
050    import org.apache.directory.shared.ldap.schema.normalizers.ConcreteNameComponentNormalizer;
051    import org.apache.directory.shared.ldap.schema.registries.OidRegistry;
052    import org.slf4j.Logger;
053    import org.slf4j.LoggerFactory;
054    
055    
056    /**
057     * An {@link Interceptor} based service for notifying {@link 
058     * DirectoryListener}s of changes to the DIT.
059     *
060     * @org.apache.xbean.XBean
061     *
062     * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
063     * @version $Rev: 666516 $
064     */
065    public class EventInterceptor extends BaseInterceptor
066    {
067        private final static Logger LOG = LoggerFactory.getLogger( EventInterceptor.class );
068    
069        
070        private List<RegistrationEntry> registrations = new CopyOnWriteArrayList<RegistrationEntry>();
071        private DirectoryService ds;
072        private FilterNormalizingVisitor filterNormalizer;
073        private Evaluator evaluator;
074        private ExecutorService executor;
075        
076        
077        @Override
078        public void init( DirectoryService ds ) throws Exception
079        {
080            LOG.info( "Initializing ..." );
081            super.init( ds );
082            
083            this.ds = ds;
084            OidRegistry oidRegistry = ds.getSchemaManager().getGlobalOidRegistry();
085            SchemaManager schemaManager = ds.getSchemaManager();
086            NameComponentNormalizer ncn = new ConcreteNameComponentNormalizer( schemaManager );
087            filterNormalizer = new FilterNormalizingVisitor( ncn, schemaManager );
088            evaluator = new ExpressionEvaluator( oidRegistry, schemaManager );
089            executor = new ThreadPoolExecutor( 1, 10, 1000, TimeUnit.MILLISECONDS, 
090                new ArrayBlockingQueue<Runnable>( 100 ) );
091            
092            this.ds.setEventService( new DefaultEventService() );
093            LOG.info( "Initialization complete." );
094        }
095    
096        
097        private void fire( final OperationContext opContext, EventType type, final DirectoryListener listener )
098        {
099            switch ( type )
100            {
101                case ADD:
102                    executor.execute( new Runnable() 
103                    {
104                        public void run()
105                        {
106                            listener.entryAdded( ( AddOperationContext ) opContext );
107                        }
108                    });
109                    break;
110                case DELETE:
111                    executor.execute( new Runnable() 
112                    {
113                        public void run()
114                        {
115                            listener.entryDeleted( ( DeleteOperationContext ) opContext );
116                        }
117                    });
118                    break;
119                case MODIFY:
120                    executor.execute( new Runnable() 
121                    {
122                        public void run()
123                        {
124                            listener.entryModified( ( ModifyOperationContext ) opContext );
125                        }
126                    });
127                    break;
128                case MOVE:
129                    executor.execute( new Runnable() 
130                    {
131                        public void run()
132                        {
133                            listener.entryMoved( ( MoveOperationContext ) opContext );
134                        }
135                    });
136                    break;
137                case RENAME:
138                    executor.execute( new Runnable() 
139                    {
140                        public void run()
141                        {
142                            listener.entryRenamed( ( RenameOperationContext ) opContext );
143                        }
144                    });
145                    break;
146            }
147        }
148        
149        
150        public void add( NextInterceptor next, final AddOperationContext opContext ) throws Exception
151        {
152            next.add( opContext );
153            List<RegistrationEntry> selecting = getSelectingRegistrations( opContext.getDn(), opContext.getEntry() );
154    
155            if ( selecting.isEmpty() )
156            {
157                return;
158            }
159    
160            for ( final RegistrationEntry registration : selecting )
161            {
162                if ( EventType.isAdd( registration.getCriteria().getEventMask() ) )
163                {
164                    fire( opContext, EventType.ADD, registration.getListener() );
165                }
166            }
167        }
168    
169    
170        public void delete( NextInterceptor next, final DeleteOperationContext opContext ) throws Exception
171        {
172            List<RegistrationEntry> selecting = getSelectingRegistrations( opContext.getDn(), opContext.getEntry() );
173            next.delete( opContext );
174    
175            if ( selecting.isEmpty() )
176            {
177                return;
178            }
179    
180            for ( final RegistrationEntry registration : selecting )
181            {
182                if ( EventType.isDelete( registration.getCriteria().getEventMask() ) )
183                {
184                    fire( opContext, EventType.DELETE, registration.getListener() );
185                }
186            }
187        }
188    
189    
190        public void modify( NextInterceptor next, final ModifyOperationContext opContext ) throws Exception
191        {
192            ClonedServerEntry oriEntry = opContext.lookup( opContext.getDn(), ByPassConstants.LOOKUP_BYPASS );
193            List<RegistrationEntry> selecting = getSelectingRegistrations( opContext.getDn(), oriEntry );
194            
195            next.modify( opContext );
196    
197            if ( selecting.isEmpty() )
198            {
199                return;
200            }
201    
202            // Get the modified entry
203            ClonedServerEntry alteredEntry = opContext.lookup( opContext.getDn(), ByPassConstants.LOOKUP_BYPASS );
204            opContext.setAlteredEntry( alteredEntry );
205    
206            for ( final RegistrationEntry registration : selecting )
207            {
208                if ( EventType.isModify( registration.getCriteria().getEventMask() ) )
209                {
210                    fire( opContext, EventType.MODIFY, registration.getListener() );
211                }
212            }
213        }
214    
215    
216        public void rename( NextInterceptor next, RenameOperationContext opContext ) throws Exception
217        {
218            ClonedServerEntry oriEntry = opContext.lookup( opContext.getDn(), ByPassConstants.LOOKUP_BYPASS );
219            List<RegistrationEntry> selecting = getSelectingRegistrations( opContext.getDn(), oriEntry );
220            
221            next.rename( opContext );
222    
223            if ( selecting.isEmpty() )
224            {
225                return;
226            }
227    
228            // Get the modifed entry
229            ClonedServerEntry alteredEntry = opContext.lookup( opContext.getNewDn(), ByPassConstants.LOOKUP_BYPASS );
230            opContext.setAlteredEntry( alteredEntry );
231            
232            for ( final RegistrationEntry registration : selecting )
233            {
234                if ( EventType.isRename( registration.getCriteria().getEventMask() ) )
235                {
236                    fire( opContext, EventType.RENAME, registration.getListener() );
237                }
238            }
239        }
240    
241    
242        public void moveAndRename( NextInterceptor next, final MoveAndRenameOperationContext opContext ) throws Exception
243        {
244            ClonedServerEntry oriEntry = opContext.lookup( opContext.getDn(), ByPassConstants.LOOKUP_BYPASS );
245            List<RegistrationEntry> selecting = getSelectingRegistrations( opContext.getDn(), oriEntry );
246            next.moveAndRename( opContext );
247    
248            if ( selecting.isEmpty() )
249            {
250                return;
251            }
252    
253            opContext.setAlteredEntry( opContext.lookup( opContext.getNewDn(), ByPassConstants.LOOKUP_BYPASS ) );
254            
255            for ( final RegistrationEntry registration : selecting )
256            {
257                if ( EventType.isMoveAndRename( registration.getCriteria().getEventMask() ) )
258                {
259                    executor.execute( new Runnable() 
260                    {
261                        public void run()
262                        {
263                            registration.getListener().entryMovedAndRenamed( opContext );
264                        }
265                    });
266                }
267            }
268        }
269    
270    
271        public void move( NextInterceptor next, MoveOperationContext opContext ) throws Exception
272        {
273            ClonedServerEntry oriEntry = opContext.lookup( opContext.getDn(), ByPassConstants.LOOKUP_BYPASS );
274            List<RegistrationEntry> selecting = getSelectingRegistrations( opContext.getDn(), oriEntry );
275            
276            next.move( opContext );
277    
278            if ( selecting.isEmpty() )
279            {
280                return;
281            }
282    
283            for ( final RegistrationEntry registration : selecting )
284            {
285                if ( EventType.isMove( registration.getCriteria().getEventMask() ) )
286                {
287                    fire( opContext, EventType.MOVE, registration.getListener() );
288                }
289            }
290        }
291        
292        
293        List<RegistrationEntry> getSelectingRegistrations( DN name, ServerEntry entry ) throws Exception
294        {
295            if ( registrations.isEmpty() )
296            {
297                return Collections.emptyList();
298            }
299    
300            List<RegistrationEntry> selecting = new ArrayList<RegistrationEntry>();
301            
302            for ( RegistrationEntry registration : registrations )
303            {
304                NotificationCriteria criteria = registration.getCriteria();
305                
306                if ( evaluator.evaluate( criteria.getFilter(), criteria.getBase().getNormName(), entry ) )
307                {
308                    selecting.add( registration );
309                }
310            }
311    
312            return selecting;
313        }
314        
315        
316        // -----------------------------------------------------------------------
317        // EventService Inner Class
318        // -----------------------------------------------------------------------
319        
320        
321        class DefaultEventService implements EventService
322        {
323            /*
324             * Does not need normalization since default values in criteria is used.
325             */
326            public void addListener( DirectoryListener listener )
327            {
328                registrations.add( new RegistrationEntry( listener ) );
329            }
330    
331            
332            /*
333             * Normalizes the criteria filter and the base.
334             */
335            public void addListener( DirectoryListener listener, NotificationCriteria criteria ) throws Exception
336            {
337                criteria.getBase().normalize( ds.getSchemaManager().getNormalizerMapping() );
338                ExprNode result = ( ExprNode ) criteria.getFilter().accept( filterNormalizer );
339                criteria.setFilter( result );
340                registrations.add( new RegistrationEntry( listener, criteria ) );
341            }
342    
343            
344            public void removeListener( DirectoryListener listener )
345            {
346                for ( RegistrationEntry entry : registrations )
347                {
348                    if ( entry.getListener() == listener )
349                    {
350                        registrations.remove( entry );
351                    }
352                }
353            }
354    
355    
356            public List<RegistrationEntry> getRegistrationEntries()
357            {
358                return Collections.unmodifiableList( registrations );
359            }
360        }
361    }