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 }