1 package org.apache.velocity.app.event; 2 3 /* 4 * Licensed to the Apache Software Foundation (ASF) under one 5 * or more contributor license agreements. See the NOTICE file 6 * distributed with this work for additional information 7 * regarding copyright ownership. The ASF licenses this file 8 * to you under the Apache License, Version 2.0 (the 9 * "License"); you may not use this file except in compliance 10 * with the License. You may obtain a copy of the License at 11 * 12 * http://www.apache.org/licenses/LICENSE-2.0 13 * 14 * Unless required by applicable law or agreed to in writing, 15 * software distributed under the License is distributed on an 16 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 * KIND, either express or implied. See the License for the 18 * specific language governing permissions and limitations 19 * under the License. 20 */ 21 22 import org.apache.velocity.context.Context; 23 import org.apache.velocity.context.InternalContextAdapter; 24 import org.apache.velocity.context.InternalEventContext; 25 import org.apache.velocity.exception.VelocityException; 26 import org.apache.velocity.runtime.RuntimeServices; 27 import org.apache.velocity.util.RuntimeServicesAware; 28 import org.apache.velocity.util.introspection.Info; 29 import org.slf4j.Logger; 30 import org.slf4j.LoggerFactory; 31 32 import java.util.ArrayList; 33 import java.util.HashSet; 34 import java.util.List; 35 import java.util.Set; 36 37 /** 38 * <p>Stores the event handlers. Event handlers can be assigned on a per 39 * VelocityEngine instance basis by specifying the class names in the 40 * velocity.properties file. Event handlers may also be assigned on a per-page 41 * basis by creating a new instance of EventCartridge, adding the event 42 * handlers, and then calling attachToContext. For clarity, it's recommended 43 * that one approach or the other be followed, as the second method is primarily 44 * presented for backwards compatibility.</p> 45 * <p>Note that Event Handlers follow a filter pattern, with multiple event 46 * handlers allowed for each event. When the appropriate event occurs, all the 47 * appropriate event handlers are called in the sequence they were added to the 48 * Event Cartridge. See the javadocs of the specific event handler interfaces 49 * for more details.</p> 50 * 51 * @author <a href="mailto:wglass@wglass@forio.com">Will Glass-Husain </a> 52 * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr. </a> 53 * @author <a href="mailto:j_a_fernandez@yahoo.com">Jose Alberto Fernandez </a> 54 * @version $Id$ 55 */ 56 public class EventCartridge 57 { 58 private List<ReferenceInsertionEventHandler> referenceHandlers = new ArrayList<>(); 59 private MethodExceptionEventHandler methodExceptionHandler = null; 60 private List<IncludeEventHandler> includeHandlers = new ArrayList<>(); 61 private List<InvalidReferenceEventHandler> invalidReferenceHandlers = new ArrayList<>(); 62 63 /** 64 * Ensure that handlers are not initialized more than once. 65 */ 66 Set<EventHandler> initializedHandlers = new HashSet<>(); 67 68 protected RuntimeServices rsvc = null; 69 getLog()70 protected Logger getLog() 71 { 72 return rsvc == null ? LoggerFactory.getLogger(EventCartridge.class) : rsvc.getLog(); 73 } 74 75 /** 76 * runtime services setter, called during initialization 77 * 78 * @param rs runtime services 79 * @since 2.0 80 */ setRuntimeServices(RuntimeServices rs)81 public synchronized void setRuntimeServices(RuntimeServices rs) 82 { 83 if (rsvc == null) 84 { 85 rsvc = rs; 86 /* allow for this method to be called *after* adding event handlers */ 87 for (EventHandler handler : referenceHandlers) 88 { 89 if (handler instanceof RuntimeServicesAware && !initializedHandlers.contains(handler)) 90 { 91 ((RuntimeServicesAware) handler).setRuntimeServices(rs); 92 initializedHandlers.add(handler); 93 } 94 } 95 if (methodExceptionHandler != null && 96 methodExceptionHandler instanceof RuntimeServicesAware && 97 !initializedHandlers.contains(methodExceptionHandler)) 98 { 99 ((RuntimeServicesAware) methodExceptionHandler).setRuntimeServices(rs); 100 initializedHandlers.add(methodExceptionHandler); 101 } 102 for (EventHandler handler : includeHandlers) 103 { 104 if (handler instanceof RuntimeServicesAware && !initializedHandlers.contains(handler)) 105 { 106 ((RuntimeServicesAware) handler).setRuntimeServices(rs); 107 initializedHandlers.add(handler); 108 } 109 } 110 for (EventHandler handler : invalidReferenceHandlers) 111 { 112 if (handler instanceof RuntimeServicesAware && !initializedHandlers.contains(handler)) 113 { 114 ((RuntimeServicesAware) handler).setRuntimeServices(rs); 115 initializedHandlers.add(handler); 116 } 117 } 118 } 119 else if (rsvc != rs) 120 { 121 throw new VelocityException("an event cartridge cannot be used by several different runtime services instances"); 122 } 123 } 124 125 /** 126 * Adds an event handler(s) to the Cartridge. This method 127 * will find all possible event handler interfaces supported 128 * by the passed in object. 129 * 130 * @param ev object implementing a valid EventHandler-derived interface 131 * @return true if a supported interface, false otherwise or if null 132 */ addEventHandler(EventHandler ev)133 public boolean addEventHandler(EventHandler ev) 134 { 135 if (ev == null) 136 { 137 return false; 138 } 139 140 boolean found = false; 141 142 if (ev instanceof ReferenceInsertionEventHandler) 143 { 144 addReferenceInsertionEventHandler((ReferenceInsertionEventHandler) ev); 145 found = true; 146 } 147 148 if (ev instanceof MethodExceptionEventHandler) 149 { 150 addMethodExceptionHandler((MethodExceptionEventHandler) ev); 151 found = true; 152 } 153 154 if (ev instanceof IncludeEventHandler) 155 { 156 addIncludeEventHandler((IncludeEventHandler) ev); 157 found = true; 158 } 159 160 if (ev instanceof InvalidReferenceEventHandler) 161 { 162 addInvalidReferenceEventHandler((InvalidReferenceEventHandler) ev); 163 found = true; 164 } 165 166 if (found && rsvc != null && ev instanceof RuntimeServicesAware && !initializedHandlers.contains(ev)) 167 { 168 ((RuntimeServicesAware) ev).setRuntimeServices(rsvc); 169 initializedHandlers.add(ev); 170 } 171 172 return found; 173 } 174 175 /** 176 * Add a reference insertion event handler to the Cartridge. 177 * 178 * @param ev ReferenceInsertionEventHandler 179 * @since 1.5 180 */ addReferenceInsertionEventHandler(ReferenceInsertionEventHandler ev)181 public void addReferenceInsertionEventHandler(ReferenceInsertionEventHandler ev) 182 { 183 referenceHandlers.add(ev); 184 } 185 186 /** 187 * Add a method exception event handler to the Cartridge. 188 * 189 * @param ev MethodExceptionEventHandler 190 * @since 1.5 191 */ addMethodExceptionHandler(MethodExceptionEventHandler ev)192 public void addMethodExceptionHandler(MethodExceptionEventHandler ev) 193 { 194 if (methodExceptionHandler == null) 195 { 196 methodExceptionHandler = ev; 197 } 198 else 199 { 200 getLog().warn("ignoring extra method exception handler"); 201 } 202 } 203 204 /** 205 * Add an include event handler to the Cartridge. 206 * 207 * @param ev IncludeEventHandler 208 * @since 1.5 209 */ addIncludeEventHandler(IncludeEventHandler ev)210 public void addIncludeEventHandler(IncludeEventHandler ev) 211 { 212 includeHandlers.add(ev); 213 } 214 215 /** 216 * Add an invalid reference event handler to the Cartridge. 217 * 218 * @param ev InvalidReferenceEventHandler 219 * @since 1.5 220 */ addInvalidReferenceEventHandler(InvalidReferenceEventHandler ev)221 public void addInvalidReferenceEventHandler(InvalidReferenceEventHandler ev) 222 { 223 invalidReferenceHandlers.add(ev); 224 } 225 226 227 /** 228 * Removes an event handler(s) from the Cartridge. This method will find all 229 * possible event handler interfaces supported by the passed in object and 230 * remove them. 231 * 232 * @param ev object impementing a valid EventHandler-derived interface 233 * @return true if event handler was previously registered, false if not 234 * found 235 */ removeEventHandler(EventHandler ev)236 public boolean removeEventHandler(EventHandler ev) 237 { 238 if (ev == null) 239 { 240 return false; 241 } 242 243 if (ev instanceof ReferenceInsertionEventHandler) 244 { 245 return referenceHandlers.remove(ev); 246 } 247 248 if (ev instanceof MethodExceptionEventHandler) 249 { 250 if (ev == methodExceptionHandler) 251 { 252 methodExceptionHandler = null; 253 return true; 254 } 255 } 256 257 if (ev instanceof IncludeEventHandler) 258 { 259 return includeHandlers.remove(ev); 260 } 261 262 if (ev instanceof InvalidReferenceEventHandler) 263 { 264 return invalidReferenceHandlers.remove(ev); 265 } 266 267 return false; 268 } 269 270 /** 271 * Call reference insertion handlers 272 * @param context 273 * @param reference 274 * @param value 275 * @return value returned by handlers 276 * @since 2.0 277 */ referenceInsert(InternalContextAdapter context, String reference, Object value)278 public Object referenceInsert(InternalContextAdapter context, String reference, Object value) 279 { 280 for (ReferenceInsertionEventHandler handler : referenceHandlers) 281 { 282 value = handler.referenceInsert(context, reference, value); 283 } 284 return value; 285 } 286 287 /** 288 * Check whether this event cartridge has a method exception event handler 289 * 290 * @return true if a method exception event handler has been registered 291 * @since 2.0 292 */ hasMethodExceptionEventHandler()293 boolean hasMethodExceptionEventHandler() 294 { 295 return methodExceptionHandler != null; 296 } 297 298 /** 299 * Call method exception event handler 300 * @param context 301 * @param claz 302 * @param method 303 * @param e exception 304 * @param info template name, line and column infos 305 * @return value returned by handler 306 * @since 2.0 307 */ methodException(Context context, Class<?> claz, String method, Exception e, Info info)308 public Object methodException(Context context, Class<?> claz, String method, Exception e, Info info) 309 { 310 if (methodExceptionHandler != null) 311 { 312 return methodExceptionHandler.methodException(context, claz, method, e, info); 313 } 314 return null; 315 } 316 317 /** 318 * Call include event handlers 319 * 320 * @param context 321 * @param includeResourcePath 322 * @param currentResourcePath 323 * @param directiveName 324 * @return include path 325 * @since 2.0 326 */ includeEvent(Context context, String includeResourcePath, String currentResourcePath, String directiveName)327 public String includeEvent(Context context, String includeResourcePath, String currentResourcePath, String directiveName) 328 { 329 for (IncludeEventHandler handler : includeHandlers) 330 { 331 includeResourcePath = handler.includeEvent(context, includeResourcePath, currentResourcePath, directiveName); 332 /* reflect 1.x behavior: exit after at least one execution whenever a null include path has been found */ 333 if (includeResourcePath == null) 334 { 335 break; 336 } 337 } 338 return includeResourcePath; 339 } 340 341 /** 342 * Call invalid reference handlers for an invalid getter 343 * 344 * @param context 345 * @param reference 346 * @param object 347 * @param property 348 * @param info 349 * @return value returned by handlers 350 * @since 2.0 351 */ invalidGetMethod(Context context, String reference, Object object, String property, Info info)352 public Object invalidGetMethod(Context context, String reference, Object object, String property, Info info) 353 { 354 Object result = null; 355 for (InvalidReferenceEventHandler handler : invalidReferenceHandlers) 356 { 357 result = handler.invalidGetMethod(context, reference, object, property, info); 358 /* reflect 1.x behavior: exit after at least one execution whenever a non-null value has been found */ 359 if (result != null) 360 { 361 break; 362 } 363 } 364 return result; 365 } 366 367 /** 368 * Call invalid reference handlers for an invalid setter 369 * 370 * @param context 371 * @param leftreference 372 * @param rightreference 373 * @param info 374 * @return whether to stop further chaining in the next cartridge 375 * @since 2.0 376 */ invalidSetMethod(Context context, String leftreference, String rightreference, Info info)377 public boolean invalidSetMethod(Context context, String leftreference, String rightreference, Info info) 378 { 379 for (InvalidReferenceEventHandler handler : invalidReferenceHandlers) 380 { 381 if (handler.invalidSetMethod(context, leftreference, rightreference, info)) 382 { 383 return true; 384 } 385 } 386 return false; 387 } 388 389 /** 390 * Call invalid reference handlers for an invalid method call 391 * 392 * @param context 393 * @param reference 394 * @param object 395 * @param method 396 * @param info 397 * @return value returned by handlers 398 * @since 2.0 399 */ invalidMethod(Context context, String reference, Object object, String method, Info info)400 public Object invalidMethod(Context context, String reference, Object object, String method, Info info) 401 { 402 Object result = null; 403 for (InvalidReferenceEventHandler handler : invalidReferenceHandlers) 404 { 405 result = handler.invalidMethod(context, reference, object, method, info); 406 /* reflect 1.x behavior: exit after at least one execution whenever a non-null value has been found */ 407 if (result != null) 408 { 409 break; 410 } 411 } 412 return result; 413 } 414 415 /** 416 * Attached the EventCartridge to the context 417 * 418 * Final because not something one should mess with lightly :) 419 * 420 * @param context context to attach to 421 * @return true if successful, false otherwise 422 */ attachToContext(Context context)423 public final boolean attachToContext(Context context) 424 { 425 if (context instanceof InternalEventContext) 426 { 427 InternalEventContext iec = (InternalEventContext) context; 428 429 iec.attachEventCartridge(this); 430 431 /* 432 * while it's tempting to call setContext on each handler from here, 433 * this needs to be done before each method call. This is 434 * because the specific context will change as inner contexts 435 * are linked in through macros, foreach, or directly by the user. 436 */ 437 438 return true; 439 } 440 else 441 { 442 return false; 443 } 444 } 445 } 446