org.wildfly.common.rpc.RemoteExceptionCause Maven / Gradle / Ivy
Go to download
This artifact provides a single jar that contains all classes required to use remote EJB and JMS, including
all dependencies. It is intended for use by those not using maven, maven users should just import the EJB and
JMS BOM's instead (shaded JAR's cause lots of problems with maven, as it is very easy to inadvertently end up
with different versions on classes on the class path).
/*
* JBoss, Home of Professional Open Source.
* Copyright 2017 Red Hat, Inc., and individual contributors
* as indicated by the @author tags.
*
* 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.wildfly.common.rpc;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.function.Function;
import org.wildfly.common.Assert;
import org.wildfly.common._private.CommonMessages;
/**
* A remote exception cause. Instances of this class are intended to aid with diagnostics and are not intended to be
* directly thrown. They may be added to other exception types as a cause or suppressed throwable.
*
* @author David M. Lloyd
*/
public final class RemoteExceptionCause extends Throwable {
private static final long serialVersionUID = 7849011228540958997L;
private static final ClassValue>> fieldGetterValue = new ClassValue>>() {
protected Function> computeValue(final Class> type) {
final Field[] fields = type.getFields();
final int length = fields.length;
int i, j;
for (i = 0, j = 0; i < length; i ++) {
if ((fields[i].getModifiers() & (Modifier.STATIC | Modifier.PUBLIC)) == Modifier.PUBLIC) {
fields[j ++] = fields[i];
}
}
final int finalLength = j;
final Field[] finalFields;
if (j < i) {
finalFields = Arrays.copyOf(fields, j);
} else {
finalFields = fields;
}
if (finalLength == 0) {
return t -> Collections.emptyMap();
} else if (finalLength == 1) {
final Field field = finalFields[0];
return t -> {
try {
return Collections.singletonMap(field.getName(), String.valueOf(field.get(t)));
} catch (IllegalAccessException e) {
// impossible
throw new IllegalStateException(e);
}
};
}
return t -> {
Map map = new TreeMap<>();
for (Field field : finalFields) {
try {
map.put(field.getName(), String.valueOf(field.get(t)));
} catch (IllegalAccessException e) {
// impossible
throw new IllegalStateException(e);
}
}
return Collections.unmodifiableMap(map);
};
}
};
private static final StackTraceElement[] EMPTY_STACK = new StackTraceElement[0];
private final String exceptionClassName;
private final Map fields;
private transient String toString;
RemoteExceptionCause(final String msg, final RemoteExceptionCause cause, final String exceptionClassName, final Map fields, boolean cloneFields) {
super(msg);
if (cause != null) {
initCause(cause);
}
Assert.checkNotNullParam("exceptionClassName", exceptionClassName);
this.exceptionClassName = exceptionClassName;
if (cloneFields) {
final Iterator> iterator = fields.entrySet().iterator();
if (! iterator.hasNext()) {
this.fields = Collections.emptyMap();
} else {
final Map.Entry e1 = iterator.next();
final String name1 = e1.getKey();
final String value1 = e1.getValue();
if (name1 == null || value1 == null) {
throw CommonMessages.msg.cannotContainNullFieldNameOrValue();
}
if (! iterator.hasNext()) {
this.fields = Collections.singletonMap(name1, value1);
} else {
Map map = new TreeMap<>();
map.put(name1, value1);
do {
final Map.Entry next = iterator.next();
map.put(next.getKey(), next.getValue());
} while (iterator.hasNext());
this.fields = Collections.unmodifiableMap(map);
}
}
} else {
this.fields = fields;
}
}
/**
* Constructs a new {@code RemoteExceptionCause} instance with an initial message. No
* cause is specified.
*
* @param msg the message
* @param exceptionClassName the name of the exception's class (must not be {@code null})
*/
public RemoteExceptionCause(final String msg, final String exceptionClassName) {
this(msg, null, exceptionClassName, Collections.emptyMap(), false);
}
/**
* Constructs a new {@code RemoteExceptionCause} instance with an initial message and cause.
*
* @param msg the message
* @param cause the cause
* @param exceptionClassName the name of the exception's class (must not be {@code null})
*/
public RemoteExceptionCause(final String msg, final RemoteExceptionCause cause, final String exceptionClassName) {
this(msg, cause, exceptionClassName, Collections.emptyMap(), false);
}
/**
* Constructs a new {@code RemoteExceptionCause} instance with an initial message. No
* cause is specified.
*
* @param msg the message
* @param exceptionClassName the name of the exception's class (must not be {@code null})
* @param fields the public fields of the remote exception (must not be {@code null})
*/
public RemoteExceptionCause(final String msg, final String exceptionClassName, final Map fields) {
this(msg, null, exceptionClassName, fields, true);
}
/**
* Constructs a new {@code RemoteExceptionCause} instance with an initial message and cause.
*
* @param msg the message
* @param cause the cause
* @param exceptionClassName the name of the exception's class (must not be {@code null})
* @param fields the public fields of the remote exception (must not be {@code null})
*/
public RemoteExceptionCause(final String msg, final RemoteExceptionCause cause, final String exceptionClassName, final Map fields) {
this(msg, cause, exceptionClassName, fields, true);
}
/**
* Get a remote exception cause for the given {@link Throwable}. All of the cause and suppressed exceptions will
* also be converted.
*
* @param t the throwable, or {@code null}
* @return the remote exception cause, or {@code null} if {@code null} was passed in
*/
public static RemoteExceptionCause of(Throwable t) {
return of(t, new IdentityHashMap<>());
}
private static RemoteExceptionCause of(Throwable t, IdentityHashMap seen) {
if (t == null) return null;
if (t instanceof RemoteExceptionCause) {
return (RemoteExceptionCause) t;
} else {
final RemoteExceptionCause existing = seen.get(t);
if (existing != null) {
return existing;
}
final RemoteExceptionCause e = new RemoteExceptionCause(t.getMessage(), t.getClass().getName(), fieldGetterValue.get(t.getClass()).apply(t));
e.setStackTrace(t.getStackTrace());
seen.put(t, e);
final Throwable cause = t.getCause();
if (cause != null) e.initCause(of(cause, seen));
for (Throwable throwable : t.getSuppressed()) {
e.addSuppressed(of(throwable, seen));
}
return e;
}
}
/**
* Convert this remote exception cause to a plain throwable for sending to peers which use serialization and do not
* have this class present. Note that this does not recursively apply; normally, a serialization framework will
* handle the recursive application of this operation through object resolution.
*
* @return the throwable (not {@code null})
*/
public Throwable toPlainThrowable() {
final Throwable throwable = new Throwable(toString(), getCause());
throwable.setStackTrace(getStackTrace());
for (Throwable s : getSuppressed()) {
throwable.addSuppressed(s);
}
return throwable;
}
/**
* Get the original exception class name.
*
* @return the original exception class name (not {@code null})
*/
public String getExceptionClassName() {
return exceptionClassName;
}
/**
* Get the field names of the remote exception.
*
* @return the field names of the remote exception
*/
public Set getFieldNames() {
return fields.keySet();
}
/**
* Get the string value of the given field name.
*
* @param fieldName the name of the field (must not be {@code null})
* @return the string value of the given field name
*/
public String getFieldValue(String fieldName) {
Assert.checkNotNullParam("fieldName", fieldName);
return fields.get(fieldName);
}
/**
* Get a string representation of this exception. The representation will return an indication of the fact that
* this was a remote exception, the remote exception type, and optionally details of the exception content, followed
* by the exception message.
*
* @return the string representation of the exception
*/
public String toString() {
final String toString = this.toString;
if (toString == null) {
final String message = getMessage();
StringBuilder b = new StringBuilder();
b.append(message == null ? CommonMessages.msg.remoteException(exceptionClassName) : CommonMessages.msg.remoteException(exceptionClassName, message));
Iterator> iterator = fields.entrySet().iterator();
if (iterator.hasNext()) {
b.append("\n\tPublic fields:");
do {
final Map.Entry entry = iterator.next();
b.append('\n').append('\t').append('\t').append(entry.getKey()).append('=').append(entry.getValue());
} while (iterator.hasNext());
}
return this.toString = b.toString();
}
return toString;
}
// Format:
// class name
// null | message
// stack trace
// count ( field-name field-value )*
// null | caused-by
// count suppressed*
// Add new data to the end; old versions must ignore extra data
private static final int ST_NULL = 0;
private static final int ST_NEW_STRING = 1; // utf8 data follows
private static final int ST_NEW_STACK_ELEMENT_V8 = 2; // string string string int
private static final int ST_NEW_STACK_ELEMENT_V9 = 3; // string string string string string string int
private static final int ST_NEW_EXCEPTION_CAUSE = 4; // recurse
private static final int ST_INT8 = 5; // one byte
private static final int ST_INT16 = 6; // two bytes
private static final int ST_INT32 = 7; // four bytes
private static final int ST_INT_MINI = 0x20; // low 5 bits == signed value
private static final int ST_BACKREF_FAR = 0x40; // low 6 bits + next byte are distance
private static final int ST_BACKREF_NEAR = 0x80; // low 7 bits are distance
/**
* Write this remote exception cause to the given stream, without using serialization.
*
* @param output the output stream (must not be {@code null})
* @throws IOException if an error occurs writing the data
*/
public void writeToStream(DataOutput output) throws IOException {
Assert.checkNotNullParam("output", output);
writeToStream(output, new IdentityIntMap