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 java.util.Iterator;
import org.apache.velocity.context.InternalContextAdapter;
import org.apache.velocity.runtime.RuntimeServices;
import org.apache.velocity.util.ExceptionUtils;
import org.apache.velocity.util.introspection.Info;
/**
* Calls on request all registered event handlers for a particular event. Each
* method accepts two event cartridges (typically one from the application and
* one from the context). All appropriate event handlers are executed in order
* until a stopping condition is met. See the docs for the individual methods to
* see what the stopping condition is for that method.
*
* @author <a href="mailto:wglass@wglass@forio.com">Will Glass-Husain </a>
* @version $Id: EventHandlerUtil.java 685685 2008-08-13 21:43:27Z nbubna $
* @since 1.5
*/
public class EventHandlerUtil {
/**
* Called before a reference is inserted. All event handlers are called in
* sequence. The default implementation inserts the reference as is.
*
* This is a major hotspot method called by ASTReference render.
*
* @param reference reference from template about to be inserted
* @param value value about to be inserted (after toString() )
* @param rsvc current instance of RuntimeServices
* @param context The internal context adapter.
* @return Object on which toString() should be called for output.
*/
public static Object referenceInsert(RuntimeServices rsvc,
InternalContextAdapter context, String reference, Object value)
{
// app level cartridges have already been initialized
/*
* Performance modification: EventCartridge.getReferenceInsertionEventHandlers
* now returns a null if there are no handlers. Thus we can avoid creating the
* Iterator object.
*/
EventCartridge ev1 = rsvc.getApplicationEventCartridge();
Iterator applicationEventHandlerIterator =
(ev1 == null) ? null: ev1.getReferenceInsertionEventHandlers();
EventCartridge ev2 = context.getEventCartridge();
initializeEventCartridge(rsvc, ev2);
Iterator contextEventHandlerIterator =
(ev2 == null) ? null: ev2.getReferenceInsertionEventHandlers();
try
{
/*
* Performance modification: methodExecutor is created only if one of the
* iterators is not null.
*/
EventHandlerMethodExecutor methodExecutor = null;
if( applicationEventHandlerIterator != null )
{
methodExecutor =
new ReferenceInsertionEventHandler.referenceInsertExecutor(context, reference, value);
iterateOverEventHandlers(applicationEventHandlerIterator, methodExecutor);
}
if( contextEventHandlerIterator != null )
{
if( methodExecutor == null )
methodExecutor =
new ReferenceInsertionEventHandler.referenceInsertExecutor(context, reference, value);
iterateOverEventHandlers(contextEventHandlerIterator, methodExecutor);
}
return methodExecutor != null ? methodExecutor.getReturnValue() : value;
}
catch (RuntimeException e)
{
throw e;
}
catch (Exception e)
{
throw ExceptionUtils.createRuntimeException("Exception in event handler.",e);
}
}
/**
* Called when a null is evaluated during a #set. All event handlers are
* called in sequence until a false is returned. The default implementation
* always returns true.
*
* @param lhs Left hand side of the expression.
* @param rhs Right hand side of the expression.
* @param rsvc current instance of RuntimeServices
* @param context The internal context adapter.
* @return true if to be logged, false otherwise
*/
public static boolean shouldLogOnNullSet(RuntimeServices rsvc,
InternalContextAdapter context, String lhs, String rhs)
{
// app level cartridges have already been initialized
EventCartridge ev1 = rsvc.getApplicationEventCartridge();
Iterator applicationEventHandlerIterator =
(ev1 == null) ? null: ev1.getNullSetEventHandlers();
EventCartridge ev2 = context.getEventCartridge();
initializeEventCartridge(rsvc, ev2);
Iterator contextEventHandlerIterator =
(ev2 == null) ? null: ev2.getNullSetEventHandlers();
try
{
EventHandlerMethodExecutor methodExecutor =
new NullSetEventHandler.ShouldLogOnNullSetExecutor(context, lhs, rhs);
callEventHandlers(
applicationEventHandlerIterator,
contextEventHandlerIterator, methodExecutor);
return ((Boolean) methodExecutor.getReturnValue()).booleanValue();
}
catch (RuntimeException e)
{
throw e;
}
catch (Exception e)
{
throw ExceptionUtils.createRuntimeException("Exception in event handler.",e);
}
}
/**
* Called when a method exception is generated during Velocity merge. Only
* the first valid event handler in the sequence is called. The default
* implementation simply rethrows the exception.
*
* @param claz
* Class that is causing the exception
* @param method
* method called that causes the exception
* @param e
* Exception thrown by the method
* @param rsvc current instance of RuntimeServices
* @param context The internal context adapter.
* @return Object to return as method result
* @throws Exception
* to be wrapped and propogated to app
*/
public static Object methodException(RuntimeServices rsvc,
InternalContextAdapter context, Class claz, String method,
Exception e) throws Exception
{
// app level cartridges have already been initialized
EventCartridge ev1 = rsvc.getApplicationEventCartridge();
Iterator applicationEventHandlerIterator =
(ev1 == null) ? null: ev1.getMethodExceptionEventHandlers();
EventCartridge ev2 = context.getEventCartridge();
initializeEventCartridge(rsvc, ev2);
Iterator contextEventHandlerIterator =
(ev2 == null) ? null: ev2.getMethodExceptionEventHandlers();
EventHandlerMethodExecutor methodExecutor =
new MethodExceptionEventHandler.MethodExceptionExecutor(context, claz, method, e);
if ( ((applicationEventHandlerIterator == null) || !applicationEventHandlerIterator.hasNext()) &&
((contextEventHandlerIterator == null) || !contextEventHandlerIterator.hasNext()) )
{
throw e;
}
callEventHandlers(
applicationEventHandlerIterator,
contextEventHandlerIterator, methodExecutor);
return methodExecutor.getReturnValue();
}
/**
* Called when an include-type directive is encountered (#include or
* #parse). All the registered event handlers are called unless null is
* returned. The default implementation always processes the included
* resource.
*
* @param includeResourcePath
* the path as given in the include directive.
* @param currentResourcePath
* the path of the currently rendering template that includes the
* include directive.
* @param directiveName
* name of the directive used to include the resource. (With the
* standard directives this is either "parse" or "include").
* @param rsvc current instance of RuntimeServices
* @param context The internal context adapter.
*
* @return a new resource path for the directive, or null to block the
* include from occurring.
*/
public static String includeEvent(RuntimeServices rsvc,
InternalContextAdapter context, String includeResourcePath,
String currentResourcePath, String directiveName)
{
// app level cartridges have already been initialized
EventCartridge ev1 = rsvc.getApplicationEventCartridge();
Iterator applicationEventHandlerIterator =
(ev1 == null) ? null: ev1.getIncludeEventHandlers();
EventCartridge ev2 = context.getEventCartridge();
initializeEventCartridge(rsvc, ev2);
Iterator contextEventHandlerIterator =
(ev2 == null) ? null: ev2.getIncludeEventHandlers();
try
{
EventHandlerMethodExecutor methodExecutor =
new IncludeEventHandler.IncludeEventExecutor(
context, includeResourcePath,
currentResourcePath, directiveName);
callEventHandlers(
applicationEventHandlerIterator,
contextEventHandlerIterator, methodExecutor);
return (String) methodExecutor.getReturnValue();
}
catch (RuntimeException e)
{
throw e;
}
catch (Exception e)
{
throw ExceptionUtils.createRuntimeException("Exception in event handler.",e);
}
}
/**
* Called when an invalid get method is encountered.
*
* @param rsvc current instance of RuntimeServices
* @param context the context when the reference was found invalid
* @param reference complete invalid reference
* @param object object from reference, or null if not available
* @param property name of property, or null if not relevant
* @param info contains info on template, line, col
* @return substitute return value for missing reference, or null if no substitute
*/
public static Object invalidGetMethod(RuntimeServices rsvc,
InternalContextAdapter context, String reference,
Object object, String property, Info info)
{
return
invalidReferenceHandlerCall (
new InvalidReferenceEventHandler.InvalidGetMethodExecutor
(context, reference, object, property, info),
rsvc,
context);
}
/**
* Called when an invalid set method is encountered.
*
* @param rsvc current instance of RuntimeServices
* @param context the context when the reference was found invalid
* @param leftreference left reference being assigned to
* @param rightreference invalid reference on the right
* @param info contains info on template, line, col
*/
public static void invalidSetMethod(RuntimeServices rsvc,
InternalContextAdapter context, String leftreference,
String rightreference, Info info)
{
/**
* ignore return value
*/
invalidReferenceHandlerCall (
new InvalidReferenceEventHandler.InvalidSetMethodExecutor
(context, leftreference, rightreference, info),
rsvc,
context);
}
/**
* Called when an invalid method is encountered.
*
* @param rsvc current instance of RuntimeServices
* @param context the context when the reference was found invalid
* @param reference complete invalid reference
* @param object object from reference, or null if not available
* @param method name of method, or null if not relevant
* @param info contains info on template, line, col
* @return substitute return value for missing reference, or null if no substitute
*/
public static Object invalidMethod(RuntimeServices rsvc,
InternalContextAdapter context, String reference,
Object object, String method, Info info)
{
return
invalidReferenceHandlerCall (
new InvalidReferenceEventHandler.InvalidMethodExecutor
(context, reference, object, method, info),
rsvc,
context);
}
/**
* Calls event handler method with appropriate chaining across event handlers.
*
* @param methodExecutor
* @param rsvc current instance of RuntimeServices
* @param context The current context
* @return return value from method, or null if no return value
*/
public static Object invalidReferenceHandlerCall(
EventHandlerMethodExecutor methodExecutor,
RuntimeServices rsvc,
InternalContextAdapter context)
{
// app level cartridges have already been initialized
EventCartridge ev1 = rsvc.getApplicationEventCartridge();
Iterator applicationEventHandlerIterator =
(ev1 == null) ? null: ev1.getInvalidReferenceEventHandlers();
EventCartridge ev2 = context.getEventCartridge();
initializeEventCartridge(rsvc, ev2);
Iterator contextEventHandlerIterator =
(ev2 == null) ? null: ev2.getInvalidReferenceEventHandlers();
try
{
callEventHandlers(
applicationEventHandlerIterator,
contextEventHandlerIterator, methodExecutor);
return methodExecutor.getReturnValue();
}
catch (RuntimeException e)
{
throw e;
}
catch (Exception e)
{
throw ExceptionUtils.createRuntimeException("Exception in event handler.",e);
}
}
/**
* Initialize the event cartridge if appropriate.
*
* @param rsvc current instance of RuntimeServices
* @param eventCartridge the event cartridge to be initialized
*/
private static void initializeEventCartridge(RuntimeServices rsvc, EventCartridge eventCartridge)
{
if (eventCartridge != null)
{
try
{
eventCartridge.initialize(rsvc);
}
catch (Exception e)
{
throw ExceptionUtils.createRuntimeException("Couldn't initialize event cartridge : ", e);
}
}
}
/**
* Loop through both the application level and context-attached event handlers.
*
* @param applicationEventHandlerIterator Iterator that loops through all global event handlers declared at application level
* @param contextEventHandlerIterator Iterator that loops through all global event handlers attached to context
* @param eventExecutor Strategy object that executes event handler method
* @exception Exception generic exception potentially thrown by event handlers
*/
private static void callEventHandlers(
Iterator applicationEventHandlerIterator,
Iterator contextEventHandlerIterator,
EventHandlerMethodExecutor eventExecutor)
throws Exception
{
/**
* First loop through the event handlers configured at the app level
* in the properties file.
*/
iterateOverEventHandlers(applicationEventHandlerIterator, eventExecutor);
/**
* Then loop through the event handlers attached to the context.
*/
iterateOverEventHandlers(contextEventHandlerIterator, eventExecutor);
}
/**
* Loop through a given iterator of event handlers.
*
* @param handlerIterator Iterator that loops through event handlers
* @param eventExecutor Strategy object that executes event handler method
* @exception Exception generic exception potentially thrown by event handlers
*/
private static void iterateOverEventHandlers(
Iterator handlerIterator,
EventHandlerMethodExecutor eventExecutor)
throws Exception
{
if (handlerIterator != null)
{
for (Iterator i = handlerIterator; i.hasNext();)
{
EventHandler eventHandler = (EventHandler) i.next();
if (!eventExecutor.isDone())
{
eventExecutor.execute(eventHandler);
}
}
}
}
}