Merge remote-tracking branch 'open/master' into kat-merge-20180626

This commit is contained in:
Katelyn Baker 2018-06-26 12:27:24 +01:00
commit 7a2c15fc5e
18 changed files with 245 additions and 53 deletions

View File

@ -100,7 +100,6 @@ buildscript {
ext.jcabi_manifests_version = '1.1' ext.jcabi_manifests_version = '1.1'
ext.picocli_version = '3.0.0' ext.picocli_version = '3.0.0'
ext.deterministic_rt_version = '1.0-SNAPSHOT'
// Name of the IntelliJ SDK created for the deterministic Java rt.jar. // Name of the IntelliJ SDK created for the deterministic Java rt.jar.
// ext.deterministic_idea_sdk = '1.8 (Deterministic)' // ext.deterministic_idea_sdk = '1.8 (Deterministic)'

View File

@ -19,7 +19,7 @@ class TransactionVerificationRequest(val wtxToVerify: SerializedBytes<WireTransa
val deps = dependencies.map { it.deserialize() }.associateBy(WireTransaction::id) val deps = dependencies.map { it.deserialize() }.associateBy(WireTransaction::id)
val attachments = attachments.map { it.deserialize<Attachment>() } val attachments = attachments.map { it.deserialize<Attachment>() }
val attachmentMap = attachments.mapNotNull { it as? MockContractAttachment } val attachmentMap = attachments.mapNotNull { it as? MockContractAttachment }
.associateBy(Attachment::id, { ContractAttachment(it, it.contract, uploader=TEST_UPLOADER) }) .associateBy(Attachment::id) { ContractAttachment(it, it.contract, uploader=TEST_UPLOADER) }
val contractAttachmentMap = emptyMap<ContractClassName, ContractAttachment>() val contractAttachmentMap = emptyMap<ContractClassName, ContractAttachment>()
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
return wtxToVerify.deserialize().toLedgerTransaction( return wtxToVerify.deserialize().toLedgerTransaction(

View File

@ -57,6 +57,7 @@ task makeJdk(type: Exec) {
task runtimeJar(type: Jar, dependsOn: makeJdk) { task runtimeJar(type: Jar, dependsOn: makeJdk) {
baseName 'deterministic-rt' baseName 'deterministic-rt'
inputs.dir "libs"
from(zipTree("libs/rt.jar")) from(zipTree("libs/rt.jar"))
from(zipTree("libs/jce.jar")) from(zipTree("libs/jce.jar"))

View File

@ -104,6 +104,24 @@ This is possible, but slightly tricky to configure because IntelliJ will not rec
Gradle be configured to use the Project's SDK. Gradle be configured to use the Project's SDK.
Creating the Deterministic SDK Creating the Deterministic SDK
Gradle creates a suitable JDK image in the project's ``jdk8u-deterministic/jdk`` directory, and you can
configure IntelliJ to use this location for this SDK. However, you should also be aware that IntelliJ SDKs
are available for *all* projects to use.
To create this JDK image, execute the following:
.. code-block:: bash
$ gradlew jdk8u-deterministic:copyJdk
..
Now select ``File/Project Structure/Platform Settings/SDKs`` and add a new JDK SDK with the
``jdk8u-deterministic/jdk`` directory as its home. Rename this SDK to something like "1.8 (Deterministic)".
This *should* be sufficient for IntelliJ. However, if IntelliJ realises that this SDK does not contain a
full JDK then you will need to configure the new SDK by hand:
#. Create a JDK Home directory with the following contents: #. Create a JDK Home directory with the following contents:
``jre/lib/rt.jar`` ``jre/lib/rt.jar``
@ -120,18 +138,6 @@ Creating the Deterministic SDK
.. ..
.. note:: Gradle already creates this JDK in the project's ``jdk8u-deterministic/jdk`` directory, and you could
configure IntelliJ to use this location as well. However, you should also be aware that IntelliJ SDKs
are available for *all* projects to use.
To create this deterministic JDK image, execute the following:
.. code-block:: bash
$ gradlew jdk8u-deterministic:copyJdk
..
#. While IntelliJ is *not* running, locate the ``config/options/jdk.table.xml`` file in IntelliJ's configuration #. While IntelliJ is *not* running, locate the ``config/options/jdk.table.xml`` file in IntelliJ's configuration
directory. Add an empty ``<jdk>`` section to this file: directory. Add an empty ``<jdk>`` section to this file:
@ -148,10 +154,10 @@ Creating the Deterministic SDK
.. ..
#. Open IntelliJ and select ``File/Project Structure/Platform Settings/SDKs``. The "1.8 (Deterministic)" SDK should #. Open IntelliJ and select ``File/Project Structure/Platform Settings/SDKs``. The "1.8 (Deterministic)" SDK
now be present. Select it and then click on the ``Classpath`` tab. Press the "Add" / "Plus" button to add should now be present. Select it and then click on the ``Classpath`` tab. Press the "Add" / "Plus" button to
``rt.jar`` to the SDK's classpath. Then select the ``Annotations`` tab and include the same JAR(s) as the other add ``rt.jar`` to the SDK's classpath. Then select the ``Annotations`` tab and include the same JAR(s) as
SDKs. the other SDKs.
Configuring the Corda Project Configuring the Corda Project
#. Open the root ``build.gradle`` file and define this property: #. Open the root ``build.gradle`` file and define this property:

View File

@ -8,23 +8,39 @@ repositories {
} }
ext { ext {
jdk_home = "$projectDir/jdk" jdk_home = "$projectDir/jdk".toString()
rt_jar = "$jdk_home/jre/lib/rt.jar".toString() rt_jar = "$jdk_home/jre/lib/rt.jar".toString()
} }
configurations { configurations {
jdk jdk.resolutionStrategy {
cacheChangingModulesFor 0, 'seconds'
}
} }
dependencies { dependencies {
jdk "net.corda:deterministic-rt:$deterministic_rt_version:api" // Ensure everyone uses the latest SNAPSHOT.
jdk "net.corda:deterministic-rt:latest.integration:api"
} }
task copyJdk(type: Copy) { task copyJdk(type: Copy) {
outputs.dir jdk_home
from(configurations.jdk.asPath) { from(configurations.jdk.asPath) {
rename 'deterministic-rt-(.*).jar', 'rt.jar' rename 'deterministic-rt-(.*).jar', 'rt.jar'
} }
into "$jdk_home/jre/lib" into "$jdk_home/jre/lib"
doLast {
def eol = System.getProperty('line.separator')
file("$jdk_home/release").write "JAVA_VERSION=\"1.8.0_172\"$eol"
mkdir "$jdk_home/bin"
file("$jdk_home/bin/javac").with {
write "#!/bin/sh\necho \"javac 1.8.0_172\"\n"
setExecutable true, false
return
}
}
} }
assemble.dependsOn copyJdk assemble.dependsOn copyJdk

View File

@ -26,6 +26,7 @@ import net.corda.node.NodeRegistrationOption
import net.corda.node.SerialFilter import net.corda.node.SerialFilter
import net.corda.node.VersionInfo import net.corda.node.VersionInfo
import net.corda.node.defaultSerialFilter import net.corda.node.defaultSerialFilter
import net.corda.node.internal.cordapp.MultipleCordappsForFlowException
import net.corda.node.services.config.NodeConfiguration import net.corda.node.services.config.NodeConfiguration
import net.corda.node.services.config.NodeConfigurationImpl import net.corda.node.services.config.NodeConfigurationImpl
import net.corda.node.services.config.shouldStartLocalShell import net.corda.node.services.config.shouldStartLocalShell
@ -148,9 +149,13 @@ open class NodeStartup(val args: Array<String>) {
try { try {
cmdlineOptions.baseDirectory.createDirectories() cmdlineOptions.baseDirectory.createDirectories()
startNode(conf, versionInfo, startTime, cmdlineOptions) startNode(conf, versionInfo, startTime, cmdlineOptions)
} catch (e: DatabaseMigrationException) { } catch (e: DatabaseMigrationException) {
logger.error(e.message) logger.error(e.message)
return false return false
} catch (e: MultipleCordappsForFlowException) {
logger.error(e.message)
return false
} catch (e: CouldNotCreateDataSourceException) { } catch (e: CouldNotCreateDataSourceException) {
logger.error(e.message, e.cause) logger.error(e.message, e.cause)
return false return false

View File

@ -80,7 +80,7 @@ class CordappLoader private constructor(private val cordappJarPaths: List<Restri
cordapps.flatMap { corDapp -> corDapp.allFlows.map { flow -> flow to corDapp } } cordapps.flatMap { corDapp -> corDapp.allFlows.map { flow -> flow to corDapp } }
.groupBy { it.first } .groupBy { it.first }
.mapValues { .mapValues {
require(it.value.size == 1) { "There are multiple CorDapp jars on the classpath for flow ${it.value.first().first.name}: ${it.value.map { it.second.name }.joinToString()}." } if(it.value.size > 1) { throw MultipleCordappsForFlowException("There are multiple CorDapp JARs on the classpath for flow ${it.value.first().first.name}: [ ${it.value.joinToString { it.second.name }} ].") }
it.value.single().second it.value.single().second
} }
} }
@ -401,3 +401,8 @@ class CordappLoader private constructor(private val cordappJarPaths: List<Restri
} }
} }
} }
/**
* Thrown when scanning CorDapps.
*/
class MultipleCordappsForFlowException(message: String) : Exception(message)

View File

@ -51,6 +51,9 @@ open class CordappProviderImpl(private val cordappLoader: CordappLoader,
private fun verifyInstalledCordapps(attachmentStorage: AttachmentStorage) { private fun verifyInstalledCordapps(attachmentStorage: AttachmentStorage) {
// This will invoke the lazy flowCordappMap property, thus triggering the MultipleCordappsForFlow check.
cordappLoader.flowCordappMap
if (whitelistedContractImplementations.isEmpty()) { if (whitelistedContractImplementations.isEmpty()) {
log.warn("The network parameters don't specify any whitelisted contract implementations. Please contact your zone operator. See https://docs.corda.net/network-map.html") log.warn("The network parameters don't specify any whitelisted contract implementations. Please contact your zone operator. See https://docs.corda.net/network-map.html")
return return

View File

@ -24,6 +24,7 @@ import java.lang.reflect.Type
import kotlin.reflect.KFunction import kotlin.reflect.KFunction
import kotlin.reflect.full.findAnnotation import kotlin.reflect.full.findAnnotation
import kotlin.reflect.jvm.javaType import kotlin.reflect.jvm.javaType
import kotlin.reflect.jvm.jvmErasure
/** /**
@ -127,12 +128,29 @@ abstract class EvolutionSerializer(
readersAsSerialized: Map<String, OldParam>): AMQPSerializer<Any> { readersAsSerialized: Map<String, OldParam>): AMQPSerializer<Any> {
val constructorArgs = arrayOfNulls<Any?>(constructor.parameters.size) val constructorArgs = arrayOfNulls<Any?>(constructor.parameters.size)
// Java doesn't care about nullability unless it's a primitive in which
// case it can't be referenced. Unfortunately whilst Kotlin does apply
// Nullability annotations we cannot use them here as they aren't
// retained at runtime so we cannot rely on the absence of
// any particular NonNullable annotation type to indicate cross
// compiler nullability
val isKotlin = (new.type.javaClass.declaredAnnotations.any {
it.annotationClass.qualifiedName == "kotlin.Metadata"
})
constructor.parameters.withIndex().forEach { constructor.parameters.withIndex().forEach {
readersAsSerialized[it.value.name!!]?.apply { if ((readersAsSerialized[it.value.name!!] ?.apply { this.resultsIndex = it.index }) == null) {
this.resultsIndex = it.index // If there is no value in the byte stream to map to the parameter of the constructor
} ?: if (!it.value.type.isMarkedNullable) { // this is ok IFF it's a Kotlin class and the parameter is non nullable OR
// its a Java class and the parameter is anything but an unboxed primitive.
// Otherwise we throw the error and leave
if ((isKotlin && !it.value.type.isMarkedNullable)
|| (!isKotlin && isJavaPrimitive(it.value.type.jvmErasure.java))
) {
throw NotSerializableException( throw NotSerializableException(
"New parameter ${it.value.name} is mandatory, should be nullable for evolution to work") "New parameter \"${it.value.name}\" is mandatory, should be nullable for evolution " +
"to work, isKotlinClass=$isKotlin type=${it.value.type}")
}
} }
} }
return EvolutionSerializerViaConstructor(new.type, factory, readersAsSerialized, constructor, constructorArgs) return EvolutionSerializerViaConstructor(new.type, factory, readersAsSerialized, constructor, constructorArgs)
@ -161,8 +179,10 @@ abstract class EvolutionSerializer(
* @param factory the [SerializerFactory] associated with the serialization * @param factory the [SerializerFactory] associated with the serialization
* context this serializer is being built for * context this serializer is being built for
*/ */
fun make(old: CompositeType, new: ObjectSerializer, fun make(old: CompositeType,
factory: SerializerFactory): AMQPSerializer<Any> { new: ObjectSerializer,
factory: SerializerFactory
): AMQPSerializer<Any> {
// The order in which the properties were serialised is important and must be preserved // The order in which the properties were serialised is important and must be preserved
val readersAsSerialized = LinkedHashMap<String, OldParam>() val readersAsSerialized = LinkedHashMap<String, OldParam>()
old.fields.forEach { old.fields.forEach {

View File

@ -550,3 +550,18 @@ fun hasCordaSerializable(type: Class<*>): Boolean {
|| type.interfaces.any(::hasCordaSerializable) || type.interfaces.any(::hasCordaSerializable)
|| (type.superclass != null && hasCordaSerializable(type.superclass)) || (type.superclass != null && hasCordaSerializable(type.superclass))
} }
fun isJavaPrimitive(type: Class<*>) = type in JavaPrimitiveTypes.primativeTypes
private object JavaPrimitiveTypes {
val primativeTypes = hashSetOf<Class<*>>(
Boolean::class.java,
Char::class.java,
Byte::class.java,
Short::class.java,
Int::class.java,
Long::class.java,
Float::class.java,
Double::class.java,
Void::class.java)
}

View File

@ -88,7 +88,7 @@ open class SerializerFactory(
lenientCarpenter: Boolean = false, lenientCarpenter: Boolean = false,
evolutionSerializerGetter: EvolutionSerializerGetterBase = EvolutionSerializerGetter(), evolutionSerializerGetter: EvolutionSerializerGetterBase = EvolutionSerializerGetter(),
fingerPrinter: FingerPrinter = SerializerFingerPrinter() fingerPrinter: FingerPrinter = SerializerFingerPrinter()
) : this(whitelist, ClassCarpenterImpl(classLoader, whitelist, lenientCarpenter), evolutionSerializerGetter, fingerPrinter) ) : this(whitelist, ClassCarpenterImpl(whitelist, classLoader, lenientCarpenter), evolutionSerializerGetter, fingerPrinter)
init { init {
fingerPrinter.setOwner(this) fingerPrinter.setOwner(this)

View File

@ -114,10 +114,10 @@ interface ClassCarpenter {
* Equals/hashCode methods are not yet supported. * Equals/hashCode methods are not yet supported.
*/ */
@DeleteForDJVM @DeleteForDJVM
class ClassCarpenterImpl(cl: ClassLoader = Thread.currentThread().contextClassLoader, class ClassCarpenterImpl @JvmOverloads constructor (override val whitelist: ClassWhitelist,
override val whitelist: ClassWhitelist, cl: ClassLoader = Thread.currentThread().contextClassLoader,
private val lenient: Boolean = false) : ClassCarpenter { private val lenient: Boolean = false
) : ClassCarpenter {
// TODO: Generics. // TODO: Generics.
// TODO: Sandbox the generated code when a security manager is in use. // TODO: Sandbox the generated code when a security manager is in use.
// TODO: Generate equals/hashCode. // TODO: Generate equals/hashCode.

View File

@ -0,0 +1,100 @@
package net.corda.serialization.internal.amqp;
import kotlin.Suppress;
import net.corda.core.serialization.SerializedBytes;
import net.corda.serialization.internal.amqp.testutils.AMQPTestUtilsKt;
import net.corda.serialization.internal.amqp.testutils.TestSerializationContext;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import java.io.*;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
public class JavaEvolutionTests {
@Rule
public final ExpectedException exception = ExpectedException.none();
// Class as it was when it was serialized and written to disk. Uncomment
// if the test referencing the object needs regenerating.
/*
static class N1 {
private String word;
public N1(String word) { this.word = word; }
public String getWord() { return word; }
}
*/
// Class as it exists now with the newly added element
static class N1 {
private String word;
private Integer wibble;
public N1(String word, Integer wibble) {
this.word = word;
this.wibble = wibble;
}
public String getWord() { return word; }
public Integer getWibble() { return wibble; }
}
// Class as it was when it was serialized and written to disk. Uncomment
// if the test referencing the object needs regenerating.
/*
static class N2 {
private String word;
public N2(String word) { this.word = word; }
public String getWord() { return word; }
}
*/
// Class as it exists now with the newly added element
@SuppressWarnings("unused")
static class N2 {
private String word;
private float wibble;
public N2(String word, float wibble) {
this.word = word;
this.wibble = wibble;
}
public String getWord() { return word; }
public float getWibble() { return wibble; }
}
SerializerFactory factory = AMQPTestUtilsKt.testDefaultFactory();
@Test
public void testN1AddsNullableInt() throws IOException {
// Uncomment to regenerate the base state of the test
/*
N1 n = new N1("potato");
AMQPTestUtilsKt.writeTestResource(this, new SerializationOutput(factory).serialize(
n, TestSerializationContext.testSerializationContext));
*/
N1 n2 = new DeserializationInput(factory).deserialize(
new SerializedBytes<>(AMQPTestUtilsKt.readTestResource(this)),
N1.class,
TestSerializationContext.testSerializationContext);
assertEquals(n2.getWord(), "potato");
assertNull(n2.getWibble());
}
@Test
public void testN2AddsPrimitive() throws IOException {
// Uncomment to regenerate the base state of the test
/*
N2 n = new N2("This is only a test");
AMQPTestUtilsKt.writeTestResource(this, new SerializationOutput(factory).serialize(
n, TestSerializationContext.testSerializationContext));
*/
exception.expect(NotSerializableException.class);
new DeserializationInput(factory).deserialize(
new SerializedBytes<>(AMQPTestUtilsKt.readTestResource(this)),
N2.class,
TestSerializationContext.testSerializationContext);
}
}

View File

@ -108,7 +108,7 @@ class StaticInitialisationOfSerializedObjectTest {
// Version of a serializer factory that will allow the class carpenter living on the // Version of a serializer factory that will allow the class carpenter living on the
// factory to have a different whitelist applied to it than the factory // factory to have a different whitelist applied to it than the factory
class TestSerializerFactory(wl1: ClassWhitelist, wl2: ClassWhitelist) : class TestSerializerFactory(wl1: ClassWhitelist, wl2: ClassWhitelist) :
SerializerFactory(wl1, ClassCarpenterImpl(ClassLoader.getSystemClassLoader(), wl2)) SerializerFactory(wl1, ClassCarpenterImpl(wl2, ClassLoader.getSystemClassLoader()))
// This time have the serialization factory and the carpenter use different whitelists // This time have the serialization factory and the carpenter use different whitelists
@Test @Test

View File

@ -89,7 +89,7 @@ class CarpenterExceptionTests {
// carpent that class up. However, when looking at the fields specified as properties of that class // carpent that class up. However, when looking at the fields specified as properties of that class
// we set the class loader of the ClassCarpenter to reject one of them, resulting in a CarpentryError // we set the class loader of the ClassCarpenter to reject one of them, resulting in a CarpentryError
// which we then want the code to wrap in a NotSerializeableException // which we then want the code to wrap in a NotSerializeableException
val cc = ClassCarpenterImpl(TestClassLoader(listOf(C2::class.jvmName)), AllWhitelist) val cc = ClassCarpenterImpl(AllWhitelist, TestClassLoader(listOf(C2::class.jvmName)))
val factory = TestFactory(cc) val factory = TestFactory(cc)
Assertions.assertThatThrownBy { Assertions.assertThatThrownBy {

View File

@ -12,9 +12,15 @@
* This build.gradle exists to package Node Explorer as an executable fat jar. * This build.gradle exists to package Node Explorer as an executable fat jar.
*/ */
apply plugin: 'us.kirchmeier.capsule' apply plugin: 'us.kirchmeier.capsule'
apply plugin: 'net.corda.plugins.publish-utils'
apply plugin: 'com.jfrog.artifactory'
description 'Node Explorer' description 'Node Explorer'
configurations {
runtimeArtifacts.extendsFrom runtime
}
repositories { repositories {
mavenLocal() mavenLocal()
mavenCentral() mavenCentral()
@ -57,3 +63,19 @@ task buildExplorerJAR(type: FatCapsule, dependsOn: project(':tools:explorer').co
} }
build.dependsOn buildExplorerJAR build.dependsOn buildExplorerJAR
artifacts {
runtimeArtifacts buildExplorerJAR
publish buildExplorerJAR {
classifier ""
}
}
jar {
classifier "ignore"
}
publish {
disableDefaultJar = true
name 'corda-tools-explorer'
}