CORDA-540: Expose and fix AMQP serialization problem with UniquenessException (#1470)

This commit is contained in:
Viktor Kolomeyko 2017-09-11 18:43:26 +01:00 committed by GitHub
parent 1565b395b6
commit 64963d587c
6 changed files with 38 additions and 11 deletions

View File

@ -1,5 +1,6 @@
package net.corda.core.node.services
import net.corda.core.CordaException
import net.corda.core.contracts.StateRef
import net.corda.core.crypto.SecureHash
import net.corda.core.identity.Party
@ -32,5 +33,4 @@ interface UniquenessProvider {
data class ConsumingTx(val id: SecureHash, val inputIndex: Int, val requestingParty: Party)
}
@CordaSerializable
class UniquenessException(val error: UniquenessProvider.Conflict) : Exception()
class UniquenessException(val error: UniquenessProvider.Conflict) : CordaException(UniquenessException::class.java.name)

View File

@ -0,0 +1,26 @@
package net.corda.core.serialization
import net.corda.core.contracts.StateRef
import net.corda.core.crypto.SecureHash
import net.corda.core.node.services.UniquenessException
import net.corda.core.node.services.UniquenessProvider
import net.corda.testing.DUMMY_PARTY
import net.corda.testing.TestDependencyInjectionBase
import org.junit.Test
import kotlin.test.assertEquals
class UniquenessExceptionSerializationTest : TestDependencyInjectionBase() {
@Test
fun testSerializationRoundTrip() {
val txhash = SecureHash.randomSHA256()
val txHash2 = SecureHash.randomSHA256()
val stateHistory: Map<StateRef, UniquenessProvider.ConsumingTx> = mapOf(StateRef(txhash, 0) to UniquenessProvider.ConsumingTx(txHash2, 1, DUMMY_PARTY))
val conflict = UniquenessProvider.Conflict(stateHistory)
val instance = UniquenessException(conflict)
val instanceOnTheOtherSide = instance.serialize().deserialize()
assertEquals(instance.error, instanceOnTheOtherSide.error)
}
}

View File

@ -37,6 +37,7 @@ class MapSerializer(private val declaredType: ParameterizedType, factory: Serial
}
fun deriveParameterizedType(declaredType: Type, declaredClass: Class<*>, actualClass: Class<*>?): ParameterizedType {
declaredClass.checkSupportedMapType()
if(supportedTypes.containsKey(declaredClass)) {
// Simple case - it is already known to be a map.
@Suppress("UNCHECKED_CAST")
@ -71,7 +72,7 @@ class MapSerializer(private val declaredType: ParameterizedType, factory: Serial
}
override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput) {
obj.javaClass.checkNotUnsupportedHashMap()
obj.javaClass.checkSupportedMapType()
// Write described
data.withDescribed(typeNotation.descriptor) {
// Write map
@ -96,7 +97,7 @@ class MapSerializer(private val declaredType: ParameterizedType, factory: Serial
input.readObjectOrNull(entry.value, schema, declaredType.actualTypeArguments[1])
}
internal fun Class<*>.checkNotUnsupportedHashMap() {
internal fun Class<*>.checkSupportedMapType() {
if (HashMap::class.java.isAssignableFrom(this) && !LinkedHashMap::class.java.isAssignableFrom(this)) {
throw IllegalArgumentException(
"Map type $this is unstable under iteration. Suggested fix: use java.util.LinkedHashMap instead.")

View File

@ -75,7 +75,7 @@ class SerializerFactory(val whitelist: ClassWhitelist, cl: ClassLoader) {
(Map::class.java.isAssignableFrom(declaredClass) ||
(actualClass != null && Map::class.java.isAssignableFrom(actualClass))) -> {
val declaredTypeAmended= MapSerializer.deriveParameterizedType(declaredType, declaredClass, actualClass)
serializersByType.computeIfAbsent(declaredClass) {
serializersByType.computeIfAbsent(declaredTypeAmended) {
makeMapSerializer(declaredTypeAmended)
}
}
@ -288,7 +288,7 @@ class SerializerFactory(val whitelist: ClassWhitelist, cl: ClassLoader) {
private fun makeMapSerializer(declaredType: ParameterizedType): AMQPSerializer<Any> {
val rawType = declaredType.rawType as Class<*>
rawType.checkNotUnsupportedHashMap()
rawType.checkSupportedMapType()
return MapSerializer(declaredType, this)
}

View File

@ -26,6 +26,7 @@ class ThrowableSerializer(factory: SerializerFactory) : CustomSerializer.Proxy<T
extraProperties[prop.name] = prop.readMethod!!.invoke(obj)
}
} catch(e: NotSerializableException) {
logger.warn("Unexpected exception", e)
}
obj.originalMessage
} else {

View File

@ -2,10 +2,9 @@ package net.corda.nodeapi.internal.serialization.amqp
import org.assertj.core.api.Assertions
import org.junit.Test
import java.io.NotSerializableException
import java.util.*
class DeserializeCollectionTests {
class DeserializeMapTests {
companion object {
/**
* If you want to see the schema encoded into the envelope after serialisation change this to true
@ -82,7 +81,7 @@ class DeserializeCollectionTests {
// expected to throw
Assertions.assertThatThrownBy { TestSerializationOutput(VERBOSE, sf).serialize(c) }
.isInstanceOf(NotSerializableException::class.java).hasMessageContaining("Cannot derive map type for declaredType")
.isInstanceOf(IllegalArgumentException::class.java).hasMessageContaining("Unable to serialise deprecated type class java.util.Hashtable. Suggested fix: prefer java.util.map implementations")
}
@Test
@ -92,7 +91,7 @@ class DeserializeCollectionTests {
// expect this to throw
Assertions.assertThatThrownBy { TestSerializationOutput(VERBOSE, sf).serialize(c) }
.isInstanceOf(NotSerializableException::class.java).hasMessageContaining("Cannot derive map type for declaredType")
.isInstanceOf(IllegalArgumentException::class.java).hasMessageContaining("Map type class java.util.HashMap is unstable under iteration. Suggested fix: use java.util.LinkedHashMap instead.")
}
@Test
@ -101,7 +100,7 @@ class DeserializeCollectionTests {
val c = C (WeakHashMap (mapOf("A" to 1, "B" to 2)))
Assertions.assertThatThrownBy { TestSerializationOutput(VERBOSE, sf).serialize(c) }
.isInstanceOf(NotSerializableException::class.java).hasMessageContaining("Cannot derive map type for declaredType")
.isInstanceOf(IllegalArgumentException::class.java).hasMessageContaining("Weak references with map types not supported. Suggested fix: use java.util.LinkedHashMap instead.")
}
@Test