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 */ 020package org.apache.directory.server.core.event; 021 022 023import static org.apache.directory.api.ldap.model.message.SearchScope.OBJECT; 024import static org.apache.directory.api.ldap.model.message.SearchScope.ONELEVEL; 025import static org.apache.directory.api.ldap.model.message.SearchScope.SUBTREE; 026 027import java.util.ArrayList; 028import java.util.Collections; 029import java.util.List; 030import java.util.concurrent.ArrayBlockingQueue; 031import java.util.concurrent.ExecutorService; 032import java.util.concurrent.Executors; 033import java.util.concurrent.ThreadFactory; 034import java.util.concurrent.ThreadPoolExecutor; 035import java.util.concurrent.TimeUnit; 036 037import org.apache.directory.api.ldap.model.constants.SchemaConstants; 038import org.apache.directory.api.ldap.model.entry.Entry; 039import org.apache.directory.api.ldap.model.exception.LdapException; 040import org.apache.directory.api.ldap.model.message.SearchScope; 041import org.apache.directory.api.ldap.model.name.Dn; 042import org.apache.directory.server.core.api.CoreSession; 043import org.apache.directory.server.core.api.DirectoryService; 044import org.apache.directory.server.core.api.InterceptorEnum; 045import org.apache.directory.server.core.api.entry.ClonedServerEntry; 046import org.apache.directory.server.core.api.event.DirectoryListener; 047import org.apache.directory.server.core.api.event.Evaluator; 048import org.apache.directory.server.core.api.event.EventType; 049import org.apache.directory.server.core.api.event.ExpressionEvaluator; 050import org.apache.directory.server.core.api.event.NotificationCriteria; 051import org.apache.directory.server.core.api.event.RegistrationEntry; 052import org.apache.directory.server.core.api.interceptor.BaseInterceptor; 053import org.apache.directory.server.core.api.interceptor.context.AddOperationContext; 054import org.apache.directory.server.core.api.interceptor.context.DeleteOperationContext; 055import org.apache.directory.server.core.api.interceptor.context.LookupOperationContext; 056import org.apache.directory.server.core.api.interceptor.context.ModifyOperationContext; 057import org.apache.directory.server.core.api.interceptor.context.MoveAndRenameOperationContext; 058import org.apache.directory.server.core.api.interceptor.context.MoveOperationContext; 059import org.apache.directory.server.core.api.interceptor.context.OperationContext; 060import org.apache.directory.server.core.api.interceptor.context.RenameOperationContext; 061import org.slf4j.Logger; 062import org.slf4j.LoggerFactory; 063 064/** 065 * An {@link org.apache.directory.server.core.api.interceptor.Interceptor} based service for notifying {@link 066 * DirectoryListener}s of changes to the DIT. 067 * 068 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> 069 */ 070public class EventInterceptor extends BaseInterceptor 071{ 072 /** A logger for this class */ 073 private static final Logger LOG = LoggerFactory.getLogger( EventInterceptor.class ); 074 075 private Evaluator evaluator; 076 private ExecutorService executor; 077 078 079 /** 080 * Creates a new instance of a EventInterceptor. 081 */ 082 public EventInterceptor() 083 { 084 super( InterceptorEnum.EVENT_INTERCEPTOR ); 085 } 086 087 088 /** 089 * Initialize the event interceptor. It creates a pool of executor which will be used 090 * to call the listeners in separate threads. 091 */ 092 @Override 093 public void init( DirectoryService directoryService ) throws LdapException 094 { 095 LOG.info( "Initializing ..." ); 096 super.init( directoryService ); 097 098 evaluator = new ExpressionEvaluator( schemaManager ); 099 executor = new ThreadPoolExecutor( 1, 10, 1000, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>( 100 ) ); 100 101 ThreadFactory threadFactory = new ThreadFactory() 102 { 103 @Override 104 public Thread newThread( Runnable runnable ) 105 { 106 Thread newThread = Executors.defaultThreadFactory().newThread( runnable ); 107 newThread.setDaemon( true ); 108 109 return newThread; 110 } 111 }; 112 113 executor = new ThreadPoolExecutor( 1, 10, 1000, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>( 100 ), 114 threadFactory ); 115 116 this.directoryService.setEventService( new DefaultEventService( directoryService ) ); 117 LOG.info( "Initialization complete." ); 118 } 119 120 121 /** 122 * Call the listener passing it the context. 123 */ 124 private void fire( final OperationContext opContext, EventType type, final DirectoryListener listener ) 125 { 126 switch ( type ) 127 { 128 case ADD: 129 if ( listener.isSynchronous() ) 130 { 131 listener.entryAdded( ( AddOperationContext ) opContext ); 132 } 133 else 134 { 135 executor.execute( new Runnable() 136 { 137 @Override 138 public void run() 139 { 140 listener.entryAdded( ( AddOperationContext ) opContext ); 141 } 142 } ); 143 } 144 145 break; 146 147 case DELETE: 148 if ( listener.isSynchronous() ) 149 { 150 listener.entryDeleted( ( DeleteOperationContext ) opContext ); 151 } 152 else 153 { 154 executor.execute( new Runnable() 155 { 156 @Override 157 public void run() 158 { 159 listener.entryDeleted( ( DeleteOperationContext ) opContext ); 160 } 161 } ); 162 } 163 164 break; 165 166 case MODIFY: 167 if ( listener.isSynchronous() ) 168 { 169 listener.entryModified( ( ModifyOperationContext ) opContext ); 170 } 171 else 172 { 173 executor.execute( new Runnable() 174 { 175 @Override 176 public void run() 177 { 178 listener.entryModified( ( ModifyOperationContext ) opContext ); 179 } 180 } ); 181 } 182 183 break; 184 185 case MOVE: 186 if ( listener.isSynchronous() ) 187 { 188 listener.entryMoved( ( MoveOperationContext ) opContext ); 189 } 190 else 191 { 192 executor.execute( new Runnable() 193 { 194 @Override 195 public void run() 196 { 197 listener.entryMoved( ( MoveOperationContext ) opContext ); 198 } 199 } ); 200 } 201 202 break; 203 204 case RENAME: 205 if ( listener.isSynchronous() ) 206 { 207 listener.entryRenamed( ( RenameOperationContext ) opContext ); 208 } 209 else 210 { 211 executor.execute( new Runnable() 212 { 213 @Override 214 public void run() 215 { 216 listener.entryRenamed( ( RenameOperationContext ) opContext ); 217 } 218 } ); 219 } 220 221 break; 222 223 case MOVE_AND_RENAME: 224 if ( listener.isSynchronous() ) 225 { 226 listener.entryMovedAndRenamed( ( MoveAndRenameOperationContext ) opContext ); 227 } 228 else 229 { 230 executor.execute( new Runnable() 231 { 232 @Override 233 public void run() 234 { 235 listener.entryMovedAndRenamed( ( MoveAndRenameOperationContext ) opContext ); 236 } 237 } ); 238 } 239 240 break; 241 242 default: 243 throw new IllegalArgumentException( "Unexpected event type " + type ); 244 } 245 } 246 247 248 /** 249 * {@inheritDoc} 250 */ 251 @Override 252 public void add( final AddOperationContext addContext ) throws LdapException 253 { 254 next( addContext ); 255 256 List<RegistrationEntry> selecting = getSelectingRegistrations( addContext.getDn(), addContext.getEntry() ); 257 258 if ( selecting.isEmpty() ) 259 { 260 return; 261 } 262 263 for ( final RegistrationEntry registration : selecting ) 264 { 265 if ( EventType.isAdd( registration.getCriteria().getEventMask() ) ) 266 { 267 fire( addContext, EventType.ADD, registration.getListener() ); 268 } 269 } 270 } 271 272 273 /** 274 * {@inheritDoc} 275 */ 276 @Override 277 public void delete( final DeleteOperationContext deleteContext ) throws LdapException 278 { 279 next( deleteContext ); 280 281 List<RegistrationEntry> selecting = getSelectingRegistrations( deleteContext.getDn(), deleteContext.getEntry() ); 282 283 if ( selecting.isEmpty() ) 284 { 285 return; 286 } 287 288 for ( final RegistrationEntry registration : selecting ) 289 { 290 if ( EventType.isDelete( registration.getCriteria().getEventMask() ) ) 291 { 292 fire( deleteContext, EventType.DELETE, registration.getListener() ); 293 } 294 } 295 } 296 297 298 /** 299 * {@inheritDoc} 300 */ 301 @Override 302 public void modify( final ModifyOperationContext modifyContext ) throws LdapException 303 { 304 Entry oriEntry = modifyContext.getEntry(); 305 306 // modification was already done when this flag is turned on, move to sending the events 307 if ( !modifyContext.isPushToEvtInterceptor() ) 308 { 309 next( modifyContext ); 310 } 311 312 List<RegistrationEntry> selecting = getSelectingRegistrations( modifyContext.getDn(), oriEntry ); 313 314 if ( selecting.isEmpty() ) 315 { 316 return; 317 } 318 319 // Get the modified entry 320 CoreSession session = modifyContext.getSession(); 321 LookupOperationContext lookupContext = new LookupOperationContext( session, modifyContext.getDn(), 322 SchemaConstants.ALL_ATTRIBUTES_ARRAY ); 323 lookupContext.setPartition( modifyContext.getPartition() ); 324 lookupContext.setTransaction( modifyContext.getTransaction() ); 325 326 Entry alteredEntry = directoryService.getPartitionNexus().lookup( lookupContext ); 327 modifyContext.setAlteredEntry( alteredEntry ); 328 329 for ( final RegistrationEntry registration : selecting ) 330 { 331 if ( EventType.isModify( registration.getCriteria().getEventMask() ) ) 332 { 333 fire( modifyContext, EventType.MODIFY, registration.getListener() ); 334 } 335 } 336 } 337 338 339 /** 340 * {@inheritDoc} 341 */ 342 @Override 343 public void move( MoveOperationContext moveContext ) throws LdapException 344 { 345 Entry oriEntry = moveContext.getOriginalEntry(); 346 347 next( moveContext ); 348 349 List<RegistrationEntry> selecting = getSelectingRegistrations( moveContext.getDn(), oriEntry ); 350 351 if ( selecting.isEmpty() ) 352 { 353 return; 354 } 355 356 for ( final RegistrationEntry registration : selecting ) 357 { 358 if ( EventType.isMove( registration.getCriteria().getEventMask() ) ) 359 { 360 fire( moveContext, EventType.MOVE, registration.getListener() ); 361 } 362 } 363 } 364 365 366 /** 367 * {@inheritDoc} 368 */ 369 @Override 370 public void moveAndRename( final MoveAndRenameOperationContext moveAndRenameContext ) throws LdapException 371 { 372 Entry oriEntry = moveAndRenameContext.getOriginalEntry(); 373 next( moveAndRenameContext ); 374 375 List<RegistrationEntry> selecting = getSelectingRegistrations( moveAndRenameContext.getDn(), oriEntry ); 376 377 if ( selecting.isEmpty() ) 378 { 379 return; 380 } 381 382 for ( final RegistrationEntry registration : selecting ) 383 { 384 if ( EventType.isMoveAndRename( registration.getCriteria().getEventMask() ) ) 385 { 386 fire( moveAndRenameContext, EventType.MOVE_AND_RENAME, registration.getListener() ); 387 } 388 } 389 } 390 391 392 /** 393 * {@inheritDoc} 394 */ 395 @Override 396 public void rename( RenameOperationContext renameContext ) throws LdapException 397 { 398 Entry oriEntry = ( ( ClonedServerEntry ) renameContext.getEntry() ).getOriginalEntry(); 399 400 next( renameContext ); 401 402 List<RegistrationEntry> selecting = getSelectingRegistrations( renameContext.getDn(), oriEntry ); 403 404 if ( selecting.isEmpty() ) 405 { 406 return; 407 } 408 409 // Get the modified entry 410 CoreSession session = renameContext.getSession(); 411 LookupOperationContext lookupContext = new LookupOperationContext( session, renameContext.getNewDn(), 412 SchemaConstants.ALL_ATTRIBUTES_ARRAY ); 413 lookupContext.setPartition( renameContext.getPartition() ); 414 lookupContext.setTransaction( renameContext.getTransaction() ); 415 416 Entry alteredEntry = directoryService.getPartitionNexus().lookup( lookupContext ); 417 renameContext.setModifiedEntry( alteredEntry ); 418 419 for ( final RegistrationEntry registration : selecting ) 420 { 421 if ( EventType.isRename( registration.getCriteria().getEventMask() ) ) 422 { 423 fire( renameContext, EventType.RENAME, registration.getListener() ); 424 } 425 } 426 } 427 428 429 /** 430 * Find a list of registrationEntries given an entry and a name. We check against 431 * the criteria for each registrationEntry 432 */ 433 private List<RegistrationEntry> getSelectingRegistrations( Dn name, Entry entry ) throws LdapException 434 { 435 List<RegistrationEntry> registrations = directoryService.getEventService().getRegistrationEntries(); 436 437 if ( registrations.isEmpty() ) 438 { 439 return Collections.emptyList(); 440 } 441 442 List<RegistrationEntry> selecting = new ArrayList<>(); 443 444 for ( RegistrationEntry registration : registrations ) 445 { 446 NotificationCriteria criteria = registration.getCriteria(); 447 448 Dn base = criteria.getBase(); 449 450 SearchScope scope = criteria.getScope(); 451 452 // fix for DIRSERVER-1502 453 boolean inscope = 454 ( ( ( scope == OBJECT ) && name.equals( base ) ) 455 || ( ( scope == ONELEVEL ) && name.getParent().equals( base ) ) 456 || ( ( scope == SUBTREE ) && ( name.isDescendantOf( base ) || name.equals( base ) ) ) ); 457 458 if ( inscope && evaluator.evaluate( criteria.getFilter(), base, entry ) ) 459 { 460 selecting.add( registration ); 461 } 462 } 463 464 return selecting; 465 } 466 467 468 /** 469 * {@inheritDoc} 470 */ 471 @Override 472 public void destroy() 473 { 474 executor.shutdown(); 475 } 476}