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 java.util.Iterator;
23
24 import org.apache.velocity.context.InternalContextAdapter;
25 import org.apache.velocity.runtime.RuntimeServices;
26 import org.apache.velocity.util.ExceptionUtils;
27 import org.apache.velocity.util.introspection.Info;
28
29
30 /**
31 * Calls on request all registered event handlers for a particular event. Each
32 * method accepts two event cartridges (typically one from the application and
33 * one from the context). All appropriate event handlers are executed in order
34 * until a stopping condition is met. See the docs for the individual methods to
35 * see what the stopping condition is for that method.
36 *
37 * @author <a href="mailto:wglass@wglass@forio.com">Will Glass-Husain </a>
38 * @version $Id: EventHandlerUtil.java 685685 2008-08-13 21:43:27Z nbubna $
39 * @since 1.5
40 */
41 public class EventHandlerUtil {
42
43
44 /**
45 * Called before a reference is inserted. All event handlers are called in
46 * sequence. The default implementation inserts the reference as is.
47 *
48 * This is a major hotspot method called by ASTReference render.
49 *
50 * @param reference reference from template about to be inserted
51 * @param value value about to be inserted (after toString() )
52 * @param rsvc current instance of RuntimeServices
53 * @param context The internal context adapter.
54 * @return Object on which toString() should be called for output.
55 */
56 public static Object referenceInsert(RuntimeServices rsvc,
57 InternalContextAdapter context, String reference, Object value)
58 {
59 // app level cartridges have already been initialized
60
61 /*
62 * Performance modification: EventCartridge.getReferenceInsertionEventHandlers
63 * now returns a null if there are no handlers. Thus we can avoid creating the
64 * Iterator object.
65 */
66 EventCartridge ev1 = rsvc.getApplicationEventCartridge();
67 Iterator applicationEventHandlerIterator =
68 (ev1 == null) ? null: ev1.getReferenceInsertionEventHandlers();
69
70 EventCartridge ev2 = context.getEventCartridge();
71 initializeEventCartridge(rsvc, ev2);
72 Iterator contextEventHandlerIterator =
73 (ev2 == null) ? null: ev2.getReferenceInsertionEventHandlers();
74
75 try
76 {
77 /*
78 * Performance modification: methodExecutor is created only if one of the
79 * iterators is not null.
80 */
81
82 EventHandlerMethodExecutor methodExecutor = null;
83
84 if( applicationEventHandlerIterator != null )
85 {
86 methodExecutor =
87 new ReferenceInsertionEventHandler.referenceInsertExecutor(context, reference, value);
88 iterateOverEventHandlers(applicationEventHandlerIterator, methodExecutor);
89 }
90
91 if( contextEventHandlerIterator != null )
92 {
93 if( methodExecutor == null )
94 methodExecutor =
95 new ReferenceInsertionEventHandler.referenceInsertExecutor(context, reference, value);
96
97 iterateOverEventHandlers(contextEventHandlerIterator, methodExecutor);
98 }
99
100
101 return methodExecutor != null ? methodExecutor.getReturnValue() : value;
102 }
103 catch (RuntimeException e)
104 {
105 throw e;
106 }
107 catch (Exception e)
108 {
109 throw ExceptionUtils.createRuntimeException("Exception in event handler.",e);
110 }
111 }
112
113 /**
114 * Called when a null is evaluated during a #set. All event handlers are
115 * called in sequence until a false is returned. The default implementation
116 * always returns true.
117 *
118 * @param lhs Left hand side of the expression.
119 * @param rhs Right hand side of the expression.
120 * @param rsvc current instance of RuntimeServices
121 * @param context The internal context adapter.
122 * @return true if to be logged, false otherwise
123 */
124 public static boolean shouldLogOnNullSet(RuntimeServices rsvc,
125 InternalContextAdapter context, String lhs, String rhs)
126 {
127 // app level cartridges have already been initialized
128 EventCartridge ev1 = rsvc.getApplicationEventCartridge();
129 Iterator applicationEventHandlerIterator =
130 (ev1 == null) ? null: ev1.getNullSetEventHandlers();
131
132 EventCartridge ev2 = context.getEventCartridge();
133 initializeEventCartridge(rsvc, ev2);
134 Iterator contextEventHandlerIterator =
135 (ev2 == null) ? null: ev2.getNullSetEventHandlers();
136
137 try
138 {
139 EventHandlerMethodExecutor methodExecutor =
140 new NullSetEventHandler.ShouldLogOnNullSetExecutor(context, lhs, rhs);
141
142 callEventHandlers(
143 applicationEventHandlerIterator,
144 contextEventHandlerIterator, methodExecutor);
145
146 return ((Boolean) methodExecutor.getReturnValue()).booleanValue();
147 }
148 catch (RuntimeException e)
149 {
150 throw e;
151 }
152 catch (Exception e)
153 {
154 throw ExceptionUtils.createRuntimeException("Exception in event handler.",e);
155 }
156 }
157
158 /**
159 * Called when a method exception is generated during Velocity merge. Only
160 * the first valid event handler in the sequence is called. The default
161 * implementation simply rethrows the exception.
162 *
163 * @param claz
164 * Class that is causing the exception
165 * @param method
166 * method called that causes the exception
167 * @param e
168 * Exception thrown by the method
169 * @param rsvc current instance of RuntimeServices
170 * @param context The internal context adapter.
171 * @return Object to return as method result
172 * @throws Exception
173 * to be wrapped and propogated to app
174 */
175 public static Object methodException(RuntimeServices rsvc,
176 InternalContextAdapter context, Class claz, String method,
177 Exception e) throws Exception
178 {
179 // app level cartridges have already been initialized
180 EventCartridge ev1 = rsvc.getApplicationEventCartridge();
181 Iterator applicationEventHandlerIterator =
182 (ev1 == null) ? null: ev1.getMethodExceptionEventHandlers();
183
184 EventCartridge ev2 = context.getEventCartridge();
185 initializeEventCartridge(rsvc, ev2);
186 Iterator contextEventHandlerIterator =
187 (ev2 == null) ? null: ev2.getMethodExceptionEventHandlers();
188
189 EventHandlerMethodExecutor methodExecutor =
190 new MethodExceptionEventHandler.MethodExceptionExecutor(context, claz, method, e);
191
192 if ( ((applicationEventHandlerIterator == null) || !applicationEventHandlerIterator.hasNext()) &&
193 ((contextEventHandlerIterator == null) || !contextEventHandlerIterator.hasNext()) )
194 {
195 throw e;
196 }
197
198 callEventHandlers(
199 applicationEventHandlerIterator,
200 contextEventHandlerIterator, methodExecutor);
201
202 return methodExecutor.getReturnValue();
203 }
204
205 /**
206 * Called when an include-type directive is encountered (#include or
207 * #parse). All the registered event handlers are called unless null is
208 * returned. The default implementation always processes the included
209 * resource.
210 *
211 * @param includeResourcePath
212 * the path as given in the include directive.
213 * @param currentResourcePath
214 * the path of the currently rendering template that includes the
215 * include directive.
216 * @param directiveName
217 * name of the directive used to include the resource. (With the
218 * standard directives this is either "parse" or "include").
219 * @param rsvc current instance of RuntimeServices
220 * @param context The internal context adapter.
221 *
222 * @return a new resource path for the directive, or null to block the
223 * include from occurring.
224 */
225 public static String includeEvent(RuntimeServices rsvc,
226 InternalContextAdapter context, String includeResourcePath,
227 String currentResourcePath, String directiveName)
228 {
229 // app level cartridges have already been initialized
230 EventCartridge ev1 = rsvc.getApplicationEventCartridge();
231 Iterator applicationEventHandlerIterator =
232 (ev1 == null) ? null: ev1.getIncludeEventHandlers();
233
234 EventCartridge ev2 = context.getEventCartridge();
235 initializeEventCartridge(rsvc, ev2);
236 Iterator contextEventHandlerIterator =
237 (ev2 == null) ? null: ev2.getIncludeEventHandlers();
238
239 try
240 {
241 EventHandlerMethodExecutor methodExecutor =
242 new IncludeEventHandler.IncludeEventExecutor(
243 context, includeResourcePath,
244 currentResourcePath, directiveName);
245
246 callEventHandlers(
247 applicationEventHandlerIterator,
248 contextEventHandlerIterator, methodExecutor);
249
250 return (String) methodExecutor.getReturnValue();
251 }
252 catch (RuntimeException e)
253 {
254 throw e;
255 }
256 catch (Exception e)
257 {
258 throw ExceptionUtils.createRuntimeException("Exception in event handler.",e);
259 }
260 }
261
262
263 /**
264 * Called when an invalid get method is encountered.
265 *
266 * @param rsvc current instance of RuntimeServices
267 * @param context the context when the reference was found invalid
268 * @param reference complete invalid reference
269 * @param object object from reference, or null if not available
270 * @param property name of property, or null if not relevant
271 * @param info contains info on template, line, col
272 * @return substitute return value for missing reference, or null if no substitute
273 */
274 public static Object invalidGetMethod(RuntimeServices rsvc,
275 InternalContextAdapter context, String reference,
276 Object object, String property, Info info)
277 {
278 return
279 invalidReferenceHandlerCall (
280 new InvalidReferenceEventHandler.InvalidGetMethodExecutor
281 (context, reference, object, property, info),
282 rsvc,
283 context);
284 }
285
286
287 /**
288 * Called when an invalid set method is encountered.
289 *
290 * @param rsvc current instance of RuntimeServices
291 * @param context the context when the reference was found invalid
292 * @param leftreference left reference being assigned to
293 * @param rightreference invalid reference on the right
294 * @param info contains info on template, line, col
295 */
296 public static void invalidSetMethod(RuntimeServices rsvc,
297 InternalContextAdapter context, String leftreference,
298 String rightreference, Info info)
299 {
300 /**
301 * ignore return value
302 */
303 invalidReferenceHandlerCall (
304 new InvalidReferenceEventHandler.InvalidSetMethodExecutor
305 (context, leftreference, rightreference, info),
306 rsvc,
307 context);
308 }
309
310 /**
311 * Called when an invalid method is encountered.
312 *
313 * @param rsvc current instance of RuntimeServices
314 * @param context the context when the reference was found invalid
315 * @param reference complete invalid reference
316 * @param object object from reference, or null if not available
317 * @param method name of method, or null if not relevant
318 * @param info contains info on template, line, col
319 * @return substitute return value for missing reference, or null if no substitute
320 */
321 public static Object invalidMethod(RuntimeServices rsvc,
322 InternalContextAdapter context, String reference,
323 Object object, String method, Info info)
324 {
325 return
326 invalidReferenceHandlerCall (
327 new InvalidReferenceEventHandler.InvalidMethodExecutor
328 (context, reference, object, method, info),
329 rsvc,
330 context);
331 }
332
333
334 /**
335 * Calls event handler method with appropriate chaining across event handlers.
336 *
337 * @param methodExecutor
338 * @param rsvc current instance of RuntimeServices
339 * @param context The current context
340 * @return return value from method, or null if no return value
341 */
342 public static Object invalidReferenceHandlerCall(
343 EventHandlerMethodExecutor methodExecutor,
344 RuntimeServices rsvc,
345 InternalContextAdapter context)
346 {
347 // app level cartridges have already been initialized
348 EventCartridge ev1 = rsvc.getApplicationEventCartridge();
349 Iterator applicationEventHandlerIterator =
350 (ev1 == null) ? null: ev1.getInvalidReferenceEventHandlers();
351
352 EventCartridge ev2 = context.getEventCartridge();
353 initializeEventCartridge(rsvc, ev2);
354 Iterator contextEventHandlerIterator =
355 (ev2 == null) ? null: ev2.getInvalidReferenceEventHandlers();
356
357 try
358 {
359 callEventHandlers(
360 applicationEventHandlerIterator,
361 contextEventHandlerIterator, methodExecutor);
362
363 return methodExecutor.getReturnValue();
364 }
365 catch (RuntimeException e)
366 {
367 throw e;
368 }
369 catch (Exception e)
370 {
371 throw ExceptionUtils.createRuntimeException("Exception in event handler.",e);
372 }
373
374 }
375
376 /**
377 * Initialize the event cartridge if appropriate.
378 *
379 * @param rsvc current instance of RuntimeServices
380 * @param eventCartridge the event cartridge to be initialized
381 */
382 private static void initializeEventCartridge(RuntimeServices rsvc, EventCartridge eventCartridge)
383 {
384 if (eventCartridge != null)
385 {
386 try
387 {
388 eventCartridge.initialize(rsvc);
389 }
390 catch (Exception e)
391 {
392 throw ExceptionUtils.createRuntimeException("Couldn't initialize event cartridge : ", e);
393 }
394 }
395 }
396
397
398 /**
399 * Loop through both the application level and context-attached event handlers.
400 *
401 * @param applicationEventHandlerIterator Iterator that loops through all global event handlers declared at application level
402 * @param contextEventHandlerIterator Iterator that loops through all global event handlers attached to context
403 * @param eventExecutor Strategy object that executes event handler method
404 * @exception Exception generic exception potentially thrown by event handlers
405 */
406 private static void callEventHandlers(
407 Iterator applicationEventHandlerIterator,
408 Iterator contextEventHandlerIterator,
409 EventHandlerMethodExecutor eventExecutor)
410 throws Exception
411 {
412 /**
413 * First loop through the event handlers configured at the app level
414 * in the properties file.
415 */
416 iterateOverEventHandlers(applicationEventHandlerIterator, eventExecutor);
417
418 /**
419 * Then loop through the event handlers attached to the context.
420 */
421 iterateOverEventHandlers(contextEventHandlerIterator, eventExecutor);
422 }
423
424 /**
425 * Loop through a given iterator of event handlers.
426 *
427 * @param handlerIterator Iterator that loops through event handlers
428 * @param eventExecutor Strategy object that executes event handler method
429 * @exception Exception generic exception potentially thrown by event handlers
430 */
431 private static void iterateOverEventHandlers(
432 Iterator handlerIterator,
433 EventHandlerMethodExecutor eventExecutor)
434 throws Exception
435 {
436 if (handlerIterator != null)
437 {
438 for (Iterator i = handlerIterator; i.hasNext();)
439 {
440 EventHandler eventHandler = (EventHandler) i.next();
441
442 if (!eventExecutor.isDone())
443 {
444 eventExecutor.execute(eventHandler);
445 }
446 }
447 }
448 }
449
450 }