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 }