Skip to content

Instantly share code, notes, and snippets.

@vmassol
Last active January 15, 2016 20:29
ExceptionCatchingUberspector
/*
* See the NOTICE file distributed with this work for additional
* information regarding copyright ownership.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.xwiki.velocity.introspection;
import java.lang.reflect.InvocationTargetException;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.runtime.RuntimeServices;
import org.apache.velocity.runtime.log.Log;
import org.apache.velocity.util.RuntimeServicesAware;
import org.apache.velocity.util.introspection.Info;
import org.apache.velocity.util.introspection.VelMethod;
import org.xwiki.component.manager.ComponentLookupException;
import org.xwiki.component.manager.ComponentManager;
import org.xwiki.context.Execution;
import org.xwiki.velocity.internal.VelocityExecutionContextInitializer;
/**
* Chainable Velocity Uberspector that catches Exception and make them available through a {@code $lastException}
* Context binding.
*
* @since 8.0M1
* @version $Id$
* @see ChainableUberspector
*/
public class ExceptionCatchingUberspector extends AbstractChainableUberspector implements RuntimeServicesAware
{
private static final String LAST_EXCEPTION = "exception";
private Execution execution;
private boolean isDisabled;
@Override
public void setRuntimeServices(RuntimeServices runtimeServices)
{
ComponentManager componentManager =
(ComponentManager) runtimeServices.getApplicationAttribute(ComponentManager.class.getName());
try {
this.execution = componentManager.getInstance(Execution.class);
} catch (ComponentLookupException e) {
// There's no component implementation in the system. We continue and disable exception catching.
// Note: Right now this is done because we don't provide an implementation of VelocityManager in commons.
// See the comment inside the DefaultVelocityManager class in xwiki-platform.
this.log.error(String.format(
"No implementation of [%s] found. Automatic exception-catching in Velocity is disabled.",
Execution.class.getName()));
isDisabled = true;
}
}
@Override
public VelMethod getMethod(Object obj, String methodName, Object[] args, Info i) throws Exception
{
VelMethod result = super.getMethod(obj, methodName, args, i);
if (result != null && !this.isDisabled) {
result = new ExceptionCatchingVelMethod(result, this.execution, this.log);
}
return result;
}
/**
* Wrapper for a real VelMethod that catches Exceptions.
*
* @version $Id$
*/
private class ExceptionCatchingVelMethod implements VelMethod
{
/** The real method that performs the actual call. */
private VelMethod innerMethod;
private Execution execution;
private Log log;
/**
* Constructor.
*
* @param realMethod the real method to wrap
*/
ExceptionCatchingVelMethod(VelMethod realMethod, Execution execution, Log log)
{
this.innerMethod = realMethod;
this.execution = execution;
this.log = log;
}
@Override
public Object invoke(Object o, Object[] params) throws Exception
{
// Make sure to clear any previous exception
VelocityContext vcontext = (VelocityContext) this.execution.getContext().getProperty(
VelocityExecutionContextInitializer.VELOCITY_CONTEXT_ID);
if (vcontext != null) {
vcontext.remove(LAST_EXCEPTION);
}
try {
return this.innerMethod.invoke(o, params);
} catch (Exception e) {
// Note that the exception thrown by the script API is wrapped into an InvocationTargetException
// since Velocity calls the script API using reflection! Also note that if we store this exception
// directly in the Velocity Context then the script won't be able to access it since it would use
// something like "$exception.getMessage()" and since "$exception" would be a class located in the
// java.lang.reflect package, the Secure Uberspector would prevent the script from accessing it!
Exception targetException = e;
if (e instanceof InvocationTargetException) {
Throwable targetThrowable = ((InvocationTargetException) e).getTargetException();
if (targetThrowable instanceof Exception) {
targetException = (Exception) targetThrowable;
}
}
if (vcontext != null) {
// Are we inside a #try() directive? If so, we need to let the exception be thrown since it'll be
// caught by the try directive itself.
Boolean isInTryDirective = (Boolean) vcontext.get(TryCatchDirective.IN_TRY_DIRECTIVE_KEY_NAME);
if (isInTryDirective != null && isInTryDirective) {
throw e;
}
vcontext.put(LAST_EXCEPTION, targetException);
} else {
this.log.error(String.format("No Velocity Context found in the Execution Context. The exception "
+ "raised by the last script API call ([%s]) couldn't be saved in the Velocity Context and "
+ "has been raised instead.", this.innerMethod.getMethodName()));
throw targetException;
}
return null;
}
}
@Override
public boolean isCacheable()
{
return this.innerMethod.isCacheable();
}
@Override
public String getMethodName()
{
return this.innerMethod.getMethodName();
}
@Override
public Class<?> getReturnType()
{
return this.innerMethod.getReturnType();
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment