/* * JBoss, Home of Professional Open Source * Copyright 2009, Red Hat Middleware LLC, and individual contributors * by the @authors tag. See the copyright.txt in the distribution for a * full listing of individual contributors. * * Licensed 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. */ package org.jboss.arquillian.spi; import java.io.Serializable; import java.lang.reflect.Constructor; /** * Takes an exception class and creates a proxy that can be used to rebuild the * exception. The problem stems from problems serializing exceptions and * deserializing them in another application where the exception classes might * not exist, or they might exist in different version. This proxy also * propagates the stacktrace and the cause exception to create totally portable * exceptions. </p> This class creates a serializable proxy of the exception and * when unserialized can be used to re-create the exception based on the * following rules : * <ul> * <li>If the exception class exists on the client, the original exception is * created</li> * <li>If the exception class exists, but doesn't have a suitable constructor * then another exception is thrown referencing the original exception</li> * <li>If the exception class exists, but is not throwable, another exception is * thrown referencing the original exception</li> * <li>If the exception class doesn't exist, another exception is raised instead * </li> * </ul> * * * @author <a href="mailto:contact@andygibson.net">Andy Gibson</a> * */ public class ExceptionProxy implements Serializable { private static final long serialVersionUID = 2321010311438950147L; private String className; private String message; private StackTraceElement[] trace; private ExceptionProxy causeProxy; private Throwable cause; public ExceptionProxy(Throwable throwable) { this.className = throwable.getClass().getName(); this.message = throwable.getMessage(); this.trace = throwable.getStackTrace(); this.causeProxy = ExceptionProxy.createForException(throwable .getCause()); } @Override public String toString() { return super.toString() + String.format("[class=%s, message=%s],cause = %s", className, message, causeProxy); } /** * Indicates whether this proxy wraps an exception * * @return Flag indicating an exception is wrapped. */ public boolean hasException() { return className != null; } /** * Constructs an instance of the proxied exception based on the class name, * message, stack trace and if applicable, the cause. * * @return The constructed {@link Throwable} instance */ public Throwable createException() { if (!hasException()) { return null; } Class<?> clazz = null; try { clazz = Class.forName(className); } catch (ClassNotFoundException e) { return createProxyException("Exception class not found on client"); } Throwable throwable = constructExceptionForClass(clazz); throwable.setStackTrace(trace); return throwable; } public ArquillianProxyException createProxyException(String reason) { ArquillianProxyException exception = new ArquillianProxyException(message, className, reason, getCause()); exception.setStackTrace(trace); return exception; } /** * Constructs an instance of the passed in class if a suitable constructor * is found. If the constructor subclasses {@link Throwable} it is returned. * If a Class instance is not constructed ot is not a {@link Throwable} then * another exception is returned indicating this. * * @param clazz * Class to construct * @return Instance of the Throwable class. */ private Throwable constructExceptionForClass(Class<?> clazz) { Object object = buildObjectFromClassConstructors(clazz); if (object == null) { return createProxyException("Could not find suitable constructor"); } if (!(object instanceof Throwable)) { return createProxyException("Proxy references non-Throwable type"); } return (Throwable) object; } private Object buildObjectFromClassConstructors(Class<?> clazz) { // try the (String,Throwable) constructor first Object object = buildExceptionFromConstructor(clazz, new Class<?>[] { String.class, Throwable.class }, new Object[] { message, getCause() }); if (object != null) { return object; } // try the (String,Exception) constructor first object = buildExceptionFromConstructor(clazz, new Class<?>[] { String.class, Exception.class }, new Object[] { message, getCause() }); if (object != null) { return object; } // try the (String) constructor next object = buildExceptionFromConstructor(clazz, new Class<?>[] { String.class }, new Object[] { message }); if (object != null) { return object; } return null; } /** * Attempt to build an exception of the given class type using the * constructor signature and parameters passed in. If no constructor matches * or the constructor throws an exception, then null is returned. * * @param clazz * Class to construct * @param signature * Array of class types to match the signature on * @param params * Parameter values to pass to the constructor if found. * @return The object instance created using the constructor */ private <T> T buildExceptionFromConstructor(Class<T> clazz, Class<?>[] signature, Object[] params) { Constructor<?> constructor = null; // try the message,cause constructor first try { constructor = clazz.getConstructor(signature); } catch (SecurityException e) { // we'll try the next signature } catch (NoSuchMethodException e) { // we'll try the next signature } // if we found a working constructor, use it if (constructor != null) { try { @SuppressWarnings("unchecked") T result = (T) constructor.newInstance(params); return result; } catch (Throwable e) { return null; } } // no matching constructor, no result return null; } /** * Static method to create an exception proxy for the passed in * {@link Throwable} class. If null is passed in, null is returned as the * exception proxy * * @param throwable * Exception to proxy * @return An ExceptionProxy representing the exception passed in */ public static ExceptionProxy createForException(Throwable throwable) { if (throwable == null) { return null; } return new ExceptionProxy(throwable); } /** * Returns the cause of the exception represented by this proxy * * @return The cause of this exception */ public Throwable getCause() { // lazy create cause if (cause == null) { if (causeProxy != null) { cause = causeProxy.createException(); } } return cause; } }