| package org.apache.velocity.app.event; |
| |
| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| import org.apache.velocity.context.Context; |
| import org.apache.velocity.context.InternalContextAdapter; |
| import org.apache.velocity.context.InternalEventContext; |
| import org.apache.velocity.exception.VelocityException; |
| import org.apache.velocity.runtime.RuntimeServices; |
| import org.apache.velocity.util.RuntimeServicesAware; |
| import org.apache.velocity.util.introspection.Info; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| import java.util.ArrayList; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Set; |
| |
| /** |
| * <p>Stores the event handlers. Event handlers can be assigned on a per |
| * VelocityEngine instance basis by specifying the class names in the |
| * velocity.properties file. Event handlers may also be assigned on a per-page |
| * basis by creating a new instance of EventCartridge, adding the event |
| * handlers, and then calling attachToContext. For clarity, it's recommended |
| * that one approach or the other be followed, as the second method is primarily |
| * presented for backwards compatibility.</p> |
| * <p>Note that Event Handlers follow a filter pattern, with multiple event |
| * handlers allowed for each event. When the appropriate event occurs, all the |
| * appropriate event handlers are called in the sequence they were added to the |
| * Event Cartridge. See the javadocs of the specific event handler interfaces |
| * for more details.</p> |
| * |
| * @author <a href="mailto:wglass@wglass@forio.com">Will Glass-Husain </a> |
| * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr. </a> |
| * @author <a href="mailto:j_a_fernandez@yahoo.com">Jose Alberto Fernandez </a> |
| * @version $Id$ |
| */ |
| public class EventCartridge |
| { |
| private List<ReferenceInsertionEventHandler> referenceHandlers = new ArrayList(); |
| private MethodExceptionEventHandler methodExceptionHandler = null; |
| private List<IncludeEventHandler> includeHandlers = new ArrayList(); |
| private List<InvalidReferenceEventHandler> invalidReferenceHandlers = new ArrayList(); |
| |
| /** |
| * Ensure that handlers are not initialized more than once. |
| */ |
| Set initializedHandlers = new HashSet(); |
| |
| protected RuntimeServices rsvc = null; |
| |
| protected Logger getLog() |
| { |
| return rsvc == null ? LoggerFactory.getLogger(EventCartridge.class) : rsvc.getLog(); |
| } |
| |
| /** |
| * runtime services setter, called during initialization |
| * |
| * @param rs runtime services |
| * @since 2.0 |
| */ |
| public synchronized void setRuntimeServices(RuntimeServices rs) |
| { |
| if (rsvc == null) |
| { |
| rsvc = rs; |
| /* allow for this method to be called *after* adding event handlers */ |
| for (EventHandler handler : referenceHandlers) |
| { |
| if (handler instanceof RuntimeServicesAware && !initializedHandlers.contains(handler)) |
| { |
| ((RuntimeServicesAware) handler).setRuntimeServices(rs); |
| initializedHandlers.add(handler); |
| } |
| } |
| if (methodExceptionHandler != null && |
| methodExceptionHandler instanceof RuntimeServicesAware && |
| !initializedHandlers.contains(methodExceptionHandler)) |
| { |
| ((RuntimeServicesAware) methodExceptionHandler).setRuntimeServices(rs); |
| initializedHandlers.add(methodExceptionHandler); |
| } |
| for (EventHandler handler : includeHandlers) |
| { |
| if (handler instanceof RuntimeServicesAware && !initializedHandlers.contains(handler)) |
| { |
| ((RuntimeServicesAware) handler).setRuntimeServices(rs); |
| initializedHandlers.add(handler); |
| } |
| } |
| for (EventHandler handler : invalidReferenceHandlers) |
| { |
| if (handler instanceof RuntimeServicesAware && !initializedHandlers.contains(handler)) |
| { |
| ((RuntimeServicesAware) handler).setRuntimeServices(rs); |
| initializedHandlers.add(handler); |
| } |
| } |
| } |
| else if (rsvc != rs) |
| { |
| throw new VelocityException("an event cartridge cannot be used by several different runtime services instances"); |
| } |
| } |
| |
| /** |
| * Adds an event handler(s) to the Cartridge. This method |
| * will find all possible event handler interfaces supported |
| * by the passed in object. |
| * |
| * @param ev object implementing a valid EventHandler-derived interface |
| * @return true if a supported interface, false otherwise or if null |
| */ |
| public boolean addEventHandler(EventHandler ev) |
| { |
| if (ev == null) |
| { |
| return false; |
| } |
| |
| boolean found = false; |
| |
| if (ev instanceof ReferenceInsertionEventHandler) |
| { |
| addReferenceInsertionEventHandler((ReferenceInsertionEventHandler) ev); |
| found = true; |
| } |
| |
| if (ev instanceof MethodExceptionEventHandler) |
| { |
| addMethodExceptionHandler((MethodExceptionEventHandler) ev); |
| found = true; |
| } |
| |
| if (ev instanceof IncludeEventHandler) |
| { |
| addIncludeEventHandler((IncludeEventHandler) ev); |
| found = true; |
| } |
| |
| if (ev instanceof InvalidReferenceEventHandler) |
| { |
| addInvalidReferenceEventHandler((InvalidReferenceEventHandler) ev); |
| found = true; |
| } |
| |
| if (found && rsvc != null && ev instanceof RuntimeServicesAware && !initializedHandlers.contains(ev)) |
| { |
| ((RuntimeServicesAware) ev).setRuntimeServices(rsvc); |
| initializedHandlers.add(ev); |
| } |
| |
| return found; |
| } |
| |
| /** |
| * Add a reference insertion event handler to the Cartridge. |
| * |
| * @param ev ReferenceInsertionEventHandler |
| * @since 1.5 |
| */ |
| public void addReferenceInsertionEventHandler(ReferenceInsertionEventHandler ev) |
| { |
| referenceHandlers.add(ev); |
| } |
| |
| /** |
| * Add a method exception event handler to the Cartridge. |
| * |
| * @param ev MethodExceptionEventHandler |
| * @since 1.5 |
| */ |
| public void addMethodExceptionHandler(MethodExceptionEventHandler ev) |
| { |
| if (methodExceptionHandler == null) |
| { |
| methodExceptionHandler = ev; |
| } |
| else |
| { |
| getLog().warn("ignoring extra method exception handler"); |
| } |
| } |
| |
| /** |
| * Add an include event handler to the Cartridge. |
| * |
| * @param ev IncludeEventHandler |
| * @since 1.5 |
| */ |
| public void addIncludeEventHandler(IncludeEventHandler ev) |
| { |
| includeHandlers.add(ev); |
| } |
| |
| /** |
| * Add an invalid reference event handler to the Cartridge. |
| * |
| * @param ev InvalidReferenceEventHandler |
| * @since 1.5 |
| */ |
| public void addInvalidReferenceEventHandler(InvalidReferenceEventHandler ev) |
| { |
| invalidReferenceHandlers.add(ev); |
| } |
| |
| |
| /** |
| * Removes an event handler(s) from the Cartridge. This method will find all |
| * possible event handler interfaces supported by the passed in object and |
| * remove them. |
| * |
| * @param ev object impementing a valid EventHandler-derived interface |
| * @return true if event handler was previously registered, false if not |
| * found |
| */ |
| public boolean removeEventHandler(EventHandler ev) |
| { |
| if (ev == null) |
| { |
| return false; |
| } |
| |
| if (ev instanceof ReferenceInsertionEventHandler) |
| { |
| return referenceHandlers.remove(ev); |
| } |
| |
| if (ev instanceof MethodExceptionEventHandler) |
| { |
| if (ev == methodExceptionHandler) |
| { |
| methodExceptionHandler = null; |
| return true; |
| } |
| } |
| |
| if (ev instanceof IncludeEventHandler) |
| { |
| return includeHandlers.remove(ev); |
| } |
| |
| if (ev instanceof InvalidReferenceEventHandler) |
| { |
| return invalidReferenceHandlers.remove(ev); |
| } |
| |
| return false; |
| } |
| |
| /** |
| * Call reference insertion handlers |
| * @param context |
| * @param reference |
| * @param value |
| * @return value returned by handlers |
| * @since 2.0 |
| */ |
| public Object referenceInsert(InternalContextAdapter context, String reference, Object value) |
| { |
| for (ReferenceInsertionEventHandler handler : referenceHandlers) |
| { |
| value = handler.referenceInsert(context, reference, value); |
| } |
| return value; |
| } |
| |
| /** |
| * Check whether this event cartridge has a method exception event handler |
| * |
| * @return true if a method exception event handler has been registered |
| * @since 2.0 |
| */ |
| boolean hasMethodExceptionEventHandler() |
| { |
| return methodExceptionHandler != null; |
| } |
| |
| /** |
| * Call method exception event handler |
| * @param context |
| * @param claz |
| * @param method |
| * @param e exception |
| * @param info template name, line and column infos |
| * @return value returned by handler |
| * @since 2.0 |
| */ |
| public Object methodException(Context context, Class claz, String method, Exception e, Info info) |
| { |
| if (methodExceptionHandler != null) |
| { |
| return methodExceptionHandler.methodException(context, claz, method, e, info); |
| } |
| return null; |
| } |
| |
| /** |
| * Call include event handlers |
| * |
| * @param context |
| * @param includeResourcePath |
| * @param currentResourcePath |
| * @param directiveName |
| * @return include path |
| * @since 2.0 |
| */ |
| public String includeEvent(Context context, String includeResourcePath, String currentResourcePath, String directiveName) |
| { |
| for (IncludeEventHandler handler : includeHandlers) |
| { |
| includeResourcePath = handler.includeEvent(context, includeResourcePath, currentResourcePath, directiveName); |
| /* reflect 1.x behavior: exit after at least one execution whenever a null include path has been found */ |
| if (includeResourcePath == null) |
| { |
| break; |
| } |
| } |
| return includeResourcePath; |
| } |
| |
| /** |
| * Call invalid reference handlers for an invalid getter |
| * |
| * @param context |
| * @param reference |
| * @param object |
| * @param property |
| * @param info |
| * @return value returned by handlers |
| * @since 2.0 |
| */ |
| public Object invalidGetMethod(Context context, String reference, Object object, String property, Info info) |
| { |
| Object result = null; |
| for (InvalidReferenceEventHandler handler : invalidReferenceHandlers) |
| { |
| result = handler.invalidGetMethod(context, reference, object, property, info); |
| /* reflect 1.x behavior: exit after at least one execution whenever a non-null value has been found */ |
| if (result != null) |
| { |
| break; |
| } |
| } |
| return result; |
| } |
| |
| /** |
| * Call invalid reference handlers for an invalid setter |
| * |
| * @param context |
| * @param leftreference |
| * @param rightreference |
| * @param info |
| * @return whether to stop further chaining in the next cartridge |
| * @since 2.0 |
| */ |
| public boolean invalidSetMethod(Context context, String leftreference, String rightreference, Info info) |
| { |
| for (InvalidReferenceEventHandler handler : invalidReferenceHandlers) |
| { |
| if (handler.invalidSetMethod(context, leftreference, rightreference, info)) |
| { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Call invalid reference handlers for an invalid method call |
| * |
| * @param context |
| * @param reference |
| * @param object |
| * @param method |
| * @param info |
| * @return value returned by handlers |
| * @since 2.0 |
| */ |
| public Object invalidMethod(Context context, String reference, Object object, String method, Info info) |
| { |
| Object result = null; |
| for (InvalidReferenceEventHandler handler : invalidReferenceHandlers) |
| { |
| result = handler.invalidMethod(context, reference, object, method, info); |
| /* reflect 1.x behavior: exit after at least one execution whenever a non-null value has been found */ |
| if (result != null) |
| { |
| break; |
| } |
| } |
| return result; |
| } |
| |
| /** |
| * Attached the EventCartridge to the context |
| * |
| * Final because not something one should mess with lightly :) |
| * |
| * @param context context to attach to |
| * @return true if successful, false otherwise |
| */ |
| public final boolean attachToContext(Context context) |
| { |
| if (context instanceof InternalEventContext) |
| { |
| InternalEventContext iec = (InternalEventContext) context; |
| |
| iec.attachEventCartridge(this); |
| |
| /** |
| * while it's tempting to call setContext on each handler from here, |
| * this needs to be done before each method call. This is |
| * because the specific context will change as inner contexts |
| * are linked in through macros, foreach, or directly by the user. |
| */ |
| |
| return true; |
| } |
| else |
| { |
| return false; |
| } |
| } |
| } |