CORDA-1672 - Enable better user-helpful error messages (#3445)

* CORDA-1672 - Enable better user-helpful error messages

The issue is that the error reporting framework in the serializer is
targeted at developers in the node. However, because we pass exceptions
to users over RPC those error messages aren't always helpful.

Keep an internal exception that tracks debug useful information and log
that just before any exception escapes the framework and allow for
specific user "problem mitigation" issues to be set.

* wip

* update remaining excepions
This commit is contained in:
Katelyn Baker 2018-07-27 10:56:17 +01:00 committed by GitHub
parent c8de5ce08d
commit e871b83464
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 435 additions and 130 deletions

View File

@ -1,7 +1,82 @@
package net.corda.serialization.internal.amqp
import net.corda.core.internal.VisibleForTesting
import org.slf4j.Logger
import java.io.NotSerializableException
import java.lang.reflect.Type
class SyntheticParameterException(val type: Type) : NotSerializableException("Type '${type.typeName} has synthetic "
/**
* Not a public property so will have to use reflection
*/
private fun Throwable.setMessage(newMsg: String) {
val detailMessageField = Throwable::class.java.getDeclaredField("detailMessage")
detailMessageField.isAccessible = true
detailMessageField.set(this, newMsg)
}
/**
* Utility function which helps tracking the path in the object graph when exceptions are thrown.
* Since there might be a chain of nested calls it is useful to record which part of the graph caused an issue.
* Path information is added to the message of the exception being thrown.
*/
internal inline fun <T> ifThrowsAppend(strToAppendFn: () -> String, block: () -> T): T {
try {
return block()
} catch (th: Throwable) {
if (th is AMQPNotSerializableException) {
th.classHierarchy.add(strToAppendFn())
} else {
th.setMessage("${strToAppendFn()} -> ${th.message}")
}
throw th
}
}
class AMQPNoTypeNotSerializableException(
msg: String,
mitigation: String = msg
) : AMQPNotSerializableException(Class::class.java, msg, mitigation, mutableListOf("Unknown"))
/**
* The purpose of the [AMQPNotSerializableException] is to encapsulate internal serialization errors
* within the serialization framework and to capture errors when building serializer objects
* that will aid in debugging whilst also allowing "user helpful" information to be communicated
* outward to end users.
*
* @property type the class that failed to serialize
* @property msg the specific error
* @property mitigation information useful to an end user.
* @property classHierarchy represents the call hierarchy to the point of error. This is useful
* when debugging a deeply nested class that fails to serialize as that classes position in
* the class hierarchy is preserved, otherwise that information is lost. This list is automatically
* updated by the [ifThrowsAppend] function used within this library.
*/
open class AMQPNotSerializableException(
val type: Type,
val msg: String,
val mitigation: String = msg,
val classHierarchy : MutableList<String> = mutableListOf(type.typeName)
) : NotSerializableException(msg) {
@Suppress("Unused")
constructor(type: Type) : this (type, "type=${type.typeName} is not serializable")
@VisibleForTesting
fun errorMessage(direction: String) : String {
return "Serialization failed direction=\"$direction\", type=\"${type.typeName}\", " +
"msg=\"$msg\", " +
"ClassChain=\"${classHierarchy.asReversed().joinToString(" -> ")}\""
}
fun log(direction: String, logger: Logger) {
logger.error(errorMessage(direction))
// if debug is enabled print the stack, the exception we allow to escape
// will be printed into the log anyway by the handling thread
logger.debug("", cause)
}
}
class SyntheticParameterException(type: Type) : AMQPNotSerializableException(
type,
"Type '${type.typeName} has synthetic "
+ "fields and is likely a nested inner class. This is not support by the Corda AMQP serialization framework")

View File

@ -8,7 +8,6 @@ import net.corda.core.utilities.loggerFor
import net.corda.core.utilities.trace
import org.apache.qpid.proton.amqp.Symbol
import org.apache.qpid.proton.codec.Data
import java.io.NotSerializableException
import java.lang.reflect.Type
/**
@ -90,11 +89,11 @@ open class ArraySerializer(override val type: Type, factory: SerializerFactory)
): Any {
if (obj is List<*>) {
return obj.map { input.readObjectOrNull(it, schemas, elementType, context) }.toArrayOfType(elementType)
} else throw NotSerializableException("Expected a List but found $obj")
} else throw AMQPNotSerializableException(type, "Expected a List but found $obj")
}
open fun <T> List<T>.toArrayOfType(type: Type): Any {
val elementType = type.asClass() ?: throw NotSerializableException("Unexpected array element type $type")
val elementType = type.asClass() ?: throw AMQPNotSerializableException(type, "Unexpected array element type $type")
val list = this
return java.lang.reflect.Array.newInstance(elementType, this.size).apply {
(0..lastIndex).forEach { java.lang.reflect.Array.set(this, it, list[it]) }
@ -106,7 +105,7 @@ open class ArraySerializer(override val type: Type, factory: SerializerFactory)
// the array since Kotlin won't allow an implicit cast from Int (as they're stored as 16bit ints) to Char
class CharArraySerializer(factory: SerializerFactory) : ArraySerializer(Array<Char>::class.java, factory) {
override fun <T> List<T>.toArrayOfType(type: Type): Any {
val elementType = type.asClass() ?: throw NotSerializableException("Unexpected array element type $type")
val elementType = type.asClass() ?: throw AMQPNotSerializableException(type, "Unexpected array element type $type")
val list = this
return java.lang.reflect.Array.newInstance(elementType, this.size).apply {
(0..lastIndex).forEach { java.lang.reflect.Array.set(this, it, (list[it] as Int).toChar()) }
@ -160,7 +159,11 @@ class PrimCharArraySerializer(factory: SerializerFactory) : PrimArraySerializer(
}
override fun <T> List<T>.toArrayOfType(type: Type): Any {
val elementType = type.asClass() ?: throw NotSerializableException("Unexpected array element type $type")
val elementType = type.asClass() ?: throw AMQPNotSerializableException(
type,
"Unexpected array element type $type",
"blob is corrupt")
val list = this
return java.lang.reflect.Array.newInstance(elementType, this.size).apply {
val array = this

View File

@ -6,7 +6,6 @@ import net.corda.core.serialization.SerializationContext
import net.corda.core.utilities.NonEmptySet
import org.apache.qpid.proton.amqp.Symbol
import org.apache.qpid.proton.codec.Data
import java.io.NotSerializableException
import java.lang.reflect.ParameterizedType
import java.lang.reflect.Type
import java.util.*
@ -35,7 +34,10 @@ class CollectionSerializer(private val declaredType: ParameterizedType, factory:
))
private fun findConcreteType(clazz: Class<*>): (List<*>) -> Collection<*> {
return supportedTypes[clazz] ?: throw NotSerializableException("Unsupported collection type $clazz.")
return supportedTypes[clazz] ?: throw AMQPNotSerializableException(
clazz,
"Unsupported collection type $clazz.",
"Supported Collections are ${supportedTypes.keys.joinToString(",")}")
}
fun deriveParameterizedType(declaredType: Type, declaredClass: Class<*>, actualClass: Class<*>?): ParameterizedType {
@ -48,7 +50,10 @@ class CollectionSerializer(private val declaredType: ParameterizedType, factory:
return deriveParametrizedType(declaredType, collectionClass)
}
throw NotSerializableException("Cannot derive collection type for declaredType: '$declaredType', declaredClass: '$declaredClass', actualClass: '$actualClass'")
throw AMQPNotSerializableException(
declaredType,
"Cannot derive collection type for declaredType: '$declaredType', " +
"declaredClass: '$declaredClass', actualClass: '$actualClass'")
}
private fun deriveParametrizedType(declaredType: Type, collectionClass: Class<out Collection<*>>): ParameterizedType =

View File

@ -7,7 +7,6 @@ import net.corda.core.serialization.SerializationCustomSerializer
import net.corda.serialization.internal.amqp.SerializerFactory.Companion.nameForType
import org.apache.qpid.proton.amqp.Symbol
import org.apache.qpid.proton.codec.Data
import java.io.NotSerializableException
import java.lang.reflect.Type
import kotlin.reflect.jvm.javaType
import kotlin.reflect.jvm.jvmErasure
@ -56,7 +55,9 @@ class CorDappCustomSerializer(
init {
if (types.size != 2) {
throw NotSerializableException("Unable to determine serializer parent types")
throw AMQPNotSerializableException(
CorDappCustomSerializer::class.java,
"Unable to determine serializer parent types")
}
}

View File

@ -29,25 +29,32 @@ data class ObjectAndEnvelope<out T>(val obj: T, val envelope: Envelope)
* instances and threads.
*/
@KeepForDJVM
class DeserializationInput @JvmOverloads constructor(private val serializerFactory: SerializerFactory,
private val encodingWhitelist: EncodingWhitelist = NullEncodingWhitelist) {
class DeserializationInput @JvmOverloads constructor(
private val serializerFactory: SerializerFactory,
private val encodingWhitelist: EncodingWhitelist = NullEncodingWhitelist
) {
private val objectHistory: MutableList<Any> = mutableListOf()
private val logger = loggerFor<DeserializationInput>()
companion object {
@VisibleForTesting
@Throws(NotSerializableException::class)
fun <T> withDataBytes(byteSequence: ByteSequence, encodingWhitelist: EncodingWhitelist, task: (ByteBuffer) -> T): T {
@Throws(AMQPNoTypeNotSerializableException::class)
fun <T> withDataBytes(
byteSequence: ByteSequence,
encodingWhitelist: EncodingWhitelist,
task: (ByteBuffer) -> T
) : T {
// Check that the lead bytes match expected header
val amqpSequence = amqpMagic.consume(byteSequence)
?: throw NotSerializableException("Serialization header does not match.")
?: throw AMQPNoTypeNotSerializableException("Serialization header does not match.")
var stream: InputStream = ByteBufferInputStream(amqpSequence)
try {
while (true) {
when (SectionId.reader.readFrom(stream)) {
SectionId.ENCODING -> {
val encoding = CordaSerializationEncoding.reader.readFrom(stream)
encodingWhitelist.acceptEncoding(encoding) || throw NotSerializableException(encodingNotPermittedFormat.format(encoding))
encodingWhitelist.acceptEncoding(encoding) ||
throw AMQPNoTypeNotSerializableException(encodingNotPermittedFormat.format(encoding))
stream = encoding.wrap(stream)
}
SectionId.DATA_AND_STOP, SectionId.ALT_DATA_AND_STOP -> return task(stream.asByteBuffer())
@ -58,29 +65,40 @@ class DeserializationInput @JvmOverloads constructor(private val serializerFacto
}
}
@Throws(NotSerializableException::class)
@Throws(AMQPNoTypeNotSerializableException::class)
fun getEnvelope(byteSequence: ByteSequence, encodingWhitelist: EncodingWhitelist = NullEncodingWhitelist): Envelope {
return withDataBytes(byteSequence, encodingWhitelist) { dataBytes ->
val data = Data.Factory.create()
val expectedSize = dataBytes.remaining()
if (data.decode(dataBytes) != expectedSize.toLong()) throw NotSerializableException("Unexpected size of data")
if (data.decode(dataBytes) != expectedSize.toLong()) {
throw AMQPNoTypeNotSerializableException(
"Unexpected size of data",
"Blob is corrupted!.")
}
Envelope.get(data)
}
}
}
@Throws(NotSerializableException::class)
@Throws(AMQPNoTypeNotSerializableException::class)
fun getEnvelope(byteSequence: ByteSequence) = getEnvelope(byteSequence, encodingWhitelist)
@Throws(NotSerializableException::class)
@Throws(
AMQPNotSerializableException::class,
AMQPNoTypeNotSerializableException::class)
inline fun <reified T : Any> deserialize(bytes: SerializedBytes<T>, context: SerializationContext): T =
deserialize(bytes, T::class.java, context)
@Throws(NotSerializableException::class)
@Throws(
AMQPNotSerializableException::class,
AMQPNoTypeNotSerializableException::class)
private fun <R> des(generator: () -> R): R {
try {
return generator()
} catch (amqp : AMQPNotSerializableException) {
amqp.log("Deserialize", logger)
throw NotSerializableException(amqp.mitigation)
} catch (nse: NotSerializableException) {
throw nse
} catch (t: Throwable) {
@ -133,12 +151,15 @@ class DeserializationInput @JvmOverloads constructor(private val serializerFacto
// It must be a reference to an instance that has already been read, cheaply and quickly returning it by reference.
val objectIndex = (obj.described as UnsignedInteger).toInt()
if (objectIndex !in 0..objectHistory.size)
throw NotSerializableException("Retrieval of existing reference failed. Requested index $objectIndex " +
throw AMQPNotSerializableException(
type,
"Retrieval of existing reference failed. Requested index $objectIndex " +
"is outside of the bounds for the list of size: ${objectHistory.size}")
val objectRetrieved = objectHistory[objectIndex]
if (!objectRetrieved::class.java.isSubClassOf(type.asClass()!!)) {
throw NotSerializableException(
throw AMQPNotSerializableException(
type,
"Existing reference type mismatch. Expected: '$type', found: '${objectRetrieved::class.java}' " +
"@ $objectIndex")
}
@ -150,8 +171,11 @@ class DeserializationInput @JvmOverloads constructor(private val serializerFacto
val serializer = serializerFactory.get(obj.descriptor, schemas)
if (SerializerFactory.AnyType != type && serializer.type != type && with(serializer.type) {
!isSubClassOf(type) && !materiallyEquivalentTo(type)
}) {
throw NotSerializableException("Described type with descriptor ${obj.descriptor} was " +
}
) {
throw AMQPNotSerializableException(
type,
"Described type with descriptor ${obj.descriptor} was " +
"expected to be of type $type but was ${serializer.type}")
}
serializer.readObject(obj.described, schemas, this, context)

View File

@ -13,13 +13,19 @@ import java.util.*
* of the JDK implementation which we use as the textual format in the AMQP schema.
*/
@KeepForDJVM
class DeserializedParameterizedType(private val rawType: Class<*>, private val params: Array<out Type>, private val ownerType: Type? = null) : ParameterizedType {
class DeserializedParameterizedType(
private val rawType: Class<*>,
private val params: Array<out Type>,
private val ownerType: Type? = null
) : ParameterizedType {
init {
if (params.isEmpty()) {
throw NotSerializableException("Must be at least one parameter type in a ParameterizedType")
throw AMQPNotSerializableException(rawType, "Must be at least one parameter type in a ParameterizedType")
}
if (params.size != rawType.typeParameters.size) {
throw NotSerializableException("Expected ${rawType.typeParameters.size} for ${rawType.name} but found ${params.size}")
throw AMQPNotSerializableException(
rawType,
"Expected ${rawType.typeParameters.size} for ${rawType.name} but found ${params.size}")
}
}
@ -42,10 +48,11 @@ class DeserializedParameterizedType(private val rawType: Class<*>, private val p
val paramTypes = ArrayList<Type>()
val pos = parseTypeList("$name>", paramTypes, cl)
if (pos <= name.length) {
throw NotSerializableException("Malformed string form of ParameterizedType. Unexpected '>' at character position $pos of $name.")
throw AMQPNoTypeNotSerializableException(
"Malformed string form of ParameterizedType. Unexpected '>' at character position $pos of $name.")
}
if (paramTypes.size != 1) {
throw NotSerializableException("Expected only one type, but got $paramTypes")
throw AMQPNoTypeNotSerializableException("Expected only one type, but got $paramTypes")
}
return paramTypes[0]
}
@ -70,7 +77,7 @@ class DeserializedParameterizedType(private val rawType: Class<*>, private val p
if (!typeName.isEmpty()) {
types += makeType(typeName, cl)
} else if (needAType) {
throw NotSerializableException("Expected a type, not ','")
throw AMQPNoTypeNotSerializableException("Expected a type, not ','")
}
typeStart = pos
needAType = true
@ -80,7 +87,7 @@ class DeserializedParameterizedType(private val rawType: Class<*>, private val p
if (!typeName.isEmpty()) {
types += makeType(typeName, cl)
} else if (needAType) {
throw NotSerializableException("Expected a type, not '>'")
throw AMQPNoTypeNotSerializableException("Expected a type, not '>'")
}
return pos
} else {
@ -90,11 +97,11 @@ class DeserializedParameterizedType(private val rawType: Class<*>, private val p
if (params[pos].isWhitespace()) {
typeStart = ++pos
} else if (!needAType) {
throw NotSerializableException("Not expecting a type")
throw AMQPNoTypeNotSerializableException("Not expecting a type")
} else if (params[pos] == '?') {
pos++
} else if (!params[pos].isJavaIdentifierStart()) {
throw NotSerializableException("Invalid character at start of type: ${params[pos]}")
throw AMQPNoTypeNotSerializableException("Invalid character at start of type: ${params[pos]}")
} else {
pos++
}
@ -105,12 +112,13 @@ class DeserializedParameterizedType(private val rawType: Class<*>, private val p
} else if (!skippingWhitespace && (params[pos] == '.' || params[pos].isJavaIdentifierPart())) {
pos++
} else {
throw NotSerializableException("Invalid character ${params[pos]} in middle of type $params at idx $pos")
throw AMQPNoTypeNotSerializableException(
"Invalid character ${params[pos]} in middle of type $params at idx $pos")
}
}
}
}
throw NotSerializableException("Missing close generics '>'")
throw AMQPNoTypeNotSerializableException("Missing close generics '>'")
}
private fun makeType(typeName: String, cl: ClassLoader): Type {
@ -124,9 +132,15 @@ class DeserializedParameterizedType(private val rawType: Class<*>, private val p
return DeserializedParameterizedType(makeType(rawTypeName, cl) as Class<*>, args.toTypedArray(), null)
}
private fun parseTypeParams(params: String, startPos: Int, paramTypes: MutableList<Type>, cl: ClassLoader, depth: Int): Int {
private fun parseTypeParams(
params: String,
startPos: Int,
paramTypes: MutableList<Type>,
cl: ClassLoader,
depth: Int
): Int {
if (depth == MAX_DEPTH) {
throw NotSerializableException("Maximum depth of nested generics reached: $depth")
throw AMQPNoTypeNotSerializableException("Maximum depth of nested generics reached: $depth")
}
return startPos + parseTypeList(params.substring(startPos), paramTypes, cl, depth)
}

View File

@ -112,7 +112,9 @@ class EnumEvolutionSerializer(
.associateBy({ it.value.toInt() }, { conversions[it.name] }))
if (ordinals.filterNot { serialisedOrds[it.value] == it.key }.isNotEmpty()) {
throw NotSerializableException("Constants have been reordered, additions must be appended to the end")
throw AMQPNotSerializableException(
new.type,
"Constants have been reordered, additions must be appended to the end")
}
return EnumEvolutionSerializer(new.type, factory, conversions, ordinals)
@ -125,7 +127,7 @@ class EnumEvolutionSerializer(
val enumName = (obj as List<*>)[0] as String
if (enumName !in conversions) {
throw NotSerializableException("No rule to evolve enum constant $type::$enumName")
throw AMQPNotSerializableException(type, "No rule to evolve enum constant $type::$enumName")
}
return type.asClass()!!.enumConstants[ordinals[conversions[enumName]]!!]

View File

@ -37,8 +37,9 @@ class EnumSerializer(declaredType: Type, declaredClass: Class<*>, factory: Seria
val fromOrd = type.asClass()!!.enumConstants[enumOrd] as Enum<*>?
if (enumName != fromOrd?.name) {
throw NotSerializableException("Deserializing obj as enum $type with value $enumName.$enumOrd but "
+ "ordinality has changed")
throw AMQPNotSerializableException(
type,
"Deserializing obj as enum $type with value $enumName.$enumOrd but ordinality has changed")
}
return fromOrd
}
@ -46,7 +47,7 @@ class EnumSerializer(declaredType: Type, declaredClass: Class<*>, factory: Seria
override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput,
context: SerializationContext, debugIndent: Int
) {
if (obj !is Enum<*>) throw NotSerializableException("Serializing $obj as enum when it isn't")
if (obj !is Enum<*>) throw AMQPNotSerializableException(type, "Serializing $obj as enum when it isn't")
data.withDescribed(typeNotation.descriptor) {
withList {

View File

@ -31,7 +31,8 @@ data class Envelope(val obj: Any?, val schema: Schema, val transformsSchema: Tra
fun get(data: Data): Envelope {
val describedType = data.`object` as DescribedType
if (describedType.descriptor != DESCRIPTOR) {
throw NotSerializableException("Unexpected descriptor ${describedType.descriptor}, should be $DESCRIPTOR.")
throw AMQPNoTypeNotSerializableException(
"Unexpected descriptor ${describedType.descriptor}, should be $DESCRIPTOR.")
}
val list = describedType.described as List<*>
@ -40,7 +41,8 @@ data class Envelope(val obj: Any?, val schema: Schema, val transformsSchema: Tra
val transformSchema: Any? = when (list.size) {
ENVELOPE_WITHOUT_TRANSFORMS -> null
ENVELOPE_WITH_TRANSFORMS -> list[TRANSFORMS_SCHEMA_IDX]
else -> throw NotSerializableException("Malformed list, bad length of ${list.size} (should be 2 or 3)")
else -> throw AMQPNoTypeNotSerializableException(
"Malformed list, bad length of ${list.size} (should be 2 or 3)")
}
return newInstance(listOf(list[BLOB_IDX], Schema.get(list[SCHEMA_IDX]!!),
@ -57,7 +59,8 @@ data class Envelope(val obj: Any?, val schema: Schema, val transformsSchema: Tra
val transformSchema = when (list.size) {
ENVELOPE_WITHOUT_TRANSFORMS -> TransformsSchema.newInstance(null)
ENVELOPE_WITH_TRANSFORMS -> list[TRANSFORMS_SCHEMA_IDX] as TransformsSchema
else -> throw NotSerializableException("Malformed list, bad length of ${list.size} (should be 2 or 3)")
else -> throw AMQPNoTypeNotSerializableException(
"Malformed list, bad length of ${list.size} (should be 2 or 3)")
}
return Envelope(list[BLOB_IDX], list[SCHEMA_IDX] as Schema, transformSchema)

View File

@ -32,8 +32,8 @@ abstract class EvolutionSerializer(
clazz: Type,
factory: SerializerFactory,
protected val oldReaders: Map<String, OldParam>,
override val kotlinConstructor: KFunction<Any>?) : ObjectSerializer(clazz, factory) {
override val kotlinConstructor: KFunction<Any>?
) : ObjectSerializer(clazz, factory) {
// explicitly set as empty to indicate it's unused by this type of serializer
override val propertySerializers = PropertySerializersEvolution()
@ -137,7 +137,8 @@ abstract class EvolutionSerializer(
if ((isKotlin && !it.value.type.isMarkedNullable)
|| (!isKotlin && isJavaPrimitive(it.value.type.jvmErasure.java))
) {
throw NotSerializableException(
throw AMQPNotSerializableException(
new.type,
"New parameter \"${it.value.name}\" is mandatory, should be nullable for evolution " +
"to work, isKotlinClass=$isKotlin type=${it.value.type}")
}
@ -180,7 +181,7 @@ abstract class EvolutionSerializer(
OldParam(-1, PropertySerializer.make(it.name, EvolutionPropertyReader(),
it.getTypeAsClass(factory.classloader), factory))
} catch (e: ClassNotFoundException) {
throw NotSerializableException(e.message)
throw AMQPNotSerializableException(new.type, e.message ?: "")
}
}

View File

@ -100,11 +100,12 @@ class SerializerFingerPrinter : FingerPrinter {
return if ((type in alreadySeen)
&& (type !== SerializerFactory.AnyType)
&& (type !is TypeVariable<*>)
&& (type !is WildcardType)) {
&& (type !is WildcardType)
) {
hasher.putUnencodedChars(ALREADY_SEEN_HASH)
} else {
alreadySeen += type
try {
ifThrowsAppend<Hasher>({ type.typeName }) {
when (type) {
is ParameterizedType -> {
// Hash the rawType + params
@ -158,8 +159,8 @@ class SerializerFingerPrinter : FingerPrinter {
} else {
hasher.fingerprintWithCustomSerializerOrElse(factory!!, type, type) {
if (type.kotlinObjectInstance != null) {
// TODO: name collision is too likely for kotlin objects, we need to introduce some reference
// to the CorDapp but maybe reference to the JAR in the short term.
// TODO: name collision is too likely for kotlin objects, we need to introduce some
// reference to the CorDapp but maybe reference to the JAR in the short term.
hasher.putUnencodedChars(type.name)
} else {
fingerprintForObject(type, type, alreadySeen, hasher, factory!!, debugIndent + 1)
@ -172,12 +173,8 @@ class SerializerFingerPrinter : FingerPrinter {
fingerprintForType(type.genericComponentType, contextType, alreadySeen,
hasher, debugIndent + 1).putUnencodedChars(ARRAY_HASH)
}
else -> throw NotSerializableException("Don't know how to hash")
else -> throw AMQPNotSerializableException(type, "Don't know how to hash")
}
} catch (e: NotSerializableException) {
val msg = "${e.message} -> $type"
logger.error(msg, e)
throw NotSerializableException(msg)
}
}
}
@ -191,7 +188,7 @@ class SerializerFingerPrinter : FingerPrinter {
debugIndent: Int = 0): Hasher {
// Hash the class + properties + interfaces
val name = type.asClass()?.name
?: throw NotSerializableException("Expected only Class or ParameterizedType but found $type")
?: throw AMQPNotSerializableException(type, "Expected only Class or ParameterizedType but found $type")
propertiesForSerialization(constructorForDeserialization(type), contextType ?: type, factory)
.serializationOrder

View File

@ -40,7 +40,7 @@ class MapSerializer(private val declaredType: ParameterizedType, factory: Serial
))
private fun findConcreteType(clazz: Class<*>): MapCreationFunction {
return supportedTypes[clazz] ?: throw NotSerializableException("Unsupported map type $clazz.")
return supportedTypes[clazz] ?: throw AMQPNotSerializableException(clazz, "Unsupported map type $clazz.")
}
fun deriveParameterizedType(declaredType: Type, declaredClass: Class<*>, actualClass: Class<*>?): ParameterizedType {
@ -54,7 +54,8 @@ class MapSerializer(private val declaredType: ParameterizedType, factory: Serial
return deriveParametrizedType(declaredType, mapClass)
}
throw NotSerializableException("Cannot derive map type for declaredType: '$declaredType', declaredClass: '$declaredClass', actualClass: '$actualClass'")
throw AMQPNotSerializableException(declaredType,
"Cannot derive map type for declaredType=\"$declaredType\", declaredClass=\"$declaredClass\", actualClass=\"$actualClass\"")
}
private fun deriveParametrizedType(declaredType: Type, collectionClass: Class<out Map<*, *>>): ParameterizedType =
@ -88,7 +89,8 @@ class MapSerializer(private val declaredType: ParameterizedType, factory: Serial
type: Type,
output: SerializationOutput,
context: SerializationContext,
debugIndent: Int) = ifThrowsAppend({ declaredType.typeName }) {
debugIndent: Int
) = ifThrowsAppend({ declaredType.typeName }) {
obj.javaClass.checkSupportedMapType()
// Write described
data.withDescribed(typeNotation.descriptor) {

View File

@ -63,7 +63,7 @@ open class ObjectSerializer(val clazz: Type, factory: SerializerFactory) : AMQPS
if (propertySerializers.size != javaConstructor?.parameterCount &&
javaConstructor?.parameterCount ?: 0 > 0
) {
throw NotSerializableException("Serialization constructor for class $type expects "
throw AMQPNotSerializableException(type, "Serialization constructor for class $type expects "
+ "${javaConstructor?.parameterCount} parameters but we have ${propertySerializers.size} "
+ "properties to serialize.")
}
@ -86,7 +86,7 @@ open class ObjectSerializer(val clazz: Type, factory: SerializerFactory) : AMQPS
context: SerializationContext): Any = ifThrowsAppend({ clazz.typeName }) {
if (obj is List<*>) {
if (obj.size > propertySerializers.size) {
throw NotSerializableException("Too many properties in described type $typeName")
throw AMQPNotSerializableException(type, "Too many properties in described type $typeName")
}
return if (propertySerializers.byConstructor) {
@ -95,7 +95,7 @@ open class ObjectSerializer(val clazz: Type, factory: SerializerFactory) : AMQPS
readObjectBuildViaSetters(obj, schemas, input, context)
}
} else {
throw NotSerializableException("Body of described type is unexpected $obj")
throw AMQPNotSerializableException(type, "Body of described type is unexpected $obj")
}
}
@ -120,7 +120,8 @@ open class ObjectSerializer(val clazz: Type, factory: SerializerFactory) : AMQPS
context: SerializationContext): Any = ifThrowsAppend({ clazz.typeName }) {
logger.trace { "Calling setter based construction for ${clazz.typeName}" }
val instance: Any = javaConstructor?.newInstanceUnwrapped() ?: throw NotSerializableException(
val instance: Any = javaConstructor?.newInstanceUnwrapped() ?: throw AMQPNotSerializableException(
type,
"Failed to instantiate instance of object $clazz")
// read the properties out of the serialised form, since we're invoking the setters the order we
@ -149,13 +150,15 @@ open class ObjectSerializer(val clazz: Type, factory: SerializerFactory) : AMQPS
logger.trace { "Calling constructor: '$javaConstructor' with properties '$properties'" }
if (properties.size != javaConstructor?.parameterCount) {
throw NotSerializableException("Serialization constructor for class $type expects "
throw AMQPNotSerializableException(type, "Serialization constructor for class $type expects "
+ "${javaConstructor?.parameterCount} parameters but we have ${properties.size} "
+ "serialized properties.")
}
return javaConstructor?.newInstanceUnwrapped(*properties.toTypedArray())
?: throw NotSerializableException("Attempt to deserialize an interface: $clazz. Serialized form is invalid.")
?: throw AMQPNotSerializableException(
type,
"Attempt to deserialize an interface: $clazz. Serialized form is invalid.")
}
private fun <T> Constructor<T>.newInstanceUnwrapped(vararg args: Any?): T {

View File

@ -191,7 +191,7 @@ abstract class PropertySerializers(
is PropertyAccessorGetterSetter -> PropertySerializersSetter(serializationOrder)
null -> PropertySerializersNoProperties()
else -> {
throw NotSerializableException("Unknown Property Accessor type, cannot create set")
throw AMQPNoTypeNotSerializableException("Unknown Property Accessor type, cannot create set")
}
}
}

View File

@ -220,7 +220,7 @@ data class RestrictedType(override val name: String,
fun get(describedType: DescribedType): RestrictedType {
if (describedType.descriptor != DESCRIPTOR) {
throw NotSerializableException("Unexpected descriptor ${describedType.descriptor}.")
throw AMQPNoTypeNotSerializableException("Unexpected descriptor ${describedType.descriptor}.")
}
val list = describedType.described as List<*>
return newInstance(listOf(list[0], list[1], list[2], list[3], Descriptor.get(list[4]!!), (list[5] as List<*>).map { Choice.get(it!!) }))
@ -297,7 +297,7 @@ data class ReferencedObject(private val refCounter: Int) : DescribedType {
fun get(obj: Any): ReferencedObject {
val describedType = obj as DescribedType
if (describedType.descriptor != DESCRIPTOR) {
throw NotSerializableException("Unexpected descriptor ${describedType.descriptor}.")
throw AMQPNoTypeNotSerializableException("Unexpected descriptor ${describedType.descriptor}.")
}
return newInstance(describedType.described)
}

View File

@ -41,18 +41,24 @@ fun constructorForDeserialization(type: Type): KFunction<Any>? {
for (kotlinConstructor in kotlinConstructors) {
if (preferredCandidate == null && kotlinConstructors.size == 1) {
preferredCandidate = kotlinConstructor
} else if (preferredCandidate == null && kotlinConstructors.size == 2 && hasDefault && kotlinConstructor.parameters.isNotEmpty()) {
} else if (preferredCandidate == null &&
kotlinConstructors.size == 2 &&
hasDefault &&
kotlinConstructor.parameters.isNotEmpty()
) {
preferredCandidate = kotlinConstructor
} else if (kotlinConstructor.findAnnotation<ConstructorForDeserialization>() != null) {
if (annotatedCount++ > 0) {
throw NotSerializableException("More than one constructor for $clazz is annotated with @ConstructorForDeserialization.")
throw AMQPNotSerializableException(
type,
"More than one constructor for $clazz is annotated with @ConstructorForDeserialization.")
}
preferredCandidate = kotlinConstructor
}
}
return preferredCandidate?.apply { isAccessible = true }
?: throw NotSerializableException("No constructor for deserialization found for $clazz.")
?: throw AMQPNotSerializableException(type, "No constructor for deserialization found for $clazz.")
} else {
return null
}
@ -249,13 +255,14 @@ internal fun <T : Any> propertiesForSerializationFromConstructor(
// with the case we don't know the case of A when the parameter doesn't match a property
// but has a getter
val matchingProperty = classProperties[name] ?: classProperties[name.capitalize()]
?: throw NotSerializableException(
?: throw AMQPNotSerializableException(type,
"Constructor parameter - \"$name\" - doesn't refer to a property of \"$clazz\"")
// If the property has a getter we'll use that to retrieve it's value from the instance, if it doesn't
// *for *know* we switch to a reflection based method
val propertyReader = if (matchingProperty.getter != null) {
val getter = matchingProperty.getter ?: throw NotSerializableException(
val getter = matchingProperty.getter ?: throw AMQPNotSerializableException(
type,
"Property has no getter method for - \"$name\" - of \"$clazz\". If using Java and the parameter name"
+ "looks anonymous, check that you have the -parameters option specified in the "
+ "Java compiler. Alternately, provide a proxy serializer "
@ -263,7 +270,8 @@ internal fun <T : Any> propertiesForSerializationFromConstructor(
val returnType = resolveTypeVariables(getter.genericReturnType, type)
if (!constructorParamTakesReturnTypeOfGetter(returnType, getter.genericReturnType, param.value)) {
throw NotSerializableException(
throw AMQPNotSerializableException(
type,
"Property - \"$name\" - has type \"$returnType\" on \"$clazz\" but differs from constructor " +
"parameter type \"${param.value.type.javaType}\"")
}
@ -271,7 +279,8 @@ internal fun <T : Any> propertiesForSerializationFromConstructor(
Pair(PublicPropertyReader(getter), returnType)
} else {
val field = classProperties[name]!!.field
?: throw NotSerializableException("No property matching constructor parameter named - \"$name\" - " +
?: throw AMQPNotSerializableException(type,
"No property matching constructor parameter named - \"$name\" - " +
"of \"$clazz\". If using Java, check that you have the -parameters option specified " +
"in the Java compiler. Alternately, provide a proxy serializer " +
"(SerializationCustomSerializer) if recompiling isn't an option")
@ -304,22 +313,28 @@ fun propertiesForSerializationFromSetters(
if (getter == null || setter == null) return@forEach
if (setter.parameterCount != 1) {
throw NotSerializableException("Defined setter for parameter ${property.value.field?.name} " +
"takes too many arguments")
throw AMQPNotSerializableException(
type,
"Defined setter for parameter ${property.value.field?.name} takes too many arguments")
}
val setterType = setter.genericParameterTypes[0]!!
if ((property.value.field != null) &&
(!(TypeToken.of(property.value.field?.genericType!!).isSupertypeOf(setterType)))) {
throw NotSerializableException("Defined setter for parameter ${property.value.field?.name} " +
(!(TypeToken.of(property.value.field?.genericType!!).isSupertypeOf(setterType)))
) {
throw AMQPNotSerializableException(
type,
"Defined setter for parameter ${property.value.field?.name} " +
"takes parameter of type $setterType yet underlying type is " +
"${property.value.field?.genericType!!}")
}
// Make sure the getter returns the same type (within inheritance bounds) the setter accepts.
if (!(TypeToken.of(getter.genericReturnType).isSupertypeOf(setterType))) {
throw NotSerializableException("Defined setter for parameter ${property.value.field?.name} " +
throw AMQPNotSerializableException(
type,
"Defined setter for parameter ${property.value.field?.name} " +
"takes parameter of type $setterType yet the defined getter returns a value of type " +
"${getter.returnType} [${getter.genericReturnType}]")
}
@ -436,7 +451,9 @@ fun resolveTypeVariables(actualType: Type, contextType: Type?): Type {
SerializerFactory.AnyType
} else if (bounds.size == 1) {
resolveTypeVariables(bounds[0], contextType)
} else throw NotSerializableException("Got bounded type $actualType but only support single bound.")
} else throw AMQPNotSerializableException(
actualType,
"Got bounded type $actualType but only support single bound.")
} else {
resolvedType
}
@ -478,7 +495,7 @@ internal fun Type.asParameterizedType(): ParameterizedType {
return when (this) {
is Class<*> -> this.asParameterizedType()
is ParameterizedType -> this
else -> throw NotSerializableException("Don't know how to convert to ParameterizedType")
else -> throw AMQPNotSerializableException(this, "Don't know how to convert to ParameterizedType")
}
}
@ -499,32 +516,13 @@ internal enum class CommonPropertyNames {
IncludeInternalInfo,
}
/**
* Utility function which helps tracking the path in the object graph when exceptions are thrown.
* Since there might be a chain of nested calls it is useful to record which part of the graph caused an issue.
* Path information is added to the message of the exception being thrown.
*/
internal inline fun <T> ifThrowsAppend(strToAppendFn: () -> String, block: () -> T): T {
try {
return block()
} catch (th: Throwable) {
th.setMessage("${strToAppendFn()} -> ${th.message}")
throw th
}
}
/**
* Not a public property so will have to use reflection
*/
private fun Throwable.setMessage(newMsg: String) {
val detailMessageField = Throwable::class.java.getDeclaredField("detailMessage")
detailMessageField.isAccessible = true
detailMessageField.set(this, newMsg)
}
fun ClassWhitelist.requireWhitelisted(type: Type) {
if (!this.isWhitelisted(type.asClass()!!)) {
throw NotSerializableException("Class \"$type\" is not on the whitelist or annotated with @CordaSerializable.")
throw AMQPNotSerializableException(
type,
"Class \"$type\" is not on the whitelist or annotated with @CordaSerializable.")
}
}

View File

@ -4,6 +4,7 @@ import net.corda.core.KeepForDJVM
import net.corda.core.serialization.SerializationContext
import net.corda.core.serialization.SerializationEncoding
import net.corda.core.serialization.SerializedBytes
import net.corda.core.utilities.contextLogger
import net.corda.serialization.internal.CordaSerializationEncoding
import net.corda.serialization.internal.SectionId
import net.corda.serialization.internal.byteArrayOutput
@ -31,6 +32,10 @@ open class SerializationOutput @JvmOverloads constructor(
internal val serializerFactory: SerializerFactory,
private val encoding: SerializationEncoding? = null
) {
companion object {
private val logger = contextLogger()
}
private val objectHistory: MutableMap<Any, Int> = IdentityHashMap()
private val serializerHistory: MutableSet<AMQPSerializer<*>> = LinkedHashSet()
internal val schemaHistory: MutableSet<TypeNotation> = LinkedHashSet()
@ -44,11 +49,16 @@ open class SerializationOutput @JvmOverloads constructor(
fun <T : Any> serialize(obj: T, context: SerializationContext): SerializedBytes<T> {
try {
return _serialize(obj, context)
} catch (amqp: AMQPNotSerializableException) {
amqp.log("Serialize", logger)
throw NotSerializableException(amqp.mitigation)
} finally {
andFinally()
}
}
// NOTE: No need to handle AMQPNotSerializableExceptions here as this is an internal
// only / testing function and it doesn't matter if they escape
@Throws(NotSerializableException::class)
fun <T : Any> serializeAndReturnSchema(obj: T, context: SerializationContext): BytesAndSchemas<T> {
try {

View File

@ -118,7 +118,8 @@ open class SerializerFactory(
// can be useful to enable but will be *extremely* chatty if you do
logger.trace { "Get Serializer for $actualClass ${declaredType.typeName}" }
val declaredClass = declaredType.asClass() ?: throw NotSerializableException(
val declaredClass = declaredType.asClass() ?: throw AMQPNotSerializableException(
declaredType,
"Declared types of $declaredType are not supported.")
val actualType: Type = inferTypeVariables(actualClass, declaredClass, declaredType) ?: declaredType
@ -205,9 +206,12 @@ open class SerializerFactory(
val endType = DeserializedParameterizedType(actualClass, actualClass.typeParameters)
val resolvedType = resolver.resolveType(endType)
resolvedType
} else throw NotSerializableException("No inheritance path between actual $actualClass and declared $declaredType.")
} else throw AMQPNotSerializableException(declaredType,
"No inheritance path between actual $actualClass and declared $declaredType.")
} else actualClass
} else throw NotSerializableException("Found object of type $actualClass in a property expecting $declaredType")
} else throw AMQPNotSerializableException(
declaredType,
"Found object of type $actualClass in a property expecting $declaredType")
}
// Stop when reach declared type or return null if we don't find it.
@ -317,7 +321,7 @@ open class SerializerFactory(
// prevent carpenter exceptions escaping into the world, convert things into a nice
// NotSerializableException for when this escapes over the wire
throw NotSerializableException(e.name)
NotSerializableException(e.name)
}
processSchema(schemaAndDescriptor, true)
}
@ -342,7 +346,9 @@ open class SerializerFactory(
private fun processCompositeType(typeNotation: CompositeType): AMQPSerializer<Any> {
// TODO: class loader logic, and compare the schema.
val type = typeForName(typeNotation.name, classloader)
return get(type.asClass() ?: throw NotSerializableException("Unable to build composite type for $type"), type)
return get(
type.asClass() ?: throw AMQPNotSerializableException(type, "Unable to build composite type for $type"),
type)
}
private fun makeClassSerializer(
@ -354,13 +360,15 @@ open class SerializerFactory(
if (clazz.isSynthetic) {
// Explicitly ban synthetic classes, we have no way of recreating them when deserializing. This also
// captures Lambda expressions and other anonymous functions
throw NotSerializableException(type.typeName)
throw AMQPNotSerializableException(
type,
"Serializer does not support synthetic classes")
} else if (isPrimitive(clazz)) {
AMQPPrimitiveSerializer(clazz)
} else {
findCustomSerializer(clazz, declaredType) ?: run {
if (onlyCustomSerializers) {
throw NotSerializableException("Only allowing custom serializers")
throw AMQPNotSerializableException(type, "Only allowing custom serializers")
}
if (type.isArray()) {
// Don't need to check the whitelist since each element will come back through the whitelisting process.
@ -385,7 +393,6 @@ open class SerializerFactory(
}
private fun doFindCustomSerializer(key: CustomSerializersCacheKey): AMQPSerializer<Any>? {
val (clazz, declaredType) = key
// e.g. Imagine if we provided a Map serializer this way, then it won't work if the declared type is
@ -471,7 +478,7 @@ open class SerializerFactory(
is GenericArrayType -> "${nameForType(type.genericComponentType)}[]"
is WildcardType -> "?"
is TypeVariable<*> -> "?"
else -> throw NotSerializableException("Unable to render type $type to a string.")
else -> throw AMQPNotSerializableException(type, "Unable to render type $type to a string.")
}
private fun typeForName(name: String, classloader: ClassLoader): Type {
@ -482,7 +489,7 @@ open class SerializerFactory(
} else if (elementType is Class<*>) {
java.lang.reflect.Array.newInstance(elementType, 0).javaClass
} else {
throw NotSerializableException("Not able to deserialize array type: $name")
throw AMQPNoTypeNotSerializableException("Not able to deserialize array type: $name")
}
} else if (name.endsWith("[p]")) {
// There is no need to handle the ByteArray case as that type is coercible automatically
@ -496,7 +503,7 @@ open class SerializerFactory(
"double[p]" -> DoubleArray::class.java
"short[p]" -> ShortArray::class.java
"long[p]" -> LongArray::class.java
else -> throw NotSerializableException("Not able to deserialize array type: $name")
else -> throw AMQPNoTypeNotSerializableException("Not able to deserialize array type: $name")
}
} else {
DeserializedParameterizedType.make(name, classloader)

View File

@ -31,7 +31,7 @@ abstract class Transform : DescribedType {
val describedType = obj as DescribedType
if (describedType.descriptor != DESCRIPTOR) {
throw NotSerializableException("Unexpected descriptor ${describedType.descriptor}.")
throw AMQPNoTypeNotSerializableException("Unexpected descriptor ${describedType.descriptor}.")
}
return describedType.described
@ -221,7 +221,8 @@ data class TransformsSchema(val types: Map<String, EnumMap<TransformTypes, Mutab
// ignore them it feels like a good thing to alert the user to since this is
// more than likely a typo in their code so best make it an actual error
if (transforms.computeIfAbsent(transform.enum) { mutableListOf() }.any { t == it }) {
throw NotSerializableException(
throw AMQPNotSerializableException(
clazz,
"Repeated unique transformation annotation of type ${t.name}")
}

View File

@ -1,6 +1,9 @@
package net.corda.serialization.internal.amqp.custom
import net.corda.core.KeepForDJVM
import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.trace
import net.corda.serialization.internal.amqp.AMQPNotSerializableException
import net.corda.serialization.internal.amqp.CustomSerializer
import net.corda.serialization.internal.amqp.SerializerFactory
import net.corda.serialization.internal.amqp.custom.ClassSerializer.ClassProxy
@ -8,10 +11,36 @@ import net.corda.serialization.internal.amqp.custom.ClassSerializer.ClassProxy
/**
* A serializer for [Class] that uses [ClassProxy] proxy object to write out
*/
class ClassSerializer(factory: SerializerFactory) : CustomSerializer.Proxy<Class<*>, ClassSerializer.ClassProxy>(Class::class.java, ClassProxy::class.java, factory) {
override fun toProxy(obj: Class<*>): ClassProxy = ClassProxy(obj.name)
class ClassSerializer(
factory: SerializerFactory
) : CustomSerializer.Proxy<Class<*>, ClassSerializer.ClassProxy>(
Class::class.java,
ClassProxy::class.java,
factory
) {
companion object {
private val logger = contextLogger()
}
override fun fromProxy(proxy: ClassProxy): Class<*> = Class.forName(proxy.className, true, factory.classloader)
override fun toProxy(obj: Class<*>): ClassProxy {
logger.trace { "serializer=custom, type=ClassSerializer, name=\"${obj.name}\", action=toProxy" }
return ClassProxy(obj.name)
}
override fun fromProxy(proxy: ClassProxy): Class<*> {
logger.trace { "serializer=custom, type=ClassSerializer, name=\"${proxy.className}\", action=fromProxy" }
return try {
Class.forName(proxy.className, true, factory.classloader)
} catch (e: ClassNotFoundException) {
throw AMQPNotSerializableException(
type,
"Could not instantiate ${proxy.className} - not on the classpath",
"${proxy.className} was not found by the node, check the Node containing the CorDapp that " +
"implements ${proxy.className} is loaded and on the Classpath",
mutableListOf(proxy.className))
}
}
@KeepForDJVM
data class ClassProxy(val className: String)

View File

@ -4,6 +4,7 @@ import com.google.common.collect.Maps;
import net.corda.core.serialization.SerializationContext;
import net.corda.core.serialization.SerializationFactory;
import net.corda.core.serialization.SerializedBytes;
import net.corda.serialization.internal.amqp.AMQPNotSerializableException;
import net.corda.serialization.internal.amqp.SchemaKt;
import net.corda.testing.core.SerializationEnvironmentRule;
import org.junit.Before;
@ -43,7 +44,7 @@ public final class ForbiddenLambdaSerializationTests {
assertThat(throwable)
.isNotNull()
.isInstanceOf(NotSerializableException.class)
.hasMessageContaining(getClass().getName());
.hasMessageContaining("Serializer does not support synthetic classes");
});
}
@ -61,7 +62,7 @@ public final class ForbiddenLambdaSerializationTests {
assertThat(throwable)
.isNotNull()
.isInstanceOf(NotSerializableException.class)
.hasMessageContaining(getClass().getName());
.hasMessageContaining("Serializer does not support synthetic classes");
});
}

View File

@ -0,0 +1,130 @@
package net.corda.serialization.internal.amqp
import org.assertj.core.api.Assertions
import org.junit.Test
import java.io.NotSerializableException
import kotlin.test.assertEquals
class AMQPExceptionsTests {
interface Runner {
fun run()
}
data class A<T : Runner>(val a: T, val throws: Boolean = false) : Runner {
override fun run() = ifThrowsAppend({ javaClass.name }) {
if (throws) {
throw AMQPNotSerializableException(A::class.java, "it went bang!")
} else {
a.run()
}
}
}
data class B<T : Runner>(val b: T, val throws: Boolean = false) : Runner {
override fun run() = ifThrowsAppend({ javaClass.name }) {
if (throws) {
throw NotSerializableException(javaClass.name)
} else {
b.run()
}
}
}
data class C<T : Runner>(val c: T, val throws: Boolean = false) : Runner {
override fun run() = ifThrowsAppend({ javaClass.name }) {
if (throws) {
throw NotSerializableException(javaClass.name)
} else {
c.run()
}
}
}
data class END(val throws: Boolean = false) : Runner {
override fun run() = ifThrowsAppend({ "END" }) {
if (throws) {
throw NotSerializableException(javaClass.name)
}
}
}
data class ENDAMQP(val throws: Boolean = false) : Runner {
override fun run() {
if (throws) {
throw AMQPNotSerializableException(javaClass, "End it all")
}
}
}
private val aName get() = A::class.java.name
private val bName get() = B::class.java.name
private val cName get() = C::class.java.name
private val eName get() = END::class.java.name
private val eaName get() = ENDAMQP::class.java.name
// if the exception is a normal not serializable exception we'll have manipulated the
// message
@Test
fun catchNotSerializable() {
fun catchAssert(msg: String, f: () -> Unit) {
Assertions.assertThatThrownBy { f() }
.isInstanceOf(NotSerializableException::class.java)
.hasMessageContaining(msg)
}
catchAssert("$aName -> END") {
A(END(true)).run()
}
catchAssert("$aName -> $aName -> END") {
A(A(END(true))).run()
}
catchAssert("$aName -> $bName -> END") {
A(B(END(true))).run()
}
catchAssert("$aName -> $bName -> $cName -> END") {
A(B(C(END(true)))).run()
}
}
// However, if its a shiny new AMQPNotSerializable one, we have cool new toys, so
// lets make sure those are set
@Test
fun catchAMQPNotSerializable() {
fun catchAssert(stack: List<String>, f: () -> Unit): AMQPNotSerializableException {
try {
f()
} catch (e: AMQPNotSerializableException) {
assertEquals(stack, e.classHierarchy)
return e
}
throw Exception("FAILED")
}
catchAssert(listOf(ENDAMQP::class.java.name, aName)) {
A(ENDAMQP(true)).run()
}.apply {
assertEquals(
"Serialization failed direction=\"up\", type=\"$eaName\", msg=\"End it all\", " +
"ClassChain=\"$aName -> $eaName\"",
errorMessage("up"))
}
catchAssert(listOf(ENDAMQP::class.java.name, aName, aName)) {
A(A(ENDAMQP(true))).run()
}
catchAssert(listOf(ENDAMQP::class.java.name, bName, aName)) {
A(B(ENDAMQP(true))).run()
}
catchAssert(listOf(ENDAMQP::class.java.name, cName, bName, aName)) {
A(B(C(ENDAMQP(true)))).run()
}
}
}

View File

@ -6,7 +6,6 @@ import net.corda.serialization.internal.amqp.testutils.*
import net.corda.testing.common.internal.ProjectStructure.projectRootDir
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatThrownBy
import org.assertj.core.api.Condition
import org.junit.Test
import java.io.NotSerializableException
import java.net.URI

View File

@ -6,7 +6,6 @@ import net.corda.core.serialization.SerializedBytes
import net.corda.serialization.internal.amqp.testutils.TestSerializationOutput
import net.corda.serialization.internal.amqp.testutils.deserialize
import net.corda.serialization.internal.amqp.testutils.deserializeAndReturnEnvelope
import net.corda.serialization.internal.amqp.testutils.serialize
import net.corda.serialization.internal.amqp.testutils.serializeAndReturnSchema
import net.corda.serialization.internal.amqp.testutils.testDefaultFactoryNoEvolution
import net.corda.serialization.internal.amqp.testutils.testName

View File

@ -123,7 +123,7 @@ class PrivatePropertyTests {
SerializationOutput(factory).serialize(c1)
}.isInstanceOf(NotSerializableException::class.java).hasMessageContaining(
"Defined setter for parameter a takes parameter of type class java.lang.String " +
"yet underlying type is int ")
"yet underlying type is int")
}
@Test