All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jboss.arquillian.test.spi.ExceptionProxy Maven / Gradle / Ivy

The newest version!
/*
 * 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.io.Serializable;
import java.util.ArrayList;
import java.util.List;

/**
 * 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. 

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 : *
    *
  • If the exception class exists on the client, the original exception is * created
  • *
  • If the exception class exists, but doesn't have a suitable constructor * then another exception is thrown referencing the original exception
  • *
  • If the exception class doesn't exist, one of its superclasses is used *
  • *
* * @author Andy Gibson */ public class ExceptionProxy implements Externalizable { // The serialVersionUID of the ExceptionProxy that existed in Arquillian 1.9.1.Final private static final long serialVersionUID = 2321010311438950147L; // This is the className of the exception in the container passed into TestResult#setThrowable(Throwable) private String className; // This is the message of the exception in the container passed into TestResult#setThrowable(Throwable) private String message; // This is the stack trace of the exception in the container passed into TestResult#setThrowable(Throwable) private StackTraceElement[] trace; // This is a proxy to the cause exception in the container, not used post 1.9.1.Final private ExceptionProxy causeProxy; // This is the causeProxy#createException() instance private Throwable cause; // This only exists if the original container exception could be deserialized in the client private Throwable original; // This would exist if the original exception could not be serialized in the container private Throwable serializationProcessException = null; // New fields added in 1.9.2.Final private Version version; // The list of superclasses of the exception class that was serialized private List exceptionHierarchy; public static class Version implements Serializable { private static final long serialVersionUID = 1L; int version = 2; } public ExceptionProxy() { version = new Version(); } public ExceptionProxy(Throwable throwable) { this.version = new Version(); this.className = throwable.getClass().getName(); this.message = throwable.getMessage(); this.trace = throwable.getStackTrace(); //this.causeProxy = ExceptionProxy.createForException(throwable.getCause()); this.original = throwable; this.exceptionHierarchy = getExceptionHierarchy(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; } //System.out.println("ExceptionProxy.createForException, throwable=" + throwable); 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, and the cause if the cause could be * deserialized in the client. Otherwise, this returns an ArquillianProxyException * * @return The constructed {@link Throwable} instance if one exists, null otherwise */ public Throwable createException() { if (!hasException()) { return null; } //System.out.println("ExceptionProxy.createException, this=" + this); 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 serializationProcessException; } // Accessors to the fields of the ExceptionProxy public String getClassName() { return className; } public String getMessage() { return message; } public StackTraceElement[] getTrace() { return trace; } public ExceptionProxy getCauseProxy() { return causeProxy; } public Throwable getOriginal() { return original; } public Throwable getSerializationProcessException() { return serializationProcessException; } public Version getVersion() { return version; } public List getExceptionHierarchy() { return exceptionHierarchy; } /** * Override to provide the full details of the exception being proxied * @return The full details of the exception being proxied */ @Override public String toString() { StringBuilder tmp = new StringBuilder(); tmp.append("ExceptionProxy("+className+")"); if(message != null) { tmp.append("msg: ").append(message); } if(causeProxy != null) { tmp.append("\ncause: ").append(causeProxy); } if(trace != null) { for(StackTraceElement element : trace) { tmp.append("\n\tat ").append(element); } } if(exceptionHierarchy != null) { tmp.append("\nexceptionHierarchy: "); for(String t : exceptionHierarchy) { tmp.append("\n\t").append(t); } } if(serializationProcessException != null) { tmp.append("\nserializationProcessException: ").append(serializationProcessException); } return tmp.toString(); } /** * Custom Serialization logic. *

* If possible, we try to keep the original Exception form the Container side. *

* If we can't load the Exception on the client side, return a ArquillianProxyException that keeps the original stack * trace etc. *

* 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 { // Read the first object to see if it is the version object Object firstObject = in.readObject(); if (firstObject instanceof Version) { version = (Version) firstObject; className = (String) in.readObject(); message = (String) in.readObject(); trace = (StackTraceElement[]) in.readObject(); exceptionHierarchy = (List) in.readObject(); // Try to deserialize the original exception try { byte[] originalExceptionData = (byte[]) in.readObject(); if (originalExceptionData != null && originalExceptionData.length > 0) { ByteArrayInputStream originalIn = new ByteArrayInputStream(originalExceptionData); ObjectInputStream input = new ObjectInputStream(originalIn); original = (Throwable) input.readObject(); } } catch (Throwable e) { this.serializationProcessException = e; } // Override with the remote serialization issue cause if exists Throwable tmpSerializationProcessException = (Throwable) in.readObject(); if (tmpSerializationProcessException != null) { serializationProcessException = tmpSerializationProcessException; } // If we were not able to create original from originalExceptionData, try to create it from exceptionHierarchy if(original == null) { original = buildOriginalException(); } } else { serializationProcessException = new IOException("Unknown version of ExceptionProxy"); } } @Override public void writeExternal(ObjectOutput out) throws IOException { out.writeObject(version); out.writeObject(className); out.writeObject(message); out.writeObject(trace); out.writeObject(exceptionHierarchy); byte[] originalBytes = new byte[0]; try { /* Try to serialize the original exception. Here we do it in a separate try-catch block to avoid because default serialization will serialize whatever it can and leave non-serializable fields out. We have to make the write of the root exception atomic. */ ByteArrayOutputStream originalOut = new ByteArrayOutputStream(); ObjectOutputStream output = new ObjectOutputStream(originalOut); output.writeObject(original); output.flush(); originalBytes = originalOut.toByteArray(); } catch (NotSerializableException e) { // ignore, could not serialize original exception this.serializationProcessException = e; } out.writeObject(originalBytes); out.writeObject(serializationProcessException); } /** * Get the exception hierarchy for the exception class being proxied. * * @return list of exception types in the hierarchy */ protected List getExceptionHierarchy(Throwable t) { List hierarchy = new ArrayList<>(); Class tclass = t.getClass(); while(Throwable.class.isAssignableFrom(tclass)) { hierarchy.add(tclass.getName()); tclass = tclass.getSuperclass(); } return hierarchy; } /** * Build the original exception based on the exception class name. This first * tries to use a ctor with a message, then a default ctor. * * @return the original exception */ protected Throwable buildOriginalException() { Throwable original = null; for(String tclassName : exceptionHierarchy) { try { Class tclass = Class.forName(tclassName).asSubclass(Throwable.class); try { original = tclass.getDeclaredConstructor(String.class).newInstance(message); original.setStackTrace(trace); break; } catch (Exception e) { try { original = tclass.getDeclaredConstructor().newInstance(); original.setStackTrace(trace); break; } catch (Exception ex) { // ignore, could not load class on client side, try next base class } } } catch (ClassNotFoundException e) { // ignore, could not load class on client side, try next base class } } return original; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy