• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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