Last active
January 15, 2016 20:29
ExceptionCatchingUberspector
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* 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