View Javadoc

1   /*
2    *  Licensed to the Apache Software Foundation (ASF) under one
3    *  or more contributor license agreements.  See the NOTICE file
4    *  distributed with this work for additional information
5    *  regarding copyright ownership.  The ASF licenses this file
6    *  to you under the Apache License, Version 2.0 (the
7    *  "License"); you may not use this file except in compliance
8    *  with the License.  You may obtain a copy of the License at
9    *  
10   *    http://www.apache.org/licenses/LICENSE-2.0
11   *  
12   *  Unless required by applicable law or agreed to in writing,
13   *  software distributed under the License is distributed on an
14   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   *  KIND, either express or implied.  See the License for the
16   *  specific language governing permissions and limitations
17   *  under the License. 
18   *  
19   */
20  package org.apache.directory.server.core.event;
21  
22  
23  import java.util.ArrayList;
24  import java.util.Collections;
25  import java.util.List;
26  import java.util.concurrent.ArrayBlockingQueue;
27  import java.util.concurrent.CopyOnWriteArrayList;
28  import java.util.concurrent.ExecutorService;
29  import java.util.concurrent.ThreadPoolExecutor;
30  import java.util.concurrent.TimeUnit;
31  
32  import org.apache.directory.server.core.DirectoryService;
33  import org.apache.directory.server.core.entry.ClonedServerEntry;
34  import org.apache.directory.server.core.entry.ServerEntry;
35  import org.apache.directory.server.core.interceptor.BaseInterceptor;
36  import org.apache.directory.server.core.interceptor.NextInterceptor;
37  import org.apache.directory.server.core.interceptor.context.AddOperationContext;
38  import org.apache.directory.server.core.interceptor.context.DeleteOperationContext;
39  import org.apache.directory.server.core.interceptor.context.ModifyOperationContext;
40  import org.apache.directory.server.core.interceptor.context.MoveAndRenameOperationContext;
41  import org.apache.directory.server.core.interceptor.context.MoveOperationContext;
42  import org.apache.directory.server.core.interceptor.context.OperationContext;
43  import org.apache.directory.server.core.interceptor.context.RenameOperationContext;
44  import org.apache.directory.server.core.normalization.NormalizingVisitor;
45  import org.apache.directory.server.core.partition.ByPassConstants;
46  import org.apache.directory.server.schema.ConcreteNameComponentNormalizer;
47  import org.apache.directory.server.schema.registries.AttributeTypeRegistry;
48  import org.apache.directory.server.schema.registries.OidRegistry;
49  import org.apache.directory.shared.ldap.filter.ExprNode;
50  import org.apache.directory.shared.ldap.name.LdapDN;
51  import org.apache.directory.shared.ldap.name.NameComponentNormalizer;
52  
53  import org.slf4j.Logger;
54  import org.slf4j.LoggerFactory;
55  
56  
57  /**
58   * An {@link Interceptor} based service for notifying {@link 
59   * DirectoryListener}s of changes to the DIT.
60   *
61   * @org.apache.xbean.XBean
62   *
63   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
64   * @version $Rev: 666516 $
65   */
66  public class EventInterceptor extends BaseInterceptor
67  {
68      private final static Logger LOG = LoggerFactory.getLogger( EventInterceptor.class );
69  
70      
71      private List<RegistrationEntry> registrations = new CopyOnWriteArrayList<RegistrationEntry>();
72      private DirectoryService ds;
73      private NormalizingVisitor filterNormalizer;
74      private Evaluator evaluator;
75      private ExecutorService executor;
76      
77      
78      @Override
79      public void init( DirectoryService ds ) throws Exception
80      {
81          LOG.info( "Initializing ..." );
82          super.init( ds );
83          
84          this.ds = ds;
85          OidRegistry oidRegistry = ds.getRegistries().getOidRegistry();
86          AttributeTypeRegistry attributeRegistry = ds.getRegistries().getAttributeTypeRegistry();
87          NameComponentNormalizer ncn = new ConcreteNameComponentNormalizer( attributeRegistry, oidRegistry );
88          filterNormalizer = new NormalizingVisitor( ncn, ds.getRegistries() );
89          evaluator = new ExpressionEvaluator( oidRegistry, attributeRegistry );
90          executor = new ThreadPoolExecutor( 1, 10, 1000, TimeUnit.MILLISECONDS, 
91              new ArrayBlockingQueue<Runnable>( 100 ) );
92          
93          this.ds.setEventService( new DefaultEventService() );
94          LOG.info( "Initialization complete." );
95      }
96  
97      
98      private void fire( final OperationContext opContext, EventType type, final DirectoryListener listener )
99      {
100         switch ( type )
101         {
102             case ADD:
103                 executor.execute( new Runnable() 
104                 {
105                     public void run()
106                     {
107                         listener.entryAdded( ( AddOperationContext ) opContext );
108                     }
109                 });
110                 break;
111             case DELETE:
112                 executor.execute( new Runnable() 
113                 {
114                     public void run()
115                     {
116                         listener.entryDeleted( ( DeleteOperationContext ) opContext );
117                     }
118                 });
119                 break;
120             case MODIFY:
121                 executor.execute( new Runnable() 
122                 {
123                     public void run()
124                     {
125                         listener.entryModified( ( ModifyOperationContext ) opContext );
126                     }
127                 });
128                 break;
129             case MOVE:
130                 executor.execute( new Runnable() 
131                 {
132                     public void run()
133                     {
134                         listener.entryMoved( ( MoveOperationContext ) opContext );
135                     }
136                 });
137                 break;
138             case RENAME:
139                 executor.execute( new Runnable() 
140                 {
141                     public void run()
142                     {
143                         listener.entryRenamed( ( RenameOperationContext ) opContext );
144                     }
145                 });
146                 break;
147         }
148     }
149     
150     
151     public void add( NextInterceptor next, final AddOperationContext opContext ) throws Exception
152     {
153         next.add( opContext );
154         List<RegistrationEntry> selecting = getSelectingRegistrations( opContext.getDn(), opContext.getEntry() );
155 
156         if ( selecting.isEmpty() )
157         {
158             return;
159         }
160 
161         for ( final RegistrationEntry registration : selecting )
162         {
163             if ( EventType.isAdd( registration.getCriteria().getEventMask() ) )
164             {
165                 fire( opContext, EventType.ADD, registration.getListener() );
166             }
167         }
168     }
169 
170 
171     public void delete( NextInterceptor next, final DeleteOperationContext opContext ) throws Exception
172     {
173         List<RegistrationEntry> selecting = getSelectingRegistrations( opContext.getDn(), opContext.getEntry() );
174         next.delete( opContext );
175 
176         if ( selecting.isEmpty() )
177         {
178             return;
179         }
180 
181         for ( final RegistrationEntry registration : selecting )
182         {
183             if ( EventType.isDelete( registration.getCriteria().getEventMask() ) )
184             {
185                 fire( opContext, EventType.DELETE, registration.getListener() );
186             }
187         }
188     }
189 
190 
191     public void modify( NextInterceptor next, final ModifyOperationContext opContext ) throws Exception
192     {
193         ClonedServerEntry oriEntry = opContext.lookup( opContext.getDn(), ByPassConstants.LOOKUP_BYPASS );
194         List<RegistrationEntry> selecting = getSelectingRegistrations( opContext.getDn(), oriEntry );
195         next.modify( opContext );
196 
197         if ( selecting.isEmpty() )
198         {
199             return;
200         }
201 
202         for ( final RegistrationEntry registration : selecting )
203         {
204             if ( EventType.isModify( registration.getCriteria().getEventMask() ) )
205             {
206                 fire( opContext, EventType.MODIFY, registration.getListener() );
207             }
208         }
209     }
210 
211 
212     public void rename( NextInterceptor next, RenameOperationContext opContext ) throws Exception
213     {
214         ClonedServerEntry oriEntry = opContext.lookup( opContext.getDn(), ByPassConstants.LOOKUP_BYPASS );
215         List<RegistrationEntry> selecting = getSelectingRegistrations( opContext.getDn(), oriEntry );
216         next.rename( opContext );
217 
218         if ( selecting.isEmpty() )
219         {
220             return;
221         }
222 
223         opContext.setAlteredEntry( opContext.lookup( opContext.getNewDn(), ByPassConstants.LOOKUP_BYPASS ) );
224         
225         for ( final RegistrationEntry registration : selecting )
226         {
227             if ( EventType.isRename( registration.getCriteria().getEventMask() ) )
228             {
229                 fire( opContext, EventType.RENAME, registration.getListener() );
230             }
231         }
232     }
233 
234 
235     public void moveAndRename( NextInterceptor next, final MoveAndRenameOperationContext opContext ) throws Exception
236     {
237         ClonedServerEntry oriEntry = opContext.lookup( opContext.getDn(), ByPassConstants.LOOKUP_BYPASS );
238         List<RegistrationEntry> selecting = getSelectingRegistrations( opContext.getDn(), oriEntry );
239         next.moveAndRename( opContext );
240 
241         if ( selecting.isEmpty() )
242         {
243             return;
244         }
245 
246         opContext.setAlteredEntry( opContext.lookup( opContext.getNewDn(), ByPassConstants.LOOKUP_BYPASS ) );
247         
248         for ( final RegistrationEntry registration : selecting )
249         {
250             if ( EventType.isMoveAndRename( registration.getCriteria().getEventMask() ) )
251             {
252                 executor.execute( new Runnable() 
253                 {
254                     public void run()
255                     {
256                         registration.getListener().entryMovedAndRenamed( opContext );
257                     }
258                 });
259             }
260         }
261     }
262 
263 
264     public void move( NextInterceptor next, MoveOperationContext opContext ) throws Exception
265     {
266         ClonedServerEntry oriEntry = opContext.lookup( opContext.getDn(), ByPassConstants.LOOKUP_BYPASS );
267         List<RegistrationEntry> selecting = getSelectingRegistrations( opContext.getDn(), oriEntry );
268         next.move( opContext );
269 
270         if ( selecting.isEmpty() )
271         {
272             return;
273         }
274 
275         for ( final RegistrationEntry registration : selecting )
276         {
277             if ( EventType.isMove( registration.getCriteria().getEventMask() ) )
278             {
279                 fire( opContext, EventType.MOVE, registration.getListener() );
280             }
281         }
282     }
283     
284     
285     List<RegistrationEntry> getSelectingRegistrations( LdapDN name, ServerEntry entry ) throws Exception
286     {
287         if ( registrations.isEmpty() )
288         {
289             return Collections.emptyList();
290         }
291 
292         List<RegistrationEntry> selecting = new ArrayList<RegistrationEntry>();
293         
294         for ( RegistrationEntry registration : registrations )
295         {
296             NotificationCriteria criteria = registration.getCriteria();
297             
298             if ( evaluator.evaluate( criteria.getFilter(), criteria.getBase().toNormName(), entry ) )
299             {
300                 selecting.add( registration );
301             }
302         }
303 
304         return selecting;
305     }
306     
307     
308     // -----------------------------------------------------------------------
309     // EventService Inner Class
310     // -----------------------------------------------------------------------
311     
312     
313     class DefaultEventService implements EventService
314     {
315         /*
316          * Does not need normalization since default values in criteria is used.
317          */
318         public void addListener( DirectoryListener listener )
319         {
320             registrations.add( new RegistrationEntry( listener ) );
321         }
322 
323         
324         /*
325          * Normalizes the criteria filter and the base.
326          */
327         public void addListener( DirectoryListener listener, NotificationCriteria criteria ) throws Exception
328         {
329             criteria.getBase().normalize( ds.getRegistries().getAttributeTypeRegistry().getNormalizerMapping() );
330             ExprNode result = ( ExprNode ) criteria.getFilter().accept( filterNormalizer );
331             criteria.setFilter( result );
332             registrations.add( new RegistrationEntry( listener, criteria ) );
333         }
334 
335         
336         public void removeListener( DirectoryListener listener )
337         {
338             for ( RegistrationEntry entry : registrations )
339             {
340                 if ( entry.getListener() == listener )
341                 {
342                     registrations.remove( entry );
343                 }
344             }
345         }
346 
347 
348         public List<RegistrationEntry> getRegistrationEntries()
349         {
350             return Collections.unmodifiableList( registrations );
351         }
352     }
353 }