Skip to content
Permalink
Branch: master
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
246 lines (220 sloc) 9.51 KB
/*
* JBoss, Home of Professional Open Source
* Copyright 2009 Red Hat Inc. and/or its affiliates and other 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.test.spi;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.Externalizable;
import java.io.IOException;
import java.io.NotSerializableException;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
import java.lang.reflect.InvocationTargetException;
/**
* 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 Externalizable {
private static final long serialVersionUID = 2321010311438950147L;
private String className;
private String message;
private StackTraceElement[] trace;
private ExceptionProxy causeProxy;
private Throwable cause;
private Throwable original;
private Throwable serializationProcessException = null;
public ExceptionProxy() {
}
public ExceptionProxy(Throwable throwable) {
this.className = throwable.getClass().getName();
this.message = throwable.getMessage();
this.trace = throwable.getStackTrace();
this.causeProxy = ExceptionProxy.createForException(throwable.getCause());
this.original = throwable;
}
/**
* 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);
}
/**
* 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;
}
if (original != null) {
return original;
}
Throwable throwable = createProxyException(
"Original exception caused: " + (serializationProcessException != null
? serializationProcessException.getClass() + ": " + serializationProcessException.getMessage()
: "Unknown serialization issue"));
return throwable;
}
public ArquillianProxyException createProxyException(String reason) {
ArquillianProxyException exception = new ArquillianProxyException(message, className, reason, getCause());
exception.setStackTrace(trace);
return exception;
}
/**
* 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;
}
/**
* Custom Serialization logic.
* <p>
* If possible, we try to keep the original Exception form the Container side.
* <p>
* If we can't load the Exception on the client side, return a ArquillianProxyException that keeps the original stack
* trace etc.
* <p>
* We can't use in.readObject() on the Throwable cause, because if a ClassNotFoundException is thrown, the stream is
* marked with the exception
* and that stream is the same stream that is deserializing us, so we will fail outside of our control. Store the
* Throwable cause as a
* serialized byte array instead, so we can deserialize it outside of our own stream.
*/
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
className = (String) in.readObject();
message = (String) in.readObject();
trace = (StackTraceElement[]) in.readObject();
causeProxy = (ExceptionProxy) in.readObject();
/*
* Attempt to deserialize the original Exception. It might fail due to ClassNotFoundExceptions, ignore and move on
*/
byte[] originalExceptionData = (byte[]) in.readObject();
if (originalExceptionData != null && originalExceptionData.length > 0) {
try {
ByteArrayInputStream originalIn = new ByteArrayInputStream(originalExceptionData);
ObjectInputStream input = new ObjectInputStream(originalIn);
// // Uncomment to run ExceptionProxySerializationTestCase
// {
// @Override
// protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException
// {
// return Class.forName(desc.getName(), false, Thread.currentThread().getContextClassLoader());
// }
// };
original = (Throwable) input.readObject();
if (causeProxy != null) {
// reset the cause, so we can de-serialize them individual
Throwable cause = causeProxy.createException();
if (original instanceof InvocationTargetException) {
SecurityActions.setFieldValue(InvocationTargetException.class, original, "target", cause);
} else {
SecurityActions.setFieldValue(Throwable.class, original, "cause", cause);
}
}
} catch (Throwable e) // Possible ClassNotFoundExcpetion / NoClassDefFoundError
{
// ignore, could not load class on client side, move on and create a fake 'proxy' later
serializationProcessException = e;
}
}
// Override with the remote serialization issue cause if exists
Throwable tmpSerializationProcessException = (Throwable) in.readObject();
if (tmpSerializationProcessException != null) {
serializationProcessException = tmpSerializationProcessException;
}
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeObject(className);
out.writeObject(message);
out.writeObject(trace);
out.writeObject(causeProxy);
byte[] originalBytes = new byte[0];
if (original != null) {
try {
// reset the cause, so we can serialize the exception chain individual
SecurityActions.setFieldValue(Throwable.class, original, "cause", null);
} catch (Exception e) {
// move on, try to serialize anyway
}
try {
ByteArrayOutputStream originalOut = new ByteArrayOutputStream();
ObjectOutputStream output = new ObjectOutputStream(originalOut);
output.writeObject(original);
output.flush();
originalBytes = originalOut.toByteArray();
} catch (NotSerializableException e) {
// in case some class breaks Serialization contract
serializationProcessException = e;
}
}
out.writeObject(originalBytes);
out.writeObject(serializationProcessException);
}
@Override
public String toString() {
return super.toString() + String.format("[class=%s, message=%s],cause = %s", className, message, causeProxy);
}
}
You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.