From 72c6577b237db9331a27e31574c0f3e34ef96db8 Mon Sep 17 00:00:00 2001 From: Maksymilian Pawlak <120831+m4ksio@users.noreply.github.com> Date: Tue, 19 Sep 2017 10:10:24 +0100 Subject: [PATCH 01/94] Check array size before accessing --- constants.properties | 2 +- .../src/main/groovy/net/corda/plugins/Node.groovy | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/constants.properties b/constants.properties index 0c4e4ef5b1..0df5b1245b 100644 --- a/constants.properties +++ b/constants.properties @@ -1,4 +1,4 @@ -gradlePluginsVersion=0.16.4 +gradlePluginsVersion=0.16.5 kotlinVersion=1.1.4 guavaVersion=21.0 bouncycastleVersion=1.57 diff --git a/gradle-plugins/cordformation/src/main/groovy/net/corda/plugins/Node.groovy b/gradle-plugins/cordformation/src/main/groovy/net/corda/plugins/Node.groovy index 19e3a6b9b3..cd6d6bc66a 100644 --- a/gradle-plugins/cordformation/src/main/groovy/net/corda/plugins/Node.groovy +++ b/gradle-plugins/cordformation/src/main/groovy/net/corda/plugins/Node.groovy @@ -3,6 +3,7 @@ package net.corda.plugins import com.typesafe.config.* import net.corda.cordform.CordformNode import org.bouncycastle.asn1.x500.X500Name +import org.bouncycastle.asn1.x500.RDN import org.bouncycastle.asn1.x500.style.BCStyle import org.gradle.api.Project import java.nio.charset.StandardCharsets @@ -87,10 +88,13 @@ class Node extends CordformNode { } protected void rootDir(Path rootDir) { - def dirName + def dirName = name try { X500Name x500Name = new X500Name(name) - dirName = x500Name.getRDNs(BCStyle.O).getAt(0).getFirst().getValue().toString() + RDN[] o = x500Name.getRDNs(BCStyle.O) + if (o.length > 0) { + dirName = o.getAt(0).getFirst().getValue().toString() + } } catch(IllegalArgumentException ignore) { // Can't parse as an X500 name, use the full string dirName = name From 32e727264241b2dee9103f3062bc775931a131ad Mon Sep 17 00:00:00 2001 From: Maksymilian Pawlak <120831+m4ksio@users.noreply.github.com> Date: Tue, 10 Oct 2017 15:27:29 +0100 Subject: [PATCH 02/94] Review fixes --- .../src/main/groovy/net/corda/plugins/Node.groovy | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/gradle-plugins/cordformation/src/main/groovy/net/corda/plugins/Node.groovy b/gradle-plugins/cordformation/src/main/groovy/net/corda/plugins/Node.groovy index cb30030bd0..5049ee260f 100644 --- a/gradle-plugins/cordformation/src/main/groovy/net/corda/plugins/Node.groovy +++ b/gradle-plugins/cordformation/src/main/groovy/net/corda/plugins/Node.groovy @@ -95,10 +95,7 @@ class Node extends CordformNode { if (o.length > 0) { dirName = o.getAt(0).getFirst().getValue().toString() } - } catch(IllegalArgumentException ignore) { - // Can't parse as an X500 name, use the full string - dirName = name - } + } catch(IllegalArgumentException ignore) {} nodeDir = new File(rootDir.toFile(), dirName.replaceAll("\\s","")) } From cac3057877f597f4b2cc0172f579b010921d010a Mon Sep 17 00:00:00 2001 From: Viktor Kolomeyko Date: Tue, 17 Oct 2017 10:44:27 +0100 Subject: [PATCH 03/94] CORDA-540: Make Verifier work in AMQP mode (#1870) --- .ci/api-current.txt | 12 +++++++ .../net/corda/core/contracts/Attachment.kt | 2 ++ .../TransactionVerificationException.kt | 2 +- .../core/serialization/SerializationAPI.kt | 27 ++++++++++++++-- .../kotlin/net/corda/nodeapi/VerifierApi.kt | 23 ++++++++------ .../serialization/GeneratedAttachment.kt | 2 +- .../serialization/SerializationScheme.kt | 31 +++++++++++++++---- .../serialization/CordaClassResolverTests.kt | 11 ------- .../corda/testing/SerializationTestHelpers.kt | 6 +++- .../net/corda/verifier/VerifierTests.kt | 17 ++++++++++ .../kotlin/net/corda/verifier/Verifier.kt | 31 +++++++++++++------ 11 files changed, 123 insertions(+), 41 deletions(-) diff --git a/.ci/api-current.txt b/.ci/api-current.txt index 6e8e8df371..8104b88ab2 100644 --- a/.ci/api-current.txt +++ b/.ci/api-current.txt @@ -2435,6 +2435,17 @@ public final class net.corda.core.serialization.MissingAttachmentsException exte public (List) @org.jetbrains.annotations.NotNull public final List getIds() ## +public final class net.corda.core.serialization.ObjectWithCompatibleContext extends java.lang.Object + public (Object, net.corda.core.serialization.SerializationContext) + @org.jetbrains.annotations.NotNull public final Object component1() + @org.jetbrains.annotations.NotNull public final net.corda.core.serialization.SerializationContext component2() + @org.jetbrains.annotations.NotNull public final net.corda.core.serialization.ObjectWithCompatibleContext copy(Object, net.corda.core.serialization.SerializationContext) + public boolean equals(Object) + @org.jetbrains.annotations.NotNull public final net.corda.core.serialization.SerializationContext getContext() + @org.jetbrains.annotations.NotNull public final Object getObj() + public int hashCode() + public String toString() +## public final class net.corda.core.serialization.SerializationAPIKt extends java.lang.Object @org.jetbrains.annotations.NotNull public static final net.corda.core.serialization.SerializedBytes serialize(Object, net.corda.core.serialization.SerializationFactory, net.corda.core.serialization.SerializationContext) ## @@ -2476,6 +2487,7 @@ public abstract class net.corda.core.serialization.SerializationFactory extends public () public final Object asCurrent(kotlin.jvm.functions.Function1) @org.jetbrains.annotations.NotNull public abstract Object deserialize(net.corda.core.utilities.ByteSequence, Class, net.corda.core.serialization.SerializationContext) + @org.jetbrains.annotations.NotNull public abstract net.corda.core.serialization.ObjectWithCompatibleContext deserializeWithCompatibleContext(net.corda.core.utilities.ByteSequence, Class, net.corda.core.serialization.SerializationContext) @org.jetbrains.annotations.Nullable public final net.corda.core.serialization.SerializationContext getCurrentContext() @org.jetbrains.annotations.NotNull public final net.corda.core.serialization.SerializationContext getDefaultContext() @org.jetbrains.annotations.NotNull public abstract net.corda.core.serialization.SerializedBytes serialize(Object, net.corda.core.serialization.SerializationContext) diff --git a/core/src/main/kotlin/net/corda/core/contracts/Attachment.kt b/core/src/main/kotlin/net/corda/core/contracts/Attachment.kt index b55055e529..e55a91d546 100644 --- a/core/src/main/kotlin/net/corda/core/contracts/Attachment.kt +++ b/core/src/main/kotlin/net/corda/core/contracts/Attachment.kt @@ -2,6 +2,7 @@ package net.corda.core.contracts import net.corda.core.identity.Party import net.corda.core.internal.extractFile +import net.corda.core.serialization.CordaSerializable import java.io.FileNotFoundException import java.io.InputStream import java.io.OutputStream @@ -17,6 +18,7 @@ import java.util.jar.JarInputStream * - Legal documents * - Facts generated by oracles which might be reused a lot */ +@CordaSerializable interface Attachment : NamedByHash { fun open(): InputStream fun openAsJAR(): JarInputStream { diff --git a/core/src/main/kotlin/net/corda/core/contracts/TransactionVerificationException.kt b/core/src/main/kotlin/net/corda/core/contracts/TransactionVerificationException.kt index eb935965b3..26145b38a1 100644 --- a/core/src/main/kotlin/net/corda/core/contracts/TransactionVerificationException.kt +++ b/core/src/main/kotlin/net/corda/core/contracts/TransactionVerificationException.kt @@ -19,7 +19,7 @@ sealed class TransactionVerificationException(val txId: SecureHash, message: Str class ContractConstraintRejection(txId: SecureHash, contractClass: String) : TransactionVerificationException(txId, "Contract constraints failed for $contractClass", null) - class MissingAttachmentRejection(txId: SecureHash, contractClass: String) + class MissingAttachmentRejection(txId: SecureHash, val contractClass: String) : TransactionVerificationException(txId, "Contract constraints failed, could not find attachment for: $contractClass", null) class ContractCreationError(txId: SecureHash, contractClass: String, cause: Throwable) diff --git a/core/src/main/kotlin/net/corda/core/serialization/SerializationAPI.kt b/core/src/main/kotlin/net/corda/core/serialization/SerializationAPI.kt index a91636ddc7..2b5705a187 100644 --- a/core/src/main/kotlin/net/corda/core/serialization/SerializationAPI.kt +++ b/core/src/main/kotlin/net/corda/core/serialization/SerializationAPI.kt @@ -8,6 +8,8 @@ import net.corda.core.utilities.OpaqueBytes import net.corda.core.utilities.sequence import java.sql.Blob +data class ObjectWithCompatibleContext(val obj: T, val context: SerializationContext) + /** * An abstraction for serializing and deserializing objects, with support for versioning of the wire format via * a header / prefix in the bytes. @@ -22,6 +24,16 @@ abstract class SerializationFactory { */ abstract fun deserialize(byteSequence: ByteSequence, clazz: Class, context: SerializationContext): T + /** + * Deserialize the bytes in to an object, using the prefixed bytes to determine the format. + * + * @param byteSequence The bytes to deserialize, including a format header prefix. + * @param clazz The class or superclass or the object to be deserialized, or [Any] or [Object] if unknown. + * @param context A context that configures various parameters to deserialization. + * @return deserialized object along with [SerializationContext] to identify encoding used. + */ + abstract fun deserializeWithCompatibleContext(byteSequence: ByteSequence, clazz: Class, context: SerializationContext): ObjectWithCompatibleContext + /** * Serialize an object to bytes using the preferred serialization format version from the context. * @@ -87,6 +99,8 @@ abstract class SerializationFactory { } } +typealias VersionHeader = ByteSequence + /** * Parameters to serialization and deserialization. */ @@ -94,7 +108,7 @@ interface SerializationContext { /** * When serializing, use the format this header sequence represents. */ - val preferredSerializationVersion: ByteSequence + val preferredSerializationVersion: VersionHeader /** * The class loader to use for deserialization. */ @@ -147,7 +161,7 @@ interface SerializationContext { /** * Helper method to return a new context based on this context but with serialization using the format this header sequence represents. */ - fun withPreferredSerializationVersion(versionHeader: ByteSequence): SerializationContext + fun withPreferredSerializationVersion(versionHeader: VersionHeader): SerializationContext /** * The use case that we are serializing for, since it influences the implementations chosen. @@ -174,6 +188,15 @@ inline fun ByteSequence.deserialize(serializationFactory: Seri return serializationFactory.deserialize(this, T::class.java, context) } +/** + * Additionally returns [SerializationContext] which was used for encoding. + * It might be helpful to know [SerializationContext] to use the same encoding in the reply. + */ +inline fun ByteSequence.deserializeWithCompatibleContext(serializationFactory: SerializationFactory = SerializationFactory.defaultFactory, + context: SerializationContext = serializationFactory.defaultContext): ObjectWithCompatibleContext { + return serializationFactory.deserializeWithCompatibleContext(this, T::class.java, context) +} + /** * Convenience extension method for deserializing SerializedBytes with type matching, utilising the defaults. */ diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/VerifierApi.kt b/node-api/src/main/kotlin/net/corda/nodeapi/VerifierApi.kt index eee653b30f..ca9f03cc07 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/VerifierApi.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/VerifierApi.kt @@ -1,8 +1,8 @@ package net.corda.nodeapi -import net.corda.core.serialization.deserialize -import net.corda.core.serialization.serialize +import net.corda.core.serialization.* import net.corda.core.transactions.LedgerTransaction +import net.corda.core.utilities.sequence import org.apache.activemq.artemis.api.core.SimpleString import org.apache.activemq.artemis.api.core.client.ClientMessage import org.apache.activemq.artemis.reader.MessageUtil @@ -20,12 +20,15 @@ object VerifierApi { val responseAddress: SimpleString ) { companion object { - fun fromClientMessage(message: ClientMessage): VerificationRequest { - return VerificationRequest( + fun fromClientMessage(message: ClientMessage): ObjectWithCompatibleContext { + val bytes = ByteArray(message.bodySize).apply { message.bodyBuffer.readBytes(this) } + val bytesSequence = bytes.sequence() + val (transaction, context) = bytesSequence.deserializeWithCompatibleContext() + val request = VerificationRequest( message.getLongProperty(VERIFICATION_ID_FIELD_NAME), - ByteArray(message.bodySize).apply { message.bodyBuffer.readBytes(this) }.deserialize(), - MessageUtil.getJMSReplyTo(message) - ) + transaction, + MessageUtil.getJMSReplyTo(message)) + return ObjectWithCompatibleContext(request, context) } } @@ -49,11 +52,11 @@ object VerifierApi { } } - fun writeToClientMessage(message: ClientMessage) { + fun writeToClientMessage(message: ClientMessage, context: SerializationContext) { message.putLongProperty(VERIFICATION_ID_FIELD_NAME, verificationId) if (exception != null) { - message.putBytesProperty(RESULT_EXCEPTION_FIELD_NAME, exception.serialize().bytes) + message.putBytesProperty(RESULT_EXCEPTION_FIELD_NAME, exception.serialize(context = context).bytes) } } } -} +} \ No newline at end of file diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/GeneratedAttachment.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/GeneratedAttachment.kt index e42f18ef4b..82601202e6 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/GeneratedAttachment.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/GeneratedAttachment.kt @@ -3,6 +3,6 @@ package net.corda.nodeapi.internal.serialization import net.corda.core.crypto.sha256 import net.corda.core.internal.AbstractAttachment -class GeneratedAttachment(bytes: ByteArray) : AbstractAttachment({ bytes }) { +class GeneratedAttachment(val bytes: ByteArray) : AbstractAttachment({ bytes }) { override val id = bytes.sha256() } diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/SerializationScheme.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/SerializationScheme.kt index 1e7c257a1d..a6be235f88 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/SerializationScheme.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/SerializationScheme.kt @@ -19,6 +19,7 @@ import net.corda.core.serialization.* import net.corda.core.utilities.ByteSequence import net.corda.core.utilities.OpaqueBytes import net.corda.nodeapi.internal.AttachmentsClassLoader +import org.slf4j.LoggerFactory import java.io.ByteArrayOutputStream import java.io.NotSerializableException import java.util.* @@ -37,7 +38,7 @@ object NotSupportedSerializationScheme : SerializationScheme { override fun serialize(obj: T, context: SerializationContext): SerializedBytes = doThrow() } -data class SerializationContextImpl(override val preferredSerializationVersion: ByteSequence, +data class SerializationContextImpl(override val preferredSerializationVersion: VersionHeader, override val deserializationClassLoader: ClassLoader, override val whitelist: ClassWhitelist, override val properties: Map, @@ -88,36 +89,54 @@ data class SerializationContextImpl(override val preferredSerializationVersion: }) } - override fun withPreferredSerializationVersion(versionHeader: ByteSequence) = copy(preferredSerializationVersion = versionHeader) + override fun withPreferredSerializationVersion(versionHeader: VersionHeader) = copy(preferredSerializationVersion = versionHeader) } private const val HEADER_SIZE: Int = 8 +fun ByteSequence.obtainHeaderSignature(): VersionHeader = take(HEADER_SIZE).copy() + open class SerializationFactoryImpl : SerializationFactory() { private val creator: List = Exception().stackTrace.asList() private val registeredSchemes: MutableCollection = Collections.synchronizedCollection(mutableListOf()) + private val logger = LoggerFactory.getLogger(javaClass) + // TODO: This is read-mostly. Probably a faster implementation to be found. private val schemes: ConcurrentHashMap, SerializationScheme> = ConcurrentHashMap() - private fun schemeFor(byteSequence: ByteSequence, target: SerializationContext.UseCase): SerializationScheme { + private fun schemeFor(byteSequence: ByteSequence, target: SerializationContext.UseCase): Pair { // truncate sequence to 8 bytes, and make sure it's a copy to avoid holding onto large ByteArrays - return schemes.computeIfAbsent(byteSequence.take(HEADER_SIZE).copy() to target) { + val lookupKey = byteSequence.obtainHeaderSignature() to target + val scheme = schemes.computeIfAbsent(lookupKey) { registeredSchemes .filter { scheme -> scheme.canDeserializeVersion(it.first, it.second) } .forEach { return@computeIfAbsent it } + logger.warn("Cannot find serialization scheme for: $lookupKey, registeredSchemes are: $registeredSchemes") NotSupportedSerializationScheme } + return scheme to lookupKey.first } @Throws(NotSerializableException::class) override fun deserialize(byteSequence: ByteSequence, clazz: Class, context: SerializationContext): T { - return asCurrent { withCurrentContext(context) { schemeFor(byteSequence, context.useCase).deserialize(byteSequence, clazz, context) } } + return asCurrent { withCurrentContext(context) { schemeFor(byteSequence, context.useCase).first.deserialize(byteSequence, clazz, context) } } + } + + @Throws(NotSerializableException::class) + override fun deserializeWithCompatibleContext(byteSequence: ByteSequence, clazz: Class, context: SerializationContext): ObjectWithCompatibleContext { + return asCurrent { + withCurrentContext(context) { + val (scheme, versionHeader) = schemeFor(byteSequence, context.useCase) + val deserializedObject = scheme.deserialize(byteSequence, clazz, context) + ObjectWithCompatibleContext(deserializedObject, context.withPreferredSerializationVersion(versionHeader)) + } + } } override fun serialize(obj: T, context: SerializationContext): SerializedBytes { - return asCurrent { withCurrentContext(context) { schemeFor(context.preferredSerializationVersion, context.useCase).serialize(obj, context) } } + return asCurrent { withCurrentContext(context) { schemeFor(context.preferredSerializationVersion, context.useCase).first.serialize(obj, context) } } } fun registerScheme(scheme: SerializationScheme) { diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/CordaClassResolverTests.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/CordaClassResolverTests.kt index e684ef1f29..cdd54295a6 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/CordaClassResolverTests.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/CordaClassResolverTests.kt @@ -10,7 +10,6 @@ import com.nhaarman.mockito_kotlin.verify import com.nhaarman.mockito_kotlin.whenever import net.corda.core.node.services.AttachmentStorage import net.corda.core.serialization.* -import net.corda.core.utilities.ByteSequence import net.corda.nodeapi.internal.AttachmentsClassLoader import net.corda.nodeapi.internal.AttachmentsClassLoaderTests import net.corda.testing.node.MockAttachmentStorage @@ -108,16 +107,6 @@ class CordaClassResolverTests { val emptyMapClass = mapOf().javaClass } - val factory: SerializationFactory = object : SerializationFactory() { - override fun deserialize(byteSequence: ByteSequence, clazz: Class, context: SerializationContext): T { - TODO("not implemented") - } - - override fun serialize(obj: T, context: SerializationContext): SerializedBytes { - TODO("not implemented") - } - } - private val emptyWhitelistContext: SerializationContext = SerializationContextImpl(KryoHeaderV0_1, this.javaClass.classLoader, EmptyWhitelist, emptyMap(), true, SerializationContext.UseCase.P2P) private val allButBlacklistedContext: SerializationContext = SerializationContextImpl(KryoHeaderV0_1, this.javaClass.classLoader, AllButBlacklisted, emptyMap(), true, SerializationContext.UseCase.P2P) diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/SerializationTestHelpers.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/SerializationTestHelpers.kt index 891808e4b5..830316435e 100644 --- a/testing/test-utils/src/main/kotlin/net/corda/testing/SerializationTestHelpers.kt +++ b/testing/test-utils/src/main/kotlin/net/corda/testing/SerializationTestHelpers.kt @@ -103,6 +103,10 @@ class TestSerializationFactory : SerializationFactory() { return delegate!!.deserialize(byteSequence, clazz, context) } + override fun deserializeWithCompatibleContext(byteSequence: ByteSequence, clazz: Class, context: SerializationContext): ObjectWithCompatibleContext { + return delegate!!.deserializeWithCompatibleContext(byteSequence, clazz, context) + } + override fun serialize(obj: T, context: SerializationContext): SerializedBytes { return delegate!!.serialize(obj, context) } @@ -147,7 +151,7 @@ class TestSerializationContext : SerializationContext { return TestSerializationContext().apply { delegate = this@TestSerializationContext.delegate!!.withWhitelisted(clazz) } } - override fun withPreferredSerializationVersion(versionHeader: ByteSequence): SerializationContext { + override fun withPreferredSerializationVersion(versionHeader: VersionHeader): SerializationContext { return TestSerializationContext().apply { delegate = this@TestSerializationContext.delegate!!.withPreferredSerializationVersion(versionHeader) } } diff --git a/verifier/src/integration-test/kotlin/net/corda/verifier/VerifierTests.kt b/verifier/src/integration-test/kotlin/net/corda/verifier/VerifierTests.kt index 05bfc06673..aec877b3d8 100644 --- a/verifier/src/integration-test/kotlin/net/corda/verifier/VerifierTests.kt +++ b/verifier/src/integration-test/kotlin/net/corda/verifier/VerifierTests.kt @@ -1,5 +1,6 @@ package net.corda.verifier +import net.corda.core.contracts.TransactionVerificationException import net.corda.core.internal.concurrent.map import net.corda.core.internal.concurrent.transpose import net.corda.core.messaging.startFlow @@ -18,6 +19,7 @@ import net.corda.testing.driver.NetworkMapStartStrategy import org.junit.Test import java.util.* import java.util.concurrent.atomic.AtomicInteger +import kotlin.test.assertTrue import kotlin.test.assertNotNull class VerifierTests { @@ -50,6 +52,21 @@ class VerifierTests { } } + @Test + fun `single verification fails`() { + verifierDriver(extraCordappPackagesToScan = listOf("net.corda.finance.contracts")) { + val aliceFuture = startVerificationRequestor(ALICE.name) + // Generate transactions as per usual, but then remove attachments making transaction invalid. + val transactions = generateTransactions(1).map { it.copy(attachments = emptyList()) } + val alice = aliceFuture.get() + startVerifier(alice) + alice.waitUntilNumberOfVerifiers(1) + val verificationRejection = transactions.map { alice.verifyTransaction(it) }.transpose().get().single() + assertTrue { verificationRejection is TransactionVerificationException.MissingAttachmentRejection} + assertTrue { verificationRejection!!.message!!.contains("Contract constraints failed, could not find attachment") } + } + } + @Test fun `multiple verifiers work with requestor`() { verifierDriver { diff --git a/verifier/src/main/kotlin/net/corda/verifier/Verifier.kt b/verifier/src/main/kotlin/net/corda/verifier/Verifier.kt index aaf0c3ac73..22ff4e9f60 100644 --- a/verifier/src/main/kotlin/net/corda/verifier/Verifier.kt +++ b/verifier/src/main/kotlin/net/corda/verifier/Verifier.kt @@ -17,17 +17,16 @@ import net.corda.nodeapi.VerifierApi.VERIFICATION_REQUESTS_QUEUE_NAME import net.corda.nodeapi.config.NodeSSLConfiguration import net.corda.nodeapi.config.getValue import net.corda.nodeapi.internal.addShutdownHook -import net.corda.nodeapi.internal.serialization.AbstractKryoSerializationScheme -import net.corda.nodeapi.internal.serialization.KRYO_P2P_CONTEXT -import net.corda.nodeapi.internal.serialization.KryoHeaderV0_1 -import net.corda.nodeapi.internal.serialization.SerializationFactoryImpl +import net.corda.nodeapi.internal.serialization.* +import net.corda.nodeapi.internal.serialization.amqp.AmqpHeaderV1_0 +import net.corda.nodeapi.internal.serialization.amqp.SerializerFactory import org.apache.activemq.artemis.api.core.client.ActiveMQClient import java.nio.file.Path import java.nio.file.Paths data class VerifierConfiguration( override val baseDirectory: Path, - val config: Config + val config: Config // NB: This property is being used via reflection. ) : NodeSSLConfiguration { val nodeHostAndPort: NetworkHostAndPort by config override val keyStorePassword: String by config @@ -66,7 +65,7 @@ class Verifier { val consumer = session.createConsumer(VERIFICATION_REQUESTS_QUEUE_NAME) val replyProducer = session.createProducer() consumer.setMessageHandler { - val request = VerifierApi.VerificationRequest.fromClientMessage(it) + val (request, context) = VerifierApi.VerificationRequest.fromClientMessage(it) log.debug { "Received verification request with id ${request.verificationId}" } val error = try { request.transaction.verify() @@ -77,7 +76,7 @@ class Verifier { } val reply = session.createMessage(false) val response = VerifierApi.VerificationResponse(request.verificationId, error) - response.writeToClientMessage(reply) + response.writeToClientMessage(reply, context) replyProducer.send(request.responseAddress, reply) it.acknowledge() } @@ -88,13 +87,18 @@ class Verifier { private fun initialiseSerialization() { SerializationDefaults.SERIALIZATION_FACTORY = SerializationFactoryImpl().apply { - registerScheme(KryoVerifierSerializationScheme()) + registerScheme(KryoVerifierSerializationScheme) + registerScheme(AMQPVerifierSerializationScheme) } + /** + * Even though default context is set to Kryo P2P, the encoding will be adjusted depending on the incoming + * request received, see use of [context] in [main] method. + */ SerializationDefaults.P2P_CONTEXT = KRYO_P2P_CONTEXT } } - class KryoVerifierSerializationScheme : AbstractKryoSerializationScheme() { + private object KryoVerifierSerializationScheme : AbstractKryoSerializationScheme() { override fun canDeserializeVersion(byteSequence: ByteSequence, target: SerializationContext.UseCase): Boolean { return byteSequence == KryoHeaderV0_1 && target == SerializationContext.UseCase.P2P } @@ -102,4 +106,13 @@ class Verifier { override fun rpcClientKryoPool(context: SerializationContext) = throw UnsupportedOperationException() override fun rpcServerKryoPool(context: SerializationContext) = throw UnsupportedOperationException() } + + private object AMQPVerifierSerializationScheme : AbstractAMQPSerializationScheme() { + override fun canDeserializeVersion(byteSequence: ByteSequence, target: SerializationContext.UseCase): Boolean { + return (byteSequence == AmqpHeaderV1_0 && (target == SerializationContext.UseCase.P2P)) + } + + override fun rpcClientSerializerFactory(context: SerializationContext): SerializerFactory = throw UnsupportedOperationException() + override fun rpcServerSerializerFactory(context: SerializationContext): SerializerFactory = throw UnsupportedOperationException() + } } \ No newline at end of file From 6a00df322be13fdcc5c92358804772baeebd3a64 Mon Sep 17 00:00:00 2001 From: szymonsztuka Date: Tue, 17 Oct 2017 11:03:49 +0100 Subject: [PATCH 04/94] reference to finance module via not hardcoded group ID (#1515) * generic way to reference to group id when loading finance.jar via cordapp --- samples/bank-of-corda-demo/build.gradle | 6 +++--- samples/irs-demo/build.gradle | 6 +++--- samples/simm-valuation-demo/build.gradle | 8 ++++---- samples/trader-demo/build.gradle | 8 ++++---- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/samples/bank-of-corda-demo/build.gradle b/samples/bank-of-corda-demo/build.gradle index be0c49807d..d95d203fc4 100644 --- a/samples/bank-of-corda-demo/build.gradle +++ b/samples/bank-of-corda-demo/build.gradle @@ -55,7 +55,7 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) { notary = [validating : true] p2pPort 10002 rpcPort 10003 - cordapps = ["net.corda:finance:$corda_release_version"] + cordapps = ["$project.group:finance:$corda_release_version"] } node { name "O=BankOfCorda,L=London,C=GB" @@ -63,7 +63,7 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) { p2pPort 10005 rpcPort 10006 webPort 10007 - cordapps = ["net.corda:finance:$corda_release_version"] + cordapps = ["$project.group:finance:$corda_release_version"] rpcUsers = [ ['username' : "bankUser", 'password' : "test", @@ -79,7 +79,7 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) { p2pPort 10008 rpcPort 10009 webPort 10010 - cordapps = ["net.corda:finance:$corda_release_version"] + cordapps = ["$project.group:finance:$corda_release_version"] rpcUsers = [ ['username' : "bigCorpUser", 'password' : "test", diff --git a/samples/irs-demo/build.gradle b/samples/irs-demo/build.gradle index abbfaf3772..bcb20c214a 100644 --- a/samples/irs-demo/build.gradle +++ b/samples/irs-demo/build.gradle @@ -57,7 +57,7 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) { p2pPort 10002 rpcPort 10003 webPort 10004 - cordapps = ["net.corda:finance:$corda_release_version"] + cordapps = ["$project.group:finance:$corda_release_version"] useTestClock true } node { @@ -65,7 +65,7 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) { p2pPort 10005 rpcPort 10006 webPort 10007 - cordapps = ["net.corda:finance:$corda_release_version"] + cordapps = ["$project.group:finance:$corda_release_version"] useTestClock true } node { @@ -73,7 +73,7 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) { p2pPort 10008 rpcPort 10009 webPort 10010 - cordapps = ["net.corda:finance:$corda_release_version"] + cordapps = ["$project.group:finance:$corda_release_version"] useTestClock true } } diff --git a/samples/simm-valuation-demo/build.gradle b/samples/simm-valuation-demo/build.gradle index 5ad72562d2..832e2eb283 100644 --- a/samples/simm-valuation-demo/build.gradle +++ b/samples/simm-valuation-demo/build.gradle @@ -68,14 +68,14 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) { name "O=Notary Service,L=Zurich,C=CH" notary = [validating : true] p2pPort 10002 - cordapps = ["net.corda:finance:$corda_release_version"] + cordapps = ["$project.group:finance:$corda_release_version"] } node { name "O=Bank A,L=London,C=GB" p2pPort 10004 webPort 10005 rpcPort 10006 - cordapps = ["net.corda:finance:$corda_release_version"] + cordapps = ["$project.group:finance:$corda_release_version"] rpcUsers = ext.rpcUsers } node { @@ -83,7 +83,7 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) { p2pPort 10007 webPort 10008 rpcPort 10009 - cordapps = ["net.corda:finance:$corda_release_version"] + cordapps = ["$project.group:finance:$corda_release_version"] rpcUsers = ext.rpcUsers } node { @@ -91,7 +91,7 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) { p2pPort 10010 webPort 10011 rpcPort 10012 - cordapps = ["net.corda:finance:$corda_release_version"] + cordapps = ["$project.group:finance:$corda_release_version"] rpcUsers = ext.rpcUsers } } diff --git a/samples/trader-demo/build.gradle b/samples/trader-demo/build.gradle index e3d363e516..392ecd60ac 100644 --- a/samples/trader-demo/build.gradle +++ b/samples/trader-demo/build.gradle @@ -55,27 +55,27 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) { name "O=Notary Service,L=Zurich,C=CH" notary = [validating : true] p2pPort 10002 - cordapps = ["net.corda:finance:$corda_release_version"] + cordapps = ["$project.group:finance:$corda_release_version"] } node { name "O=Bank A,L=London,C=GB" p2pPort 10005 rpcPort 10006 - cordapps = ["net.corda:finance:$corda_release_version"] + cordapps = ["$project.group:finance:$corda_release_version"] rpcUsers = ext.rpcUsers } node { name "O=Bank B,L=New York,C=US" p2pPort 10008 rpcPort 10009 - cordapps = ["net.corda:finance:$corda_release_version"] + cordapps = ["$project.group:finance:$corda_release_version"] rpcUsers = ext.rpcUsers } node { name "O=BankOfCorda,L=New York,C=US" p2pPort 10011 rpcPort 10012 - cordapps = ["net.corda:finance:$corda_release_version"] + cordapps = ["$project.group:finance:$corda_release_version"] rpcUsers = ext.rpcUsers } } From 3372e49898b748ea3e9412796ddff80f4ef3030b Mon Sep 17 00:00:00 2001 From: Shams Asari Date: Fri, 13 Oct 2017 17:28:05 +0100 Subject: [PATCH 05/94] Fixed the node shell to work with the DataFeed class --- .../net/corda/node/shell/InteractiveShell.kt | 33 ++++++++----------- 1 file changed, 13 insertions(+), 20 deletions(-) diff --git a/node/src/main/kotlin/net/corda/node/shell/InteractiveShell.kt b/node/src/main/kotlin/net/corda/node/shell/InteractiveShell.kt index a671fdb1dd..75c4f85a5c 100644 --- a/node/src/main/kotlin/net/corda/node/shell/InteractiveShell.kt +++ b/node/src/main/kotlin/net/corda/node/shell/InteractiveShell.kt @@ -7,23 +7,20 @@ import com.fasterxml.jackson.databind.* import com.fasterxml.jackson.databind.module.SimpleModule import com.fasterxml.jackson.dataformat.yaml.YAMLFactory import com.google.common.io.Closeables +import net.corda.client.jackson.JacksonSupport +import net.corda.client.jackson.StringToMethodCallParser +import net.corda.core.CordaException import net.corda.core.concurrent.CordaFuture import net.corda.core.contracts.UniqueIdentifier import net.corda.core.flows.FlowInitiator import net.corda.core.flows.FlowLogic -import net.corda.core.internal.FlowStateMachine +import net.corda.core.internal.* import net.corda.core.internal.concurrent.OpenFuture import net.corda.core.internal.concurrent.openFuture -import net.corda.core.internal.createDirectories -import net.corda.core.internal.div -import net.corda.core.internal.write -import net.corda.core.internal.* import net.corda.core.messaging.CordaRPCOps +import net.corda.core.messaging.DataFeed import net.corda.core.messaging.StateMachineUpdate import net.corda.core.utilities.loggerFor -import net.corda.client.jackson.JacksonSupport -import net.corda.client.jackson.StringToMethodCallParser -import net.corda.core.CordaException import net.corda.node.internal.Node import net.corda.node.internal.StartedNode import net.corda.node.services.messaging.CURRENT_RPC_CONTEXT @@ -200,7 +197,7 @@ object InteractiveShell { } private fun createOutputMapper(factory: JsonFactory): ObjectMapper { - return JacksonSupport.createNonRpcMapper(factory).apply({ + return JacksonSupport.createNonRpcMapper(factory).apply { // Register serializers for stateful objects from libraries that are special to the RPC system and don't // make sense to print out to the screen. For classes we own, annotations can be used instead. val rpcModule = SimpleModule() @@ -210,7 +207,7 @@ object InteractiveShell { disable(SerializationFeature.FAIL_ON_EMPTY_BEANS) enable(SerializationFeature.INDENT_OUTPUT) - }) + } } // TODO: This should become the default renderer rather than something used specifically by commands. @@ -397,7 +394,7 @@ object InteractiveShell { } private fun printAndFollowRPCResponse(response: Any?, toStream: PrintWriter): CordaFuture? { - val printerFun = { obj: Any? -> yamlMapper.writeValueAsString(obj) } + val printerFun = yamlMapper::writeValueAsString toStream.println(printerFun(response)) toStream.flush() return maybeFollow(response, printerFun, toStream) @@ -443,13 +440,9 @@ object InteractiveShell { val observable: Observable<*> = when (response) { is Observable<*> -> response - is Pair<*, *> -> when { - response.first is Observable<*> -> response.first as Observable<*> - response.second is Observable<*> -> response.second as Observable<*> - else -> null - } - else -> null - } ?: return null + is DataFeed<*, *> -> response.updates + else -> return null + } val subscriber = PrintingSubscriber(printerFun, toStream) uncheckedCast(observable).subscribe(subscriber) @@ -500,8 +493,8 @@ object InteractiveShell { gen.writeString("") } else { val path = Paths.get(toPath) - path.write { value.copyTo(it) } - gen.writeString("") + value.copyTo(path) + gen.writeString("") } } finally { try { From c621c7d1234fe76564eefcac6f15497de0f4327a Mon Sep 17 00:00:00 2001 From: Viktor Kolomeyko Date: Wed, 18 Oct 2017 09:14:03 +0100 Subject: [PATCH 06/94] Attempt to make NodeStatePersistenceTests more stable (#1895) By ensuring that the nodes are properly started and aware of each other before firing any flows through them. Also minor refactoring. --- .../test/node/NodeStatePersistenceTests.kt | 30 +++++++++++++------ 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/node/src/integration-test/kotlin/net/corda/test/node/NodeStatePersistenceTests.kt b/node/src/integration-test/kotlin/net/corda/test/node/NodeStatePersistenceTests.kt index 0e8f062c9c..5b4cd1276a 100644 --- a/node/src/integration-test/kotlin/net/corda/test/node/NodeStatePersistenceTests.kt +++ b/node/src/integration-test/kotlin/net/corda/test/node/NodeStatePersistenceTests.kt @@ -7,6 +7,7 @@ import net.corda.core.flows.FlowLogic import net.corda.core.flows.StartableByRPC import net.corda.core.identity.AbstractParty import net.corda.core.identity.Party +import net.corda.core.internal.concurrent.transpose import net.corda.core.messaging.startFlow import net.corda.core.schemas.MappedSchema import net.corda.core.schemas.PersistentState @@ -21,6 +22,8 @@ import net.corda.node.services.FlowPermissions import net.corda.nodeapi.User import net.corda.testing.DUMMY_NOTARY import net.corda.testing.chooseIdentity +import net.corda.testing.driver.DriverDSLExposedInterface +import net.corda.testing.driver.NodeHandle import net.corda.testing.driver.driver import org.junit.Test import java.lang.management.ManagementFactory @@ -36,15 +39,20 @@ class NodeStatePersistenceTests { val user = User("mark", "dadada", setOf(FlowPermissions.startFlowPermission())) val message = Message("Hello world!") driver(isDebug = true, startNodesInProcess = isQuasarAgentSpecified()) { - startNotaryNode(DUMMY_NOTARY.name, validating = false).getOrThrow() - var nodeHandle = startNode(rpcUsers = listOf(user)).getOrThrow() - val nodeName = nodeHandle.nodeInfo.chooseIdentity().name - nodeHandle.rpcClientToNode().start(user.username, user.password).use { - it.proxy.startFlow(::SendMessageFlow, message).returnValue.getOrThrow() - } - nodeHandle.stop().getOrThrow() + val (nodeName, notaryNodeHandle) = { + val notaryNodeHandle = startNotaryNode(DUMMY_NOTARY.name, validating = false).getOrThrow() + val nodeHandle = startNode(rpcUsers = listOf(user)).getOrThrow() + ensureAcquainted(notaryNodeHandle, nodeHandle) + val nodeName = nodeHandle.nodeInfo.chooseIdentity().name + nodeHandle.rpcClientToNode().start(user.username, user.password).use { + it.proxy.startFlow(::SendMessageFlow, message).returnValue.getOrThrow() + } + nodeHandle.stop().getOrThrow() + nodeName to notaryNodeHandle + }() - nodeHandle = startNode(providedName = nodeName, rpcUsers = listOf(user)).getOrThrow() + val nodeHandle = startNode(providedName = nodeName, rpcUsers = listOf(user)).getOrThrow() + ensureAcquainted(notaryNodeHandle, nodeHandle) nodeHandle.rpcClientToNode().start(user.username, user.password).use { val page = it.proxy.vaultQuery(MessageState::class.java) val retrievedMessage = page.states.singleOrNull()?.state?.data?.message @@ -52,6 +60,10 @@ class NodeStatePersistenceTests { } } } + + private fun DriverDSLExposedInterface.ensureAcquainted(one: NodeHandle, another: NodeHandle) { + listOf(one.pollUntilKnowsAbout(another), another.pollUntilKnowsAbout(one)).transpose().getOrThrow() + } } fun isQuasarAgentSpecified(): Boolean { @@ -95,7 +107,7 @@ object MessageSchemaV1 : MappedSchema( ) : PersistentState() } -val MESSAGE_CONTRACT_PROGRAM_ID = "net.corda.test.node.MessageContract" +const val MESSAGE_CONTRACT_PROGRAM_ID = "net.corda.test.node.MessageContract" open class MessageContract : Contract { override fun verify(tx: LedgerTransaction) { From aa41120f6c17389e64bd83346e2de8ff1c125acc Mon Sep 17 00:00:00 2001 From: Viktor Kolomeyko Date: Wed, 18 Oct 2017 11:20:51 +0100 Subject: [PATCH 07/94] Disable unstable test on Windows (#1899) --- .../net/corda/test/node/NodeStatePersistenceTests.kt | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/node/src/integration-test/kotlin/net/corda/test/node/NodeStatePersistenceTests.kt b/node/src/integration-test/kotlin/net/corda/test/node/NodeStatePersistenceTests.kt index 5b4cd1276a..9a71e5c60f 100644 --- a/node/src/integration-test/kotlin/net/corda/test/node/NodeStatePersistenceTests.kt +++ b/node/src/integration-test/kotlin/net/corda/test/node/NodeStatePersistenceTests.kt @@ -25,17 +25,23 @@ import net.corda.testing.chooseIdentity import net.corda.testing.driver.DriverDSLExposedInterface import net.corda.testing.driver.NodeHandle import net.corda.testing.driver.driver +import org.junit.Assume import org.junit.Test import java.lang.management.ManagementFactory import javax.persistence.Column import javax.persistence.Entity import javax.persistence.Table import kotlin.test.assertEquals +import kotlin.test.assertNotNull class NodeStatePersistenceTests { @Test fun `persistent state survives node restart`() { + // Temporary disable this test when executed on Windows. It is known to be sporadically failing. + // More investigation is needed to establish why. + Assume.assumeFalse(System.getProperty("os.name").toLowerCase().startsWith("win")) + val user = User("mark", "dadada", setOf(FlowPermissions.startFlowPermission())) val message = Message("Hello world!") driver(isDebug = true, startNodesInProcess = isQuasarAgentSpecified()) { @@ -55,7 +61,9 @@ class NodeStatePersistenceTests { ensureAcquainted(notaryNodeHandle, nodeHandle) nodeHandle.rpcClientToNode().start(user.username, user.password).use { val page = it.proxy.vaultQuery(MessageState::class.java) - val retrievedMessage = page.states.singleOrNull()?.state?.data?.message + val stateAndRef = page.states.singleOrNull() + assertNotNull(stateAndRef) + val retrievedMessage = stateAndRef!!.state.data.message assertEquals(message, retrievedMessage) } } From b4c53647cd6d8a8b78d6d95e2d17dfd54830f046 Mon Sep 17 00:00:00 2001 From: Andrzej Cichocki Date: Wed, 18 Oct 2017 13:40:57 +0100 Subject: [PATCH 08/94] CORDA-530 Don't soft-lock non-fungible states (#1794) * Don't run unlock query if nothing was locked * Constructors should not have side-effects --- .../net/corda/node/internal/AbstractNode.kt | 14 +- .../events/ScheduledActivityObserver.kt | 29 ++-- .../node/services/schema/HibernateObserver.kt | 21 ++- .../statemachine/FlowStateMachineImpl.kt | 12 +- .../node/services/vault/NodeVaultService.kt | 8 +- .../services/vault/VaultSoftLockManager.kt | 72 ++++---- .../persistence/DBTransactionStorageTests.kt | 2 +- .../services/schema/HibernateObserverTests.kt | 3 +- .../vault/VaultSoftLockManagerTest.kt | 161 ++++++++++++++++++ .../net/corda/testing/node/MockServices.kt | 2 +- 10 files changed, 246 insertions(+), 78 deletions(-) create mode 100644 node/src/test/kotlin/net/corda/node/services/vault/VaultSoftLockManagerTest.kt diff --git a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt index 16829873aa..df01661af0 100644 --- a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt +++ b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt @@ -274,8 +274,7 @@ abstract class AbstractNode(config: NodeConfiguration, require(customNotaryServiceList.size == 1) { "Attempting to install more than one notary service: ${customNotaryServiceList.joinToString()}" } - } - else return loadedServices - customNotaryServiceList + } else return loadedServices - customNotaryServiceList } return loadedServices } @@ -490,9 +489,9 @@ abstract class AbstractNode(config: NodeConfiguration, protected open fun makeTransactionStorage(): WritableTransactionStorage = DBTransactionStorage() private fun makeVaultObservers() { - VaultSoftLockManager(services.vaultService, smm) - ScheduledActivityObserver(services) - HibernateObserver(services.vaultService.rawUpdates, services.database.hibernateConfig) + VaultSoftLockManager.install(services.vaultService, smm) + ScheduledActivityObserver.install(services.vaultService, services.schedulerService) + HibernateObserver.install(services.vaultService.rawUpdates, database.hibernateConfig) } private fun makeInfo(legalIdentity: PartyAndCertificate): NodeInfo { @@ -758,6 +757,9 @@ abstract class AbstractNode(config: NodeConfiguration, } protected open fun generateKeyPair() = cryptoGenerateKeyPair() + protected open fun makeVaultService(keyManagementService: KeyManagementService, stateLoader: StateLoader): VaultServiceInternal { + return NodeVaultService(platformClock, keyManagementService, stateLoader, database.hibernateConfig) + } private inner class ServiceHubInternalImpl( override val schemaService: SchemaService, @@ -771,7 +773,7 @@ abstract class AbstractNode(config: NodeConfiguration, override val auditService = DummyAuditService() override val transactionVerifierService by lazy { makeTransactionVerifierService() } override val networkMapCache by lazy { PersistentNetworkMapCache(this) } - override val vaultService by lazy { NodeVaultService(platformClock, keyManagementService, stateLoader, this@AbstractNode.database.hibernateConfig) } + override val vaultService by lazy { makeVaultService(keyManagementService, stateLoader) } override val contractUpgradeService by lazy { ContractUpgradeServiceImpl() } // Place the long term identity key in the KMS. Eventually, this is likely going to be separated again because diff --git a/node/src/main/kotlin/net/corda/node/services/events/ScheduledActivityObserver.kt b/node/src/main/kotlin/net/corda/node/services/events/ScheduledActivityObserver.kt index 3509fa2d34..3020a9e528 100644 --- a/node/src/main/kotlin/net/corda/node/services/events/ScheduledActivityObserver.kt +++ b/node/src/main/kotlin/net/corda/node/services/events/ScheduledActivityObserver.kt @@ -4,18 +4,28 @@ import net.corda.core.contracts.ContractState import net.corda.core.contracts.SchedulableState import net.corda.core.contracts.ScheduledStateRef import net.corda.core.contracts.StateAndRef -import net.corda.node.services.api.ServiceHubInternal +import net.corda.core.node.services.VaultService +import net.corda.node.services.api.SchedulerService import net.corda.node.services.statemachine.FlowLogicRefFactoryImpl /** * This observes the vault and schedules and unschedules activities appropriately based on state production and * consumption. */ -class ScheduledActivityObserver(val services: ServiceHubInternal) { - init { - services.vaultService.rawUpdates.subscribe { (consumed, produced) -> - consumed.forEach { services.schedulerService.unscheduleStateActivity(it.ref) } - produced.forEach { scheduleStateActivity(it) } +class ScheduledActivityObserver private constructor(private val schedulerService: SchedulerService) { + companion object { + @JvmStatic + fun install(vaultService: VaultService, schedulerService: SchedulerService) { + val observer = ScheduledActivityObserver(schedulerService) + vaultService.rawUpdates.subscribe { (consumed, produced) -> + consumed.forEach { schedulerService.unscheduleStateActivity(it.ref) } + produced.forEach { observer.scheduleStateActivity(it) } + } + } + + // TODO: Beware we are calling dynamically loaded contract code inside here. + private inline fun sandbox(code: () -> T?): T? { + return code() } } @@ -23,12 +33,7 @@ class ScheduledActivityObserver(val services: ServiceHubInternal) { val producedState = produced.state.data if (producedState is SchedulableState) { val scheduledAt = sandbox { producedState.nextScheduledActivity(produced.ref, FlowLogicRefFactoryImpl)?.scheduledAt } ?: return - services.schedulerService.scheduleStateActivity(ScheduledStateRef(produced.ref, scheduledAt)) + schedulerService.scheduleStateActivity(ScheduledStateRef(produced.ref, scheduledAt)) } } - - // TODO: Beware we are calling dynamically loaded contract code inside here. - private inline fun sandbox(code: () -> T?): T? { - return code() - } } diff --git a/node/src/main/kotlin/net/corda/node/services/schema/HibernateObserver.kt b/node/src/main/kotlin/net/corda/node/services/schema/HibernateObserver.kt index 45a3334ef1..2babcc1d70 100644 --- a/node/src/main/kotlin/net/corda/node/services/schema/HibernateObserver.kt +++ b/node/src/main/kotlin/net/corda/node/services/schema/HibernateObserver.kt @@ -3,6 +3,7 @@ package net.corda.node.services.schema import net.corda.core.contracts.ContractState import net.corda.core.contracts.StateAndRef import net.corda.core.contracts.StateRef +import net.corda.core.internal.VisibleForTesting import net.corda.core.node.services.Vault import net.corda.core.schemas.MappedSchema import net.corda.core.schemas.PersistentStateRef @@ -17,14 +18,15 @@ import rx.Observable * A vault observer that extracts Object Relational Mappings for contract states that support it, and persists them with Hibernate. */ // TODO: Manage version evolution of the schemas via additional tooling. -class HibernateObserver(vaultUpdates: Observable>, val config: HibernateConfiguration) { - +class HibernateObserver private constructor(private val config: HibernateConfiguration) { companion object { - val logger = loggerFor() - } - - init { - vaultUpdates.subscribe { persist(it.produced) } + private val log = loggerFor() + @JvmStatic + fun install(vaultUpdates: Observable>, config: HibernateConfiguration): HibernateObserver { + val observer = HibernateObserver(config) + vaultUpdates.subscribe { observer.persist(it.produced) } + return observer + } } private fun persist(produced: Set>) { @@ -33,11 +35,12 @@ class HibernateObserver(vaultUpdates: Observable>, v private fun persistState(stateAndRef: StateAndRef) { val state = stateAndRef.state.data - logger.debug { "Asked to persist state ${stateAndRef.ref}" } + log.debug { "Asked to persist state ${stateAndRef.ref}" } config.schemaService.selectSchemas(state).forEach { persistStateWithSchema(state, stateAndRef.ref, it) } } - fun persistStateWithSchema(state: ContractState, stateRef: StateRef, schema: MappedSchema) { + @VisibleForTesting + internal fun persistStateWithSchema(state: ContractState, stateRef: StateRef, schema: MappedSchema) { val sessionFactory = config.sessionFactoryForSchemas(setOf(schema)) val session = sessionFactory.withOptions(). connection(DatabaseTransactionManager.current().connection). diff --git a/node/src/main/kotlin/net/corda/node/services/statemachine/FlowStateMachineImpl.kt b/node/src/main/kotlin/net/corda/node/services/statemachine/FlowStateMachineImpl.kt index 89f0bf6dc6..a3df2461c8 100644 --- a/node/src/main/kotlin/net/corda/node/services/statemachine/FlowStateMachineImpl.kt +++ b/node/src/main/kotlin/net/corda/node/services/statemachine/FlowStateMachineImpl.kt @@ -68,12 +68,10 @@ class FlowStateMachineImpl(override val id: StateMachineRunId, * is not necessary. */ override val logger: Logger = LoggerFactory.getLogger("net.corda.flow.$id") - - @Transient private var _resultFuture: OpenFuture? = openFuture() + @Transient private var resultFutureTransient: OpenFuture? = openFuture() + private val _resultFuture get() = resultFutureTransient ?: openFuture().also { resultFutureTransient = it } /** This future will complete when the call method returns. */ - override val resultFuture: CordaFuture - get() = _resultFuture ?: openFuture().also { _resultFuture = it } - + override val resultFuture: CordaFuture get() = _resultFuture // This state IS serialised, as we need it to know what the fiber is waiting for. internal val openSessions = HashMap, Party>, FlowSessionInternal>() internal var waitingForResponse: WaitingRequest? = null @@ -115,7 +113,7 @@ class FlowStateMachineImpl(override val id: StateMachineRunId, recordDuration(startTime) // This is to prevent actionOnEnd being called twice if it throws an exception actionOnEnd(Try.Success(result), false) - _resultFuture?.set(result) + _resultFuture.set(result) logic.progressTracker?.currentStep = ProgressTracker.DONE logger.debug { "Flow finished with result ${result.toString().abbreviate(300)}" } } @@ -128,7 +126,7 @@ class FlowStateMachineImpl(override val id: StateMachineRunId, private fun processException(exception: Throwable, propagated: Boolean) { actionOnEnd(Try.Failure(exception), propagated) - _resultFuture?.setException(exception) + _resultFuture.setException(exception) logic.progressTracker?.endWithError(exception) } diff --git a/node/src/main/kotlin/net/corda/node/services/vault/NodeVaultService.kt b/node/src/main/kotlin/net/corda/node/services/vault/NodeVaultService.kt index d5a189b0ee..934cc2cb83 100644 --- a/node/src/main/kotlin/net/corda/node/services/vault/NodeVaultService.kt +++ b/node/src/main/kotlin/net/corda/node/services/vault/NodeVaultService.kt @@ -269,7 +269,7 @@ class NodeVaultService(private val clock: Clock, private val keyManagementServic update.where(stateStatusPredication, lockIdPredicate, *commonPredicates) } if (updatedRows > 0 && updatedRows == stateRefs.size) { - log.trace("Reserving soft lock states for $lockId: $stateRefs") + log.trace { "Reserving soft lock states for $lockId: $stateRefs" } FlowStateMachineImpl.currentStateMachine()?.hasSoftLockedStates = true } else { // revert partial soft locks @@ -280,7 +280,7 @@ class NodeVaultService(private val clock: Clock, private val keyManagementServic update.where(lockUpdateTime, lockIdPredicate, *commonPredicates) } if (revertUpdatedRows > 0) { - log.trace("Reverting $revertUpdatedRows partially soft locked states for $lockId") + log.trace { "Reverting $revertUpdatedRows partially soft locked states for $lockId" } } throw StatesNotAvailableException("Attempted to reserve $stateRefs for $lockId but only $updatedRows rows available") } @@ -309,7 +309,7 @@ class NodeVaultService(private val clock: Clock, private val keyManagementServic update.where(*commonPredicates) } if (update > 0) { - log.trace("Releasing $update soft locked states for $lockId") + log.trace { "Releasing $update soft locked states for $lockId" } } } else { try { @@ -320,7 +320,7 @@ class NodeVaultService(private val clock: Clock, private val keyManagementServic update.where(*commonPredicates, stateRefsPredicate) } if (updatedRows > 0) { - log.trace("Releasing $updatedRows soft locked states for $lockId and stateRefs $stateRefs") + log.trace { "Releasing $updatedRows soft locked states for $lockId and stateRefs $stateRefs" } } } catch (e: Exception) { log.error("""soft lock update error attempting to release states for $lockId and $stateRefs") diff --git a/node/src/main/kotlin/net/corda/node/services/vault/VaultSoftLockManager.kt b/node/src/main/kotlin/net/corda/node/services/vault/VaultSoftLockManager.kt index 2d063a1468..8bf233589b 100644 --- a/node/src/main/kotlin/net/corda/node/services/vault/VaultSoftLockManager.kt +++ b/node/src/main/kotlin/net/corda/node/services/vault/VaultSoftLockManager.kt @@ -1,8 +1,8 @@ package net.corda.node.services.vault +import net.corda.core.contracts.FungibleAsset import net.corda.core.contracts.StateRef import net.corda.core.flows.FlowLogic -import net.corda.core.flows.StateMachineRunId import net.corda.core.node.services.VaultService import net.corda.core.utilities.NonEmptySet import net.corda.core.utilities.loggerFor @@ -12,50 +12,50 @@ import net.corda.node.services.statemachine.FlowStateMachineImpl import net.corda.node.services.statemachine.StateMachineManager import java.util.* -class VaultSoftLockManager(val vault: VaultService, smm: StateMachineManager) { - - private companion object { - val log = loggerFor() - } - - init { - smm.changes.subscribe { change -> - if (change is StateMachineManager.Change.Removed && (FlowStateMachineImpl.currentStateMachine())?.hasSoftLockedStates == true) { - log.trace { "Remove flow name ${change.logic.javaClass} with id $change.id" } - unregisterSoftLocks(change.logic.runId, change.logic) +class VaultSoftLockManager private constructor(private val vault: VaultService) { + companion object { + private val log = loggerFor() + @JvmStatic + fun install(vault: VaultService, smm: StateMachineManager) { + val manager = VaultSoftLockManager(vault) + smm.changes.subscribe { change -> + if (change is StateMachineManager.Change.Removed) { + val logic = change.logic + // Don't run potentially expensive query if the flow didn't lock any states: + if ((logic.stateMachine as FlowStateMachineImpl<*>).hasSoftLockedStates) { + manager.unregisterSoftLocks(logic.runId.uuid, logic) + } + } } - } - - // Discussion - // - // The intent of the following approach is to support what might be a common pattern in a flow: - // 1. Create state - // 2. Do something with state - // without possibility of another flow intercepting the state between 1 and 2, - // since we cannot lock the state before it exists. e.g. Issue and then Move some Cash. - // - // The downside is we could have a long running flow that holds a lock for a long period of time. - // However, the lock can be programmatically released, like any other soft lock, - // should we want a long running flow that creates a visible state mid way through. - - vault.rawUpdates.subscribe { (_, produced, flowId) -> - flowId?.let { - if (produced.isNotEmpty()) { - registerSoftLocks(flowId, (produced.map { it.ref }).toNonEmptySet()) + // Discussion + // + // The intent of the following approach is to support what might be a common pattern in a flow: + // 1. Create state + // 2. Do something with state + // without possibility of another flow intercepting the state between 1 and 2, + // since we cannot lock the state before it exists. e.g. Issue and then Move some Cash. + // + // The downside is we could have a long running flow that holds a lock for a long period of time. + // However, the lock can be programmatically released, like any other soft lock, + // should we want a long running flow that creates a visible state mid way through. + vault.rawUpdates.subscribe { (_, produced, flowId) -> + if (flowId != null) { + val fungible = produced.filter { it.state.data is FungibleAsset<*> } + if (fungible.isNotEmpty()) { + manager.registerSoftLocks(flowId, fungible.map { it.ref }.toNonEmptySet()) + } } } } } private fun registerSoftLocks(flowId: UUID, stateRefs: NonEmptySet) { - log.trace("Reserving soft locks for flow id $flowId and states $stateRefs") + log.trace { "Reserving soft locks for flow id $flowId and states $stateRefs" } vault.softLockReserve(flowId, stateRefs) } - private fun unregisterSoftLocks(id: StateMachineRunId, logic: FlowLogic<*>) { - val flowClassName = logic.javaClass.simpleName - log.trace("Releasing soft locks for flow $flowClassName with flow id ${id.uuid}") - vault.softLockRelease(id.uuid) - + private fun unregisterSoftLocks(flowId: UUID, logic: FlowLogic<*>) { + log.trace { "Releasing soft locks for flow ${logic.javaClass.simpleName} with flow id $flowId" } + vault.softLockRelease(flowId) } } \ No newline at end of file diff --git a/node/src/test/kotlin/net/corda/node/services/persistence/DBTransactionStorageTests.kt b/node/src/test/kotlin/net/corda/node/services/persistence/DBTransactionStorageTests.kt index 2b9ff96f1c..160ea87bf3 100644 --- a/node/src/test/kotlin/net/corda/node/services/persistence/DBTransactionStorageTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/persistence/DBTransactionStorageTests.kt @@ -45,7 +45,7 @@ class DBTransactionStorageTests : TestDependencyInjectionBase() { override val vaultService: VaultServiceInternal get() { val vaultService = NodeVaultService(clock, keyManagementService, stateLoader, database.hibernateConfig) - hibernatePersister = HibernateObserver(vaultService.rawUpdates, database.hibernateConfig) + hibernatePersister = HibernateObserver.install(vaultService.rawUpdates, database.hibernateConfig) return vaultService } diff --git a/node/src/test/kotlin/net/corda/node/services/schema/HibernateObserverTests.kt b/node/src/test/kotlin/net/corda/node/services/schema/HibernateObserverTests.kt index 33e8bcdad4..30d877ab93 100644 --- a/node/src/test/kotlin/net/corda/node/services/schema/HibernateObserverTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/schema/HibernateObserverTests.kt @@ -69,8 +69,7 @@ class HibernateObserverTests { } } val database = configureDatabase(makeTestDataSourceProperties(), makeTestDatabaseProperties(), ::makeTestIdentityService, schemaService) - @Suppress("UNUSED_VARIABLE") - val observer = HibernateObserver(rawUpdatesPublisher, database.hibernateConfig) + HibernateObserver.install(rawUpdatesPublisher, database.hibernateConfig) database.transaction { rawUpdatesPublisher.onNext(Vault.Update(emptySet(), setOf(StateAndRef(TransactionState(TestState(), DummyContract.PROGRAM_ID, MEGA_CORP), StateRef(SecureHash.sha256("dummy"), 0))))) val parentRowCountResult = DatabaseTransactionManager.current().connection.prepareStatement("select count(*) from Parents").executeQuery() diff --git a/node/src/test/kotlin/net/corda/node/services/vault/VaultSoftLockManagerTest.kt b/node/src/test/kotlin/net/corda/node/services/vault/VaultSoftLockManagerTest.kt new file mode 100644 index 0000000000..d0d7562234 --- /dev/null +++ b/node/src/test/kotlin/net/corda/node/services/vault/VaultSoftLockManagerTest.kt @@ -0,0 +1,161 @@ +package net.corda.node.services.vault + +import co.paralleluniverse.fibers.Suspendable +import com.nhaarman.mockito_kotlin.mock +import com.nhaarman.mockito_kotlin.verify +import com.nhaarman.mockito_kotlin.verifyNoMoreInteractions +import net.corda.core.contracts.* +import net.corda.core.flows.FinalityFlow +import net.corda.core.flows.FlowLogic +import net.corda.core.flows.FlowSession +import net.corda.core.flows.InitiatingFlow +import net.corda.core.identity.AbstractParty +import net.corda.core.internal.FlowStateMachine +import net.corda.core.internal.packageName +import net.corda.core.internal.uncheckedCast +import net.corda.core.messaging.SingleMessageRecipient +import net.corda.core.node.StateLoader +import net.corda.core.node.services.KeyManagementService +import net.corda.core.node.services.queryBy +import net.corda.core.node.services.vault.QueryCriteria.SoftLockingCondition +import net.corda.core.node.services.vault.QueryCriteria.SoftLockingType.LOCKED_ONLY +import net.corda.core.node.services.vault.QueryCriteria.VaultQueryCriteria +import net.corda.core.transactions.LedgerTransaction +import net.corda.core.transactions.TransactionBuilder +import net.corda.core.utilities.NonEmptySet +import net.corda.core.utilities.OpaqueBytes +import net.corda.core.utilities.getOrThrow +import net.corda.core.utilities.unwrap +import net.corda.node.internal.InitiatedFlowFactory +import net.corda.node.services.api.VaultServiceInternal +import net.corda.node.services.config.NodeConfiguration +import net.corda.nodeapi.internal.ServiceInfo +import net.corda.testing.chooseIdentity +import net.corda.testing.node.MockNetwork +import org.junit.After +import org.junit.Test +import java.math.BigInteger +import java.security.KeyPair +import java.util.* +import java.util.concurrent.atomic.AtomicBoolean +import kotlin.reflect.jvm.jvmName +import kotlin.test.assertEquals + +private class NodePair(private val mockNet: MockNetwork) { + private class ServerLogic(private val session: FlowSession, private val running: AtomicBoolean) : FlowLogic() { + @Suspendable + override fun call() { + running.set(true) + session.receive().unwrap { assertEquals("ping", it) } + session.send("pong") + } + } + + @InitiatingFlow + abstract class AbstractClientLogic(nodePair: NodePair) : FlowLogic() { + protected val server = nodePair.server.info.chooseIdentity() + protected abstract fun callImpl(): T + @Suspendable + override fun call() = callImpl().also { + initiateFlow(server).sendAndReceive("ping").unwrap { assertEquals("pong", it) } + } + } + + private val serverRunning = AtomicBoolean() + val server = mockNet.createNode() + var client = mockNet.createNode().apply { + internals.disableDBCloseOnStop() // Otherwise the in-memory database may disappear (taking the checkpoint with it) while we reboot the client. + } + private set + + fun communicate(clientLogic: AbstractClientLogic, rebootClient: Boolean): FlowStateMachine { + server.internals.internalRegisterFlowFactory(AbstractClientLogic::class.java, InitiatedFlowFactory.Core { ServerLogic(it, serverRunning) }, ServerLogic::class.java, false) + client.services.startFlow(clientLogic) + while (!serverRunning.get()) mockNet.runNetwork(1) + if (rebootClient) { + client.dispose() + client = mockNet.createNode(client.internals.id) + } + return uncheckedCast(client.smm.allStateMachines.single().stateMachine) + } +} + +class VaultSoftLockManagerTest { + private val mockVault: VaultServiceInternal = mock() + private val mockNet = MockNetwork(cordappPackages = listOf(ContractImpl::class.packageName), defaultFactory = object : MockNetwork.Factory { + override fun create(config: NodeConfiguration, network: MockNetwork, networkMapAddr: SingleMessageRecipient?, id: Int, notaryIdentity: Pair?, entropyRoot: BigInteger): MockNetwork.MockNode { + return object : MockNetwork.MockNode(config, network, networkMapAddr, id, notaryIdentity, entropyRoot) { + override fun makeVaultService(keyManagementService: KeyManagementService, stateLoader: StateLoader): VaultServiceInternal { + val realVault = super.makeVaultService(keyManagementService, stateLoader) + return object : VaultServiceInternal by realVault { + override fun softLockRelease(lockId: UUID, stateRefs: NonEmptySet?) { + mockVault.softLockRelease(lockId, stateRefs) // No need to also call the real one for these tests. + } + } + } + } + } + }) + private val nodePair = NodePair(mockNet) + @After + fun tearDown() { + mockNet.stopNodes() + } + + private object CommandDataImpl : CommandData + private class ClientLogic(nodePair: NodePair, private val state: ContractState) : NodePair.AbstractClientLogic>(nodePair) { + override fun callImpl() = run { + subFlow(FinalityFlow(serviceHub.signInitialTransaction(TransactionBuilder(notary = ourIdentity).apply { + addOutputState(state, ContractImpl::class.jvmName) + addCommand(CommandDataImpl, ourIdentity.owningKey) + }))) + serviceHub.vaultService.queryBy(VaultQueryCriteria(softLockingCondition = SoftLockingCondition(LOCKED_ONLY))).states.map { + it.state.data + } + } + } + + private abstract class SingleParticipantState(nodePair: NodePair) : ContractState { + override val participants = listOf(nodePair.client.info.chooseIdentity()) + } + + private class PlainOldState(nodePair: NodePair) : SingleParticipantState(nodePair) + private class FungibleAssetImpl(nodePair: NodePair) : SingleParticipantState(nodePair), FungibleAsset { + override val owner get() = participants[0] + override fun withNewOwner(newOwner: AbstractParty) = throw UnsupportedOperationException() + override val amount get() = Amount(1, Issued(PartyAndReference(owner, OpaqueBytes.of(1)), Unit)) + override val exitKeys get() = throw UnsupportedOperationException() + override fun withNewOwnerAndAmount(newAmount: Amount>, newOwner: AbstractParty) = throw UnsupportedOperationException() + override fun equals(other: Any?) = other is FungibleAssetImpl && participants == other.participants + override fun hashCode() = participants.hashCode() + } + + class ContractImpl : Contract { + override fun verify(tx: LedgerTransaction) {} + } + + private fun run(expectSoftLock: Boolean, state: ContractState, checkpoint: Boolean) { + val fsm = nodePair.communicate(ClientLogic(nodePair, state), checkpoint) + mockNet.runNetwork() + if (expectSoftLock) { + assertEquals(listOf(state), fsm.resultFuture.getOrThrow()) + verify(mockVault).softLockRelease(fsm.id.uuid, null) + } else { + assertEquals(emptyList(), fsm.resultFuture.getOrThrow()) + // In this case we don't want softLockRelease called so that we avoid its expensive query, even after restore from checkpoint. + } + verifyNoMoreInteractions(mockVault) + } + + @Test + fun `plain old state is not soft locked`() = run(false, PlainOldState(nodePair), false) + + @Test + fun `plain old state is not soft locked with checkpoint`() = run(false, PlainOldState(nodePair), true) + + @Test + fun `fungible asset is soft locked`() = run(true, FungibleAssetImpl(nodePair), false) + + @Test + fun `fungible asset is soft locked with checkpoint`() = run(true, FungibleAssetImpl(nodePair), true) +} diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockServices.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockServices.kt index fcd5689800..fa881e1703 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockServices.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockServices.kt @@ -171,7 +171,7 @@ open class MockServices( fun makeVaultService(hibernateConfig: HibernateConfiguration): VaultServiceInternal { val vaultService = NodeVaultService(Clock.systemUTC(), keyManagementService, stateLoader, hibernateConfig) - hibernatePersister = HibernateObserver(vaultService.rawUpdates, hibernateConfig) + hibernatePersister = HibernateObserver.install(vaultService.rawUpdates, hibernateConfig) return vaultService } From b33b013284face1e7cf091a183999ed18f0ab463 Mon Sep 17 00:00:00 2001 From: Alberto Arri <30873160+al-r3@users.noreply.github.com> Date: Wed, 18 Oct 2017 13:49:32 +0100 Subject: [PATCH 09/94] [CORDA-442] let Driver run without network map (#1890) * [CORDA-442] let Driver run without network map - Nodes started by driver run without a networkMapNode. - Driver does not take a networkMapStartStrategy anymore - a new parameter in the configuration "noNetworkMapServiceMode" allows for a node not to be a networkMapNode nor to connect to one. - Driver now waits for each node to write its own NodeInfo file to disk and then copies it into each other node. - When driver starts a node N, it waits for every node to be have N nodes in their network map. Note: the code to copy around the NodeInfo files was already in DemoBench, the NodeInfoFilesCopier class was just moved from DemoBench into core (I'm very open to core not being the best place, please advise) --- .../net/corda/client/rpc/RPCStabilityTests.kt | 1 + node-api/build.gradle | 2 + .../net/corda/nodeapi/NodeInfoFilesCopier.kt | 165 ++++++++++++++++++ .../corda/nodeapi}/NodeInfoFilesCopierTest.kt | 41 ++--- .../kotlin/net/corda/node/BootTests.kt | 3 +- .../corda/node/NodeStartupPerformanceTests.kt | 4 +- .../services/network/NodeInfoWatcherTest.kt | 9 +- .../net/corda/node/internal/AbstractNode.kt | 12 +- .../node/services/config/NodeConfiguration.kt | 2 + .../node/services/network/NodeInfoWatcher.kt | 4 +- .../kotlin/net/corda/testing/RPCDriver.kt | 2 - .../kotlin/net/corda/testing/driver/Driver.kt | 146 ++++++++-------- .../testing/driver/NetworkMapStartStrategy.kt | 23 --- .../model/DemoBenchNodeInfoFilesCopier.kt | 35 ++++ .../corda/demobench/model/NodeController.kt | 2 +- .../demobench/model/NodeInfoFilesCopier.kt | 133 -------------- .../net/corda/verifier/VerifierDriver.kt | 2 - .../net/corda/verifier/VerifierTests.kt | 9 +- 18 files changed, 316 insertions(+), 279 deletions(-) create mode 100644 node-api/src/main/kotlin/net/corda/nodeapi/NodeInfoFilesCopier.kt rename {tools/demobench/src/test/kotlin/net/corda/demobench/model => node-api/src/test/kotlin/net/corda/nodeapi}/NodeInfoFilesCopierTest.kt (73%) delete mode 100644 testing/node-driver/src/main/kotlin/net/corda/testing/driver/NetworkMapStartStrategy.kt create mode 100644 tools/demobench/src/main/kotlin/net/corda/demobench/model/DemoBenchNodeInfoFilesCopier.kt delete mode 100644 tools/demobench/src/main/kotlin/net/corda/demobench/model/NodeInfoFilesCopier.kt diff --git a/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/RPCStabilityTests.kt b/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/RPCStabilityTests.kt index 7ccf1913d5..a25881f4ec 100644 --- a/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/RPCStabilityTests.kt +++ b/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/RPCStabilityTests.kt @@ -92,6 +92,7 @@ class RPCStabilityTests { startAndStop() } val numberOfThreadsAfter = waitUntilNumberOfThreadsStable(executor) + assertTrue(numberOfThreadsBefore >= numberOfThreadsAfter) executor.shutdownNow() } diff --git a/node-api/build.gradle b/node-api/build.gradle index be014cdc80..a9384bd4ab 100644 --- a/node-api/build.gradle +++ b/node-api/build.gradle @@ -18,6 +18,8 @@ dependencies { // TODO: Remove this dependency and the code that requires it compile "commons-fileupload:commons-fileupload:$fileupload_version" + compile "net.corda.plugins:cordform-common:$gradle_plugins_version" + // TypeSafe Config: for simple and human friendly config files. compile "com.typesafe:config:$typesafe_config_version" diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/NodeInfoFilesCopier.kt b/node-api/src/main/kotlin/net/corda/nodeapi/NodeInfoFilesCopier.kt new file mode 100644 index 0000000000..aadc49f4c3 --- /dev/null +++ b/node-api/src/main/kotlin/net/corda/nodeapi/NodeInfoFilesCopier.kt @@ -0,0 +1,165 @@ +package net.corda.nodeapi + +import net.corda.cordform.CordformNode +import net.corda.core.internal.ThreadBox +import net.corda.core.internal.createDirectories +import net.corda.core.internal.isRegularFile +import net.corda.core.internal.list +import net.corda.core.utilities.loggerFor +import rx.Observable +import rx.Scheduler +import rx.Subscription +import rx.schedulers.Schedulers +import java.io.IOException +import java.nio.file.Files +import java.nio.file.Path +import java.nio.file.StandardCopyOption.COPY_ATTRIBUTES +import java.nio.file.StandardCopyOption.REPLACE_EXISTING +import java.nio.file.attribute.BasicFileAttributes +import java.nio.file.attribute.FileTime +import java.util.concurrent.TimeUnit + +/** + * Utility class which copies nodeInfo files across a set of running nodes. + * + * This class will create paths that it needs to poll and to where it needs to copy files in case those + * don't exist yet. + */ +class NodeInfoFilesCopier(scheduler: Scheduler = Schedulers.io()) : AutoCloseable { + + companion object { + private val log = loggerFor() + const val NODE_INFO_FILE_NAME_PREFIX = "nodeInfo-" + } + + private val nodeDataMapBox = ThreadBox(mutableMapOf()) + /** + * Whether the NodeInfoFilesCopier is closed. When the NodeInfoFilesCopier is closed it will stop polling the + * filesystem and all the public methods except [#close] will throw. + */ + private var closed = false + private val subscription: Subscription + + init { + this.subscription = Observable.interval(5, TimeUnit.SECONDS, scheduler) + .subscribe { poll() } + } + + /** + * @param nodeDir a path to be watched for NodeInfos + * Add a path of a node which is about to be started. + * Its nodeInfo file will be copied to other nodes' additional-node-infos directory, and conversely, + * other nodes' nodeInfo files will be copied to this node additional-node-infos directory. + */ + fun addConfig(nodeDir: Path) { + require(!closed) { "NodeInfoFilesCopier is already closed" } + nodeDataMapBox.locked { + val newNodeFile = NodeData(nodeDir) + put(nodeDir, newNodeFile) + + for (previouslySeenFile in allPreviouslySeenFiles()) { + atomicCopy(previouslySeenFile, newNodeFile.additionalNodeInfoDirectory.resolve(previouslySeenFile.fileName)) + } + log.info("Now watching: $nodeDir") + } + } + + /** + * @param nodeConfig the configuration to be removed. + * Remove the configuration of a node which is about to be stopped or already stopped. + * No files written by that node will be copied to other nodes, nor files from other nodes will be copied to this + * one. + */ + fun removeConfig(nodeDir: Path) { + require(!closed) { "NodeInfoFilesCopier is already closed" } + nodeDataMapBox.locked { + remove(nodeDir) ?: return + log.info("Stopped watching: $nodeDir") + } + } + + fun reset() { + require(!closed) { "NodeInfoFilesCopier is already closed" } + nodeDataMapBox.locked { + clear() + } + } + + /** + * Stops polling the filesystem. + * This function can be called as many times as one wants. + */ + override fun close() { + if (!closed) { + closed = true + subscription.unsubscribe() + } + } + + private fun allPreviouslySeenFiles() = nodeDataMapBox.alreadyLocked { values.flatMap { it.previouslySeenFiles.keys } } + + private fun poll() { + nodeDataMapBox.locked { + for (nodeData in values) { + nodeData.nodeDir.list { paths -> + paths.filter { it.isRegularFile() } + .filter { it.fileName.toString().startsWith(NODE_INFO_FILE_NAME_PREFIX) } + .forEach { path -> processPath(nodeData, path) } + } + } + } + } + + // Takes a path under nodeData config dir and decides whether the file represented by that path needs to + // be copied. + private fun processPath(nodeData: NodeData, path: Path) { + nodeDataMapBox.alreadyLocked { + val newTimestamp = Files.readAttributes(path, BasicFileAttributes::class.java).lastModifiedTime() + val previousTimestamp = nodeData.previouslySeenFiles.put(path, newTimestamp) ?: FileTime.fromMillis(-1) + if (newTimestamp > previousTimestamp) { + for (destination in this.values.filter { it.nodeDir != nodeData.nodeDir }.map { it.additionalNodeInfoDirectory }) { + val fullDestinationPath = destination.resolve(path.fileName) + atomicCopy(path, fullDestinationPath) + } + } + } + } + + private fun atomicCopy(source: Path, destination: Path) { + val tempDestination = try { + Files.createTempFile(destination.parent, "", null) + } catch (exception: IOException) { + log.warn("Couldn't create a temporary file to copy $source", exception) + throw exception + } + try { + // First copy the file to a temporary file within the appropriate directory. + Files.copy(source, tempDestination, COPY_ATTRIBUTES, REPLACE_EXISTING) + } catch (exception: IOException) { + log.warn("Couldn't copy $source to $tempDestination.", exception) + Files.delete(tempDestination) + throw exception + } + try { + // Then rename it to the desired name. This way the file 'appears' on the filesystem as an atomic operation. + Files.move(tempDestination, destination, REPLACE_EXISTING) + } catch (exception: IOException) { + log.warn("Couldn't move $tempDestination to $destination.", exception) + Files.delete(tempDestination) + throw exception + } + } + + /** + * Convenience holder for all the paths and files relative to a single node. + */ + private class NodeData(val nodeDir: Path) { + val additionalNodeInfoDirectory: Path = nodeDir.resolve(CordformNode.NODE_INFO_DIRECTORY) + // Map from Path to its lastModifiedTime. + val previouslySeenFiles = mutableMapOf() + + init { + additionalNodeInfoDirectory.createDirectories() + } + } +} diff --git a/tools/demobench/src/test/kotlin/net/corda/demobench/model/NodeInfoFilesCopierTest.kt b/node-api/src/test/kotlin/net/corda/nodeapi/NodeInfoFilesCopierTest.kt similarity index 73% rename from tools/demobench/src/test/kotlin/net/corda/demobench/model/NodeInfoFilesCopierTest.kt rename to node-api/src/test/kotlin/net/corda/nodeapi/NodeInfoFilesCopierTest.kt index 557bd7095b..562717e641 100644 --- a/tools/demobench/src/test/kotlin/net/corda/demobench/model/NodeInfoFilesCopierTest.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/NodeInfoFilesCopierTest.kt @@ -1,8 +1,6 @@ -package net.corda.demobench.model +package net.corda.nodeapi import net.corda.cordform.CordformNode -import net.corda.core.identity.CordaX500Name -import net.corda.core.utilities.NetworkHostAndPort import net.corda.testing.eventually import org.junit.Before import org.junit.Rule @@ -30,17 +28,13 @@ class NodeInfoFilesCopierTest { private const val NODE_2_PATH = "node2" private val content = "blah".toByteArray(Charsets.UTF_8) - private val GOOD_NODE_INFO_NAME = "nodeInfo-test" - private val GOOD_NODE_INFO_NAME_2 = "nodeInfo-anotherNode" + private val GOOD_NODE_INFO_NAME = "${NodeInfoFilesCopier.NODE_INFO_FILE_NAME_PREFIX}test" + private val GOOD_NODE_INFO_NAME_2 = "${NodeInfoFilesCopier.NODE_INFO_FILE_NAME_PREFIX}anotherNode" private val BAD_NODE_INFO_NAME = "something" - private val legalName = CordaX500Name(organisation = ORGANIZATION, locality = "Nowhere", country = "GB") - private val hostAndPort = NetworkHostAndPort("localhost", 1) } private fun nodeDir(nodeBaseDir : String) = rootPath.resolve(nodeBaseDir).resolve(ORGANIZATION.toLowerCase()) - private val node1Config by lazy { createConfig(NODE_1_PATH) } - private val node2Config by lazy { createConfig(NODE_2_PATH) } private val node1RootPath by lazy { nodeDir(NODE_1_PATH) } private val node2RootPath by lazy { nodeDir(NODE_2_PATH) } private val node1AdditionalNodeInfoPath by lazy { node1RootPath.resolve(CordformNode.NODE_INFO_DIRECTORY) } @@ -56,7 +50,7 @@ class NodeInfoFilesCopierTest { @Test fun `files created before a node is started are copied to that node`() { // Configure the first node. - nodeInfoFilesCopier.addConfig(node1Config) + nodeInfoFilesCopier.addConfig(node1RootPath) // Ensure directories are created. advanceTime() @@ -65,7 +59,7 @@ class NodeInfoFilesCopierTest { Files.write(node1RootPath.resolve(BAD_NODE_INFO_NAME), content) // Configure the second node. - nodeInfoFilesCopier.addConfig(node2Config) + nodeInfoFilesCopier.addConfig(node2RootPath) advanceTime() eventually(Duration.ofMinutes(1)) { @@ -77,8 +71,8 @@ class NodeInfoFilesCopierTest { @Test fun `polling of running nodes`() { // Configure 2 nodes. - nodeInfoFilesCopier.addConfig(node1Config) - nodeInfoFilesCopier.addConfig(node2Config) + nodeInfoFilesCopier.addConfig(node1RootPath) + nodeInfoFilesCopier.addConfig(node2RootPath) advanceTime() // Create 2 files, one of which to be copied, in a node root path. @@ -95,8 +89,8 @@ class NodeInfoFilesCopierTest { @Test fun `remove nodes`() { // Configure 2 nodes. - nodeInfoFilesCopier.addConfig(node1Config) - nodeInfoFilesCopier.addConfig(node2Config) + nodeInfoFilesCopier.addConfig(node1RootPath) + nodeInfoFilesCopier.addConfig(node2RootPath) advanceTime() // Create a file, in node 2 root path. @@ -104,7 +98,7 @@ class NodeInfoFilesCopierTest { advanceTime() // Remove node 2 - nodeInfoFilesCopier.removeConfig(node2Config) + nodeInfoFilesCopier.removeConfig(node2RootPath) // Create another file in node 2 directory. Files.write(node2RootPath.resolve(GOOD_NODE_INFO_NAME_2), content) @@ -119,8 +113,8 @@ class NodeInfoFilesCopierTest { @Test fun `clear`() { // Configure 2 nodes. - nodeInfoFilesCopier.addConfig(node1Config) - nodeInfoFilesCopier.addConfig(node2Config) + nodeInfoFilesCopier.addConfig(node1RootPath) + nodeInfoFilesCopier.addConfig(node2RootPath) advanceTime() nodeInfoFilesCopier.reset() @@ -142,15 +136,4 @@ class NodeInfoFilesCopierTest { val onlyFileName = Files.list(path).toList().first().fileName.toString() assertEquals(filename, onlyFileName) } - - private fun createConfig(relativePath: String) = - NodeConfigWrapper(rootPath.resolve(relativePath), - NodeConfig(myLegalName = legalName, - p2pAddress = hostAndPort, - rpcAddress = hostAndPort, - webAddress = hostAndPort, - h2port = -1, - notary = null, - networkMapService = null, - rpcUsers = listOf())) } \ No newline at end of file diff --git a/node/src/integration-test/kotlin/net/corda/node/BootTests.kt b/node/src/integration-test/kotlin/net/corda/node/BootTests.kt index 525790ea22..f17465ac47 100644 --- a/node/src/integration-test/kotlin/net/corda/node/BootTests.kt +++ b/node/src/integration-test/kotlin/net/corda/node/BootTests.kt @@ -14,7 +14,6 @@ import net.corda.nodeapi.internal.ServiceType import net.corda.testing.ALICE import net.corda.testing.ProjectStructure.projectRootDir import net.corda.testing.driver.ListenProcessDeathException -import net.corda.testing.driver.NetworkMapStartStrategy import net.corda.testing.driver.driver import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThatThrownBy @@ -59,7 +58,7 @@ class BootTests { @Test fun `node quits on failure to register with network map`() { val tooManyAdvertisedServices = (1..100).map { ServiceInfo(ServiceType.notary.getSubType("$it")) }.toSet() - driver(networkMapStartStrategy = NetworkMapStartStrategy.Nominated(ALICE.name)) { + driver { val future = startNode(providedName = ALICE.name) assertFailsWith(ListenProcessDeathException::class) { future.getOrThrow() } } diff --git a/node/src/integration-test/kotlin/net/corda/node/NodeStartupPerformanceTests.kt b/node/src/integration-test/kotlin/net/corda/node/NodeStartupPerformanceTests.kt index 2f29d8df6d..a9f2586013 100644 --- a/node/src/integration-test/kotlin/net/corda/node/NodeStartupPerformanceTests.kt +++ b/node/src/integration-test/kotlin/net/corda/node/NodeStartupPerformanceTests.kt @@ -1,7 +1,6 @@ package net.corda.node import com.google.common.base.Stopwatch -import net.corda.testing.driver.NetworkMapStartStrategy import net.corda.testing.driver.driver import org.junit.Ignore import org.junit.Test @@ -14,8 +13,7 @@ class NodeStartupPerformanceTests { // Measure the startup time of nodes. Note that this includes an RPC roundtrip, which causes e.g. Kryo initialisation. @Test fun `single node startup time`() { - driver(networkMapStartStrategy = NetworkMapStartStrategy.Dedicated(startAutomatically = false)) { - startDedicatedNetworkMapService().get() + driver { val times = ArrayList() for (i in 1..10) { val time = Stopwatch.createStarted().apply { diff --git a/node/src/integration-test/kotlin/net/corda/node/services/network/NodeInfoWatcherTest.kt b/node/src/integration-test/kotlin/net/corda/node/services/network/NodeInfoWatcherTest.kt index 97e2deb364..d307a02705 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/network/NodeInfoWatcherTest.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/network/NodeInfoWatcherTest.kt @@ -8,6 +8,7 @@ import net.corda.core.internal.div import net.corda.core.node.NodeInfo import net.corda.core.node.services.KeyManagementService import net.corda.node.services.identity.InMemoryIdentityService +import net.corda.nodeapi.NodeInfoFilesCopier import net.corda.testing.ALICE import net.corda.testing.ALICE_KEY import net.corda.testing.DEV_TRUST_ROOT @@ -42,7 +43,6 @@ class NodeInfoWatcherTest : NodeBasedTest() { lateinit var nodeInfoWatcher: NodeInfoWatcher companion object { - val nodeInfoFileRegex = Regex("nodeInfo\\-.*") val nodeInfo = NodeInfo(listOf(), listOf(getTestPartyAndCertificate(ALICE)), 0, 0) } @@ -56,13 +56,14 @@ class NodeInfoWatcherTest : NodeBasedTest() { @Test fun `save a NodeInfo`() { - assertEquals(0, folder.root.list().filter { it.matches(nodeInfoFileRegex) }.size) + assertEquals(0, + folder.root.list().filter { it.startsWith(NodeInfoFilesCopier.NODE_INFO_FILE_NAME_PREFIX) }.size) NodeInfoWatcher.saveToFile(folder.root.toPath(), nodeInfo, keyManagementService) - val nodeInfoFiles = folder.root.list().filter { it.matches(nodeInfoFileRegex) } + val nodeInfoFiles = folder.root.list().filter { it.startsWith(NodeInfoFilesCopier.NODE_INFO_FILE_NAME_PREFIX) } assertEquals(1, nodeInfoFiles.size) val fileName = nodeInfoFiles.first() - assertTrue(fileName.matches(nodeInfoFileRegex)) + assertTrue(fileName.startsWith(NodeInfoFilesCopier.NODE_INFO_FILE_NAME_PREFIX)) val file = (folder.root.path / fileName).toFile() // Just check that something is written, another tests verifies that the written value can be read back. assertThat(contentOf(file)).isNotEmpty() diff --git a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt index df01661af0..781d8f1663 100644 --- a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt +++ b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt @@ -552,8 +552,16 @@ abstract class AbstractNode(config: NodeConfiguration, } } + private fun setupInNodeNetworkMapService(networkMapCache: NetworkMapCacheInternal) { + inNodeNetworkMapService = + if (configuration.networkMapService == null && !configuration.noNetworkMapServiceMode) + makeNetworkMapService(network, networkMapCache) + else + NullNetworkMapService + } + private fun makeNetworkServices(network: MessagingService, networkMapCache: NetworkMapCacheInternal, tokenizableServices: MutableList) { - inNodeNetworkMapService = if (configuration.networkMapService == null) makeNetworkMapService(network, networkMapCache) else NullNetworkMapService + setupInNodeNetworkMapService(networkMapCache) configuration.notary?.let { val notaryService = makeCoreNotaryService(it) tokenizableServices.add(notaryService) @@ -612,7 +620,7 @@ abstract class AbstractNode(config: NodeConfiguration, /** This is overriden by the mock node implementation to enable operation without any network map service */ protected open fun noNetworkMapConfigured(): CordaFuture { - if (services.networkMapCache.loadDBSuccess) { + if (services.networkMapCache.loadDBSuccess || configuration.noNetworkMapServiceMode) { return doneFuture(Unit) } else { // TODO: There should be a consistent approach to configuration error exceptions. diff --git a/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt b/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt index 782064bf76..6d150a3ab3 100644 --- a/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt +++ b/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt @@ -20,6 +20,7 @@ interface NodeConfiguration : NodeSSLConfiguration { * service. */ val networkMapService: NetworkMapInfo? + val noNetworkMapServiceMode: Boolean val minimumPlatformVersion: Int val emailAddress: String val exportJMXto: String @@ -78,6 +79,7 @@ data class FullNodeConfiguration( override val database: Properties?, override val certificateSigningService: URL, override val networkMapService: NetworkMapInfo?, + override val noNetworkMapServiceMode: Boolean = false, override val minimumPlatformVersion: Int = 1, override val rpcUsers: List, override val verifierType: VerifierType, diff --git a/node/src/main/kotlin/net/corda/node/services/network/NodeInfoWatcher.kt b/node/src/main/kotlin/net/corda/node/services/network/NodeInfoWatcher.kt index 65ef358c6d..4bd9c72cb4 100644 --- a/node/src/main/kotlin/net/corda/node/services/network/NodeInfoWatcher.kt +++ b/node/src/main/kotlin/net/corda/node/services/network/NodeInfoWatcher.kt @@ -9,6 +9,7 @@ import net.corda.core.serialization.deserialize import net.corda.core.serialization.serialize import net.corda.core.utilities.loggerFor import net.corda.core.utilities.seconds +import net.corda.nodeapi.NodeInfoFilesCopier import rx.Observable import rx.Scheduler import rx.schedulers.Schedulers @@ -55,7 +56,8 @@ class NodeInfoWatcher(private val nodePath: Path, val serializedBytes = nodeInfo.serialize() val regSig = keyManager.sign(serializedBytes.bytes, nodeInfo.legalIdentities.first().owningKey) val signedData = SignedData(serializedBytes, regSig) - signedData.serialize().open().copyTo(path / "nodeInfo-${serializedBytes.hash}") + signedData.serialize().open().copyTo( + path / "${NodeInfoFilesCopier.NODE_INFO_FILE_NAME_PREFIX}${serializedBytes.hash}") } catch (e: Exception) { logger.warn("Couldn't write node info to file", e) } diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/RPCDriver.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/RPCDriver.kt index c5156c16e2..4de8f86442 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/RPCDriver.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/RPCDriver.kt @@ -228,7 +228,6 @@ fun rpcDriver( systemProperties: Map = emptyMap(), useTestClock: Boolean = false, initialiseSerialization: Boolean = true, - networkMapStartStrategy: NetworkMapStartStrategy = NetworkMapStartStrategy.Dedicated(startAutomatically = false), startNodesInProcess: Boolean = false, extraCordappPackagesToScan: List = emptyList(), dsl: RPCDriverExposedDSLInterface.() -> A @@ -240,7 +239,6 @@ fun rpcDriver( systemProperties = systemProperties, driverDirectory = driverDirectory.toAbsolutePath(), useTestClock = useTestClock, - networkMapStartStrategy = networkMapStartStrategy, isDebug = isDebug, startNodesInProcess = startNodesInProcess, extraCordappPackagesToScan = extraCordappPackagesToScan diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt index ed9ca09e6d..b7eec16259 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt @@ -8,7 +8,6 @@ import com.typesafe.config.ConfigRenderOptions import net.corda.client.rpc.CordaRPCClient import net.corda.cordform.CordformContext import net.corda.cordform.CordformNode -import net.corda.cordform.NodeDefinition import net.corda.core.CordaException import net.corda.core.concurrent.CordaFuture import net.corda.core.concurrent.firstOf @@ -20,6 +19,8 @@ import net.corda.core.internal.div import net.corda.core.internal.times import net.corda.core.messaging.CordaRPCOps import net.corda.core.node.NodeInfo +import net.corda.core.node.services.NetworkMapCache +import net.corda.core.toFuture import net.corda.core.utilities.* import net.corda.node.internal.Node import net.corda.node.internal.NodeStartup @@ -28,6 +29,7 @@ import net.corda.node.internal.cordapp.CordappLoader import net.corda.node.services.config.* import net.corda.node.services.network.NetworkMapService import net.corda.node.utilities.ServiceIdentityGenerator +import net.corda.nodeapi.NodeInfoFilesCopier import net.corda.nodeapi.User import net.corda.nodeapi.config.parseAs import net.corda.nodeapi.config.toConfig @@ -37,6 +39,8 @@ import net.corda.testing.node.MockServices.Companion.MOCK_VERSION_INFO import okhttp3.OkHttpClient import okhttp3.Request import org.slf4j.Logger +import rx.Observable +import rx.observables.ConnectableObservable import java.io.File import java.net.* import java.nio.file.Path @@ -149,14 +153,6 @@ interface DriverDSLExposedInterface : CordformContext { */ fun startWebserver(handle: NodeHandle, maximumHeapSize: String): CordaFuture - /** - * Starts a network map service node. Note that only a single one should ever be running, so you will probably want - * to set networkMapStartStrategy to Dedicated(false) in your [driver] call. - * @param startInProcess Determines if the node should be started inside this process. If null the Driver-level - * value will be used. - */ - fun startDedicatedNetworkMapService(startInProcess: Boolean? = null, maximumHeapSize: String = "200m"): CordaFuture - fun waitForAllNodesToFinish() /** @@ -211,13 +207,15 @@ sealed class NodeHandle { override val configuration: FullNodeConfiguration, override val webAddress: NetworkHostAndPort, val debugPort: Int?, - val process: Process + val process: Process, + private val onStopCallback: () -> Unit ) : NodeHandle() { override fun stop(): CordaFuture { with(process) { destroy() waitFor() } + onStopCallback() return doneFuture(Unit) } } @@ -228,7 +226,8 @@ sealed class NodeHandle { override val configuration: FullNodeConfiguration, override val webAddress: NetworkHostAndPort, val node: StartedNode, - val nodeThread: Thread + val nodeThread: Thread, + private val onStopCallback: () -> Unit ) : NodeHandle() { override fun stop(): CordaFuture { node.dispose() @@ -236,6 +235,7 @@ sealed class NodeHandle { interrupt() join() } + onStopCallback() return doneFuture(Unit) } } @@ -316,7 +316,6 @@ data class NodeParameters( * @param debugPortAllocation The port allocation strategy to use for jvm debugging. Defaults to incremental. * @param systemProperties A Map of extra system properties which will be given to each new node. Defaults to empty. * @param useTestClock If true the test clock will be used in Node. - * @param networkMapStartStrategy Determines whether a network map node is started automatically. * @param startNodesInProcess Provides the default behaviour of whether new nodes should start inside this process or * not. Note that this may be overridden in [DriverDSLExposedInterface.startNode]. * @param dsl The dsl itself. @@ -331,7 +330,7 @@ fun driver( systemProperties: Map = defaultParameters.systemProperties, useTestClock: Boolean = defaultParameters.useTestClock, initialiseSerialization: Boolean = defaultParameters.initialiseSerialization, - networkMapStartStrategy: NetworkMapStartStrategy = defaultParameters.networkMapStartStrategy, + startNodesInProcess: Boolean = defaultParameters.startNodesInProcess, extraCordappPackagesToScan: List = defaultParameters.extraCordappPackagesToScan, dsl: DriverDSLExposedInterface.() -> A @@ -344,7 +343,6 @@ fun driver( driverDirectory = driverDirectory.toAbsolutePath(), useTestClock = useTestClock, isDebug = isDebug, - networkMapStartStrategy = networkMapStartStrategy, startNodesInProcess = startNodesInProcess, extraCordappPackagesToScan = extraCordappPackagesToScan ), @@ -379,7 +377,6 @@ data class DriverParameters( val systemProperties: Map = emptyMap(), val useTestClock: Boolean = false, val initialiseSerialization: Boolean = true, - val networkMapStartStrategy: NetworkMapStartStrategy = NetworkMapStartStrategy.Dedicated(startAutomatically = true), val startNodesInProcess: Boolean = false, val extraCordappPackagesToScan: List = emptyList() ) { @@ -390,7 +387,6 @@ data class DriverParameters( fun setSystemProperties(systemProperties: Map) = copy(systemProperties = systemProperties) fun setUseTestClock(useTestClock: Boolean) = copy(useTestClock = useTestClock) fun setInitialiseSerialization(initialiseSerialization: Boolean) = copy(initialiseSerialization = initialiseSerialization) - fun setNetworkMapStartStrategy(networkMapStartStrategy: NetworkMapStartStrategy) = copy(networkMapStartStrategy = networkMapStartStrategy) fun setStartNodesInProcess(startNodesInProcess: Boolean) = copy(startNodesInProcess = startNodesInProcess) fun setExtraCordappPackagesToScan(extraCordappPackagesToScan: List) = copy(extraCordappPackagesToScan = extraCordappPackagesToScan) } @@ -605,16 +601,20 @@ class DriverDSL( val driverDirectory: Path, val useTestClock: Boolean, val isDebug: Boolean, - val networkMapStartStrategy: NetworkMapStartStrategy, val startNodesInProcess: Boolean, extraCordappPackagesToScan: List ) : DriverDSLInternalInterface { - private val dedicatedNetworkMapAddress = portAllocation.nextHostAndPort() private var _executorService: ScheduledExecutorService? = null val executorService get() = _executorService!! private var _shutdownManager: ShutdownManager? = null override val shutdownManager get() = _shutdownManager!! private val cordappPackages = extraCordappPackagesToScan + getCallerPackage() + // TODO: this object will copy NodeInfo files from started nodes to other nodes additional-node-infos/ + // This uses the FileSystem and adds a delay (~5 seconds) given by the time we wait before polling the file system. + // Investigate whether we can avoid that. + private val nodeInfoFilesCopier = NodeInfoFilesCopier() + // Map from a nodes legal name to an observable emitting the number of nodes in its network map. + private val countObservables = mutableMapOf>() class State { val processes = ArrayList>() @@ -671,25 +671,6 @@ class DriverDSL( } } - private fun networkMapServiceConfigLookup(networkMapCandidates: List): (CordaX500Name) -> Map? { - return networkMapStartStrategy.run { - when (this) { - is NetworkMapStartStrategy.Dedicated -> { - serviceConfig(dedicatedNetworkMapAddress).let { - { _: CordaX500Name -> it } - } - } - is NetworkMapStartStrategy.Nominated -> { - serviceConfig(networkMapCandidates.single { - it.name == legalName.toString() - }.config.getString("p2pAddress").let(NetworkHostAndPort.Companion::parse)).let { - { nodeName: CordaX500Name -> if (nodeName == legalName) null else it } - } - } - } - } - } - override fun startNode( defaultParameters: NodeParameters, providedName: CordaX500Name?, @@ -704,10 +685,6 @@ class DriverDSL( val webAddress = portAllocation.nextHostAndPort() // TODO: Derive name from the full picked name, don't just wrap the common name val name = providedName ?: CordaX500Name(organisation = "${oneOf(names).organisation}-${p2pAddress.port}", locality = "London", country = "GB") - val networkMapServiceConfigLookup = networkMapServiceConfigLookup(listOf(object : NodeDefinition { - override fun getName() = name.toString() - override fun getConfig() = configOf("p2pAddress" to p2pAddress.toString()) - })) val config = ConfigHelper.loadConfig( baseDirectory = baseDirectory(name), allowMissingConfig = true, @@ -716,10 +693,10 @@ class DriverDSL( "p2pAddress" to p2pAddress.toString(), "rpcAddress" to rpcAddress.toString(), "webAddress" to webAddress.toString(), - "networkMapService" to networkMapServiceConfigLookup(name), "useTestClock" to useTestClock, "rpcUsers" to if (rpcUsers.isEmpty()) defaultRpcUserList else rpcUsers.map { it.toConfig().root().unwrapped() }, - "verifierType" to verifierType.name + "verifierType" to verifierType.name, + "noNetworkMapServiceMode" to true ) + customOverrides ) return startNodeInternal(config, webAddress, startInSameProcess, maximumHeapSize) @@ -735,7 +712,6 @@ class DriverDSL( } override fun startNodes(nodes: List, startInSameProcess: Boolean?, maximumHeapSize: String): List> { - val networkMapServiceConfigLookup = networkMapServiceConfigLookup(nodes) return nodes.map { node -> portAllocation.nextHostAndPort() // rpcAddress val webAddress = portAllocation.nextHostAndPort() @@ -746,8 +722,8 @@ class DriverDSL( baseDirectory = baseDirectory(name), allowMissingConfig = true, configOverrides = node.config + notary + mapOf( - "networkMapService" to networkMapServiceConfigLookup(name), - "rpcUsers" to if (rpcUsers.isEmpty()) defaultRpcUserList else rpcUsers + "rpcUsers" to if (rpcUsers.isEmpty()) defaultRpcUserList else rpcUsers, + "noNetworkMapServiceMode" to true ) ) startNodeInternal(config, webAddress, startInSameProcess, maximumHeapSize) @@ -833,9 +809,7 @@ class DriverDSL( override fun start() { _executorService = Executors.newScheduledThreadPool(2, ThreadFactoryBuilder().setNameFormat("driver-pool-thread-%d").build()) _shutdownManager = ShutdownManager(executorService) - if (networkMapStartStrategy.startDedicated) { - startDedicatedNetworkMapService().andForget(log) // Allow it to start concurrently with other nodes. - } + shutdownManager.registerShutdown { nodeInfoFilesCopier.close() } } fun baseDirectory(nodeName: CordaX500Name): Path { @@ -846,28 +820,57 @@ class DriverDSL( override fun baseDirectory(nodeName: String): Path = baseDirectory(CordaX500Name.parse(nodeName)) - override fun startDedicatedNetworkMapService(startInProcess: Boolean?, maximumHeapSize: String): CordaFuture { - val webAddress = portAllocation.nextHostAndPort() - val rpcAddress = portAllocation.nextHostAndPort() - val networkMapLegalName = networkMapStartStrategy.legalName - val config = ConfigHelper.loadConfig( - baseDirectory = baseDirectory(networkMapLegalName), - allowMissingConfig = true, - configOverrides = configOf( - "myLegalName" to networkMapLegalName.toString(), - // TODO: remove the webAddress as NMS doesn't need to run a web server. This will cause all - // node port numbers to be shifted, so all demos and docs need to be updated accordingly. - "webAddress" to webAddress.toString(), - "rpcAddress" to rpcAddress.toString(), - "rpcUsers" to defaultRpcUserList, - "p2pAddress" to dedicatedNetworkMapAddress.toString(), - "useTestClock" to useTestClock) - ) - return startNodeInternal(config, webAddress, startInProcess, maximumHeapSize) + /** + * @param initial number of nodes currently in the network map of a running node. + * @param networkMapCacheChangeObservable an observable returning the updates to the node network map. + * @return a [ConnectableObservable] which emits a new [Int] every time the number of registered nodes changes + * the initial value emitted is always [initial] + */ + private fun nodeCountObservable(initial: Int, networkMapCacheChangeObservable: Observable): + ConnectableObservable { + val count = AtomicInteger(initial) + return networkMapCacheChangeObservable.map { it -> + when (it) { + is NetworkMapCache.MapChange.Added -> count.incrementAndGet() + is NetworkMapCache.MapChange.Removed -> count.decrementAndGet() + is NetworkMapCache.MapChange.Modified -> count.get() + } + }.startWith(initial).replay() + } + + /** + * @param rpc the [CordaRPCOps] of a newly started node. + * @return a [CordaFuture] which resolves when every node started by driver has in its network map a number of nodes + * equal to the number of running nodes. The future will yield the number of connected nodes. + */ + private fun allNodesConnected(rpc: CordaRPCOps): CordaFuture { + val (snapshot, updates) = rpc.networkMapFeed() + val counterObservable = nodeCountObservable(snapshot.size, updates) + countObservables.put(rpc.nodeInfo().legalIdentities.first().name, counterObservable) + /* TODO: this might not always be the exact number of nodes one has to wait for, + * for example in the following sequence + * 1 start 3 nodes in order, A, B, C. + * 2 before the future returned by this function resolves, kill B + * At that point this future won't ever resolve as it will wait for nodes to know 3 other nodes. + */ + val requiredNodes = countObservables.size + + // This is an observable which yield the minimum number of nodes in each node network map. + val smallestSeenNetworkMapSize = Observable.combineLatest(countObservables.values.toList()) { args : Array -> + args.map { it as Int }.min() ?: 0 + } + val future = smallestSeenNetworkMapSize.filter { it >= requiredNodes }.toFuture() + counterObservable.connect() + return future } private fun startNodeInternal(config: Config, webAddress: NetworkHostAndPort, startInProcess: Boolean?, maximumHeapSize: String): CordaFuture { val nodeConfiguration = config.parseAs() + nodeInfoFilesCopier.addConfig(nodeConfiguration.baseDirectory) + val onNodeExit: () -> Unit = { + nodeInfoFilesCopier.removeConfig(nodeConfiguration.baseDirectory) + countObservables.remove(nodeConfiguration.myLegalName) + } if (startInProcess ?: startNodesInProcess) { val nodeAndThreadFuture = startInProcessNode(executorService, nodeConfiguration, config, cordappPackages) shutdownManager.registerShutdown( @@ -880,8 +883,8 @@ class DriverDSL( ) return nodeAndThreadFuture.flatMap { (node, thread) -> establishRpc(nodeConfiguration, openFuture()).flatMap { rpc -> - rpc.waitUntilNetworkReady().map { - NodeHandle.InProcess(rpc.nodeInfo(), rpc, nodeConfiguration, webAddress, node, thread) + allNodesConnected(rpc).map { + NodeHandle.InProcess(rpc.nodeInfo(), rpc, nodeConfiguration, webAddress, node, thread, onNodeExit) } } } @@ -896,7 +899,7 @@ class DriverDSL( establishRpc(nodeConfiguration, processDeathFuture).flatMap { rpc -> // Call waitUntilNetworkReady in background in case RPC is failing over: val forked = executorService.fork { - rpc.waitUntilNetworkReady() + allNodesConnected(rpc) } val networkMapFuture = forked.flatMap { it } firstOf(processDeathFuture, networkMapFuture) { @@ -905,7 +908,8 @@ class DriverDSL( } processDeathFuture.cancel(false) log.info("Node handle is ready. NodeInfo: ${rpc.nodeInfo()}, WebAddress: ${webAddress}") - NodeHandle.OutOfProcess(rpc.nodeInfo(), rpc, nodeConfiguration, webAddress, debugPort, process) + NodeHandle.OutOfProcess(rpc.nodeInfo(), rpc, nodeConfiguration, webAddress, debugPort, process, + onNodeExit) } } } diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/driver/NetworkMapStartStrategy.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/driver/NetworkMapStartStrategy.kt deleted file mode 100644 index 0086884c77..0000000000 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/driver/NetworkMapStartStrategy.kt +++ /dev/null @@ -1,23 +0,0 @@ -package net.corda.testing.driver - -import net.corda.core.identity.CordaX500Name -import net.corda.core.utilities.NetworkHostAndPort -import net.corda.testing.DUMMY_MAP - -sealed class NetworkMapStartStrategy { - internal abstract val startDedicated: Boolean - internal abstract val legalName: CordaX500Name - internal fun serviceConfig(address: NetworkHostAndPort) = mapOf( - "address" to address.toString(), - "legalName" to legalName.toString() - ) - - class Dedicated(startAutomatically: Boolean) : NetworkMapStartStrategy() { - override val startDedicated = startAutomatically - override val legalName = DUMMY_MAP.name - } - - class Nominated(override val legalName: CordaX500Name) : NetworkMapStartStrategy() { - override val startDedicated = false - } -} diff --git a/tools/demobench/src/main/kotlin/net/corda/demobench/model/DemoBenchNodeInfoFilesCopier.kt b/tools/demobench/src/main/kotlin/net/corda/demobench/model/DemoBenchNodeInfoFilesCopier.kt new file mode 100644 index 0000000000..a0818c3532 --- /dev/null +++ b/tools/demobench/src/main/kotlin/net/corda/demobench/model/DemoBenchNodeInfoFilesCopier.kt @@ -0,0 +1,35 @@ +package net.corda.demobench.model + +import net.corda.nodeapi.NodeInfoFilesCopier +import rx.Scheduler +import rx.schedulers.Schedulers +import tornadofx.* + +/** + * Utility class which copies nodeInfo files across a set of running nodes. + * + * This class will create paths that it needs to poll and to where it needs to copy files in case those + * don't exist yet. + */ +class DemoBenchNodeInfoFilesCopier(scheduler: Scheduler = Schedulers.io()): Controller() { + + private val nodeInfoFilesCopier = NodeInfoFilesCopier(scheduler) + + /** + * @param nodeConfig the configuration to be added. + * Add a [NodeConfig] for a node which is about to be started. + * Its nodeInfo file will be copied to other nodes' additional-node-infos directory, and conversely, + * other nodes' nodeInfo files will be copied to this node additional-node-infos directory. + */ + fun addConfig(nodeConfig: NodeConfigWrapper) : Unit = nodeInfoFilesCopier.addConfig(nodeConfig.nodeDir) + + /** + * @param nodeConfig the configuration to be removed. + * Remove the configuration of a node which is about to be stopped or already stopped. + * No files written by that node will be copied to other nodes, nor files from other nodes will be copied to this + * one. + */ + fun removeConfig(nodeConfig: NodeConfigWrapper) : Unit = nodeInfoFilesCopier.removeConfig(nodeConfig.nodeDir) + + fun reset() : Unit = nodeInfoFilesCopier.reset() +} \ No newline at end of file diff --git a/tools/demobench/src/main/kotlin/net/corda/demobench/model/NodeController.kt b/tools/demobench/src/main/kotlin/net/corda/demobench/model/NodeController.kt index ddd8010bb4..2830c969b8 100644 --- a/tools/demobench/src/main/kotlin/net/corda/demobench/model/NodeController.kt +++ b/tools/demobench/src/main/kotlin/net/corda/demobench/model/NodeController.kt @@ -28,7 +28,7 @@ class NodeController(check: atRuntime = ::checkExists) : Controller() { private val jvm by inject() private val cordappController by inject() - private val nodeInfoFilesCopier by inject() + private val nodeInfoFilesCopier by inject() private var baseDir: Path = baseDirFor(ManagementFactory.getRuntimeMXBean().startTime) private val cordaPath: Path = jvm.applicationDir.resolve("corda").resolve("corda.jar") diff --git a/tools/demobench/src/main/kotlin/net/corda/demobench/model/NodeInfoFilesCopier.kt b/tools/demobench/src/main/kotlin/net/corda/demobench/model/NodeInfoFilesCopier.kt deleted file mode 100644 index ed69e38b93..0000000000 --- a/tools/demobench/src/main/kotlin/net/corda/demobench/model/NodeInfoFilesCopier.kt +++ /dev/null @@ -1,133 +0,0 @@ -package net.corda.demobench.model - -import net.corda.cordform.CordformNode -import net.corda.core.internal.createDirectories -import net.corda.core.internal.isRegularFile -import net.corda.core.internal.list -import rx.Observable -import rx.Scheduler -import rx.schedulers.Schedulers -import tornadofx.* -import java.io.IOException -import java.nio.file.Files -import java.nio.file.Path -import java.nio.file.StandardCopyOption.COPY_ATTRIBUTES -import java.nio.file.StandardCopyOption.REPLACE_EXISTING -import java.nio.file.attribute.BasicFileAttributes -import java.nio.file.attribute.FileTime -import java.util.concurrent.TimeUnit -import java.util.logging.Level - -/** - * Utility class which copies nodeInfo files across a set of running nodes. - * - * This class will create paths that it needs to poll and to where it needs to copy files in case those - * don't exist yet. - */ -class NodeInfoFilesCopier(scheduler: Scheduler = Schedulers.io()): Controller() { - - private val nodeDataMap = mutableMapOf() - - init { - Observable.interval(5, TimeUnit.SECONDS, scheduler) - .subscribe { poll() } - } - - /** - * @param nodeConfig the configuration to be added. - * Add a [NodeConfig] for a node which is about to be started. - * Its nodeInfo file will be copied to other nodes' additional-node-infos directory, and conversely, - * other nodes' nodeInfo files will be copied to this node additional-node-infos directory. - */ - @Synchronized - fun addConfig(nodeConfig: NodeConfigWrapper) { - val newNodeFile = NodeData(nodeConfig.nodeDir) - nodeDataMap[nodeConfig.nodeDir] = newNodeFile - - for (previouslySeenFile in allPreviouslySeenFiles()) { - copy(previouslySeenFile, newNodeFile.destination.resolve(previouslySeenFile.fileName)) - } - log.info("Now watching: ${nodeConfig.nodeDir}") - } - - /** - * @param nodeConfig the configuration to be removed. - * Remove the configuration of a node which is about to be stopped or already stopped. - * No files written by that node will be copied to other nodes, nor files from other nodes will be copied to this - * one. - */ - @Synchronized - fun removeConfig(nodeConfig: NodeConfigWrapper) { - nodeDataMap.remove(nodeConfig.nodeDir) ?: return - log.info("Stopped watching: ${nodeConfig.nodeDir}") - } - - @Synchronized - fun reset() { - nodeDataMap.clear() - } - - private fun allPreviouslySeenFiles() = nodeDataMap.values.map { it.previouslySeenFiles.keys }.flatten() - - @Synchronized - private fun poll() { - for (nodeData in nodeDataMap.values) { - nodeData.nodeDir.list { paths -> - paths.filter { it.isRegularFile() } - .filter { it.fileName.toString().startsWith("nodeInfo-") } - .forEach { path -> processPath(nodeData, path) } - } - } - } - - // Takes a path under nodeData config dir and decides whether the file represented by that path needs to - // be copied. - private fun processPath(nodeData: NodeData, path: Path) { - val newTimestamp = Files.readAttributes(path, BasicFileAttributes::class.java).lastModifiedTime() - val previousTimestamp = nodeData.previouslySeenFiles.put(path, newTimestamp) ?: FileTime.fromMillis(-1) - if (newTimestamp > previousTimestamp) { - for (destination in nodeDataMap.values.filter { it.nodeDir != nodeData.nodeDir }.map { it.destination }) { - val fullDestinationPath = destination.resolve(path.fileName) - copy(path, fullDestinationPath) - } - } - } - - private fun copy(source: Path, destination: Path) { - val tempDestination = try { - Files.createTempFile(destination.parent, ".", null) - } catch (exception: IOException) { - log.log(Level.WARNING, "Couldn't create a temporary file to copy $source", exception) - throw exception - } - try { - // First copy the file to a temporary file within the appropriate directory. - Files.copy(source, tempDestination, COPY_ATTRIBUTES, REPLACE_EXISTING) - } catch (exception: IOException) { - log.log(Level.WARNING, "Couldn't copy $source to $tempDestination.", exception) - Files.delete(tempDestination) - throw exception - } - try { - // Then rename it to the desired name. This way the file 'appears' on the filesystem as an atomic operation. - Files.move(tempDestination, destination, REPLACE_EXISTING) - } catch (exception: IOException) { - log.log(Level.WARNING, "Couldn't move $tempDestination to $destination.", exception) - Files.delete(tempDestination) - throw exception - } - } - - /** - * Convenience holder for all the paths and files relative to a single node. - */ - private class NodeData(val nodeDir: Path) { - val destination: Path = nodeDir.resolve(CordformNode.NODE_INFO_DIRECTORY) - // Map from Path to its lastModifiedTime. - val previouslySeenFiles = mutableMapOf() - - init { - destination.createDirectories() - } - } -} diff --git a/verifier/src/integration-test/kotlin/net/corda/verifier/VerifierDriver.kt b/verifier/src/integration-test/kotlin/net/corda/verifier/VerifierDriver.kt index 00fa22724f..279d34f6b2 100644 --- a/verifier/src/integration-test/kotlin/net/corda/verifier/VerifierDriver.kt +++ b/verifier/src/integration-test/kotlin/net/corda/verifier/VerifierDriver.kt @@ -74,7 +74,6 @@ fun verifierDriver( debugPortAllocation: PortAllocation = PortAllocation.Incremental(5005), systemProperties: Map = emptyMap(), useTestClock: Boolean = false, - networkMapStartStrategy: NetworkMapStartStrategy = NetworkMapStartStrategy.Dedicated(startAutomatically = false), startNodesInProcess: Boolean = false, extraCordappPackagesToScan: List = emptyList(), dsl: VerifierExposedDSLInterface.() -> A @@ -86,7 +85,6 @@ fun verifierDriver( systemProperties = systemProperties, driverDirectory = driverDirectory.toAbsolutePath(), useTestClock = useTestClock, - networkMapStartStrategy = networkMapStartStrategy, isDebug = isDebug, startNodesInProcess = startNodesInProcess, extraCordappPackagesToScan = extraCordappPackagesToScan diff --git a/verifier/src/integration-test/kotlin/net/corda/verifier/VerifierTests.kt b/verifier/src/integration-test/kotlin/net/corda/verifier/VerifierTests.kt index aec877b3d8..dc228fd6f6 100644 --- a/verifier/src/integration-test/kotlin/net/corda/verifier/VerifierTests.kt +++ b/verifier/src/integration-test/kotlin/net/corda/verifier/VerifierTests.kt @@ -13,9 +13,9 @@ import net.corda.finance.flows.CashIssueFlow import net.corda.finance.flows.CashPaymentFlow import net.corda.node.services.config.VerifierType import net.corda.testing.ALICE +import net.corda.testing.ALICE_NAME import net.corda.testing.DUMMY_NOTARY -import net.corda.testing.* -import net.corda.testing.driver.NetworkMapStartStrategy +import net.corda.testing.DUMMY_NOTARY_SERVICE_NAME import org.junit.Test import java.util.* import java.util.concurrent.atomic.AtomicInteger @@ -129,10 +129,7 @@ class VerifierTests { @Test fun `single verifier works with a node`() { - verifierDriver( - networkMapStartStrategy = NetworkMapStartStrategy.Dedicated(startAutomatically = true), - extraCordappPackagesToScan = listOf("net.corda.finance.contracts") - ) { + verifierDriver(extraCordappPackagesToScan = listOf("net.corda.finance.contracts")) { val aliceFuture = startNode(providedName = ALICE.name) val notaryFuture = startNotaryNode(DUMMY_NOTARY.name, verifierType = VerifierType.OutOfProcess) val aliceNode = aliceFuture.get() From cf3b080d0cef8b6d22bf1378881e1c4b0f8f3e74 Mon Sep 17 00:00:00 2001 From: josecoll Date: Wed, 18 Oct 2017 14:54:47 +0100 Subject: [PATCH 10/94] Added missing cordappPackage dependencies. (#1894) --- .../net/corda/finance/contracts/asset/CashSelectionH2Test.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/finance/src/test/kotlin/net/corda/finance/contracts/asset/CashSelectionH2Test.kt b/finance/src/test/kotlin/net/corda/finance/contracts/asset/CashSelectionH2Test.kt index ef85dead0a..e0cb3297b5 100644 --- a/finance/src/test/kotlin/net/corda/finance/contracts/asset/CashSelectionH2Test.kt +++ b/finance/src/test/kotlin/net/corda/finance/contracts/asset/CashSelectionH2Test.kt @@ -1,9 +1,11 @@ package net.corda.finance.contracts.asset +import net.corda.core.internal.packageName import net.corda.core.utilities.getOrThrow import net.corda.finance.DOLLARS import net.corda.finance.flows.CashException import net.corda.finance.flows.CashPaymentFlow +import net.corda.finance.schemas.CashSchemaV1 import net.corda.testing.chooseIdentity import net.corda.testing.node.MockNetwork import org.assertj.core.api.Assertions.assertThatThrownBy @@ -14,7 +16,7 @@ class CashSelectionH2Test { @Test fun `check does not hold connection over retries`() { - val mockNet = MockNetwork(threadPerNode = true) + val mockNet = MockNetwork(threadPerNode = true, cordappPackages = listOf("net.corda.finance.contracts.asset", CashSchemaV1::class.packageName)) try { val notaryNode = mockNet.createNotaryNode() val bankA = mockNet.createNode(configOverrides = { existingConfig -> From b2454c646c96d5666fec5f002da5d1ad4a48735d Mon Sep 17 00:00:00 2001 From: Andrzej Cichocki Date: Thu, 19 Oct 2017 09:26:26 +0100 Subject: [PATCH 11/94] Eliminate circular dependency of NodeSchedulerService on ServiceHub. (#1891) --- .../core/flows/ContractUpgradeFlowTest.kt | 2 +- .../net/corda/core/flows/FinalityFlowTests.kt | 6 +- .../net/corda/docs/CustomVaultQueryTest.kt | 2 +- .../WorkflowTransactionBuildTutorialTest.kt | 6 +- .../net/corda/node/internal/AbstractNode.kt | 91 +++++++++---------- .../corda/node/internal/CordaRPCOpsImpl.kt | 6 +- .../net/corda/node/internal/StartedNode.kt | 11 ++- .../node/services/api/ServiceHubInternal.kt | 8 +- .../services/events/NodeSchedulerService.kt | 28 +++--- .../net/corda/node/CordaRPCOpsImplTest.kt | 2 +- .../events/NodeSchedulerServiceTest.kt | 3 +- .../transactions/NotaryServiceTests.kt | 6 +- .../ValidatingNotaryServiceTests.kt | 6 +- .../corda/irs/api/NodeInterestRatesTest.kt | 2 +- .../node/testing/MockServiceHubInternal.kt | 13 +-- 15 files changed, 96 insertions(+), 96 deletions(-) diff --git a/core/src/test/kotlin/net/corda/core/flows/ContractUpgradeFlowTest.kt b/core/src/test/kotlin/net/corda/core/flows/ContractUpgradeFlowTest.kt index 782d3ee35f..eba602a47e 100644 --- a/core/src/test/kotlin/net/corda/core/flows/ContractUpgradeFlowTest.kt +++ b/core/src/test/kotlin/net/corda/core/flows/ContractUpgradeFlowTest.kt @@ -119,7 +119,7 @@ class ContractUpgradeFlowTest { return startRpcClient( rpcAddress = startRpcServer( rpcUser = user, - ops = CordaRPCOpsImpl(node.services, node.smm, node.database) + ops = CordaRPCOpsImpl(node.services, node.smm, node.database, node.services) ).get().broker.hostAndPort!!, username = user.username, password = user.password diff --git a/core/src/test/kotlin/net/corda/core/flows/FinalityFlowTests.kt b/core/src/test/kotlin/net/corda/core/flows/FinalityFlowTests.kt index bdc76e1d83..eec08b4c6b 100644 --- a/core/src/test/kotlin/net/corda/core/flows/FinalityFlowTests.kt +++ b/core/src/test/kotlin/net/corda/core/flows/FinalityFlowTests.kt @@ -6,7 +6,7 @@ import net.corda.core.utilities.getOrThrow import net.corda.finance.POUNDS import net.corda.finance.contracts.asset.Cash import net.corda.finance.issuedBy -import net.corda.node.services.api.ServiceHubInternal +import net.corda.node.services.api.StartedNodeServices import net.corda.testing.* import net.corda.testing.node.MockNetwork import org.junit.After @@ -17,8 +17,8 @@ import kotlin.test.assertFailsWith class FinalityFlowTests { private lateinit var mockNet: MockNetwork - private lateinit var aliceServices: ServiceHubInternal - private lateinit var bobServices: ServiceHubInternal + private lateinit var aliceServices: StartedNodeServices + private lateinit var bobServices: StartedNodeServices private lateinit var alice: Party private lateinit var bob: Party private lateinit var notary: Party diff --git a/docs/source/example-code/src/test/kotlin/net/corda/docs/CustomVaultQueryTest.kt b/docs/source/example-code/src/test/kotlin/net/corda/docs/CustomVaultQueryTest.kt index 1ec43e7271..509cb65312 100644 --- a/docs/source/example-code/src/test/kotlin/net/corda/docs/CustomVaultQueryTest.kt +++ b/docs/source/example-code/src/test/kotlin/net/corda/docs/CustomVaultQueryTest.kt @@ -32,7 +32,7 @@ class CustomVaultQueryTest { nodeA = mockNet.createPartyNode() nodeB = mockNet.createPartyNode() nodeA.internals.registerInitiatedFlow(TopupIssuerFlow.TopupIssuer::class.java) - nodeA.internals.installCordaService(CustomVaultQuery.Service::class.java) + nodeA.installCordaService(CustomVaultQuery.Service::class.java) notary = nodeA.services.getDefaultNotary() } diff --git a/docs/source/example-code/src/test/kotlin/net/corda/docs/WorkflowTransactionBuildTutorialTest.kt b/docs/source/example-code/src/test/kotlin/net/corda/docs/WorkflowTransactionBuildTutorialTest.kt index f7b99ead79..833f4c140d 100644 --- a/docs/source/example-code/src/test/kotlin/net/corda/docs/WorkflowTransactionBuildTutorialTest.kt +++ b/docs/source/example-code/src/test/kotlin/net/corda/docs/WorkflowTransactionBuildTutorialTest.kt @@ -9,7 +9,7 @@ import net.corda.core.node.services.queryBy import net.corda.core.node.services.vault.QueryCriteria import net.corda.core.toFuture import net.corda.core.utilities.getOrThrow -import net.corda.node.services.api.ServiceHubInternal +import net.corda.node.services.api.StartedNodeServices import net.corda.testing.* import net.corda.testing.node.MockNetwork import org.junit.After @@ -19,8 +19,8 @@ import kotlin.test.assertEquals class WorkflowTransactionBuildTutorialTest { lateinit var mockNet: MockNetwork - lateinit var aliceServices: ServiceHubInternal - lateinit var bobServices: ServiceHubInternal + lateinit var aliceServices: StartedNodeServices + lateinit var bobServices: StartedNodeServices lateinit var alice: Party lateinit var bob: Party diff --git a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt index 781d8f1663..148e630def 100644 --- a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt +++ b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt @@ -78,6 +78,7 @@ import java.security.cert.CertificateFactory import java.security.cert.X509Certificate import java.sql.Connection import java.time.Clock +import java.util.* import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ExecutorService import java.util.concurrent.TimeUnit.SECONDS @@ -108,7 +109,7 @@ abstract class AbstractNode(config: NodeConfiguration, private class StartedNodeImpl( override val internals: N, - override val services: ServiceHubInternalImpl, + services: ServiceHubInternalImpl, override val info: NodeInfo, override val checkpointStorage: CheckpointStorage, override val smm: StateMachineManager, @@ -116,8 +117,11 @@ abstract class AbstractNode(config: NodeConfiguration, override val inNodeNetworkMapService: NetworkMapService, override val network: MessagingService, override val database: CordaPersistence, - override val rpcOps: CordaRPCOps) : StartedNode - + override val rpcOps: CordaRPCOps, + flowStarter: FlowStarter, + internal val schedulerService: NodeSchedulerService) : StartedNode { + override val services: StartedNodeServices = object : StartedNodeServices, ServiceHubInternal by services, FlowStarter by flowStarter {} + } // TODO: Persist this, as well as whether the node is registered. /** * Sequence number of changes sent to the network map service, when registering/de-registering this node. @@ -167,8 +171,8 @@ abstract class AbstractNode(config: NodeConfiguration, @Volatile private var _started: StartedNode? = null /** The implementation of the [CordaRPCOps] interface used by this node. */ - open fun makeRPCOps(): CordaRPCOps { - return CordaRPCOpsImpl(services, smm, database) + open fun makeRPCOps(flowStarter: FlowStarter): CordaRPCOps { + return CordaRPCOpsImpl(services, smm, database, flowStarter) } private fun saveOwnNodeInfo() { @@ -190,7 +194,8 @@ abstract class AbstractNode(config: NodeConfiguration, log.info("Generating nodeInfo ...") val schemaService = makeSchemaService() initialiseDatabasePersistence(schemaService) { - makeServices(schemaService) + val transactionStorage = makeTransactionStorage() + makeServices(schemaService, transactionStorage, StateLoaderImpl(transactionStorage)) saveOwnNodeInfo() } } @@ -202,7 +207,9 @@ abstract class AbstractNode(config: NodeConfiguration, val schemaService = makeSchemaService() // Do all of this in a database transaction so anything that might need a connection has one. val startedImpl = initialiseDatabasePersistence(schemaService) { - val tokenizableServices = makeServices(schemaService) + val transactionStorage = makeTransactionStorage() + val stateLoader = StateLoaderImpl(transactionStorage) + val tokenizableServices = makeServices(schemaService, transactionStorage, stateLoader) saveOwnNodeInfo() smm = StateMachineManager(services, checkpointStorage, @@ -210,9 +217,10 @@ abstract class AbstractNode(config: NodeConfiguration, database, busyNodeLatch, cordappLoader.appClassLoader) - + val flowStarter = FlowStarterImpl(serverThread, smm) + val schedulerService = NodeSchedulerService(platformClock, this@AbstractNode.database, flowStarter, stateLoader, unfinishedSchedules = busyNodeLatch, serverThread = serverThread) smm.tokenizableServices.addAll(tokenizableServices) - + smm.tokenizableServices.add(schedulerService) if (serverThread is ExecutorService) { runOnStop += { // We wait here, even though any in-flight messages should have been drained away because the @@ -221,20 +229,17 @@ abstract class AbstractNode(config: NodeConfiguration, MoreExecutors.shutdownAndAwaitTermination(serverThread as ExecutorService, 50, SECONDS) } } - - makeVaultObservers() - - val rpcOps = makeRPCOps() + makeVaultObservers(schedulerService) + val rpcOps = makeRPCOps(flowStarter) startMessagingService(rpcOps) installCoreFlows() - - installCordaServices() + installCordaServices(flowStarter) registerCordappFlows() _services.rpcFlows += cordappLoader.cordapps.flatMap { it.rpcFlows } FlowLogicRefFactoryImpl.classloader = cordappLoader.appClassLoader runOnStop += network::stop - StartedNodeImpl(this, _services, info, checkpointStorage, smm, attachments, inNodeNetworkMapService, network, database, rpcOps) + StartedNodeImpl(this, _services, info, checkpointStorage, smm, attachments, inNodeNetworkMapService, network, database, rpcOps, flowStarter, schedulerService) } // If we successfully loaded network data from database, we set this future to Unit. _nodeReadyFuture.captureLater(registerWithNetworkMapIfConfigured()) @@ -243,7 +248,7 @@ abstract class AbstractNode(config: NodeConfiguration, smm.start() // Shut down the SMM so no Fibers are scheduled. runOnStop += { smm.stop(acceptableLiveFiberCountOnStop()) } - services.schedulerService.start() + schedulerService.start() } _started = this } @@ -251,11 +256,11 @@ abstract class AbstractNode(config: NodeConfiguration, private class ServiceInstantiationException(cause: Throwable?) : CordaException("Service Instantiation Error", cause) - private fun installCordaServices() { + private fun installCordaServices(flowStarter: FlowStarter) { val loadedServices = cordappLoader.cordapps.flatMap { it.services } filterServicesToInstall(loadedServices).forEach { try { - installCordaService(it) + installCordaService(flowStarter, it) } catch (e: NoSuchMethodException) { log.error("${it.name}, as a Corda service, must have a constructor with a single parameter of type " + ServiceHub::class.java.name) @@ -288,7 +293,7 @@ abstract class AbstractNode(config: NodeConfiguration, /** * This customizes the ServiceHub for each CordaService that is initiating flows */ - private class AppServiceHubImpl(val serviceHub: ServiceHubInternal) : AppServiceHub, ServiceHub by serviceHub { + private class AppServiceHubImpl(private val serviceHub: ServiceHub, private val flowStarter: FlowStarter) : AppServiceHub, ServiceHub by serviceHub { lateinit var serviceInstance: T override fun startTrackedFlow(flow: FlowLogic): FlowProgressHandle { val stateMachine = startFlowChecked(flow) @@ -308,34 +313,24 @@ abstract class AbstractNode(config: NodeConfiguration, val logicType = flow.javaClass require(logicType.isAnnotationPresent(StartableByService::class.java)) { "${logicType.name} was not designed for starting by a CordaService" } val currentUser = FlowInitiator.Service(serviceInstance.javaClass.name) - return serviceHub.startFlow(flow, currentUser) + return flowStarter.startFlow(flow, currentUser) } override fun equals(other: Any?): Boolean { if (this === other) return true if (other !is AppServiceHubImpl<*>) return false - - if (serviceHub != other.serviceHub) return false - if (serviceInstance != other.serviceInstance) return false - - return true + return serviceHub == other.serviceHub + && flowStarter == other.flowStarter + && serviceInstance == other.serviceInstance } - override fun hashCode(): Int { - var result = serviceHub.hashCode() - result = 31 * result + serviceInstance.hashCode() - return result - } + override fun hashCode() = Objects.hash(serviceHub, flowStarter, serviceInstance) } - /** - * Use this method to install your Corda services in your tests. This is automatically done by the node when it - * starts up for all classes it finds which are annotated with [CordaService]. - */ - fun installCordaService(serviceClass: Class): T { + internal fun installCordaService(flowStarter: FlowStarter, serviceClass: Class): T { serviceClass.requireAnnotation() val service = try { - val serviceContext = AppServiceHubImpl(services) + val serviceContext = AppServiceHubImpl(services, flowStarter) if (isNotaryService(serviceClass)) { check(myNotaryIdentity != null) { "Trying to install a notary service but no notary identity specified" } val constructor = serviceClass.getDeclaredConstructor(AppServiceHub::class.java, PublicKey::class.java).apply { isAccessible = true } @@ -466,19 +461,18 @@ abstract class AbstractNode(config: NodeConfiguration, * Builds node internal, advertised, and plugin services. * Returns a list of tokenizable services to be added to the serialisation context. */ - private fun makeServices(schemaService: SchemaService): MutableList { + private fun makeServices(schemaService: SchemaService, transactionStorage: WritableTransactionStorage, stateLoader: StateLoader): MutableList { checkpointStorage = DBCheckpointStorage() - val transactionStorage = makeTransactionStorage() val metrics = MetricRegistry() attachments = NodeAttachmentService(metrics) val cordappProvider = CordappProviderImpl(cordappLoader, attachments) - _services = ServiceHubInternalImpl(schemaService, transactionStorage, StateLoaderImpl(transactionStorage), MonitoringService(metrics), cordappProvider) + _services = ServiceHubInternalImpl(schemaService, transactionStorage, stateLoader, MonitoringService(metrics), cordappProvider) legalIdentity = obtainIdentity(notaryConfig = null) network = makeMessagingService(legalIdentity) info = makeInfo(legalIdentity) val networkMapCache = services.networkMapCache val tokenizableServices = mutableListOf(attachments, network, services.vaultService, - services.keyManagementService, services.identityService, platformClock, services.schedulerService, + services.keyManagementService, services.identityService, platformClock, services.auditService, services.monitoringService, networkMapCache, services.schemaService, services.transactionVerifierService, services.validatedTransactions, services.contractUpgradeService, services, cordappProvider, this) @@ -488,9 +482,9 @@ abstract class AbstractNode(config: NodeConfiguration, protected open fun makeTransactionStorage(): WritableTransactionStorage = DBTransactionStorage() - private fun makeVaultObservers() { + private fun makeVaultObservers(schedulerService: SchedulerService) { VaultSoftLockManager.install(services.vaultService, smm) - ScheduledActivityObserver.install(services.vaultService, services.schedulerService) + ScheduledActivityObserver.install(services.vaultService, schedulerService) HibernateObserver.install(services.vaultService.rawUpdates, database.hibernateConfig) } @@ -788,7 +782,6 @@ abstract class AbstractNode(config: NodeConfiguration, // the KMS is meant for derived temporary keys used in transactions, and we're not supposed to sign things with // the identity key. But the infrastructure to make that easy isn't here yet. override val keyManagementService by lazy { makeKeyManagementService(identityService) } - override val schedulerService by lazy { NodeSchedulerService(this, unfinishedSchedules = busyNodeLatch, serverThread = serverThread) } override val identityService by lazy { val trustStore = KeyStoreWrapper(configuration.trustStoreFile, configuration.trustStorePassword) val caKeyStore = KeyStoreWrapper(configuration.nodeKeystore, configuration.keyStorePassword) @@ -808,10 +801,6 @@ abstract class AbstractNode(config: NodeConfiguration, return cordappServices.getInstance(type) ?: throw IllegalArgumentException("Corda service ${type.name} does not exist") } - override fun startFlow(logic: FlowLogic, flowInitiator: FlowInitiator, ourIdentity: Party?): FlowStateMachineImpl { - return serverThread.fetchFrom { smm.add(logic, flowInitiator, ourIdentity) } - } - override fun getFlowFactory(initiatingFlowClass: Class>): InitiatedFlowFactory<*>? { return flowFactories[initiatingFlowClass] } @@ -825,3 +814,9 @@ abstract class AbstractNode(config: NodeConfiguration, override fun jdbcSession(): Connection = database.createSession() } } + +internal class FlowStarterImpl(private val serverThread: AffinityExecutor, private val smm: StateMachineManager) : FlowStarter { + override fun startFlow(logic: FlowLogic, flowInitiator: FlowInitiator, ourIdentity: Party?): FlowStateMachineImpl { + return serverThread.fetchFrom { smm.add(logic, flowInitiator, ourIdentity) } + } +} diff --git a/node/src/main/kotlin/net/corda/node/internal/CordaRPCOpsImpl.kt b/node/src/main/kotlin/net/corda/node/internal/CordaRPCOpsImpl.kt index ee685466d3..af827b9e39 100644 --- a/node/src/main/kotlin/net/corda/node/internal/CordaRPCOpsImpl.kt +++ b/node/src/main/kotlin/net/corda/node/internal/CordaRPCOpsImpl.kt @@ -19,6 +19,7 @@ import net.corda.core.node.services.vault.QueryCriteria import net.corda.core.node.services.vault.Sort import net.corda.core.transactions.SignedTransaction import net.corda.node.services.FlowPermissions.Companion.startFlowPermission +import net.corda.node.services.api.FlowStarter import net.corda.node.services.api.ServiceHubInternal import net.corda.node.services.messaging.getRpcContext import net.corda.node.services.messaging.requirePermission @@ -37,7 +38,8 @@ import java.time.Instant class CordaRPCOpsImpl( private val services: ServiceHubInternal, private val smm: StateMachineManager, - private val database: CordaPersistence + private val database: CordaPersistence, + private val flowStarter: FlowStarter ) : CordaRPCOps { override fun networkMapSnapshot(): List { val (snapshot, updates) = networkMapFeed() @@ -150,7 +152,7 @@ class CordaRPCOpsImpl( rpcContext.requirePermission(startFlowPermission(logicType)) val currentUser = FlowInitiator.RPC(rpcContext.currentUser.username) // TODO RPC flows should have mapping user -> identity that should be resolved automatically on starting flow. - return services.invokeFlowAsync(logicType, currentUser, *args) + return flowStarter.invokeFlowAsync(logicType, currentUser, *args) } override fun attachmentExists(id: SecureHash): Boolean { diff --git a/node/src/main/kotlin/net/corda/node/internal/StartedNode.kt b/node/src/main/kotlin/net/corda/node/internal/StartedNode.kt index e0320d9c62..2d0645cef4 100644 --- a/node/src/main/kotlin/net/corda/node/internal/StartedNode.kt +++ b/node/src/main/kotlin/net/corda/node/internal/StartedNode.kt @@ -7,9 +7,11 @@ import net.corda.core.flows.FlowLogic import net.corda.core.messaging.CordaRPCOps import net.corda.core.node.NodeInfo import net.corda.core.node.StateLoader +import net.corda.core.node.services.CordaService import net.corda.core.node.services.TransactionStorage +import net.corda.core.serialization.SerializeAsToken import net.corda.node.services.api.CheckpointStorage -import net.corda.node.services.api.ServiceHubInternal +import net.corda.node.services.api.StartedNodeServices import net.corda.node.services.messaging.MessagingService import net.corda.node.services.network.NetworkMapService import net.corda.node.services.persistence.NodeAttachmentService @@ -18,7 +20,7 @@ import net.corda.node.utilities.CordaPersistence interface StartedNode { val internals: N - val services: ServiceHubInternal + val services: StartedNodeServices val info: NodeInfo val checkpointStorage: CheckpointStorage val smm: StateMachineManager @@ -29,6 +31,11 @@ interface StartedNode { val rpcOps: CordaRPCOps fun dispose() = internals.stop() fun > registerInitiatedFlow(initiatedFlowClass: Class) = internals.registerInitiatedFlow(initiatedFlowClass) + /** + * Use this method to install your Corda services in your tests. This is automatically done by the node when it + * starts up for all classes it finds which are annotated with [CordaService]. + */ + fun installCordaService(serviceClass: Class) = internals.installCordaService(services, serviceClass) } class StateLoaderImpl(private val validatedTransactions: TransactionStorage) : StateLoader { diff --git a/node/src/main/kotlin/net/corda/node/services/api/ServiceHubInternal.kt b/node/src/main/kotlin/net/corda/node/services/api/ServiceHubInternal.kt index 5fdb7993a0..36f9f1fada 100644 --- a/node/src/main/kotlin/net/corda/node/services/api/ServiceHubInternal.kt +++ b/node/src/main/kotlin/net/corda/node/services/api/ServiceHubInternal.kt @@ -84,7 +84,6 @@ interface ServiceHubInternal : ServiceHub { val monitoringService: MonitoringService val schemaService: SchemaService override val networkMapCache: NetworkMapCacheInternal - val schedulerService: SchedulerService val auditService: AuditService val rpcFlows: List>> val networkService: MessagingService @@ -109,6 +108,10 @@ interface ServiceHubInternal : ServiceHub { } } + fun getFlowFactory(initiatingFlowClass: Class>): InitiatedFlowFactory<*>? +} + +interface FlowStarter { /** * Starts an already constructed flow. Note that you must be on the server thread to call this method. [FlowInitiator] * defaults to [FlowInitiator.RPC] with username "Only For Testing". @@ -138,10 +141,9 @@ interface ServiceHubInternal : ServiceHub { val logic: FlowLogic = uncheckedCast(FlowLogicRefFactoryImpl.toFlowLogic(logicRef)) return startFlow(logic, flowInitiator, ourIdentity = null) } - - fun getFlowFactory(initiatingFlowClass: Class>): InitiatedFlowFactory<*>? } +interface StartedNodeServices : ServiceHubInternal, FlowStarter /** * Thread-safe storage of transactions. */ diff --git a/node/src/main/kotlin/net/corda/node/services/events/NodeSchedulerService.kt b/node/src/main/kotlin/net/corda/node/services/events/NodeSchedulerService.kt index 26a8322b80..a0c208a53e 100644 --- a/node/src/main/kotlin/net/corda/node/services/events/NodeSchedulerService.kt +++ b/node/src/main/kotlin/net/corda/node/services/events/NodeSchedulerService.kt @@ -14,15 +14,17 @@ import net.corda.core.flows.FlowLogic import net.corda.core.internal.ThreadBox import net.corda.core.internal.VisibleForTesting import net.corda.core.internal.until +import net.corda.core.node.StateLoader import net.corda.core.schemas.PersistentStateRef import net.corda.core.serialization.SingletonSerializeAsToken import net.corda.core.utilities.loggerFor import net.corda.core.utilities.trace import net.corda.node.internal.MutableClock +import net.corda.node.services.api.FlowStarter import net.corda.node.services.api.SchedulerService -import net.corda.node.services.api.ServiceHubInternal import net.corda.node.services.statemachine.FlowLogicRefFactoryImpl import net.corda.node.utilities.AffinityExecutor +import net.corda.node.utilities.CordaPersistence import net.corda.node.utilities.NODE_DATABASE_PREFIX import net.corda.node.utilities.PersistentMap import org.apache.activemq.artemis.utils.ReusableLatch @@ -47,12 +49,14 @@ import javax.persistence.Entity * in the nodes, maybe we can consider multiple activities and whether the activities have been completed or not, * but that starts to sound a lot like off-ledger state. * - * @param services Core node services. * @param schedulerTimerExecutor The executor the scheduler blocks on waiting for the clock to advance to the next * activity. Only replace this for unit testing purposes. This is not the executor the [FlowLogic] is launched on. */ @ThreadSafe -class NodeSchedulerService(private val services: ServiceHubInternal, +class NodeSchedulerService(private val clock: Clock, + private val database: CordaPersistence, + private val flowStarter: FlowStarter, + private val stateLoader: StateLoader, private val schedulerTimerExecutor: Executor = Executors.newSingleThreadExecutor(), private val unfinishedSchedules: ReusableLatch = ReusableLatch(), private val serverThread: AffinityExecutor) @@ -108,8 +112,8 @@ class NodeSchedulerService(private val services: ServiceHubInternal, toPersistentEntityKey = { PersistentStateRef(it.txhash.toString(), it.index) }, fromPersistentEntity = { //TODO null check will become obsolete after making DB/JPA columns not nullable - var txId = it.output.txId ?: throw IllegalStateException("DB returned null SecureHash transactionId") - var index = it.output.index ?: throw IllegalStateException("DB returned null SecureHash index") + val txId = it.output.txId ?: throw IllegalStateException("DB returned null SecureHash transactionId") + val index = it.output.index ?: throw IllegalStateException("DB returned null SecureHash index") Pair(StateRef(SecureHash.parse(txId), index), ScheduledStateRef(StateRef(SecureHash.parse(txId), index), it.scheduledAt)) }, @@ -172,7 +176,7 @@ class NodeSchedulerService(private val services: ServiceHubInternal, mutex.locked { val previousState = scheduledStates[action.ref] scheduledStates[action.ref] = action - var previousEarliest = scheduledStatesQueue.peek() + val previousEarliest = scheduledStatesQueue.peek() scheduledStatesQueue.remove(previousState) scheduledStatesQueue.add(action) if (previousState == null) { @@ -223,7 +227,7 @@ class NodeSchedulerService(private val services: ServiceHubInternal, log.trace { "Scheduling as next $scheduledState" } // This will block the scheduler single thread until the scheduled time (returns false) OR // the Future is cancelled due to rescheduling (returns true). - if (!awaitWithDeadline(services.clock, scheduledState.scheduledAt, ourRescheduledFuture)) { + if (!awaitWithDeadline(clock, scheduledState.scheduledAt, ourRescheduledFuture)) { log.trace { "Invoking as next $scheduledState" } onTimeReached(scheduledState) } else { @@ -237,11 +241,11 @@ class NodeSchedulerService(private val services: ServiceHubInternal, serverThread.execute { var flowName: String? = "(unknown)" try { - services.database.transaction { + database.transaction { val scheduledFlow = getScheduledFlow(scheduledState) if (scheduledFlow != null) { flowName = scheduledFlow.javaClass.name - val future = services.startFlow(scheduledFlow, FlowInitiator.Scheduled(scheduledState)).resultFuture + val future = flowStarter.startFlow(scheduledFlow, FlowInitiator.Scheduled(scheduledState)).resultFuture future.then { unfinishedSchedules.countDown() } @@ -265,9 +269,9 @@ class NodeSchedulerService(private val services: ServiceHubInternal, unfinishedSchedules.countDown() scheduledStates.remove(scheduledState.ref) scheduledStatesQueue.remove(scheduledState) - } else if (scheduledActivity.scheduledAt.isAfter(services.clock.instant())) { + } else if (scheduledActivity.scheduledAt.isAfter(clock.instant())) { log.info("Scheduled state $scheduledState has rescheduled to ${scheduledActivity.scheduledAt}.") - var newState = ScheduledStateRef(scheduledState.ref, scheduledActivity.scheduledAt) + val newState = ScheduledStateRef(scheduledState.ref, scheduledActivity.scheduledAt) scheduledStates[scheduledState.ref] = newState scheduledStatesQueue.remove(scheduledState) scheduledStatesQueue.add(newState) @@ -286,7 +290,7 @@ class NodeSchedulerService(private val services: ServiceHubInternal, } private fun getScheduledActivity(scheduledState: ScheduledStateRef): ScheduledActivity? { - val txState = services.loadState(scheduledState.ref) + val txState = stateLoader.loadState(scheduledState.ref) val state = txState.data as SchedulableState return try { // This can throw as running contract code. diff --git a/node/src/test/kotlin/net/corda/node/CordaRPCOpsImplTest.kt b/node/src/test/kotlin/net/corda/node/CordaRPCOpsImplTest.kt index 0020970296..ec69a89fe5 100644 --- a/node/src/test/kotlin/net/corda/node/CordaRPCOpsImplTest.kt +++ b/node/src/test/kotlin/net/corda/node/CordaRPCOpsImplTest.kt @@ -65,7 +65,7 @@ class CordaRPCOpsImplTest { mockNet = MockNetwork(cordappPackages = listOf("net.corda.finance.contracts.asset")) aliceNode = mockNet.createNode() notaryNode = mockNet.createNotaryNode(validating = false) - rpc = CordaRPCOpsImpl(aliceNode.services, aliceNode.smm, aliceNode.database) + rpc = CordaRPCOpsImpl(aliceNode.services, aliceNode.smm, aliceNode.database, aliceNode.services) CURRENT_RPC_CONTEXT.set(RpcContext(User("user", "pwd", permissions = setOf( startFlowPermission(), startFlowPermission() diff --git a/node/src/test/kotlin/net/corda/node/services/events/NodeSchedulerServiceTest.kt b/node/src/test/kotlin/net/corda/node/services/events/NodeSchedulerServiceTest.kt index d54bfa8754..2040fb562b 100644 --- a/node/src/test/kotlin/net/corda/node/services/events/NodeSchedulerServiceTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/events/NodeSchedulerServiceTest.kt @@ -12,6 +12,7 @@ import net.corda.core.node.ServiceHub import net.corda.core.serialization.SingletonSerializeAsToken import net.corda.core.transactions.TransactionBuilder import net.corda.core.utilities.days +import net.corda.node.internal.FlowStarterImpl import net.corda.node.internal.cordapp.CordappLoader import net.corda.node.internal.cordapp.CordappProviderImpl import net.corda.node.services.api.VaultServiceInternal @@ -100,8 +101,8 @@ class NodeSchedulerServiceTest : SingletonSerializeAsToken() { override val cordappProvider = CordappProviderImpl(CordappLoader.createWithTestPackages(listOf("net.corda.testing.contracts")), attachments) } smmExecutor = AffinityExecutor.ServiceAffinityExecutor("test", 1) - scheduler = NodeSchedulerService(services, schedulerGatedExecutor, serverThread = smmExecutor) val mockSMM = StateMachineManager(services, DBCheckpointStorage(), smmExecutor, database) + scheduler = NodeSchedulerService(testClock, database, FlowStarterImpl(smmExecutor, mockSMM), services.stateLoader, schedulerGatedExecutor, serverThread = smmExecutor) mockSMM.changes.subscribe { change -> if (change is StateMachineManager.Change.Removed && mockSMM.allStateMachines.isEmpty()) { smmHasRemovedAllFlows.countDown() diff --git a/node/src/test/kotlin/net/corda/node/services/transactions/NotaryServiceTests.kt b/node/src/test/kotlin/net/corda/node/services/transactions/NotaryServiceTests.kt index ec8155a18e..29ade9ec4f 100644 --- a/node/src/test/kotlin/net/corda/node/services/transactions/NotaryServiceTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/transactions/NotaryServiceTests.kt @@ -13,7 +13,7 @@ import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.TransactionBuilder import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.seconds -import net.corda.node.services.api.ServiceHubInternal +import net.corda.node.services.api.StartedNodeServices import net.corda.testing.* import net.corda.testing.contracts.DummyContract import net.corda.testing.node.MockNetwork @@ -28,8 +28,8 @@ import kotlin.test.assertFailsWith class NotaryServiceTests { lateinit var mockNet: MockNetwork - lateinit var notaryServices: ServiceHubInternal - lateinit var aliceServices: ServiceHubInternal + lateinit var notaryServices: StartedNodeServices + lateinit var aliceServices: StartedNodeServices lateinit var notary: Party lateinit var alice: Party diff --git a/node/src/test/kotlin/net/corda/node/services/transactions/ValidatingNotaryServiceTests.kt b/node/src/test/kotlin/net/corda/node/services/transactions/ValidatingNotaryServiceTests.kt index 3700848e47..6f3dd1ac66 100644 --- a/node/src/test/kotlin/net/corda/node/services/transactions/ValidatingNotaryServiceTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/transactions/ValidatingNotaryServiceTests.kt @@ -13,7 +13,7 @@ import net.corda.core.node.ServiceHub import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.TransactionBuilder import net.corda.core.utilities.getOrThrow -import net.corda.node.services.api.ServiceHubInternal +import net.corda.node.services.api.StartedNodeServices import net.corda.node.services.issueInvalidState import net.corda.testing.* import net.corda.testing.contracts.DummyContract @@ -28,8 +28,8 @@ import kotlin.test.assertFailsWith class ValidatingNotaryServiceTests { lateinit var mockNet: MockNetwork - lateinit var notaryServices: ServiceHubInternal - lateinit var aliceServices: ServiceHubInternal + lateinit var notaryServices: StartedNodeServices + lateinit var aliceServices: StartedNodeServices lateinit var notary: Party lateinit var alice: Party diff --git a/samples/irs-demo/src/test/kotlin/net/corda/irs/api/NodeInterestRatesTest.kt b/samples/irs-demo/src/test/kotlin/net/corda/irs/api/NodeInterestRatesTest.kt index d43192cdf8..3953c876b0 100644 --- a/samples/irs-demo/src/test/kotlin/net/corda/irs/api/NodeInterestRatesTest.kt +++ b/samples/irs-demo/src/test/kotlin/net/corda/irs/api/NodeInterestRatesTest.kt @@ -206,7 +206,7 @@ class NodeInterestRatesTest : TestDependencyInjectionBase() { internals.registerInitiatedFlow(NodeInterestRates.FixQueryHandler::class.java) internals.registerInitiatedFlow(NodeInterestRates.FixSignHandler::class.java) database.transaction { - internals.installCordaService(NodeInterestRates.Oracle::class.java).knownFixes = TEST_DATA + installCordaService(NodeInterestRates.Oracle::class.java).knownFixes = TEST_DATA } } val tx = makePartialTX() diff --git a/testing/node-driver/src/main/kotlin/net/corda/node/testing/MockServiceHubInternal.kt b/testing/node-driver/src/main/kotlin/net/corda/node/testing/MockServiceHubInternal.kt index 1188e1571e..3c4631376e 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/node/testing/MockServiceHubInternal.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/node/testing/MockServiceHubInternal.kt @@ -1,9 +1,7 @@ package net.corda.node.testing import com.codahale.metrics.MetricRegistry -import net.corda.core.flows.FlowInitiator import net.corda.core.flows.FlowLogic -import net.corda.core.identity.Party import net.corda.core.node.NodeInfo import net.corda.core.node.StateLoader import net.corda.core.node.services.* @@ -17,7 +15,6 @@ import net.corda.node.serialization.NodeClock import net.corda.node.services.api.* import net.corda.node.services.config.NodeConfiguration import net.corda.node.services.messaging.MessagingService -import net.corda.node.services.statemachine.FlowStateMachineImpl import net.corda.node.services.statemachine.StateMachineManager import net.corda.node.services.transactions.InMemoryTransactionVerifierService import net.corda.node.utilities.CordaPersistence @@ -43,12 +40,11 @@ open class MockServiceHubInternal( override val validatedTransactions: WritableTransactionStorage = MockTransactionStorage(), override val stateMachineRecordedTransactionMapping: StateMachineRecordedTransactionMappingStorage = MockStateMachineRecordedTransactionMappingStorage(), val mapCache: NetworkMapCacheInternal? = null, - val scheduler: SchedulerService? = null, val overrideClock: Clock? = NodeClock(), val customContractUpgradeService: ContractUpgradeService? = null, val customTransactionVerifierService: TransactionVerifierService? = InMemoryTransactionVerifierService(2), override val cordappProvider: CordappProviderInternal = CordappProviderImpl(CordappLoader.createDefault(Paths.get(".")), attachments), - protected val stateLoader: StateLoaderImpl = StateLoaderImpl(validatedTransactions) + val stateLoader: StateLoaderImpl = StateLoaderImpl(validatedTransactions) ) : ServiceHubInternal, StateLoader by stateLoader { override val transactionVerifierService: TransactionVerifierService get() = customTransactionVerifierService ?: throw UnsupportedOperationException() @@ -64,8 +60,6 @@ open class MockServiceHubInternal( get() = network ?: throw UnsupportedOperationException() override val networkMapCache: NetworkMapCacheInternal get() = mapCache ?: MockNetworkMapCache(this) - override val schedulerService: SchedulerService - get() = scheduler ?: throw UnsupportedOperationException() override val clock: Clock get() = overrideClock ?: throw UnsupportedOperationException() override val myInfo: NodeInfo @@ -79,11 +73,6 @@ open class MockServiceHubInternal( lateinit var smm: StateMachineManager override fun cordaService(type: Class): T = throw UnsupportedOperationException() - - override fun startFlow(logic: FlowLogic, flowInitiator: FlowInitiator, ourIdentity: Party?): FlowStateMachineImpl { - return smm.executor.fetchFrom { smm.add(logic, flowInitiator, ourIdentity) } - } - override fun getFlowFactory(initiatingFlowClass: Class>): InitiatedFlowFactory<*>? = null override fun jdbcSession(): Connection = database.createSession() From 717365413d6f3d130260dfe0454ff52274de3891 Mon Sep 17 00:00:00 2001 From: Andrzej Cichocki Date: Thu, 19 Oct 2017 09:26:40 +0100 Subject: [PATCH 12/94] Update customSchemas documentation. (#1902) --- docs/source/api-persistence.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/source/api-persistence.rst b/docs/source/api-persistence.rst index add8f1b785..c2c6e88e46 100644 --- a/docs/source/api-persistence.rst +++ b/docs/source/api-persistence.rst @@ -81,10 +81,10 @@ Custom schema registration Custom contract schemas are automatically registered at startup time for CorDapps. The node bootstrap process will scan for schemas (any class that extends the ``MappedSchema`` interface) in the `plugins` configuration directory in your CorDapp jar. -For testing purposes it is necessary to manually register custom schemas as follows: +For testing purposes it is necessary to manually register the packages containing custom schemas as follows: -- Tests using ``MockNetwork`` and ``MockNode`` must explicitly register custom schemas using the `registerCustomSchemas()` method of ``MockNode`` -- Tests using ``MockServices`` must explicitly register schemas using `customSchemas` attribute of the ``MockServices`` `makeTestDatabaseAndMockServices()` helper method. +- Tests using ``MockNetwork`` and ``MockNode`` must explicitly register packages using the `cordappPackages` parameter of ``MockNetwork`` +- Tests using ``MockServices`` must explicitly register packages using the `cordappPackages` parameter of the ``MockServices`` `makeTestDatabaseAndMockServices()` helper method. .. note:: Tests using the `DriverDSL` will automatically register your custom schemas if they are in the same project structure as the driver call. From 479ab9a36a25b3a16e7a280a7e8527fac20c8e06 Mon Sep 17 00:00:00 2001 From: Konstantinos Chalkias Date: Thu, 19 Oct 2017 10:21:20 +0100 Subject: [PATCH 13/94] [CORDA-694] Commands visibility for Oracles (without sacrificing privacy) (#1835) new checkCommandVisibility feature for Oracles --- .../core/contracts/ComponentGroupEnum.kt | 3 +- .../corda/core/crypto/PartialMerkleTree.kt | 37 ++ .../core/transactions/MerkleTransaction.kt | 91 ++++- .../core/transactions/WireTransaction.kt | 11 +- .../contracts/CompatibleTransactionTests.kt | 347 ++++++++++++++++-- .../core/crypto/PartialMerkleTreeTest.kt | 71 +++- docs/source/changelog.rst | 11 +- .../net/corda/irs/api/NodeInterestRates.kt | 2 +- 8 files changed, 521 insertions(+), 52 deletions(-) diff --git a/core/src/main/kotlin/net/corda/core/contracts/ComponentGroupEnum.kt b/core/src/main/kotlin/net/corda/core/contracts/ComponentGroupEnum.kt index c32792a998..514abb986a 100644 --- a/core/src/main/kotlin/net/corda/core/contracts/ComponentGroupEnum.kt +++ b/core/src/main/kotlin/net/corda/core/contracts/ComponentGroupEnum.kt @@ -10,5 +10,6 @@ enum class ComponentGroupEnum { COMMANDS_GROUP, // ordinal = 2. ATTACHMENTS_GROUP, // ordinal = 3. NOTARY_GROUP, // ordinal = 4. - TIMEWINDOW_GROUP // ordinal = 5. + TIMEWINDOW_GROUP, // ordinal = 5. + SIGNERS_GROUP // ordinal = 6. } diff --git a/core/src/main/kotlin/net/corda/core/crypto/PartialMerkleTree.kt b/core/src/main/kotlin/net/corda/core/crypto/PartialMerkleTree.kt index 77e06bf1c2..b46eb7f631 100644 --- a/core/src/main/kotlin/net/corda/core/crypto/PartialMerkleTree.kt +++ b/core/src/main/kotlin/net/corda/core/crypto/PartialMerkleTree.kt @@ -158,4 +158,41 @@ class PartialMerkleTree(val root: PartialTree) { return false return (verifyRoot == merkleRootHash) } + + /** + * Method to return the index of the input leaf in the partial Merkle tree structure. + * @param leaf the component hash to check. + * @return leaf-index of this component (starting from zero). + * @throws MerkleTreeException if the provided hash is not in the tree. + */ + @Throws(MerkleTreeException::class) + internal fun leafIndex(leaf: SecureHash): Int { + // Special handling if the tree consists of one node only. + if (root is PartialTree.IncludedLeaf && root.hash == leaf) return 0 + val flagPath = mutableListOf() + if (!leafIndexHelper(leaf, this.root, flagPath)) throw MerkleTreeException("The provided hash $leaf is not in the tree.") + return indexFromFlagPath(flagPath) + } + + // Helper function to compute the path. False means go to the left and True to the right. + // Because the path is updated recursively, the path is returned in reverse order. + private fun leafIndexHelper(leaf: SecureHash, node: PartialTree, path: MutableList): Boolean { + if (node is PartialTree.IncludedLeaf) { + return node.hash == leaf + } else if (node is PartialTree.Node) { + if (leafIndexHelper(leaf, node.left, path)) { + path.add(false) + return true + } + if (leafIndexHelper(leaf, node.right, path)) { + path.add(true) + return true + } + } + return false + } + + // Return the leaf index from the path boolean list. + private fun indexFromFlagPath(pathList: List) = + pathList.mapIndexed { index, value -> if (value) (1 shl index) else 0 }.sum() } diff --git a/core/src/main/kotlin/net/corda/core/transactions/MerkleTransaction.kt b/core/src/main/kotlin/net/corda/core/transactions/MerkleTransaction.kt index 8f2c476004..ba7cb8de1f 100644 --- a/core/src/main/kotlin/net/corda/core/transactions/MerkleTransaction.kt +++ b/core/src/main/kotlin/net/corda/core/transactions/MerkleTransaction.kt @@ -25,7 +25,7 @@ abstract class TraversableTransaction(open val componentGroups: List> = deserialiseComponentGroup(ComponentGroupEnum.OUTPUTS_GROUP, { SerializedBytes>(it).deserialize(context = SerializationFactory.defaultFactory.defaultContext.withAttachmentsClassLoader(attachments)) }) /** Ordered list of ([CommandData], [PublicKey]) pairs that instruct the contracts what to do. */ - val commands: List> = deserialiseComponentGroup(ComponentGroupEnum.COMMANDS_GROUP, { SerializedBytes>(it).deserialize(context = SerializationFactory.defaultFactory.defaultContext.withAttachmentsClassLoader(attachments)) }) + val commands: List> = deserialiseCommands() override val notary: Party? = let { val notaries: List = deserialiseComponentGroup(ComponentGroupEnum.NOTARY_GROUP, { SerializedBytes(it).deserialize() }) @@ -74,6 +74,31 @@ abstract class TraversableTransaction(open val componentGroups: List> { + // TODO: we could avoid deserialising unrelated signers. + // However, current approach ensures the transaction is not malformed + // and it will throw if any of the signers objects is not List of public keys). + val signersList = deserialiseComponentGroup(ComponentGroupEnum.SIGNERS_GROUP, { SerializedBytes>(it).deserialize() }) + val commandDataList = deserialiseComponentGroup(ComponentGroupEnum.COMMANDS_GROUP, { SerializedBytes(it).deserialize(context = SerializationFactory.defaultFactory.defaultContext.withAttachmentsClassLoader(attachments)) }) + val group = componentGroups.firstOrNull { it.groupIndex == ComponentGroupEnum.COMMANDS_GROUP.ordinal } + if (group is FilteredComponentGroup) { + check(commandDataList.size <= signersList.size) { "Invalid Transaction. Less Signers (${signersList.size}) than CommandData (${commandDataList.size}) objects" } + val componentHashes = group.components.mapIndexed { index, component -> componentHash(group.nonces[index], component) } + val leafIndices = componentHashes.map { group.partialMerkleTree.leafIndex(it) } + if (leafIndices.isNotEmpty()) + check(leafIndices.max()!! < signersList.size) { "Invalid Transaction. A command with no corresponding signer detected" } + return commandDataList.mapIndexed { index, commandData -> Command(commandData, signersList[leafIndices[index]]) } + } else { + // It is a WireTransaction + // or a FilteredTransaction with no Commands (in which case group is null). + check(commandDataList.size == signersList.size) { "Invalid Transaction. Sizes of CommandData (${commandDataList.size}) and Signers (${signersList.size}) do not match" } + return commandDataList.mapIndexed { index, commandData -> Command(commandData, signersList[index]) } + } + } } /** @@ -111,11 +136,12 @@ class FilteredTransaction private constructor( val filteredSerialisedComponents: MutableMap> = hashMapOf() val filteredComponentNonces: MutableMap> = hashMapOf() val filteredComponentHashes: MutableMap> = hashMapOf() // Required for partial Merkle tree generation. + var signersIncluded = false fun filter(t: T, componentGroupIndex: Int, internalIndex: Int) { if (filtering.test(t)) { val group = filteredSerialisedComponents[componentGroupIndex] - // Because the filter passed, we know there is a match. We also use first vs single as the init function + // Because the filter passed, we know there is a match. We also use first Vs single as the init function // of WireTransaction ensures there are no duplicated groups. val serialisedComponent = wtx.componentGroups.first { it.groupIndex == componentGroupIndex }.components[internalIndex] if (group == null) { @@ -132,6 +158,17 @@ class FilteredTransaction private constructor( filteredComponentNonces[componentGroupIndex]!!.add(wtx.availableComponentNonces[componentGroupIndex]!![internalIndex]) filteredComponentHashes[componentGroupIndex]!!.add(wtx.availableComponentHashes[componentGroupIndex]!![internalIndex]) } + // If at least one command is visible, then all command-signers should be visible as well. + // This is required for visibility purposes, see FilteredTransaction.checkAllCommandsVisible() for more details. + if (componentGroupIndex == ComponentGroupEnum.COMMANDS_GROUP.ordinal && !signersIncluded) { + signersIncluded = true + val signersGroupIndex = ComponentGroupEnum.SIGNERS_GROUP.ordinal + // There exist commands, thus the signers group is not empty. + val signersGroupComponents = wtx.componentGroups.first { it.groupIndex == signersGroupIndex } + filteredSerialisedComponents.put(signersGroupIndex, signersGroupComponents.components.toMutableList()) + filteredComponentNonces.put(signersGroupIndex, wtx.availableComponentNonces[signersGroupIndex]!!.toMutableList()) + filteredComponentHashes.put(signersGroupIndex, wtx.availableComponentHashes[signersGroupIndex]!!.toMutableList()) + } } } @@ -142,6 +179,10 @@ class FilteredTransaction private constructor( wtx.attachments.forEachIndexed { internalIndex, it -> filter(it, ComponentGroupEnum.ATTACHMENTS_GROUP.ordinal, internalIndex) } if (wtx.notary != null) filter(wtx.notary, ComponentGroupEnum.NOTARY_GROUP.ordinal, 0) if (wtx.timeWindow != null) filter(wtx.timeWindow, ComponentGroupEnum.TIMEWINDOW_GROUP.ordinal, 0) + // It is highlighted that because there is no a signers property in TraversableTransaction, + // one cannot specifically filter them in or out. + // The above is very important to ensure someone won't filter out the signers component group if at least one + // command is included in a FilteredTransaction. // It's sometimes possible that when we receive a WireTransaction for which there is a new or more unknown component groups, // we decide to filter and attach this field to a FilteredTransaction. @@ -207,7 +248,9 @@ class FilteredTransaction private constructor( /** * Function that checks if all of the components in a particular group are visible. * This functionality is required on non-Validating Notaries to check that all inputs are visible. - * It might also be applied in Oracles, where an Oracle should know it can see all commands. + * It might also be applied in Oracles or any other entity requiring [Command] visibility, but because this method + * cannot distinguish between related and unrelated to the signer [Command]s, one should use the + * [checkCommandVisibility] method, which is specifically designed for [Command] visibility purposes. * The logic behind this algorithm is that we check that the root of the provided group partialMerkleTree matches with the * root of a fullMerkleTree if computed using all visible components. * Note that this method is usually called after or before [verify], to also ensure that the provided partial Merkle @@ -229,18 +272,54 @@ class FilteredTransaction private constructor( visibilityCheck(group.groupIndex < groupHashes.size) { "There is no matching component group hash for group ${group.groupIndex}" } val groupPartialRoot = groupHashes[group.groupIndex] val groupFullRoot = MerkleTree.getMerkleTree(group.components.mapIndexed { index, component -> componentHash(group.nonces[index], component) }).hash - visibilityCheck(groupPartialRoot == groupFullRoot) { "The partial Merkle tree root does not match with the received root for group ${group.groupIndex}" } + visibilityCheck(groupPartialRoot == groupFullRoot) { "Some components for group ${group.groupIndex} are not visible" } + // Verify the top level Merkle tree from groupHashes. + visibilityCheck(MerkleTree.getMerkleTree(groupHashes).hash == id) { "Transaction is malformed. Top level Merkle tree cannot be verified against transaction's id" } } } - inline private fun verificationCheck(value: Boolean, lazyMessage: () -> Any): Unit { + /** + * Function that checks if all of the commands that should be signed by the input public key are visible. + * This functionality is required from Oracles to check that all of the commands they should sign are visible. + * This algorithm uses the [ComponentGroupEnum.SIGNERS_GROUP] to count how many commands should be signed by the + * input [PublicKey] and it then matches it with the size of received [commands]. + * Note that this method does not throw if there are no commands for this key to sign in the original [WireTransaction]. + * @param publicKey signer's [PublicKey] + * @throws ComponentVisibilityException if not all of the related commands are visible. + */ + @Throws(ComponentVisibilityException::class) + fun checkCommandVisibility(publicKey: PublicKey) { + val commandSigners = componentGroups.firstOrNull { it.groupIndex == ComponentGroupEnum.SIGNERS_GROUP.ordinal } + val expectedNumOfCommands = expectedNumOfCommands(publicKey, commandSigners) + val receivedForThisKeyNumOfCommands = commands.filter { publicKey in it.signers }.size + visibilityCheck(expectedNumOfCommands == receivedForThisKeyNumOfCommands) { "$expectedNumOfCommands commands were expected, but received $receivedForThisKeyNumOfCommands" } + } + + // Function to return number of expected commands to sign. + private fun expectedNumOfCommands(publicKey: PublicKey, commandSigners: ComponentGroup?): Int { + checkAllComponentsVisible(ComponentGroupEnum.SIGNERS_GROUP) + if (commandSigners == null) return 0 + fun signersKeys (internalIndex: Int, opaqueBytes: OpaqueBytes): List { + try { + return SerializedBytes>(opaqueBytes.bytes).deserialize() + } catch (e: Exception) { + throw Exception("Malformed transaction, signers at index $internalIndex cannot be deserialised", e) + } + } + + return commandSigners.components + .mapIndexed { internalIndex, opaqueBytes -> signersKeys(internalIndex, opaqueBytes) } + .filter { signers -> publicKey in signers }.size + } + + inline private fun verificationCheck(value: Boolean, lazyMessage: () -> Any) { if (!value) { val message = lazyMessage() throw FilteredTransactionVerificationException(id, message.toString()) } } - inline private fun visibilityCheck(value: Boolean, lazyMessage: () -> Any): Unit { + inline private fun visibilityCheck(value: Boolean, lazyMessage: () -> Any) { if (!value) { val message = lazyMessage() throw ComponentVisibilityException(id, message.toString()) diff --git a/core/src/main/kotlin/net/corda/core/transactions/WireTransaction.kt b/core/src/main/kotlin/net/corda/core/transactions/WireTransaction.kt index 2325344286..7112369667 100644 --- a/core/src/main/kotlin/net/corda/core/transactions/WireTransaction.kt +++ b/core/src/main/kotlin/net/corda/core/transactions/WireTransaction.kt @@ -6,9 +6,10 @@ import net.corda.core.crypto.* import net.corda.core.identity.Party import net.corda.core.internal.Emoji import net.corda.core.node.ServicesForResolution -import net.corda.core.serialization.* -import net.corda.core.utilities.OpaqueBytes import net.corda.core.node.services.AttachmentId +import net.corda.core.serialization.CordaSerializable +import net.corda.core.serialization.serialize +import net.corda.core.utilities.OpaqueBytes import java.security.PublicKey import java.security.SignatureException import java.util.function.Predicate @@ -213,10 +214,14 @@ class WireTransaction(componentGroups: List, val privacySalt: Pr val componentGroupMap: MutableList = mutableListOf() if (inputs.isNotEmpty()) componentGroupMap.add(ComponentGroup(INPUTS_GROUP.ordinal, inputs.map { it.serialize() })) if (outputs.isNotEmpty()) componentGroupMap.add(ComponentGroup(OUTPUTS_GROUP.ordinal, outputs.map { it.serialize() })) - if (commands.isNotEmpty()) componentGroupMap.add(ComponentGroup(COMMANDS_GROUP.ordinal, commands.map { it.serialize() })) + // Adding commandData only to the commands group. Signers are added in their own group. + if (commands.isNotEmpty()) componentGroupMap.add(ComponentGroup(COMMANDS_GROUP.ordinal, commands.map { it.value.serialize() })) if (attachments.isNotEmpty()) componentGroupMap.add(ComponentGroup(ATTACHMENTS_GROUP.ordinal, attachments.map { it.serialize() })) if (notary != null) componentGroupMap.add(ComponentGroup(NOTARY_GROUP.ordinal, listOf(notary.serialize()))) if (timeWindow != null) componentGroupMap.add(ComponentGroup(TIMEWINDOW_GROUP.ordinal, listOf(timeWindow.serialize()))) + // Adding signers to their own group. This is required for command visibility purposes: a party receiving + // a FilteredTransaction can now verify it sees all the commands it should sign. + if (commands.isNotEmpty()) componentGroupMap.add(ComponentGroup(SIGNERS_GROUP.ordinal, commands.map { it.signers.serialize() })) return componentGroupMap } } diff --git a/core/src/test/kotlin/net/corda/core/contracts/CompatibleTransactionTests.kt b/core/src/test/kotlin/net/corda/core/contracts/CompatibleTransactionTests.kt index 7ed31d3d38..b50aa66b99 100644 --- a/core/src/test/kotlin/net/corda/core/contracts/CompatibleTransactionTests.kt +++ b/core/src/test/kotlin/net/corda/core/contracts/CompatibleTransactionTests.kt @@ -1,13 +1,9 @@ package net.corda.core.contracts import net.corda.core.contracts.ComponentGroupEnum.* -import net.corda.core.crypto.MerkleTree -import net.corda.core.crypto.SecureHash -import net.corda.core.crypto.secureRandomBytes +import net.corda.core.crypto.* import net.corda.core.serialization.serialize -import net.corda.core.transactions.ComponentGroup -import net.corda.core.transactions.ComponentVisibilityException -import net.corda.core.transactions.WireTransaction +import net.corda.core.transactions.* import net.corda.core.utilities.OpaqueBytes import net.corda.testing.* import net.corda.testing.contracts.DummyContract @@ -34,22 +30,24 @@ class CompatibleTransactionTests : TestDependencyInjectionBase() { private val inputGroup by lazy { ComponentGroup(INPUTS_GROUP.ordinal, inputs.map { it.serialize() }) } private val outputGroup by lazy { ComponentGroup(OUTPUTS_GROUP.ordinal, outputs.map { it.serialize() }) } - private val commandGroup by lazy { ComponentGroup(COMMANDS_GROUP.ordinal, commands.map { it.serialize() }) } + private val commandGroup by lazy { ComponentGroup(COMMANDS_GROUP.ordinal, commands.map { it.value.serialize() }) } private val attachmentGroup by lazy { ComponentGroup(ATTACHMENTS_GROUP.ordinal, attachments.map { it.serialize() }) } // The list is empty. private val notaryGroup by lazy { ComponentGroup(NOTARY_GROUP.ordinal, listOf(notary.serialize())) } private val timeWindowGroup by lazy { ComponentGroup(TIMEWINDOW_GROUP.ordinal, listOf(timeWindow.serialize())) } + private val signersGroup by lazy { ComponentGroup(SIGNERS_GROUP.ordinal, commands.map { it.signers.serialize() }) } - private val newUnknownComponentGroup = ComponentGroup(20, listOf(OpaqueBytes(secureRandomBytes(4)), OpaqueBytes(secureRandomBytes(8)))) - private val newUnknownComponentEmptyGroup = ComponentGroup(21, emptyList()) + private val newUnknownComponentGroup = ComponentGroup(100, listOf(OpaqueBytes(secureRandomBytes(4)), OpaqueBytes(secureRandomBytes(8)))) + private val newUnknownComponentEmptyGroup = ComponentGroup(101, emptyList()) // Do not add attachments (empty list). private val componentGroupsA by lazy { listOf( - inputGroup, - outputGroup, - commandGroup, - notaryGroup, - timeWindowGroup + inputGroup, + outputGroup, + commandGroup, + notaryGroup, + timeWindowGroup, + signersGroup ) } private val wireTransactionA by lazy { WireTransaction(componentGroups = componentGroupsA, privacySalt = privacySalt) } @@ -74,7 +72,8 @@ class CompatibleTransactionTests : TestDependencyInjectionBase() { commandGroup, attachmentGroup, notaryGroup, - timeWindowGroup + timeWindowGroup, + signersGroup ) assertFails { WireTransaction(componentGroups = componentGroupsEmptyAttachment, privacySalt = privacySalt) } @@ -86,7 +85,8 @@ class CompatibleTransactionTests : TestDependencyInjectionBase() { outputGroup, commandGroup, notaryGroup, - timeWindowGroup + timeWindowGroup, + signersGroup ) val wireTransaction1ShuffledInputs = WireTransaction(componentGroups = componentGroupsB, privacySalt = privacySalt) // The ID has changed due to change of the internal ordering in inputs. @@ -106,7 +106,8 @@ class CompatibleTransactionTests : TestDependencyInjectionBase() { inputGroup, commandGroup, notaryGroup, - timeWindowGroup + timeWindowGroup, + signersGroup ) assertEquals(wireTransactionA, WireTransaction(componentGroups = shuffledComponentGroupsA, privacySalt = privacySalt)) } @@ -123,7 +124,8 @@ class CompatibleTransactionTests : TestDependencyInjectionBase() { commandGroup, ComponentGroup(ATTACHMENTS_GROUP.ordinal, inputGroup.components), notaryGroup, - timeWindowGroup + timeWindowGroup, + signersGroup ) assertFails { WireTransaction(componentGroupsB, privacySalt) } @@ -134,7 +136,8 @@ class CompatibleTransactionTests : TestDependencyInjectionBase() { commandGroup, // First commandsGroup. commandGroup, // Second commandsGroup. notaryGroup, - timeWindowGroup + timeWindowGroup, + signersGroup ) assertFails { WireTransaction(componentGroupsDuplicatedCommands, privacySalt) } @@ -144,7 +147,8 @@ class CompatibleTransactionTests : TestDependencyInjectionBase() { outputGroup, commandGroup, notaryGroup, - timeWindowGroup + timeWindowGroup, + signersGroup ) assertFails { WireTransaction(componentGroupsC, privacySalt) } @@ -154,23 +158,24 @@ class CompatibleTransactionTests : TestDependencyInjectionBase() { commandGroup, notaryGroup, timeWindowGroup, - newUnknownComponentGroup // A new unknown component with ordinal 20 that we cannot process. + signersGroup, + newUnknownComponentGroup // A new unknown component with ordinal 100 that we cannot process. ) // The old client (receiving more component types than expected) is still compatible. val wireTransactionCompatibleA = WireTransaction(componentGroupsCompatibleA, privacySalt) assertEquals(wireTransactionCompatibleA.availableComponentGroups, wireTransactionA.availableComponentGroups) // The known components are the same. assertNotEquals(wireTransactionCompatibleA, wireTransactionA) // But obviously, its Merkle root has changed Vs wireTransactionA (which doesn't include this extra component). - assertEquals(6, wireTransactionCompatibleA.componentGroups.size) - // The old client will trhow if receiving an empty component (even if this unknown). + // The old client will throw if receiving an empty component (even if this is unknown). val componentGroupsCompatibleEmptyNew = listOf( inputGroup, outputGroup, commandGroup, notaryGroup, timeWindowGroup, - newUnknownComponentEmptyGroup // A new unknown component with ordinal 21 that we cannot process. + signersGroup, + newUnknownComponentEmptyGroup // A new unknown component with ordinal 101 that we cannot process. ) assertFails { WireTransaction(componentGroupsCompatibleEmptyNew, privacySalt) } } @@ -179,7 +184,9 @@ class CompatibleTransactionTests : TestDependencyInjectionBase() { fun `FilteredTransaction constructors and compatibility`() { // Filter out all of the components. val ftxNothing = wireTransactionA.buildFilteredTransaction(Predicate { false }) // Nothing filtered. - assertEquals(6, ftxNothing.groupHashes.size) // Although nothing filtered, we still receive the group hashes for the top level Merkle tree. + // Although nothing filtered, we still receive the group hashes for the top level Merkle tree. + // Note that attachments are not sent, but group hashes include the allOnesHash flag for the attachment group hash; that's why we expect +1 group hashes. + assertEquals(wireTransactionA.componentGroups.size + 1, ftxNothing.groupHashes.size) ftxNothing.verify() // Include all of the components. @@ -191,6 +198,7 @@ class CompatibleTransactionTests : TestDependencyInjectionBase() { ftxAll.checkAllComponentsVisible(ATTACHMENTS_GROUP) ftxAll.checkAllComponentsVisible(NOTARY_GROUP) ftxAll.checkAllComponentsVisible(TIMEWINDOW_GROUP) + ftxAll.checkAllComponentsVisible(SIGNERS_GROUP) // Filter inputs only. fun filtering(elem: Any): Boolean { @@ -222,12 +230,14 @@ class CompatibleTransactionTests : TestDependencyInjectionBase() { assertNotNull(ftxOneInput.filteredComponentGroups.firstOrNull { it.groupIndex == INPUTS_GROUP.ordinal }!!.partialMerkleTree) // And the Merkle tree. // The old client (receiving more component types than expected) is still compatible. - val componentGroupsCompatibleA = listOf(inputGroup, + val componentGroupsCompatibleA = listOf( + inputGroup, outputGroup, commandGroup, notaryGroup, timeWindowGroup, - newUnknownComponentGroup // A new unknown component with ordinal 10,000 that we cannot process. + signersGroup, + newUnknownComponentGroup // A new unknown component with ordinal 100 that we cannot process. ) val wireTransactionCompatibleA = WireTransaction(componentGroupsCompatibleA, privacySalt) val ftxCompatible = wireTransactionCompatibleA.buildFilteredTransaction(Predicate(::filtering)) @@ -245,9 +255,288 @@ class CompatibleTransactionTests : TestDependencyInjectionBase() { ftxCompatibleAll.verify() assertEquals(wireTransactionCompatibleA.id, ftxCompatibleAll.id) - // Check we received the last (6th) element that we cannot process (backwards compatibility). - assertEquals(6, ftxCompatibleAll.filteredComponentGroups.size) + // Check we received the last element that we cannot process (backwards compatibility). + assertEquals(wireTransactionCompatibleA.componentGroups.size, ftxCompatibleAll.filteredComponentGroups.size) + // Hide one component group only. + // Filter inputs only. + fun filterOutInputs(elem: Any): Boolean { + return when (elem) { + is StateRef -> false + else -> true + } + } + val ftxCompatibleNoInputs = wireTransactionCompatibleA.buildFilteredTransaction(Predicate(::filterOutInputs)) + ftxCompatibleNoInputs.verify() + assertFailsWith { ftxCompatibleNoInputs.checkAllComponentsVisible(INPUTS_GROUP) } + assertEquals(wireTransactionCompatibleA.componentGroups.size - 1, ftxCompatibleNoInputs.filteredComponentGroups.size) + assertEquals(wireTransactionCompatibleA.componentGroups.map { it.groupIndex }.max()!!, ftxCompatibleNoInputs.groupHashes.size - 1) + } + + @Test + fun `Command visibility tests`() { + // 1st and 3rd commands require a signature from KEY_1. + val twoCommandsforKey1 = listOf(dummyCommand(DUMMY_KEY_1.public, DUMMY_KEY_2.public), dummyCommand(DUMMY_KEY_2.public), dummyCommand(DUMMY_KEY_1.public)) + val componentGroups = listOf( + inputGroup, + outputGroup, + ComponentGroup(COMMANDS_GROUP.ordinal, twoCommandsforKey1.map { it.value.serialize() }), + notaryGroup, + timeWindowGroup, + ComponentGroup(SIGNERS_GROUP.ordinal, twoCommandsforKey1.map { it.signers.serialize() }), + newUnknownComponentGroup // A new unknown component with ordinal 100 that we cannot process. + ) + val wtx = WireTransaction(componentGroups = componentGroups, privacySalt = PrivacySalt()) + + // Filter all commands. + fun filterCommandsOnly(elem: Any): Boolean { + return when (elem) { + is Command<*> -> true // Even if one Command is filtered, all signers are automatically filtered as well + else -> false + } + } + + // Filter out commands only. + fun filterOutCommands(elem: Any): Boolean { + return when (elem) { + is Command<*> -> false + else -> true + } + } + + // Filter KEY_1 commands. + fun filterKEY1Commands(elem: Any): Boolean { + return when (elem) { + is Command<*> -> DUMMY_KEY_1.public in elem.signers + else -> false + } + } + + // Filter only one KEY_1 command. + fun filterTwoSignersCommands(elem: Any): Boolean { + return when (elem) { + is Command<*> -> elem.signers.size == 2 // dummyCommand(DUMMY_KEY_1.public) is filtered out. + else -> false + } + } + + // Again filter only one KEY_1 command. + fun filterSingleSignersCommands(elem: Any): Boolean { + return when (elem) { + is Command<*> -> elem.signers.size == 1 // dummyCommand(DUMMY_KEY_1.public, DUMMY_KEY_2.public) is filtered out. + else -> false + } + } + + val allCommandsFtx = wtx.buildFilteredTransaction(Predicate(::filterCommandsOnly)) + val noCommandsFtx = wtx.buildFilteredTransaction(Predicate(::filterOutCommands)) + val key1CommandsFtx = wtx.buildFilteredTransaction(Predicate(::filterKEY1Commands)) + val oneKey1CommandFtxA = wtx.buildFilteredTransaction(Predicate(::filterTwoSignersCommands)) + val oneKey1CommandFtxB = wtx.buildFilteredTransaction(Predicate(::filterSingleSignersCommands)) + + allCommandsFtx.checkCommandVisibility(DUMMY_KEY_1.public) + assertFailsWith { noCommandsFtx.checkCommandVisibility(DUMMY_KEY_1.public) } + key1CommandsFtx.checkCommandVisibility(DUMMY_KEY_1.public) + assertFailsWith { oneKey1CommandFtxA.checkCommandVisibility(DUMMY_KEY_1.public) } + assertFailsWith { oneKey1CommandFtxB.checkCommandVisibility(DUMMY_KEY_1.public) } + + allCommandsFtx.checkAllComponentsVisible(SIGNERS_GROUP) + assertFailsWith { noCommandsFtx.checkAllComponentsVisible(SIGNERS_GROUP) } // If we filter out all commands, signers are not sent as well. + key1CommandsFtx.checkAllComponentsVisible(SIGNERS_GROUP) // If at least one Command is visible, then all Signers are visible. + oneKey1CommandFtxA.checkAllComponentsVisible(SIGNERS_GROUP) // If at least one Command is visible, then all Signers are visible. + oneKey1CommandFtxB.checkAllComponentsVisible(SIGNERS_GROUP) // If at least one Command is visible, then all Signers are visible. + + // We don't send a list of signers. + val componentGroupsCompatible = listOf( + inputGroup, + outputGroup, + ComponentGroup(COMMANDS_GROUP.ordinal, twoCommandsforKey1.map { it.value.serialize() }), + notaryGroup, + timeWindowGroup, + // ComponentGroup(SIGNERS_GROUP.ordinal, twoCommandsforKey1.map { it.signers.serialize() }), + newUnknownComponentGroup // A new unknown component with ordinal 100 that we cannot process. + ) + + // Invalid Transaction. Sizes of CommandData and Signers (empty) do not match. + assertFailsWith { WireTransaction(componentGroups = componentGroupsCompatible, privacySalt = PrivacySalt()) } + + // We send smaller list of signers. + val componentGroupsLessSigners = listOf( + inputGroup, + outputGroup, + ComponentGroup(COMMANDS_GROUP.ordinal, twoCommandsforKey1.map { it.value.serialize() }), + notaryGroup, + timeWindowGroup, + ComponentGroup(SIGNERS_GROUP.ordinal, twoCommandsforKey1.map { it.signers.serialize() }.subList(0, 1)), // Send first signer only. + newUnknownComponentGroup // A new unknown component with ordinal 100 that we cannot process. + ) + + // Invalid Transaction. Sizes of CommandData and Signers (empty) do not match. + assertFailsWith { WireTransaction(componentGroups = componentGroupsLessSigners, privacySalt = PrivacySalt()) } + + // Test if there is no command to sign. + val commandsNoKey1= listOf(dummyCommand(DUMMY_KEY_2.public)) + + val componentGroupsNoKey1ToSign = listOf( + inputGroup, + outputGroup, + ComponentGroup(COMMANDS_GROUP.ordinal, commandsNoKey1.map { it.value.serialize() }), + notaryGroup, + timeWindowGroup, + ComponentGroup(SIGNERS_GROUP.ordinal, commandsNoKey1.map { it.signers.serialize() }), + newUnknownComponentGroup // A new unknown component with ordinal 100 that we cannot process. + ) + + val wtxNoKey1 = WireTransaction(componentGroups = componentGroupsNoKey1ToSign, privacySalt = PrivacySalt()) + val allCommandsNoKey1Ftx= wtxNoKey1.buildFilteredTransaction(Predicate(::filterCommandsOnly)) + allCommandsNoKey1Ftx.checkCommandVisibility(DUMMY_KEY_1.public) // This will pass, because there are indeed no commands to sign in the original transaction. + } + + @Test + fun `FilteredTransaction signer manipulation tests`() { + // Required to call the private constructor. + val ftxConstructor = FilteredTransaction::class.java.declaredConstructors[1] + ftxConstructor.isAccessible = true + + // 1st and 3rd commands require a signature from KEY_1. + val twoCommandsforKey1 = listOf(dummyCommand(DUMMY_KEY_1.public, DUMMY_KEY_2.public), dummyCommand(DUMMY_KEY_2.public), dummyCommand(DUMMY_KEY_1.public)) + val componentGroups = listOf( + inputGroup, + outputGroup, + ComponentGroup(COMMANDS_GROUP.ordinal, twoCommandsforKey1.map { it.value.serialize() }), + notaryGroup, + timeWindowGroup, + ComponentGroup(SIGNERS_GROUP.ordinal, twoCommandsforKey1.map { it.signers.serialize() }) + ) + val wtx = WireTransaction(componentGroups = componentGroups, privacySalt = PrivacySalt()) + + // Filter KEY_1 commands (commands 1 and 3). + fun filterKEY1Commands(elem: Any): Boolean { + return when (elem) { + is Command<*> -> DUMMY_KEY_1.public in elem.signers + else -> false + } + } + + // Filter KEY_2 commands (commands 1 and 2). + fun filterKEY2Commands(elem: Any): Boolean { + return when (elem) { + is Command<*> -> DUMMY_KEY_2.public in elem.signers + else -> false + } + } + + val key1CommandsFtx = wtx.buildFilteredTransaction(Predicate(::filterKEY1Commands)) + val key2CommandsFtx = wtx.buildFilteredTransaction(Predicate(::filterKEY2Commands)) + + // val commandDataComponents = key1CommandsFtx.filteredComponentGroups[0].components + val commandDataHashes = wtx.availableComponentHashes[ComponentGroupEnum.COMMANDS_GROUP.ordinal]!! + val noLastCommandDataPMT = PartialMerkleTree.build( + MerkleTree.getMerkleTree(commandDataHashes), + commandDataHashes.subList(0, 1) + ) + val noLastCommandDataComponents = key1CommandsFtx.filteredComponentGroups[0].components.subList(0, 1) + val noLastCommandDataNonces = key1CommandsFtx.filteredComponentGroups[0].nonces.subList(0, 1) + val noLastCommandDataGroup = FilteredComponentGroup( + ComponentGroupEnum.COMMANDS_GROUP.ordinal, + noLastCommandDataComponents, + noLastCommandDataNonces, + noLastCommandDataPMT + ) + + val signerComponents = key1CommandsFtx.filteredComponentGroups[1].components + val signerHashes = wtx.availableComponentHashes[ComponentGroupEnum.SIGNERS_GROUP.ordinal]!! + val noLastSignerPMT = PartialMerkleTree.build( + MerkleTree.getMerkleTree(signerHashes), + signerHashes.subList(0, 2) + ) + val noLastSignerComponents = key1CommandsFtx.filteredComponentGroups[1].components.subList(0, 2) + val noLastSignerNonces = key1CommandsFtx.filteredComponentGroups[1].nonces.subList(0, 2) + val noLastSignerGroup = FilteredComponentGroup( + ComponentGroupEnum.SIGNERS_GROUP.ordinal, + noLastSignerComponents, + noLastSignerNonces, + noLastSignerPMT + ) + val noLastSignerGroupSamePartialTree = FilteredComponentGroup( + ComponentGroupEnum.SIGNERS_GROUP.ordinal, + noLastSignerComponents, + noLastSignerNonces, + key1CommandsFtx.filteredComponentGroups[1].partialMerkleTree) // We don't update that, so we can catch the index mismatch. + + val updatedFilteredComponentsNoSignersKey2 = listOf(key2CommandsFtx.filteredComponentGroups[0], noLastSignerGroup) + val updatedFilteredComponentsNoSignersKey2SamePMT = listOf(key2CommandsFtx.filteredComponentGroups[0], noLastSignerGroupSamePartialTree) + + // There are only two components in key1CommandsFtx (commandData and signers). + assertEquals(2, key1CommandsFtx.componentGroups.size) + + // Remove last signer for which there is a pointer from a visible commandData. This is the case of Key1. + // This will result to an invalid transaction. + // A command with no corresponding signer detected + // because the pointer of CommandData (3rd leaf) cannot find a corresponding (3rd) signer. + val updatedFilteredComponentsNoSignersKey1SamePMT = listOf(key1CommandsFtx.filteredComponentGroups[0], noLastSignerGroupSamePartialTree) + assertFails { ftxConstructor.newInstance(key1CommandsFtx.id, updatedFilteredComponentsNoSignersKey1SamePMT, key1CommandsFtx.groupHashes) } + + // Remove both last signer (KEY1) and related command. + // Update partial Merkle tree for signers. + val updatedFilteredComponentsNoLastCommandAndSigners = listOf(noLastCommandDataGroup, noLastSignerGroup) + val ftxNoLastCommandAndSigners = ftxConstructor.newInstance(key1CommandsFtx.id, updatedFilteredComponentsNoLastCommandAndSigners, key1CommandsFtx.groupHashes) as FilteredTransaction + // verify() will pass as the transaction is well-formed. + ftxNoLastCommandAndSigners.verify() + // checkCommandVisibility() will not pass, because checkAllComponentsVisible(ComponentGroupEnum.SIGNERS_GROUP) will fail. + assertFailsWith { ftxNoLastCommandAndSigners.checkCommandVisibility(DUMMY_KEY_1.public) } + + // Remove last signer for which there is no pointer from a visible commandData. This is the case of Key2. + // Do not change partial Merkle tree for signers. + // This time the object can be constructed as there is no pointer mismatch. + val ftxNoLastSigner = ftxConstructor.newInstance(key2CommandsFtx.id, updatedFilteredComponentsNoSignersKey2SamePMT, key2CommandsFtx.groupHashes) as FilteredTransaction + // verify() will fail as we didn't change the partial Merkle tree. + assertFailsWith { ftxNoLastSigner.verify() } + // checkCommandVisibility() will not pass. + assertFailsWith { ftxNoLastSigner.checkCommandVisibility(DUMMY_KEY_2.public) } + + // Remove last signer for which there is no pointer from a visible commandData. This is the case of Key2. + // Update partial Merkle tree for signers. + val ftxNoLastSignerB = ftxConstructor.newInstance(key2CommandsFtx.id, updatedFilteredComponentsNoSignersKey2, key2CommandsFtx.groupHashes) as FilteredTransaction + // verify() will pass, the transaction is well-formed. + ftxNoLastSignerB.verify() + // But, checkAllComponentsVisible() will not pass. + assertFailsWith { ftxNoLastSignerB.checkCommandVisibility(DUMMY_KEY_2.public) } + + // Modify last signer (we have a pointer from commandData). + // Update partial Merkle tree for signers. + val alterSignerComponents = signerComponents.subList(0, 2) + signerComponents[1] // Third one is removed and the 2nd command is added twice. + val alterSignersHashes = wtx.availableComponentHashes[ComponentGroupEnum.SIGNERS_GROUP.ordinal]!!.subList(0, 2) + componentHash(key1CommandsFtx.filteredComponentGroups[1].nonces[2], alterSignerComponents[2]) + val alterMTree = MerkleTree.getMerkleTree(alterSignersHashes) + val alterSignerPMTK = PartialMerkleTree.build( + alterMTree, + alterSignersHashes + ) + + val alterSignerGroup = FilteredComponentGroup( + ComponentGroupEnum.SIGNERS_GROUP.ordinal, + alterSignerComponents, + key1CommandsFtx.filteredComponentGroups[1].nonces, + alterSignerPMTK + ) + val alterFilteredComponents = listOf(key1CommandsFtx.filteredComponentGroups[0], alterSignerGroup) + + // Do not update groupHashes. + val ftxAlterSigner = ftxConstructor.newInstance(key1CommandsFtx.id, alterFilteredComponents, key1CommandsFtx.groupHashes) as FilteredTransaction + // Visible components in signers group cannot be verified against their partial Merkle tree. + assertFailsWith { ftxAlterSigner.verify() } + // Also, checkAllComponentsVisible() will not pass (groupHash matching will fail). + assertFailsWith { ftxAlterSigner.checkCommandVisibility(DUMMY_KEY_1.public) } + + // Update groupHashes. + val ftxAlterSignerB = ftxConstructor.newInstance(key1CommandsFtx.id, alterFilteredComponents, key1CommandsFtx.groupHashes.subList(0, 6) + alterMTree.hash) as FilteredTransaction + // Visible components in signers group cannot be verified against their partial Merkle tree. + assertFailsWith { ftxAlterSignerB.verify() } + // Also, checkAllComponentsVisible() will not pass (top level Merkle tree cannot be verified against transaction's id). + assertFailsWith { ftxAlterSignerB.checkCommandVisibility(DUMMY_KEY_1.public) } + + ftxConstructor.isAccessible = false } } + diff --git a/core/src/test/kotlin/net/corda/core/crypto/PartialMerkleTreeTest.kt b/core/src/test/kotlin/net/corda/core/crypto/PartialMerkleTreeTest.kt index a82e698a97..e476f64cb5 100644 --- a/core/src/test/kotlin/net/corda/core/crypto/PartialMerkleTreeTest.kt +++ b/core/src/test/kotlin/net/corda/core/crypto/PartialMerkleTreeTest.kt @@ -1,6 +1,5 @@ package net.corda.core.crypto - import net.corda.core.contracts.* import net.corda.core.crypto.SecureHash.Companion.zeroHash import net.corda.core.identity.Party @@ -14,10 +13,12 @@ import net.corda.testing.* import org.junit.Test import java.security.PublicKey import java.util.function.Predicate +import java.util.stream.IntStream +import kotlin.streams.toList import kotlin.test.* class PartialMerkleTreeTest : TestDependencyInjectionBase() { - val nodes = "abcdef" + private val nodes = "abcdef" private val hashed = nodes.map { initialiseTestSerialization() try { @@ -115,16 +116,18 @@ class PartialMerkleTreeTest : TestDependencyInjectionBase() { val d = testTx.serialize().deserialize() assertEquals(testTx.id, d.id) - val mt = testTx.buildFilteredTransaction(Predicate(::filtering)) + val ftx = testTx.buildFilteredTransaction(Predicate(::filtering)) - assertEquals(4, mt.filteredComponentGroups.size) - assertEquals(1, mt.inputs.size) - assertEquals(0, mt.attachments.size) - assertEquals(1, mt.outputs.size) - assertEquals(1, mt.commands.size) - assertNull(mt.notary) - assertNotNull(mt.timeWindow) - mt.verify() + // We expect 5 and not 4 component groups, because there is at least one command in the ftx and thus, + // the signers component is also sent (required for visibility purposes). + assertEquals(5, ftx.filteredComponentGroups.size) + assertEquals(1, ftx.inputs.size) + assertEquals(0, ftx.attachments.size) + assertEquals(1, ftx.outputs.size) + assertEquals(1, ftx.commands.size) + assertNull(ftx.notary) + assertNotNull(ftx.timeWindow) + ftx.verify() } @Test @@ -246,4 +249,50 @@ class PartialMerkleTreeTest : TestDependencyInjectionBase() { privacySalt = privacySalt ) } + + @Test + fun `Find leaf index`() { + // A Merkle tree with 20 leaves. + val sampleLeaves = IntStream.rangeClosed(0, 19).toList().map { SecureHash.sha256(it.toString()) } + val merkleTree = MerkleTree.getMerkleTree(sampleLeaves) + + // Provided hashes are not in the tree. + assertFailsWith { PartialMerkleTree.build(merkleTree, listOf(SecureHash.sha256("20"))) } + // One of the provided hashes is not in the tree. + assertFailsWith { PartialMerkleTree.build(merkleTree, listOf(SecureHash.sha256("20"), SecureHash.sha256("1"), SecureHash.sha256("5"))) } + + val pmt = PartialMerkleTree.build(merkleTree, listOf(SecureHash.sha256("1"), SecureHash.sha256("5"), SecureHash.sha256("0"), SecureHash.sha256("19"))) + // First leaf. + assertEquals(0, pmt.leafIndex(SecureHash.sha256("0"))) + // Second leaf. + assertEquals(1, pmt.leafIndex(SecureHash.sha256("1"))) + // A random leaf. + assertEquals(5, pmt.leafIndex(SecureHash.sha256("5"))) + // The last leaf. + assertEquals(19, pmt.leafIndex(SecureHash.sha256("19"))) + // The provided hash is not in the tree. + assertFailsWith { pmt.leafIndex(SecureHash.sha256("10")) } + // The provided hash is not in the tree (using a leaf that didn't exist in the original Merkle tree). + assertFailsWith { pmt.leafIndex(SecureHash.sha256("30")) } + + val pmtFirstElementOnly = PartialMerkleTree.build(merkleTree, listOf(SecureHash.sha256("0"))) + assertEquals(0, pmtFirstElementOnly.leafIndex(SecureHash.sha256("0"))) + // The provided hash is not in the tree. + assertFailsWith { pmtFirstElementOnly.leafIndex(SecureHash.sha256("10")) } + + val pmtLastElementOnly = PartialMerkleTree.build(merkleTree, listOf(SecureHash.sha256("19"))) + assertEquals(19, pmtLastElementOnly.leafIndex(SecureHash.sha256("19"))) + // The provided hash is not in the tree. + assertFailsWith { pmtLastElementOnly.leafIndex(SecureHash.sha256("10")) } + + val pmtOneElement = PartialMerkleTree.build(merkleTree, listOf(SecureHash.sha256("5"))) + assertEquals(5, pmtOneElement.leafIndex(SecureHash.sha256("5"))) + // The provided hash is not in the tree. + assertFailsWith { pmtOneElement.leafIndex(SecureHash.sha256("10")) } + + val pmtAllIncluded = PartialMerkleTree.build(merkleTree, sampleLeaves) + for (i in 0..19) assertEquals(i, pmtAllIncluded.leafIndex(SecureHash.sha256(i.toString()))) + // The provided hash is not in the tree (using a leaf that didn't exist in the original Merkle tree). + assertFailsWith { pmtAllIncluded.leafIndex(SecureHash.sha256("30")) } + } } diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst index e3be980710..e043e0c398 100644 --- a/docs/source/changelog.rst +++ b/docs/source/changelog.rst @@ -26,7 +26,7 @@ UNRELEASED * ``Cordapp`` now has a name field for identifying CorDapps and all CorDapp names are printed to console at startup. -* Enums now respsect the whitelist applied to the Serializer factory serializing / deserializing them. If the enum isn't +* Enums now respect the whitelist applied to the Serializer factory serializing / deserializing them. If the enum isn't either annotated with the @CordaSerializable annotation or explicitly whitelisted then a NotSerializableException is thrown. @@ -54,6 +54,15 @@ UNRELEASED * ``TimeWindow`` now has a ``length`` property that returns the length of the time-window, or ``null`` if the time-window is open-ended. +* A new ``SIGNERS_GROUP`` with ordinal 6 has been added to ``ComponentGroupEnum`` that corresponds to the ``Command`` + signers. + +* ``PartialMerkleTree`` is equipped with a ``leafIndex`` function that returns the index of a hash (leaf) in the + partial Merkle tree structure. + +* A new function ``checkCommandVisibility(publicKey: PublicKey)`` has been added to ``FilteredTransaction`` to check + if every command that a signer should receive (e.g. an Oracle) is indeed visible. + .. _changelog_v1: Release 1.0 diff --git a/samples/irs-demo/src/main/kotlin/net/corda/irs/api/NodeInterestRates.kt b/samples/irs-demo/src/main/kotlin/net/corda/irs/api/NodeInterestRates.kt index 579de8cde6..bd694bd41b 100644 --- a/samples/irs-demo/src/main/kotlin/net/corda/irs/api/NodeInterestRates.kt +++ b/samples/irs-demo/src/main/kotlin/net/corda/irs/api/NodeInterestRates.kt @@ -145,7 +145,7 @@ object NodeInterestRates { } require(ftx.checkWithFun(::check)) - + ftx.checkCommandVisibility(services.myInfo.legalIdentities.first().owningKey) // It all checks out, so we can return a signature. // // Note that we will happily sign an invalid transaction, as we are only being presented with a filtered From dfd90701a021c10b15a29e763ccdb49fead0ced8 Mon Sep 17 00:00:00 2001 From: Andrzej Cichocki Date: Thu, 19 Oct 2017 11:19:55 +0100 Subject: [PATCH 14/94] CORDA-599 PersistentNetworkMapCache no longer circularly depends on SH (#1652) --- .ci/api-current.txt | 36 +++++----- .../core/node/services/NetworkMapCache.kt | 36 +++++----- .../network/PersistentNetworkMapCacheTest.kt | 4 +- .../net/corda/node/internal/AbstractNode.kt | 31 +++------ .../node/services/api/ServiceHubInternal.kt | 4 +- .../network/PersistentNetworkMapCache.kt | 69 +++++++++++-------- .../messaging/ArtemisMessagingTests.kt | 7 +- .../services/network/NetworkMapCacheTest.kt | 2 +- .../node/testing/MockServiceHubInternal.kt | 3 +- .../corda/testing/node/MockNetworkMapCache.kt | 6 +- .../kotlin/net/corda/testing/node/MockNode.kt | 21 ------ .../net/corda/testing/node/SimpleNode.kt | 4 +- 12 files changed, 104 insertions(+), 119 deletions(-) diff --git a/.ci/api-current.txt b/.ci/api-current.txt index 8104b88ab2..6b10e1b8b0 100644 --- a/.ci/api-current.txt +++ b/.ci/api-current.txt @@ -1576,24 +1576,8 @@ public interface net.corda.core.node.services.KeyManagementService @co.paralleluniverse.fibers.Suspendable @org.jetbrains.annotations.NotNull public abstract net.corda.core.crypto.TransactionSignature sign(net.corda.core.crypto.SignableData, java.security.PublicKey) @co.paralleluniverse.fibers.Suspendable @org.jetbrains.annotations.NotNull public abstract net.corda.core.crypto.DigitalSignature$WithKey sign(byte[], java.security.PublicKey) ## -public interface net.corda.core.node.services.NetworkMapCache - public abstract void clearNetworkMapCache() - @org.jetbrains.annotations.NotNull public abstract List getAllNodes() - @org.jetbrains.annotations.NotNull public abstract rx.Observable getChanged() - @org.jetbrains.annotations.Nullable public abstract net.corda.core.node.NodeInfo getNodeByAddress(net.corda.core.utilities.NetworkHostAndPort) +public interface net.corda.core.node.services.NetworkMapCache extends net.corda.core.node.services.NetworkMapCacheBase @org.jetbrains.annotations.Nullable public abstract net.corda.core.node.NodeInfo getNodeByLegalIdentity(net.corda.core.identity.AbstractParty) - @org.jetbrains.annotations.Nullable public abstract net.corda.core.node.NodeInfo getNodeByLegalName(net.corda.core.identity.CordaX500Name) - @org.jetbrains.annotations.NotNull public abstract net.corda.core.concurrent.CordaFuture getNodeReady() - @org.jetbrains.annotations.NotNull public abstract List getNodesByLegalIdentityKey(java.security.PublicKey) - @org.jetbrains.annotations.NotNull public abstract List getNodesByLegalName(net.corda.core.identity.CordaX500Name) - @org.jetbrains.annotations.Nullable public abstract net.corda.core.identity.Party getNotary(net.corda.core.identity.CordaX500Name) - @org.jetbrains.annotations.NotNull public abstract List getNotaryIdentities() - @org.jetbrains.annotations.Nullable public abstract net.corda.core.node.services.PartyInfo getPartyInfo(net.corda.core.identity.Party) - @org.jetbrains.annotations.Nullable public abstract net.corda.core.identity.Party getPeerByLegalName(net.corda.core.identity.CordaX500Name) - @org.jetbrains.annotations.Nullable public abstract net.corda.core.identity.PartyAndCertificate getPeerCertificateByLegalName(net.corda.core.identity.CordaX500Name) - public abstract boolean isNotary(net.corda.core.identity.Party) - public abstract boolean isValidatingNotary(net.corda.core.identity.Party) - @org.jetbrains.annotations.NotNull public abstract net.corda.core.messaging.DataFeed track() ## public abstract static class net.corda.core.node.services.NetworkMapCache$MapChange extends java.lang.Object @org.jetbrains.annotations.NotNull public abstract net.corda.core.node.NodeInfo getNode() @@ -1627,6 +1611,24 @@ public static final class net.corda.core.node.services.NetworkMapCache$MapChange public int hashCode() public String toString() ## +public interface net.corda.core.node.services.NetworkMapCacheBase + public abstract void clearNetworkMapCache() + @org.jetbrains.annotations.NotNull public abstract List getAllNodes() + @org.jetbrains.annotations.NotNull public abstract rx.Observable getChanged() + @org.jetbrains.annotations.Nullable public abstract net.corda.core.node.NodeInfo getNodeByAddress(net.corda.core.utilities.NetworkHostAndPort) + @org.jetbrains.annotations.Nullable public abstract net.corda.core.node.NodeInfo getNodeByLegalName(net.corda.core.identity.CordaX500Name) + @org.jetbrains.annotations.NotNull public abstract net.corda.core.concurrent.CordaFuture getNodeReady() + @org.jetbrains.annotations.NotNull public abstract List getNodesByLegalIdentityKey(java.security.PublicKey) + @org.jetbrains.annotations.NotNull public abstract List getNodesByLegalName(net.corda.core.identity.CordaX500Name) + @org.jetbrains.annotations.Nullable public abstract net.corda.core.identity.Party getNotary(net.corda.core.identity.CordaX500Name) + @org.jetbrains.annotations.NotNull public abstract List getNotaryIdentities() + @org.jetbrains.annotations.Nullable public abstract net.corda.core.node.services.PartyInfo getPartyInfo(net.corda.core.identity.Party) + @org.jetbrains.annotations.Nullable public abstract net.corda.core.identity.Party getPeerByLegalName(net.corda.core.identity.CordaX500Name) + @org.jetbrains.annotations.Nullable public abstract net.corda.core.identity.PartyAndCertificate getPeerCertificateByLegalName(net.corda.core.identity.CordaX500Name) + public abstract boolean isNotary(net.corda.core.identity.Party) + public abstract boolean isValidatingNotary(net.corda.core.identity.Party) + @org.jetbrains.annotations.NotNull public abstract net.corda.core.messaging.DataFeed track() +## public abstract class net.corda.core.node.services.NotaryService extends net.corda.core.serialization.SingletonSerializeAsToken public () @org.jetbrains.annotations.NotNull public abstract net.corda.core.flows.FlowLogic createServiceFlow(net.corda.core.flows.FlowSession) diff --git a/core/src/main/kotlin/net/corda/core/node/services/NetworkMapCache.kt b/core/src/main/kotlin/net/corda/core/node/services/NetworkMapCache.kt index 792baadb39..7e6f959e4f 100644 --- a/core/src/main/kotlin/net/corda/core/node/services/NetworkMapCache.kt +++ b/core/src/main/kotlin/net/corda/core/node/services/NetworkMapCache.kt @@ -18,8 +18,7 @@ import java.security.PublicKey * from an authoritative service, and adds easy lookup of the data stored within it. Generally it would be initialised * with a specified network map service, which it fetches data from and then subscribes to updates of. */ -interface NetworkMapCache { - +interface NetworkMapCache : NetworkMapCacheBase { @CordaSerializable sealed class MapChange { abstract val node: NodeInfo @@ -29,6 +28,22 @@ interface NetworkMapCache { data class Modified(override val node: NodeInfo, val previousNode: NodeInfo) : MapChange() } + /** + * Look up the node info for a specific party. Will attempt to de-anonymise the party if applicable; if the party + * is anonymised and the well known party cannot be resolved, it is impossible ot identify the node and therefore this + * returns null. + * Notice that when there are more than one node for a given party (in case of distributed services) first service node + * found will be returned. See also: [NetworkMapCache.getNodesByLegalIdentityKey]. + * + * @param party party to retrieve node information for. + * @return the node for the identity, or null if the node could not be found. This does not necessarily mean there is + * no node for the party, only that this cache is unaware of it. + */ + fun getNodeByLegalIdentity(party: AbstractParty): NodeInfo? +} + +/** Subset of [NetworkMapCache] that doesn't depend on an [IdentityService]. */ +interface NetworkMapCacheBase { // DOCSTART 1 /** * A list of notary services available on the network. @@ -40,7 +55,7 @@ interface NetworkMapCache { // DOCEND 1 /** Tracks changes to the network map cache. */ - val changed: Observable + val changed: Observable /** Future to track completion of the NetworkMapService registration. */ val nodeReady: CordaFuture @@ -48,20 +63,7 @@ interface NetworkMapCache { * Atomically get the current party nodes and a stream of updates. Note that the Observable buffers updates until the * first subscriber is registered so as to avoid racing with early updates. */ - fun track(): DataFeed, MapChange> - - /** - * Look up the node info for a specific party. Will attempt to de-anonymise the party if applicable; if the party - * is anonymised and the well known party cannot be resolved, it is impossible ot identify the node and therefore this - * returns null. - * Notice that when there are more than one node for a given party (in case of distributed services) first service node - * found will be returned. See also: [getNodesByLegalIdentityKey]. - * - * @param party party to retrieve node information for. - * @return the node for the identity, or null if the node could not be found. This does not necessarily mean there is - * no node for the party, only that this cache is unaware of it. - */ - fun getNodeByLegalIdentity(party: AbstractParty): NodeInfo? + fun track(): DataFeed, NetworkMapCache.MapChange> /** * Look up the node info for a legal name. diff --git a/node/src/integration-test/kotlin/net/corda/node/services/network/PersistentNetworkMapCacheTest.kt b/node/src/integration-test/kotlin/net/corda/node/services/network/PersistentNetworkMapCacheTest.kt index fae4c25c51..00671b35a9 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/network/PersistentNetworkMapCacheTest.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/network/PersistentNetworkMapCacheTest.kt @@ -46,7 +46,7 @@ class PersistentNetworkMapCacheTest : NodeBasedTest() { @Test fun `get nodes by owning key and by name, no network map service`() { val alice = startNodesWithPort(listOf(ALICE), noNetworkMap = true)[0] - val netCache = alice.services.networkMapCache as PersistentNetworkMapCache + val netCache = alice.services.networkMapCache alice.database.transaction { val res = netCache.getNodeByLegalIdentity(alice.info.chooseIdentity()) assertEquals(alice.info, res) @@ -58,7 +58,7 @@ class PersistentNetworkMapCacheTest : NodeBasedTest() { @Test fun `get nodes by address no network map service`() { val alice = startNodesWithPort(listOf(ALICE), noNetworkMap = true)[0] - val netCache = alice.services.networkMapCache as PersistentNetworkMapCache + val netCache = alice.services.networkMapCache alice.database.transaction { val res = netCache.getNodeByAddress(alice.info.addresses[0]) assertEquals(alice.info, res) diff --git a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt index 148e630def..dee4123fd0 100644 --- a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt +++ b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt @@ -24,7 +24,6 @@ import net.corda.core.node.NodeInfo import net.corda.core.node.ServiceHub import net.corda.core.node.StateLoader import net.corda.core.node.services.* -import net.corda.core.node.services.NetworkMapCache.MapChange import net.corda.core.serialization.SerializationWhitelist import net.corda.core.serialization.SerializeAsToken import net.corda.core.serialization.SingletonSerializeAsToken @@ -142,6 +141,7 @@ abstract class AbstractNode(config: NodeConfiguration, protected val services: ServiceHubInternal get() = _services private lateinit var _services: ServiceHubInternalImpl protected lateinit var legalIdentity: PartyAndCertificate + private lateinit var allIdentities: List protected lateinit var info: NodeInfo protected var myNotaryIdentity: PartyAndCertificate? = null protected lateinit var checkpointStorage: CheckpointStorage @@ -468,8 +468,12 @@ abstract class AbstractNode(config: NodeConfiguration, val cordappProvider = CordappProviderImpl(cordappLoader, attachments) _services = ServiceHubInternalImpl(schemaService, transactionStorage, stateLoader, MonitoringService(metrics), cordappProvider) legalIdentity = obtainIdentity(notaryConfig = null) + // TODO We keep only notary identity as additional legalIdentity if we run it on a node . Multiple identities need more design thinking. + myNotaryIdentity = getNotaryIdentity() + allIdentities = listOf(legalIdentity, myNotaryIdentity).filterNotNull() network = makeMessagingService(legalIdentity) - info = makeInfo(legalIdentity) + val addresses = myAddresses() // TODO There is no support for multiple IP addresses yet. + info = NodeInfo(addresses, allIdentities, versionInfo.platformVersion, platformClock.instant().toEpochMilli()) val networkMapCache = services.networkMapCache val tokenizableServices = mutableListOf(attachments, network, services.vaultService, services.keyManagementService, services.identityService, platformClock, @@ -488,15 +492,6 @@ abstract class AbstractNode(config: NodeConfiguration, HibernateObserver.install(services.vaultService.rawUpdates, database.hibernateConfig) } - private fun makeInfo(legalIdentity: PartyAndCertificate): NodeInfo { - // TODO We keep only notary identity as additional legalIdentity if we run it on a node . Multiple identities need more design thinking. - myNotaryIdentity = getNotaryIdentity() - val allIdentitiesList = mutableListOf(legalIdentity) - myNotaryIdentity?.let { allIdentitiesList.add(it) } - val addresses = myAddresses() // TODO There is no support for multiple IP addresses yet. - return NodeInfo(addresses, allIdentitiesList, versionInfo.platformVersion, platformClock.instant().toEpochMilli()) - } - /** * Obtain the node's notary identity if it's configured to be one. If part of a distributed notary then this will be * the distributed identity shared across all the nodes of the cluster. @@ -665,17 +660,7 @@ abstract class AbstractNode(config: NodeConfiguration, val caCertificates: Array = listOf(legalIdentity.certificate, clientCa?.certificate?.cert) .filterNotNull() .toTypedArray() - val service = PersistentIdentityService(info.legalIdentitiesAndCerts, trustRoot = trustRoot, caCertificates = *caCertificates) - services.networkMapCache.allNodes.forEach { it.legalIdentitiesAndCerts.forEach { service.verifyAndRegisterIdentity(it) } } - services.networkMapCache.changed.subscribe { mapChange -> - // TODO how should we handle network map removal - if (mapChange is MapChange.Added) { - mapChange.node.legalIdentitiesAndCerts.forEach { - service.verifyAndRegisterIdentity(it) - } - } - } - return service + return PersistentIdentityService(allIdentities, trustRoot = trustRoot, caCertificates = *caCertificates) } protected abstract fun makeTransactionVerifierService(): TransactionVerifierService @@ -774,7 +759,7 @@ abstract class AbstractNode(config: NodeConfiguration, override val stateMachineRecordedTransactionMapping = DBTransactionMappingStorage() override val auditService = DummyAuditService() override val transactionVerifierService by lazy { makeTransactionVerifierService() } - override val networkMapCache by lazy { PersistentNetworkMapCache(this) } + override val networkMapCache by lazy { NetworkMapCacheImpl(PersistentNetworkMapCache(this@AbstractNode.database, this@AbstractNode.configuration), identityService) } override val vaultService by lazy { makeVaultService(keyManagementService, stateLoader) } override val contractUpgradeService by lazy { ContractUpgradeServiceImpl() } diff --git a/node/src/main/kotlin/net/corda/node/services/api/ServiceHubInternal.kt b/node/src/main/kotlin/net/corda/node/services/api/ServiceHubInternal.kt index 36f9f1fada..de7b8eb2eb 100644 --- a/node/src/main/kotlin/net/corda/node/services/api/ServiceHubInternal.kt +++ b/node/src/main/kotlin/net/corda/node/services/api/ServiceHubInternal.kt @@ -16,6 +16,7 @@ import net.corda.core.messaging.StateMachineTransactionMapping import net.corda.core.node.NodeInfo import net.corda.core.node.ServiceHub import net.corda.core.node.services.NetworkMapCache +import net.corda.core.node.services.NetworkMapCacheBase import net.corda.core.node.services.TransactionStorage import net.corda.core.serialization.CordaSerializable import net.corda.core.transactions.SignedTransaction @@ -28,7 +29,8 @@ import net.corda.node.services.statemachine.FlowLogicRefFactoryImpl import net.corda.node.services.statemachine.FlowStateMachineImpl import net.corda.node.utilities.CordaPersistence -interface NetworkMapCacheInternal : NetworkMapCache { +interface NetworkMapCacheInternal : NetworkMapCache, NetworkMapCacheBaseInternal +interface NetworkMapCacheBaseInternal : NetworkMapCacheBase { /** * Deregister from updates from the given map service. * @param network the network messaging service. diff --git a/node/src/main/kotlin/net/corda/node/services/network/PersistentNetworkMapCache.kt b/node/src/main/kotlin/net/corda/node/services/network/PersistentNetworkMapCache.kt index eb45e7a0df..c6caa567da 100644 --- a/node/src/main/kotlin/net/corda/node/services/network/PersistentNetworkMapCache.kt +++ b/node/src/main/kotlin/net/corda/node/services/network/PersistentNetworkMapCache.kt @@ -12,6 +12,7 @@ import net.corda.core.internal.concurrent.openFuture import net.corda.core.messaging.DataFeed import net.corda.core.messaging.SingleMessageRecipient import net.corda.core.node.NodeInfo +import net.corda.core.node.services.IdentityService import net.corda.core.node.services.NetworkMapCache.MapChange import net.corda.core.node.services.NotaryService import net.corda.core.node.services.PartyInfo @@ -24,12 +25,14 @@ import net.corda.core.utilities.loggerFor import net.corda.core.utilities.toBase58String import net.corda.node.services.api.NetworkCacheException import net.corda.node.services.api.NetworkMapCacheInternal -import net.corda.node.services.api.ServiceHubInternal +import net.corda.node.services.api.NetworkMapCacheBaseInternal +import net.corda.node.services.config.NodeConfiguration import net.corda.node.services.messaging.MessagingService import net.corda.node.services.messaging.createMessage import net.corda.node.services.messaging.sendRequest import net.corda.node.services.network.NetworkMapService.FetchMapResponse import net.corda.node.services.network.NetworkMapService.SubscribeResponse +import net.corda.node.utilities.* import net.corda.node.utilities.AddOrRemove import net.corda.node.utilities.bufferUntilDatabaseCommit import net.corda.node.utilities.wrapWithDatabaseTransaction @@ -42,15 +45,32 @@ import java.util.* import javax.annotation.concurrent.ThreadSafe import kotlin.collections.HashMap +class NetworkMapCacheImpl(networkMapCacheBase: NetworkMapCacheBaseInternal, private val identityService: IdentityService) : NetworkMapCacheBaseInternal by networkMapCacheBase, NetworkMapCacheInternal { + init { + networkMapCacheBase.allNodes.forEach { it.legalIdentitiesAndCerts.forEach { identityService.verifyAndRegisterIdentity(it) } } + networkMapCacheBase.changed.subscribe { mapChange -> + // TODO how should we handle network map removal + if (mapChange is MapChange.Added) { + mapChange.node.legalIdentitiesAndCerts.forEach { + identityService.verifyAndRegisterIdentity(it) + } + } + } + } + + override fun getNodeByLegalIdentity(party: AbstractParty): NodeInfo? { + val wellKnownParty = identityService.wellKnownPartyFromAnonymous(party) + return wellKnownParty?.let { + getNodesByLegalIdentityKey(it.owningKey).firstOrNull() + } + } +} + /** * Extremely simple in-memory cache of the network map. - * - * @param serviceHub an optional service hub from which we'll take the identity service. We take a service hub rather - * than the identity service directly, as this avoids problems with service start sequence (network map cache - * and identity services depend on each other). Should always be provided except for unit test cases. */ @ThreadSafe -open class PersistentNetworkMapCache(private val serviceHub: ServiceHubInternal) : SingletonSerializeAsToken(), NetworkMapCacheInternal { +open class PersistentNetworkMapCache(private val database: CordaPersistence, configuration: NodeConfiguration) : SingletonSerializeAsToken(), NetworkMapCacheBaseInternal { companion object { val logger = loggerFor() } @@ -86,12 +106,12 @@ open class PersistentNetworkMapCache(private val serviceHub: ServiceHubInternal) .sortedBy { it.name.toString() } } - private val nodeInfoSerializer = NodeInfoWatcher(serviceHub.configuration.baseDirectory, - serviceHub.configuration.additionalNodeInfoPollingFrequencyMsec) + private val nodeInfoSerializer = NodeInfoWatcher(configuration.baseDirectory, + configuration.additionalNodeInfoPollingFrequencyMsec) init { loadFromFiles() - serviceHub.database.transaction { loadFromDB(session) } + database.transaction { loadFromDB(session) } } private fun loadFromFiles() { @@ -100,7 +120,7 @@ open class PersistentNetworkMapCache(private val serviceHub: ServiceHubInternal) } override fun getPartyInfo(party: Party): PartyInfo? { - val nodes = serviceHub.database.transaction { queryByIdentityKey(session, party.owningKey) } + val nodes = database.transaction { queryByIdentityKey(session, party.owningKey) } if (nodes.size == 1 && nodes[0].isLegalIdentity(party)) { return PartyInfo.SingleNode(party, nodes[0].addresses) } @@ -115,20 +135,13 @@ open class PersistentNetworkMapCache(private val serviceHub: ServiceHubInternal) } override fun getNodeByLegalName(name: CordaX500Name): NodeInfo? = getNodesByLegalName(name).firstOrNull() - override fun getNodesByLegalName(name: CordaX500Name): List = serviceHub.database.transaction { queryByLegalName(session, name) } + override fun getNodesByLegalName(name: CordaX500Name): List = database.transaction { queryByLegalName(session, name) } override fun getNodesByLegalIdentityKey(identityKey: PublicKey): List = - serviceHub.database.transaction { queryByIdentityKey(session, identityKey) } + database.transaction { queryByIdentityKey(session, identityKey) } - override fun getNodeByLegalIdentity(party: AbstractParty): NodeInfo? { - val wellKnownParty = serviceHub.identityService.wellKnownPartyFromAnonymous(party) - return wellKnownParty?.let { - getNodesByLegalIdentityKey(it.owningKey).firstOrNull() - } - } + override fun getNodeByAddress(address: NetworkHostAndPort): NodeInfo? = database.transaction { queryByAddress(session, address) } - override fun getNodeByAddress(address: NetworkHostAndPort): NodeInfo? = serviceHub.database.transaction { queryByAddress(session, address) } - - override fun getPeerCertificateByLegalName(name: CordaX500Name): PartyAndCertificate? = serviceHub.database.transaction { queryIdentityByLegalName(session, name) } + override fun getPeerCertificateByLegalName(name: CordaX500Name): PartyAndCertificate? = database.transaction { queryIdentityByLegalName(session, name) } override fun track(): DataFeed, MapChange> { synchronized(_changed) { @@ -180,13 +193,13 @@ open class PersistentNetworkMapCache(private val serviceHub: ServiceHubInternal) val previousNode = registeredNodes.put(node.legalIdentities.first().owningKey, node) // TODO hack... we left the first one as special one if (previousNode == null) { logger.info("No previous node found") - serviceHub.database.transaction { + database.transaction { updateInfoDB(node) changePublisher.onNext(MapChange.Added(node)) } } else if (previousNode != node) { logger.info("Previous node was found as: $previousNode") - serviceHub.database.transaction { + database.transaction { updateInfoDB(node) changePublisher.onNext(MapChange.Modified(node, previousNode)) } @@ -201,7 +214,7 @@ open class PersistentNetworkMapCache(private val serviceHub: ServiceHubInternal) logger.info("Removing node with info: $node") synchronized(_changed) { registeredNodes.remove(node.legalIdentities.first().owningKey) - serviceHub.database.transaction { + database.transaction { removeInfoDB(session, node) changePublisher.onNext(MapChange.Removed(node)) } @@ -236,7 +249,7 @@ open class PersistentNetworkMapCache(private val serviceHub: ServiceHubInternal) } override val allNodes: List - get() = serviceHub.database.transaction { + get() = database.transaction { getAllInfos(session).map { it.toNodeInfo() } } @@ -285,8 +298,8 @@ open class PersistentNetworkMapCache(private val serviceHub: ServiceHubInternal) private fun updateInfoDB(nodeInfo: NodeInfo) { // TODO Temporary workaround to force isolated transaction (otherwise it causes race conditions when processing // network map registration on network map node) - serviceHub.database.dataSource.connection.use { - val session = serviceHub.database.entityManagerFactory.withOptions().connection(it.apply { + database.dataSource.connection.use { + val session = database.entityManagerFactory.withOptions().connection(it.apply { transactionIsolation = 1 }).openSession() session.use { @@ -367,7 +380,7 @@ open class PersistentNetworkMapCache(private val serviceHub: ServiceHubInternal) } override fun clearNetworkMapCache() { - serviceHub.database.transaction { + database.transaction { val result = getAllInfos(session) for (nodeInfo in result) session.remove(nodeInfo) } diff --git a/node/src/test/kotlin/net/corda/node/services/messaging/ArtemisMessagingTests.kt b/node/src/test/kotlin/net/corda/node/services/messaging/ArtemisMessagingTests.kt index 418322b589..b4504546bc 100644 --- a/node/src/test/kotlin/net/corda/node/services/messaging/ArtemisMessagingTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/messaging/ArtemisMessagingTests.kt @@ -1,6 +1,7 @@ package net.corda.node.services.messaging import com.codahale.metrics.MetricRegistry +import com.nhaarman.mockito_kotlin.mock import net.corda.core.concurrent.CordaFuture import net.corda.core.crypto.generateKeyPair import net.corda.core.internal.concurrent.doneFuture @@ -12,10 +13,10 @@ import net.corda.node.services.RPCUserServiceImpl import net.corda.node.services.api.MonitoringService import net.corda.node.services.config.NodeConfiguration import net.corda.node.services.config.configureWithDevSSLCertificate +import net.corda.node.services.network.NetworkMapCacheImpl import net.corda.node.services.network.PersistentNetworkMapCache import net.corda.node.services.network.NetworkMapService import net.corda.node.services.transactions.PersistentUniquenessProvider -import net.corda.node.testing.MockServiceHubInternal import net.corda.node.utilities.AffinityExecutor.ServiceAffinityExecutor import net.corda.node.utilities.CordaPersistence import net.corda.node.utilities.configureDatabase @@ -57,7 +58,7 @@ class ArtemisMessagingTests : TestDependencyInjectionBase() { var messagingClient: NodeMessagingClient? = null var messagingServer: ArtemisMessagingServer? = null - lateinit var networkMapCache: PersistentNetworkMapCache + lateinit var networkMapCache: NetworkMapCacheImpl val rpcOps = object : RPCOps { override val protocolVersion: Int get() = throw UnsupportedOperationException() @@ -73,7 +74,7 @@ class ArtemisMessagingTests : TestDependencyInjectionBase() { LogHelper.setLevel(PersistentUniquenessProvider::class) database = configureDatabase(makeTestDataSourceProperties(), makeTestDatabaseProperties(), ::makeTestIdentityService) networkMapRegistrationFuture = doneFuture(Unit) - networkMapCache = PersistentNetworkMapCache(serviceHub = object : MockServiceHubInternal(database, config) {}) + networkMapCache = NetworkMapCacheImpl(PersistentNetworkMapCache(database, config), mock()) } @After diff --git a/node/src/test/kotlin/net/corda/node/services/network/NetworkMapCacheTest.kt b/node/src/test/kotlin/net/corda/node/services/network/NetworkMapCacheTest.kt index 10aa2c62ac..8d4f2d65a4 100644 --- a/node/src/test/kotlin/net/corda/node/services/network/NetworkMapCacheTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/network/NetworkMapCacheTest.kt @@ -83,7 +83,7 @@ class NetworkMapCacheTest { val aliceNode = mockNet.createPartyNode(ALICE.name) val notaryLegalIdentity = notaryNode.info.chooseIdentity() val alice = aliceNode.info.chooseIdentity() - val notaryCache = notaryNode.services.networkMapCache as PersistentNetworkMapCache + val notaryCache = notaryNode.services.networkMapCache mockNet.runNetwork() notaryNode.database.transaction { assertThat(notaryCache.getNodeByLegalIdentity(alice) != null) diff --git a/testing/node-driver/src/main/kotlin/net/corda/node/testing/MockServiceHubInternal.kt b/testing/node-driver/src/main/kotlin/net/corda/node/testing/MockServiceHubInternal.kt index 3c4631376e..62fdc58934 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/node/testing/MockServiceHubInternal.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/node/testing/MockServiceHubInternal.kt @@ -15,6 +15,7 @@ import net.corda.node.serialization.NodeClock import net.corda.node.services.api.* import net.corda.node.services.config.NodeConfiguration import net.corda.node.services.messaging.MessagingService +import net.corda.node.services.network.NetworkMapCacheImpl import net.corda.node.services.statemachine.StateMachineManager import net.corda.node.services.transactions.InMemoryTransactionVerifierService import net.corda.node.utilities.CordaPersistence @@ -59,7 +60,7 @@ open class MockServiceHubInternal( override val networkService: MessagingService get() = network ?: throw UnsupportedOperationException() override val networkMapCache: NetworkMapCacheInternal - get() = mapCache ?: MockNetworkMapCache(this) + get() = mapCache ?: NetworkMapCacheImpl(MockNetworkMapCache(database, configuration), identityService) override val clock: Clock get() = overrideClock ?: throw UnsupportedOperationException() override val myInfo: NodeInfo diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNetworkMapCache.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNetworkMapCache.kt index 1b82ca04c1..889f9cfd3b 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNetworkMapCache.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNetworkMapCache.kt @@ -7,9 +7,9 @@ import net.corda.core.identity.Party import net.corda.core.node.NodeInfo import net.corda.core.node.services.NetworkMapCache import net.corda.core.utilities.NetworkHostAndPort -import net.corda.core.utilities.NonEmptySet -import net.corda.node.services.api.ServiceHubInternal +import net.corda.node.services.config.NodeConfiguration import net.corda.node.services.network.PersistentNetworkMapCache +import net.corda.node.utilities.CordaPersistence import net.corda.testing.getTestPartyAndCertificate import rx.Observable import rx.subjects.PublishSubject @@ -18,7 +18,7 @@ import java.math.BigInteger /** * Network map cache with no backing map service. */ -class MockNetworkMapCache(serviceHub: ServiceHubInternal) : PersistentNetworkMapCache(serviceHub) { +class MockNetworkMapCache(database: CordaPersistence, configuration: NodeConfiguration) : PersistentNetworkMapCache(database, configuration) { private companion object { val BANK_C = getTestPartyAndCertificate(CordaX500Name(organisation = "Bank C", locality = "London", country = "GB"), entropyToKeyPair(BigInteger.valueOf(1000)).public) val BANK_D = getTestPartyAndCertificate(CordaX500Name(organisation = "Bank D", locality = "London", country = "GB"), entropyToKeyPair(BigInteger.valueOf(2000)).public) diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt index ae810b581a..1c8b4635cc 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt @@ -18,7 +18,6 @@ import net.corda.core.messaging.RPCOps import net.corda.core.messaging.SingleMessageRecipient import net.corda.core.node.services.IdentityService import net.corda.core.node.services.KeyManagementService -import net.corda.core.node.services.NetworkMapCache import net.corda.core.serialization.SerializationWhitelist import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.getOrThrow @@ -187,26 +186,6 @@ class MockNetwork(private val networkSendManuallyPumped: Boolean = false, .getOrThrow() } - override fun makeIdentityService(trustRoot: X509Certificate, - clientCa: CertificateAndKeyPair?, - legalIdentity: PartyAndCertificate): IdentityService { - val caCertificates: Array = listOf(legalIdentity.certificate, clientCa?.certificate?.cert) - .filterNotNull() - .toTypedArray() - val identityService = PersistentIdentityService(info.legalIdentitiesAndCerts, - trustRoot = trustRoot, caCertificates = *caCertificates) - services.networkMapCache.allNodes.forEach { it.legalIdentitiesAndCerts.forEach { identityService.verifyAndRegisterIdentity(it) } } - services.networkMapCache.changed.subscribe { mapChange -> - // TODO how should we handle network map removal - if (mapChange is NetworkMapCache.MapChange.Added) { - mapChange.node.legalIdentitiesAndCerts.forEach { - identityService.verifyAndRegisterIdentity(it) - } - } - } - return identityService - } - override fun makeKeyManagementService(identityService: IdentityService): KeyManagementService { return E2ETestKeyManagementService(identityService, partyKeys + (notaryIdentity?.let { setOf(it.second) } ?: emptySet())) } diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/SimpleNode.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/SimpleNode.kt index 3b7bf9130e..6dad146916 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/SimpleNode.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/SimpleNode.kt @@ -14,8 +14,8 @@ import net.corda.node.services.identity.InMemoryIdentityService import net.corda.node.services.keys.E2ETestKeyManagementService import net.corda.node.services.messaging.ArtemisMessagingServer import net.corda.node.services.messaging.NodeMessagingClient +import net.corda.node.services.network.NetworkMapCacheImpl import net.corda.node.services.schema.NodeSchemaService -import net.corda.node.testing.MockServiceHubInternal import net.corda.node.utilities.AffinityExecutor.ServiceAffinityExecutor import net.corda.node.utilities.CordaPersistence import net.corda.node.utilities.configureDatabase @@ -42,7 +42,7 @@ class SimpleNode(val config: NodeConfiguration, val address: NetworkHostAndPort val executor = ServiceAffinityExecutor(config.myLegalName.organisation, 1) // TODO: We should have a dummy service hub rather than change behaviour in tests val broker = ArtemisMessagingServer(config, address.port, rpcAddress.port, - MockNetworkMapCache(serviceHub = object : MockServiceHubInternal(database = database, configuration = config) {}), userService) + NetworkMapCacheImpl(MockNetworkMapCache(database, config), identityService), userService) val networkMapRegistrationFuture = openFuture() val network = database.transaction { NodeMessagingClient( From 1e05f27bae76c4af9c7639f73217dbc983a94558 Mon Sep 17 00:00:00 2001 From: Katelyn Baker Date: Thu, 19 Oct 2017 12:01:39 +0100 Subject: [PATCH 15/94] CORDA-725 - Change AMQP identifier to officially assigned value This does change our header format so pre-cached test files need regenerating --- .../internal/serialization/amqp/Schema.kt | 11 ++++-- .../serialization/amqp/EvolvabilityTests.kt | 34 +++++++++++++----- .../amqp/EvolvabilityTests.addAdditionalParam | Bin 261 -> 261 bytes ...bilityTests.addAdditionalParamNotMandatory | Bin 273 -> 273 bytes .../EvolvabilityTests.addAndRemoveParameters | Bin 390 -> 390 bytes ...yTests.addMandatoryFieldWithAltConstructor | Bin 285 -> 285 bytes ...andatoryFieldWithAltConstructorUnAnnotated | Bin 296 -> 296 bytes ...dMandatoryFieldWithAltReorderedConstructor | Bin 387 -> 387 bytes ...FieldWithAltReorderedConstructorAndRemoval | Bin 403 -> 403 bytes .../amqp/EvolvabilityTests.changeSubType | Bin 616 -> 616 bytes .../amqp/EvolvabilityTests.multiVersion.1 | Bin 294 -> 294 bytes .../amqp/EvolvabilityTests.multiVersion.2 | Bin 327 -> 327 bytes .../amqp/EvolvabilityTests.multiVersion.3 | Bin 372 -> 372 bytes ...volvabilityTests.multiVersionWithRemoval.1 | Bin 338 -> 338 bytes ...volvabilityTests.multiVersionWithRemoval.2 | Bin 392 -> 392 bytes ...volvabilityTests.multiVersionWithRemoval.3 | Bin 425 -> 425 bytes .../amqp/EvolvabilityTests.removeParameters | Bin 378 -> 378 bytes ...vabilityTests.simpleOrderSwapDifferentType | Bin 311 -> 311 bytes .../EvolvabilityTests.simpleOrderSwapSameType | Bin 302 -> 302 bytes 19 files changed, 35 insertions(+), 10 deletions(-) diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/Schema.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/Schema.kt index adb0c44f0c..d3d767c4eb 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/Schema.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/Schema.kt @@ -18,8 +18,15 @@ import java.util.* import net.corda.nodeapi.internal.serialization.carpenter.Field as CarpenterField import net.corda.nodeapi.internal.serialization.carpenter.Schema as CarpenterSchema -// TODO: get an assigned number as per AMQP spec -const val DESCRIPTOR_TOP_32BITS: Long = 0xc0da0000 +/** + * R3 AMQP assigned enterprise number + * + * see [https://www.iana.org/assignments/enterprise-numbers/enterprise-numbers] + * + * Repeated here for brevity: + * 50530 - R3 - Mike Hearn - mike&r3.com + */ +const val DESCRIPTOR_TOP_32BITS: Long = 0xc5620000 const val DESCRIPTOR_DOMAIN: String = "net.corda" diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.kt index cdf690029c..9c4a949384 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.kt @@ -30,6 +30,7 @@ class EvolvabilityTests { // data class C (val a: Int, val b: Int) // val sc = SerializationOutput(sf).serialize(C(A, B)) // f.writeBytes(sc.bytes) + // println (path) // new version of the class, in this case the order of the parameters has been swapped data class C(val b: Int, val a: Int) @@ -54,6 +55,7 @@ class EvolvabilityTests { // data class C (val a: Int, val b: String) // val sc = SerializationOutput(sf).serialize(C(A, B)) // f.writeBytes(sc.bytes) + // println (path) // new version of the class, in this case the order of the parameters has been swapped data class C(val b: String, val a: Int) @@ -78,7 +80,6 @@ class EvolvabilityTests { // val sc = SerializationOutput(sf).serialize(C(A)) // f.writeBytes(sc.bytes) // println ("Path = $path") - data class C(val a: Int, val b: Int?) val sc2 = f.readBytes() @@ -300,9 +301,6 @@ class EvolvabilityTests { val path2 = EvolvabilityTests::class.java.getResource("EvolvabilityTests.multiVersion.2") val path3 = EvolvabilityTests::class.java.getResource("EvolvabilityTests.multiVersion.3") - @Suppress("UNUSED_VARIABLE") - val f = File(path1.toURI()) - val a = 100 val b = 200 val c = 300 @@ -312,14 +310,24 @@ class EvolvabilityTests { // // Version 1: // data class C (val a: Int, val b: Int) + // + // val scc = SerializationOutput(sf).serialize(C(a, b)) + // File(path1.toURI()).writeBytes(scc.bytes) + // println ("Path = $path1") + // // Version 2 - add param c // data class C (val c: Int, val b: Int, val a: Int) + // + // val scc = SerializationOutput(sf).serialize(C(c, b, a)) + // File(path2.toURI()).writeBytes(scc.bytes) + // println ("Path = $path2") + // // Version 3 - add param d // data class C (val b: Int, val c: Int, val d: Int, val a: Int) // // val scc = SerializationOutput(sf).serialize(C(b, c, d, a)) - // f.writeBytes(scc.bytes) - // println ("Path = $path1") + // File(path3.toURI()).writeBytes(scc.bytes) + // println ("Path = $path3") @Suppress("UNUSED") data class C(val e: Int, val c: Int, val b: Int, val a: Int, val d: Int) { @@ -409,14 +417,24 @@ class EvolvabilityTests { // // Version 1: // data class C (val a: Int, val b: Int, val c: Int) + // + // val scc = SerializationOutput(sf).serialize(C(a, b, c)) + // File(path1.toURI()).writeBytes(scc.bytes) + // println ("Path = $path1") + // // Version 2 - add param c // data class C (val b: Int, val c: Int, val d: Int, val e: Int) + // + // val scc = SerializationOutput(sf).serialize(C(b, c, d, e)) + // File(path2.toURI()).writeBytes(scc.bytes) + // println ("Path = $path2") + // // Version 3 - add param d // data class C (val b: Int, val c: Int, val d: Int, val e: Int, val f: Int) // // val scc = SerializationOutput(sf).serialize(C(b, c, d, e, f)) - // File(path1.toURI()).writeBytes(scc.bytes) - // println ("Path = $path1") + // File(path3.toURI()).writeBytes(scc.bytes) + // println ("Path = $path3") @Suppress("UNUSED") data class C(val b: Int, val c: Int, val d: Int, val e: Int, val f: Int, val g: Int) { diff --git a/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.addAdditionalParam b/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.addAdditionalParam index 06c32d9eff45607d1bf17103d69f1d624f52ba21..e9accefe39961b96b51b5e54340ad82b03a92a81 100644 GIT binary patch delta 58 zcmZo=YGo2g&M!(yWMp7qXaHh{qe&A5OofgnF)$t2#du&lBV05*nF%Pj#~4I@6afI! C6A~@} delta 58 zcmZo=YGo2g&M!(yWMp7qXaHh{1Ggp$m4i!rP%V4oS9eRx{%QTs1X1JL?({_ delta 130 zcmZo;Zetcm&M!(yWMp7qXaHh{1Ggp$crpRWXekg`o(CeI8w1J7GK^9I2W~O291vt* q$e6T{t+=EpGcVoM!NJi9rWY!ij7w4gs550Db7o$N>q15Yphf^At12e| diff --git a/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.addMandatoryFieldWithAltConstructor b/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.addMandatoryFieldWithAltConstructor index 6daa3c7714a93d9bdc22deece8c77a0b0948c54f..a7e207ac7570921b04699d400c231783cff4597f 100644 GIT binary patch delta 83 zcmbQsG?z&rIlm|+k&%Icp#g{)jwVeMuogO+#K3gm4C8^5jBwG|Qg)yy^8q!ci83k^ d|8j`|c`OG6*cUP;E@aNkD{)=OXyD-J1OVqL7@hzC delta 83 zcmbQsG?z&rIlm|+k&%Icp#g{)4&0h3U@dgu76a3PGmHmLGQve;OWA><%m>t%Cd#Nx d{L3W<8r=W@ diff --git a/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.addMandatoryFieldWithAltConstructorUnAnnotated b/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.addMandatoryFieldWithAltConstructorUnAnnotated index e47d5270f863e3b84da683b9a81d3b5659d2938f..e712d58560a6edc527cc909c5da1b97046d640f0 100644 GIT binary patch delta 75 zcmZ3%w1P<>Ilm|+k&%Icp#g{)jwVeMuogO+#K3gmI^%&WjBwG|1}31~ePc189LoU# V_Jxdz3z;+XN?aE*8aOyQ0RT%g7asrs delta 75 zcmZ3%w1P<>Ilm|+k&%Icp#g{)4&0h3U@dgu76a3P>x>7kFv3M+8<>D{_l?DXax4b~ V*cUP;E@aNkD{)=OXyD-J1OT2j8D0PY diff --git a/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.addMandatoryFieldWithAltReorderedConstructor b/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.addMandatoryFieldWithAltReorderedConstructor index 290fad125aaea3e7124f33638b8fc97fc291de78..87506ae2030620b373c093466c52b7e777c09ea0 100644 GIT binary patch delta 145 zcmZo>Ze|up&M!(yWMp7qXaHh{qe&A5g5{1TF)&?VU|{r~MPnV)(WJ?Y zjK=;zg)9dI*cUP;E@aNkD{)=OXyD-J1XBo=OTr;12-KInkgd3+C^IkJ)d8px0Jlvf AbN~PV delta 145 zcmZo>Ze|up&M!(yWMp7qXaHh{1Ggp$1j`+`#lUodfq_vRD8dM0@PQa`6^(UF2X0Mf zWHj~%Dr7kzz`l?%aUpYNUWw~MMgs>&CzwL0ToMjBL7=|mg>1zoMVWc&t`0zr0Jldc AnE(I) diff --git a/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.addMandatoryFieldWithAltReorderedConstructorAndRemoval b/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.addMandatoryFieldWithAltReorderedConstructorAndRemoval index 769ea67e2d69fd636d0493e3a9fa5ff08f793b3b..922eecc33539c7e8c483ad07ad2090138c8a0055 100644 GIT binary patch delta 145 zcmbQtJegS_Ilm|+k&%Icp#g{)jwVeM2$nmV#K3fcfq_vSD8dM0$blGe6^(sNN0TNC zG8+2>6|x)R|lX*0O4&U A)Bpeg delta 145 zcmbQtJegS_Ilm|+k&%Icp#g{)4&0h35G;4#76a1-1_nlTpa>&~AqQf>RW$Z79k?}F zkkQy5sF3A=0Q*A5#D&b6c_pq384Vm9onQ)~a!EMk1cCaJ7qS(X6lLb6yE*_h0s!G> BC;0#X diff --git a/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.changeSubType b/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.changeSubType index 1158e284fe5773c1747e0d43d496368a0d81793d..96e11c7f09fa2c31a82b92521617dbaca74dd69e 100644 GIT binary patch delta 121 zcmaFC@`6PmIlm|+k&%Icp#g{)jwVeM$Y27JRZ<{w8i+lK(OB+i5(CQt0rrKAi3^!C r^GaM7G8#BII>Gcz4rI(=11e!XuyW#pz{vtkyg+S}<(Z7du$hi{hldC diff --git a/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.multiVersion.1 b/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.multiVersion.1 index 46714457ecb568c3ca2adfa3b814dd0fedfc1797..2cca4802acc3dc849eea285c27201058c1b816fb 100644 GIT binary patch delta 103 zcmZ3+w2Vn0Ilm|+k&%Icp#g{)jwVeMa1c70#K3gm0^@t%Cd#Nx pe9y%MRQX%V2q?yKK!ANAW70zA%)AoUg^UIcj!rOLP`N~8IRFLPCVv0` diff --git a/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.multiVersion.3 b/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.multiVersion.3 index 6b3e9a3a37306afea3aae8325fb986fd638ef465..1e36577ca927ec4b8c0ad0f9d9a5a54794c47338 100644 GIT binary patch delta 112 zcmeyu^o2bjBwHP3?`u5MPomp9LoU# f_Jxdz3z;+XN?aE*8aOyQ!PG(JlJLkSBg+8*HR&dJ delta 131 zcmcb_bcsnIIlm|+k&%Icp#g{)4&0h3;3{iYx~J$zC7R delta 107 zcmeBR?qC*3&M!(yWMp7qXaHh{1Ggp$_%Z>>cqtHBod+Uc8w1J73XCA3$u^8qMh9*& fupAIzU&xrUkU2B2#C0K~frFzH%mk=hDzY2^s#GIu diff --git a/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.multiVersionWithRemoval.3 b/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.multiVersionWithRemoval.3 index f8a77054703c2a53b92506918766efe1f7c73876..c1dcb85ab809850fa86c4efbc57ab4bfcb907f05 100644 GIT binary patch delta 157 zcmZ3%2aGBp{ArE%K-pN3n_d6 delta 157 zcmZ3&~VG3fvRW#%=0o8pl y2B~8?Ai%zmF=-)lW?qTwLPi4zM<lXV!SL6j$> dwAj%k29^T?>9eqoqJ(c@l`cZ44wQ3owF&ChIUtgD6i% dX|V&h7+4MnurFjxS;(B3SK_*m(ZIpc2>?sl9=QMj diff --git a/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.simpleOrderSwapDifferentType b/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.simpleOrderSwapDifferentType index 2e6a7098f682d45036947aa23143678a6ab348b2..6d81269d3e1d392570390edef6cda5d01aa3b645 100644 GIT binary patch delta 104 zcmdnaw4F&HIlm|+k&%Icp#g{)jwVeMa1c70#K3gmA>)C2jBwGUTqdC0MPp;29LoU# o_Jxdz3z;+XN?aE*8aOyQ!PEie1c7o%3)zZGiZb)kT^)du0B1uVWB>pF delta 104 zcmdnaw4F&HIlm|+k&%Icp#g{)4&0h3;2?D176a3Phl~gAF~UWYa+!c~7mba9ax4b~ p*cUP;E@aNkD{)=OXyD-J1XBl;69md7Eo3V$Day=CcXa?t0ssY6A{GDu diff --git a/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.simpleOrderSwapSameType b/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.simpleOrderSwapSameType index 6e04f4bdf329090688724dc39dab89427dbdae27..b96b0ce60ecd349703faf4b3e207ec9d54d41f87 100644 GIT binary patch delta 81 zcmZ3-w2nz2Ilm|+k&%Icp#g{)jwVeMuoXI*#K3gmHsgUCjBwHT3?`u5Nn;TCPf84^ Yg5`h!`$EQ~h0K|GC9Vq@4ICVu0QR;S+yDRo delta 81 zcmZ3-w2nz2Ilm|+k&%Icp#g{)4&0h3U@LUs76a3P+l&WpFv3OSGnjyKCyhbmKPfSw Z3YG%`> Date: Thu, 19 Oct 2017 12:06:00 +0100 Subject: [PATCH 16/94] CORDA-725 - update changelog --- docs/source/changelog.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst index e043e0c398..c652b586a5 100644 --- a/docs/source/changelog.rst +++ b/docs/source/changelog.rst @@ -63,6 +63,8 @@ UNRELEASED * A new function ``checkCommandVisibility(publicKey: PublicKey)`` has been added to ``FilteredTransaction`` to check if every command that a signer should receive (e.g. an Oracle) is indeed visible. +* Change the AMQP serialiser to use the oficially assigned R3 identifier rather than a placeholder + .. _changelog_v1: Release 1.0 From 12fa9450776df848a2fb8e76171d838f634f1fd1 Mon Sep 17 00:00:00 2001 From: Andrzej Cichocki Date: Thu, 19 Oct 2017 15:06:51 +0100 Subject: [PATCH 17/94] CORDA-680 Update cordapp packages documentation (#1901) * Introduce MockNetworkParameters --- .../confidential/SwapIdentitiesFlowTests.kt | 6 ++-- .../key-concepts-contract-constraints.rst | 18 +++------- docs/source/upgrade-notes.rst | 13 +++++++ .../node/messaging/TwoPartyTradeFlowTests.kt | 14 ++++---- .../net/corda/netmap/simulation/Simulation.kt | 5 ++- .../kotlin/net/corda/testing/driver/Driver.kt | 10 +++--- .../kotlin/net/corda/testing/node/MockNode.kt | 34 +++++++++++++++---- 7 files changed, 62 insertions(+), 38 deletions(-) diff --git a/confidential-identities/src/test/kotlin/net/corda/confidential/SwapIdentitiesFlowTests.kt b/confidential-identities/src/test/kotlin/net/corda/confidential/SwapIdentitiesFlowTests.kt index 8030469fe5..8e73042899 100644 --- a/confidential-identities/src/test/kotlin/net/corda/confidential/SwapIdentitiesFlowTests.kt +++ b/confidential-identities/src/test/kotlin/net/corda/confidential/SwapIdentitiesFlowTests.kt @@ -13,7 +13,7 @@ class SwapIdentitiesFlowTests { @Test fun `issue key`() { // We run this in parallel threads to help catch any race conditions that may exist. - val mockNet = MockNetwork(false, true) + val mockNet = MockNetwork(threadPerNode = true) // Set up values we'll need val notaryNode = mockNet.createNotaryNode() @@ -53,7 +53,7 @@ class SwapIdentitiesFlowTests { @Test fun `verifies identity name`() { // We run this in parallel threads to help catch any race conditions that may exist. - val mockNet = MockNetwork(false, true) + val mockNet = MockNetwork(threadPerNode = true) // Set up values we'll need val notaryNode = mockNet.createNotaryNode(DUMMY_NOTARY.name) @@ -78,7 +78,7 @@ class SwapIdentitiesFlowTests { @Test fun `verifies signature`() { // We run this in parallel threads to help catch any race conditions that may exist. - val mockNet = MockNetwork(false, true) + val mockNet = MockNetwork(threadPerNode = true) // Set up values we'll need val notaryNode = mockNet.createNotaryNode(DUMMY_NOTARY.name) diff --git a/docs/source/key-concepts-contract-constraints.rst b/docs/source/key-concepts-contract-constraints.rst index edd4c6694d..35db806193 100644 --- a/docs/source/key-concepts-contract-constraints.rst +++ b/docs/source/key-concepts-contract-constraints.rst @@ -95,9 +95,9 @@ to specify JAR URLs in the case that the CorDapp(s) involved in testing already MockNetwork/MockNode ******************** -The most simple way to ensure that a vanilla instance of a MockNode generates the correct CorDapps is to make a call -to ``setCordappPackages`` before the MockNetwork/Node are created and then ``unsetCordappPackages`` after the test -has finished. These calls will cause the ``AbstractNode`` to use the named packages as sources for CorDapps. All files +The most simple way to ensure that a vanilla instance of a MockNode generates the correct CorDapps is to use the +``cordappPackages`` constructor parameter (Kotlin) or the ``setCordappPackages`` method on ``MockNetworkParameters`` (Java) +when creating the MockNetwork. This will cause the ``AbstractNode`` to use the named packages as sources for CorDapps. All files within those packages will be zipped into a JAR and added to the attachment store and loaded as CorDapps by the ``CordappLoader``. An example of this usage would be: @@ -108,17 +108,7 @@ within those packages will be zipped into a JAR and added to the attachment stor @Before void setup() { - // The ordering of the two below lines is important - if the MockNetwork is created before the nodes and network - // are created the CorDapps will not be loaded into the MockNodes correctly. - setCordappPackages(Arrays.asList("com.domain.cordapp")) - network = new MockNetwork() - } - - @After - void teardown() { - // This must be called at the end otherwise the global state set by setCordappPackages may leak into future - // tests in the same test runner environment. - unsetCordappPackages() + network = new MockNetwork(new MockNetworkParameters().setCordappPackages(Arrays.asList("com.domain.cordapp"))) } ... // Your tests go here diff --git a/docs/source/upgrade-notes.rst b/docs/source/upgrade-notes.rst index 0040c48e05..1dcb0d72b4 100644 --- a/docs/source/upgrade-notes.rst +++ b/docs/source/upgrade-notes.rst @@ -25,6 +25,19 @@ versions you are currently using are still in force. We also strongly recommend cross referencing with the :doc:`changelog` to confirm changes. +UNRELEASED +---------- + +Testing +^^^^^^^ + +* The registration mechanism for CorDapps in ``MockNetwork`` unit tests has changed. + + It is now done via the ``cordappPackages`` constructor parameter of MockNetwork. + This takes a list of `String` values which should be the + package names of the CorDapps containing the contract verification code you wish to load. + The ``unsetCordappPackages`` method is now redundant and has been removed. + :ref:`Milestone 14 ` ------------ diff --git a/node/src/test/kotlin/net/corda/node/messaging/TwoPartyTradeFlowTests.kt b/node/src/test/kotlin/net/corda/node/messaging/TwoPartyTradeFlowTests.kt index 93edb33b92..148ca7bbb4 100644 --- a/node/src/test/kotlin/net/corda/node/messaging/TwoPartyTradeFlowTests.kt +++ b/node/src/test/kotlin/net/corda/node/messaging/TwoPartyTradeFlowTests.kt @@ -99,7 +99,7 @@ class TwoPartyTradeFlowTests(val anonymous: Boolean) { // We run this in parallel threads to help catch any race conditions that may exist. The other tests // we run in the unit test thread exclusively to speed things up, ensure deterministic results and // allow interruption half way through. - mockNet = MockNetwork(false, true, cordappPackages = cordappPackages) + mockNet = MockNetwork(threadPerNode = true, cordappPackages = cordappPackages) ledger(MockServices(cordappPackages), initialiseSerialization = false) { val notaryNode = mockNet.createNotaryNode() val aliceNode = mockNet.createPartyNode(ALICE_NAME) @@ -149,7 +149,7 @@ class TwoPartyTradeFlowTests(val anonymous: Boolean) { @Test(expected = InsufficientBalanceException::class) fun `trade cash for commercial paper fails using soft locking`() { - mockNet = MockNetwork(false, true, cordappPackages = cordappPackages) + mockNet = MockNetwork(threadPerNode = true, cordappPackages = cordappPackages) ledger(MockServices(cordappPackages), initialiseSerialization = false) { val notaryNode = mockNet.createNotaryNode() val aliceNode = mockNet.createPartyNode(ALICE_NAME) @@ -205,7 +205,7 @@ class TwoPartyTradeFlowTests(val anonymous: Boolean) { @Test fun `shutdown and restore`() { - mockNet = MockNetwork(false, cordappPackages = cordappPackages) + mockNet = MockNetwork(cordappPackages = cordappPackages) ledger(MockServices(cordappPackages), initialiseSerialization = false) { val notaryNode = mockNet.createNotaryNode() val aliceNode = mockNet.createPartyNode(ALICE_NAME) @@ -326,7 +326,7 @@ class TwoPartyTradeFlowTests(val anonymous: Boolean) { @Test fun `check dependencies of sale asset are resolved`() { - mockNet = MockNetwork(false, cordappPackages = cordappPackages) + mockNet = MockNetwork(cordappPackages = cordappPackages) val notaryNode = mockNet.createNotaryNode() val aliceNode = makeNodeWithTracking(ALICE_NAME) val bobNode = makeNodeWithTracking(BOB_NAME) @@ -433,7 +433,7 @@ class TwoPartyTradeFlowTests(val anonymous: Boolean) { @Test fun `track works`() { - mockNet = MockNetwork(false, cordappPackages = cordappPackages) + mockNet = MockNetwork(cordappPackages = cordappPackages) val notaryNode = mockNet.createNotaryNode() val aliceNode = makeNodeWithTracking(ALICE_NAME) val bobNode = makeNodeWithTracking(BOB_NAME) @@ -515,7 +515,7 @@ class TwoPartyTradeFlowTests(val anonymous: Boolean) { @Test fun `dependency with error on buyer side`() { - mockNet = MockNetwork(false, cordappPackages = cordappPackages) + mockNet = MockNetwork(cordappPackages = cordappPackages) ledger(MockServices(cordappPackages), initialiseSerialization = false) { runWithError(true, false, "at least one cash input") } @@ -523,7 +523,7 @@ class TwoPartyTradeFlowTests(val anonymous: Boolean) { @Test fun `dependency with error on seller side`() { - mockNet = MockNetwork(false, cordappPackages = cordappPackages) + mockNet = MockNetwork(cordappPackages = cordappPackages) ledger(MockServices(cordappPackages), initialiseSerialization = false) { runWithError(false, true, "Issuances have a time-window") } diff --git a/samples/network-visualiser/src/main/kotlin/net/corda/netmap/simulation/Simulation.kt b/samples/network-visualiser/src/main/kotlin/net/corda/netmap/simulation/Simulation.kt index b9abcd6538..19c5750d65 100644 --- a/samples/network-visualiser/src/main/kotlin/net/corda/netmap/simulation/Simulation.kt +++ b/samples/network-visualiser/src/main/kotlin/net/corda/netmap/simulation/Simulation.kt @@ -140,7 +140,10 @@ abstract class Simulation(val networkSendManuallyPumped: Boolean, } } - val mockNet = MockNetwork(networkSendManuallyPumped, runAsync, cordappPackages = listOf("net.corda.irs.contract", "net.corda.finance.contract")) + val mockNet = MockNetwork( + networkSendManuallyPumped = networkSendManuallyPumped, + threadPerNode = runAsync, + cordappPackages = listOf("net.corda.irs.contract", "net.corda.finance.contract")) // This one must come first. val networkMap = mockNet.startNetworkMapNode(nodeFactory = NetworkMapNodeFactory) val notary = mockNet.createNotaryNode(validating = false, nodeFactory = NotaryNodeFactory) diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt index b7eec16259..d6733b1576 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt @@ -272,9 +272,8 @@ sealed class PortAllocation { } } -/** - * Helper builder for configuring a [Node] from Java. - */ +/** Helper builder for configuring a [Node] from Java. */ +@Suppress("unused") data class NodeParameters( val providedName: CordaX500Name? = null, val rpcUsers: List = emptyList(), @@ -366,9 +365,8 @@ fun driver( return driver(defaultParameters = parameters, dsl = dsl) } -/** - * Helper builder for configuring a [driver] from Java. - */ +/** Helper builder for configuring a [driver] from Java. */ +@Suppress("unused") data class DriverParameters( val isDebug: Boolean = false, val driverDirectory: Path = Paths.get("build", getTimestampAsDirectoryName()), diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt index 1c8b4635cc..f574d4d9bb 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt @@ -61,6 +61,23 @@ fun StartedNode.pumpReceive(block: Boolean = false): InMem return (network as InMemoryMessagingNetwork.InMemoryMessaging).pumpReceive(block) } +/** Helper builder for configuring a [MockNetwork] from Java. */ +@Suppress("unused") +data class MockNetworkParameters( + val networkSendManuallyPumped: Boolean = false, + val threadPerNode: Boolean = false, + val servicePeerAllocationStrategy: InMemoryMessagingNetwork.ServicePeerAllocationStrategy = InMemoryMessagingNetwork.ServicePeerAllocationStrategy.Random(), + val defaultFactory: MockNetwork.Factory<*> = MockNetwork.DefaultFactory, + val initialiseSerialization: Boolean = true, + val cordappPackages: List = emptyList()) { + fun setNetworkSendManuallyPumped(networkSendManuallyPumped: Boolean) = copy(networkSendManuallyPumped = networkSendManuallyPumped) + fun setThreadPerNode(threadPerNode: Boolean) = copy(threadPerNode = threadPerNode) + fun setServicePeerAllocationStrategy(servicePeerAllocationStrategy: InMemoryMessagingNetwork.ServicePeerAllocationStrategy) = copy(servicePeerAllocationStrategy = servicePeerAllocationStrategy) + fun setDefaultFactory(defaultFactory: MockNetwork.Factory<*>) = copy(defaultFactory = defaultFactory) + fun setInitialiseSerialization(initialiseSerialization: Boolean) = copy(initialiseSerialization = initialiseSerialization) + fun setCordappPackages(cordappPackages: List) = copy(cordappPackages = cordappPackages) +} + /** * A mock node brings up a suite of in-memory services in a fast manner suitable for unit testing. * Components that do IO are either swapped out for mocks, or pointed to a [Jimfs] in memory filesystem or an in @@ -74,13 +91,16 @@ fun StartedNode.pumpReceive(block: Boolean = false): InMem * * LogHelper.setLevel("+messages") */ -class MockNetwork(private val networkSendManuallyPumped: Boolean = false, - private val threadPerNode: Boolean = false, - servicePeerAllocationStrategy: InMemoryMessagingNetwork.ServicePeerAllocationStrategy = - InMemoryMessagingNetwork.ServicePeerAllocationStrategy.Random(), - private val defaultFactory: Factory<*> = MockNetwork.DefaultFactory, - private val initialiseSerialization: Boolean = true, - private val cordappPackages: List = emptyList()) : Closeable { +class MockNetwork(defaultParameters: MockNetworkParameters = MockNetworkParameters(), + private val networkSendManuallyPumped: Boolean = defaultParameters.networkSendManuallyPumped, + private val threadPerNode: Boolean = defaultParameters.threadPerNode, + servicePeerAllocationStrategy: InMemoryMessagingNetwork.ServicePeerAllocationStrategy = defaultParameters.servicePeerAllocationStrategy, + private val defaultFactory: Factory<*> = defaultParameters.defaultFactory, + private val initialiseSerialization: Boolean = defaultParameters.initialiseSerialization, + private val cordappPackages: List = defaultParameters.cordappPackages) : Closeable { + /** Helper constructor for creating a [MockNetwork] with custom parameters from Java. */ + constructor(parameters: MockNetworkParameters) : this(defaultParameters = parameters) + companion object { // TODO In future PR we're removing the concept of network map node so the details of this mock are not important. val MOCK_NET_MAP = Party(CordaX500Name(organisation = "Mock Network Map", locality = "Madrid", country = "ES"), DUMMY_KEY_1.public) From 3dd09fd69b48b2902799c2e47a126026cb2f5e00 Mon Sep 17 00:00:00 2001 From: Clinton Date: Thu, 19 Oct 2017 15:41:25 +0100 Subject: [PATCH 18/94] Cordformation in Kotlin (#1873) Cordformation rewritten in kotlin. --- constants.properties | 2 +- docs/source/example-code/build.gradle | 1 - .../kotlin/net/corda/plugins/CordappPlugin.kt | 4 +- .../main/kotlin/net/corda/plugins/Utils.kt | 11 + gradle-plugins/cordformation/build.gradle | 3 +- .../groovy/net/corda/plugins/Cordform.groovy | 153 ---------- .../net/corda/plugins/Cordformation.groovy | 28 -- .../main/groovy/net/corda/plugins/Node.groovy | 268 ----------------- .../main/kotlin/net/corda/plugins/Cordform.kt | 198 ++++++++++++ .../kotlin/net/corda/plugins/Cordformation.kt | 33 ++ .../src/main/kotlin/net/corda/plugins/Node.kt | 284 ++++++++++++++++++ .../net/corda/node/internal/NodeStartup.kt | 1 + .../net/corda/notarydemo/BFTNotaryCordform.kt | 4 +- .../main/kotlin/net/corda/notarydemo/Clean.kt | 2 +- .../corda/notarydemo/CustomNotaryCordform.kt | 4 +- .../corda/notarydemo/RaftNotaryCordform.kt | 4 +- .../corda/notarydemo/SingleNotaryCordform.kt | 4 +- 17 files changed, 539 insertions(+), 465 deletions(-) delete mode 100644 gradle-plugins/cordformation/src/main/groovy/net/corda/plugins/Cordform.groovy delete mode 100644 gradle-plugins/cordformation/src/main/groovy/net/corda/plugins/Cordformation.groovy delete mode 100644 gradle-plugins/cordformation/src/main/groovy/net/corda/plugins/Node.groovy create mode 100644 gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordform.kt create mode 100644 gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordformation.kt create mode 100644 gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Node.kt diff --git a/constants.properties b/constants.properties index 48f942803c..37f3b33003 100644 --- a/constants.properties +++ b/constants.properties @@ -1,4 +1,4 @@ -gradlePluginsVersion=2.0.4 +gradlePluginsVersion=2.0.5 kotlinVersion=1.1.50 guavaVersion=21.0 bouncycastleVersion=1.57 diff --git a/docs/source/example-code/build.gradle b/docs/source/example-code/build.gradle index 0568552add..6cad970f10 100644 --- a/docs/source/example-code/build.gradle +++ b/docs/source/example-code/build.gradle @@ -1,7 +1,6 @@ apply plugin: 'kotlin' apply plugin: 'application' apply plugin: 'net.corda.plugins.cordformation' -apply plugin: 'net.corda.plugins.cordapp' apply plugin: 'net.corda.plugins.quasar-utils' repositories { diff --git a/gradle-plugins/cordapp/src/main/kotlin/net/corda/plugins/CordappPlugin.kt b/gradle-plugins/cordapp/src/main/kotlin/net/corda/plugins/CordappPlugin.kt index ce65e18e5f..e307a7b162 100644 --- a/gradle-plugins/cordapp/src/main/kotlin/net/corda/plugins/CordappPlugin.kt +++ b/gradle-plugins/cordapp/src/main/kotlin/net/corda/plugins/CordappPlugin.kt @@ -29,7 +29,7 @@ class CordappPlugin : Plugin { private fun configureCordappJar(project: Project) { // Note: project.afterEvaluate did not have full dependency resolution completed, hence a task is used instead val task = project.task("configureCordappFatJar") - val jarTask = project.tasks.single { it.name == "jar" } as Jar + val jarTask = project.tasks.getByName("jar") as Jar task.doLast { jarTask.from(getDirectNonCordaDependencies(project).map { project.zipTree(it)}).apply { exclude("META-INF/*.SF") @@ -71,6 +71,4 @@ class CordappPlugin : Plugin { } return filteredDeps.map { runtimeConfiguration.files(it) }.flatten().toSet() } - - private fun Project.configuration(name: String): Configuration = configurations.single { it.name == name } } diff --git a/gradle-plugins/cordapp/src/main/kotlin/net/corda/plugins/Utils.kt b/gradle-plugins/cordapp/src/main/kotlin/net/corda/plugins/Utils.kt index 33e552ca7d..d7200ba9f8 100644 --- a/gradle-plugins/cordapp/src/main/kotlin/net/corda/plugins/Utils.kt +++ b/gradle-plugins/cordapp/src/main/kotlin/net/corda/plugins/Utils.kt @@ -1,7 +1,17 @@ package net.corda.plugins import org.gradle.api.Project +import org.gradle.api.Task import org.gradle.api.artifacts.Configuration +import org.gradle.api.plugins.ExtraPropertiesExtension + +/** + * Mimics the "project.ext" functionality in groovy which provides a direct + * accessor to the "ext" extention (See: ExtraPropertiesExtension) + */ +@Suppress("UNCHECKED_CAST") +fun Project.ext(name: String): T = (extensions.findByName("ext") as ExtraPropertiesExtension).get(name) as T +fun Project.configuration(name: String): Configuration = configurations.single { it.name == name } class Utils { companion object { @@ -14,4 +24,5 @@ class Utils { } } } + } \ No newline at end of file diff --git a/gradle-plugins/cordformation/build.gradle b/gradle-plugins/cordformation/build.gradle index 828647d434..94eb8f3b7b 100644 --- a/gradle-plugins/cordformation/build.gradle +++ b/gradle-plugins/cordformation/build.gradle @@ -8,7 +8,6 @@ buildscript { } } -apply plugin: 'groovy' apply plugin: 'kotlin' apply plugin: 'net.corda.plugins.publish-utils' @@ -34,8 +33,8 @@ sourceSets { dependencies { compile gradleApi() - compile localGroovy() compile project(":cordapp") + compile "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version" noderunner "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version" diff --git a/gradle-plugins/cordformation/src/main/groovy/net/corda/plugins/Cordform.groovy b/gradle-plugins/cordformation/src/main/groovy/net/corda/plugins/Cordform.groovy deleted file mode 100644 index 7baf20a45a..0000000000 --- a/gradle-plugins/cordformation/src/main/groovy/net/corda/plugins/Cordform.groovy +++ /dev/null @@ -1,153 +0,0 @@ -package net.corda.plugins - -import static org.gradle.api.tasks.SourceSet.MAIN_SOURCE_SET_NAME -import net.corda.cordform.CordformContext -import net.corda.cordform.CordformDefinition -import org.apache.tools.ant.filters.FixCrLfFilter -import org.gradle.api.DefaultTask -import org.gradle.api.plugins.JavaPluginConvention -import org.gradle.api.tasks.TaskAction -import java.nio.file.Path -import java.nio.file.Paths - -/** - * Creates nodes based on the configuration of this task in the gradle configuration DSL. - * - * See documentation for examples. - */ -class Cordform extends DefaultTask { - /** - * Optionally the name of a CordformDefinition subclass to which all configuration will be delegated. - */ - String definitionClass - protected def directory = Paths.get("build", "nodes") - private def nodes = new ArrayList() - - /** - * Set the directory to install nodes into. - * - * @param directory The directory the nodes will be installed into. - * @return - */ - void directory(String directory) { - this.directory = Paths.get(directory) - } - - /** - * Add a node configuration. - * - * @param configureClosure A node configuration that will be deployed. - */ - void node(Closure configureClosure) { - nodes << (Node) project.configure(new Node(project), configureClosure) - } - - /** - * Returns a node by name. - * - * @param name The name of the node as specified in the node configuration DSL. - * @return A node instance. - */ - private Node getNodeByName(String name) { - for (Node node : nodes) { - if (node.name == name) { - return node - } - } - - return null - } - - /** - * Installs the run script into the nodes directory. - */ - private void installRunScript() { - project.copy { - from Cordformation.getPluginFile(project, "net/corda/plugins/runnodes.jar") - fileMode 0755 - into "${directory}/" - } - - project.copy { - from Cordformation.getPluginFile(project, "net/corda/plugins/runnodes") - // Replaces end of line with lf to avoid issues with the bash interpreter and Windows style line endings. - filter(FixCrLfFilter.class, eol: FixCrLfFilter.CrLf.newInstance("lf")) - fileMode 0755 - into "${directory}/" - } - - project.copy { - from Cordformation.getPluginFile(project, "net/corda/plugins/runnodes.bat") - into "${directory}/" - } - } - - /** - * The definitionClass needn't be compiled until just before our build method, so we load it manually via sourceSets.main.runtimeClasspath. - */ - private CordformDefinition loadCordformDefinition() { - def plugin = project.convention.getPlugin(JavaPluginConvention.class) - def classpath = plugin.sourceSets.getByName(MAIN_SOURCE_SET_NAME).runtimeClasspath - URL[] urls = classpath.files.collect { it.toURI().toURL() } - (CordformDefinition) new URLClassLoader(urls, CordformDefinition.classLoader).loadClass(definitionClass).newInstance() - } - - /** - * This task action will create and install the nodes based on the node configurations added. - */ - @TaskAction - void build() { - initializeConfiguration() - installRunScript() - nodes.each { - it.build() - } - generateNodeInfos() - } - - private initializeConfiguration() { - if (null != definitionClass) { - def cd = loadCordformDefinition() - cd.nodeConfigurers.each { nc -> - node { Node it -> - nc.accept it - it.rootDir directory - } - } - cd.setup new CordformContext() { - Path baseDirectory(String nodeName) { - project.projectDir.toPath().resolve(getNodeByName(nodeName).nodeDir.toPath()) - } - } - } else { - nodes.each { - it.rootDir directory - } - } - } - - Path fullNodePath(Node node) { - return project.projectDir.toPath().resolve(node.nodeDir.toPath()) - } - - private generateNodeInfos() { - nodes.each { Node node -> - def process = new ProcessBuilder("java", "-jar", Node.NODEJAR_NAME, "--just-generate-node-info") - .directory(fullNodePath(node).toFile()) - .redirectErrorStream(true) - .start() - .waitFor() - } - for (source in nodes) { - for (destination in nodes) { - if (source.nodeDir != destination.nodeDir) { - project.copy { - from fullNodePath(source).toString() - include 'nodeInfo-*' - into fullNodePath(destination).resolve(Node.NODE_INFO_DIRECTORY).toString() - } - } - } - } - } -} diff --git a/gradle-plugins/cordformation/src/main/groovy/net/corda/plugins/Cordformation.groovy b/gradle-plugins/cordformation/src/main/groovy/net/corda/plugins/Cordformation.groovy deleted file mode 100644 index eeb4443801..0000000000 --- a/gradle-plugins/cordformation/src/main/groovy/net/corda/plugins/Cordformation.groovy +++ /dev/null @@ -1,28 +0,0 @@ -package net.corda.plugins - -import org.gradle.api.Plugin -import org.gradle.api.Project -import org.gradle.api.artifacts.Configuration - -/** - * The Cordformation plugin deploys nodes to a directory in a state ready to be used by a developer for experimentation, - * testing, and debugging. It will prepopulate several fields in the configuration and create a simple node runner. - */ -class Cordformation implements Plugin { - /** - * Gets a resource file from this plugin's JAR file. - * - * @param project The project environment this plugin executes in. - * @param filePathInJar The file in the JAR, relative to root, you wish to access. - * @return A file handle to the file in the JAR. - */ - protected static File getPluginFile(Project project, String filePathInJar) { - return project.rootProject.resources.text.fromArchiveEntry(project.rootProject.buildscript.configurations.classpath.find { - it.name.contains('cordformation') - }, filePathInJar).asFile() - } - - void apply(Project project) { - Utils.createCompileConfiguration("cordapp", project) - } -} diff --git a/gradle-plugins/cordformation/src/main/groovy/net/corda/plugins/Node.groovy b/gradle-plugins/cordformation/src/main/groovy/net/corda/plugins/Node.groovy deleted file mode 100644 index 8f6dcea295..0000000000 --- a/gradle-plugins/cordformation/src/main/groovy/net/corda/plugins/Node.groovy +++ /dev/null @@ -1,268 +0,0 @@ -package net.corda.plugins - -import com.typesafe.config.* -import net.corda.cordform.CordformNode -import org.bouncycastle.asn1.x500.X500Name -import org.bouncycastle.asn1.x500.style.BCStyle -import org.gradle.api.Project -import java.nio.charset.StandardCharsets -import java.nio.file.Files -import java.nio.file.Path - -/** - * Represents a node that will be installed. - */ -class Node extends CordformNode { - static final String NODEJAR_NAME = 'corda.jar' - static final String WEBJAR_NAME = 'corda-webserver.jar' - - /** - * Set the list of CorDapps to install to the cordapps directory. Each cordapp is a fully qualified Maven - * dependency name, eg: com.example:product-name:0.1 - * - * @note Your app will be installed by default and does not need to be included here. - */ - protected List cordapps = [] - - protected File nodeDir - private Project project - - /** - * Sets whether this node will use HTTPS communication. - * - * @param isHttps True if this node uses HTTPS communication. - */ - void https(Boolean isHttps) { - config = config.withValue("useHTTPS", ConfigValueFactory.fromAnyRef(isHttps)) - } - - /** - * Sets the H2 port for this node - */ - void h2Port(Integer h2Port) { - config = config.withValue("h2port", ConfigValueFactory.fromAnyRef(h2Port)) - } - - void useTestClock(Boolean useTestClock) { - config = config.withValue("useTestClock", ConfigValueFactory.fromAnyRef(useTestClock)) - } - - /** - * Set the HTTP web server port for this node. - * - * @param webPort The web port number for this node. - */ - void webPort(Integer webPort) { - config = config.withValue("webAddress", - ConfigValueFactory.fromAnyRef("$DEFAULT_HOST:$webPort".toString())) - } - - /** - * Set the network map address for this node. - * - * @warning This should not be directly set unless you know what you are doing. Use the networkMapName in the - * Cordform task instead. - * @param networkMapAddress Network map node address. - * @param networkMapLegalName Network map node legal name. - */ - void networkMapAddress(String networkMapAddress, String networkMapLegalName) { - def networkMapService = new HashMap() - networkMapService.put("address", networkMapAddress) - networkMapService.put("legalName", networkMapLegalName) - config = config.withValue("networkMapService", ConfigValueFactory.fromMap(networkMapService)) - } - - /** - * Set the SSHD port for this node. - * - * @param sshdPort The SSHD port. - */ - void sshdPort(Integer sshdPort) { - config = config.withValue("sshdAddress", - ConfigValueFactory.fromAnyRef("$DEFAULT_HOST:$sshdPort".toString())) - } - - Node(Project project) { - this.project = project - } - - protected void rootDir(Path rootDir) { - def dirName - try { - X500Name x500Name = new X500Name(name) - dirName = x500Name.getRDNs(BCStyle.O).getAt(0).getFirst().getValue().toString() - } catch(IllegalArgumentException ignore) { - // Can't parse as an X500 name, use the full string - dirName = name - } - nodeDir = new File(rootDir.toFile(), dirName.replaceAll("\\s","")) - } - - protected void build() { - configureProperties() - installCordaJar() - if (config.hasPath("webAddress")) { - installWebserverJar() - } - installBuiltCordapp() - installCordapps() - installConfig() - appendOptionalConfig() - } - - /** - * Get the artemis address for this node. - * - * @return This node's P2P address. - */ - String getP2PAddress() { - return config.getString("p2pAddress") - } - - private void configureProperties() { - config = config.withValue("rpcUsers", ConfigValueFactory.fromIterable(rpcUsers)) - if (notary) { - config = config.withValue("notary", ConfigValueFactory.fromMap(notary)) - } - if (extraConfig) { - config = config.withFallback(ConfigFactory.parseMap(extraConfig)) - } - } - - /** - * Installs the corda fat JAR to the node directory. - */ - private void installCordaJar() { - def cordaJar = verifyAndGetCordaJar() - project.copy { - from cordaJar - into nodeDir - rename cordaJar.name, NODEJAR_NAME - fileMode 0755 - } - } - - /** - * Installs the corda webserver JAR to the node directory - */ - private void installWebserverJar() { - def webJar = verifyAndGetWebserverJar() - project.copy { - from webJar - into nodeDir - rename webJar.name, WEBJAR_NAME - } - } - - /** - * Installs this project's cordapp to this directory. - */ - private void installBuiltCordapp() { - def cordappsDir = new File(nodeDir, "cordapps") - project.copy { - from project.jar - into cordappsDir - } - } - - /** - * Installs other cordapps to this node's cordapps directory. - */ - private void installCordapps() { - def cordappsDir = new File(nodeDir, "cordapps") - def cordapps = getCordappList() - project.copy { - from cordapps - into cordappsDir - } - } - - /** - * Installs the configuration file to this node's directory and detokenises it. - */ - private void installConfig() { - def configFileText = config.root().render(new ConfigRenderOptions(false, false, true, false)).split("\n").toList() - - // Need to write a temporary file first to use the project.copy, which resolves directories correctly. - def tmpDir = new File(project.buildDir, "tmp") - def tmpConfFile = new File(tmpDir, 'node.conf') - Files.write(tmpConfFile.toPath(), configFileText, StandardCharsets.UTF_8) - - project.copy { - from tmpConfFile - into nodeDir - } - } - - /** - * Appends installed config file with properties from an optional file. - */ - private void appendOptionalConfig() { - final configFileProperty = "configFile" - File optionalConfig - if (project.findProperty(configFileProperty)) { //provided by -PconfigFile command line property when running Gradle task - optionalConfig = new File(project.findProperty(configFileProperty)) - } else if (config.hasPath(configFileProperty)) { - optionalConfig = new File(config.getString(configFileProperty)) - } - if (optionalConfig) { - if (!optionalConfig.exists()) { - println "$configFileProperty '$optionalConfig' not found" - } else { - def confFile = new File(project.buildDir.getPath() + "/../" + nodeDir, 'node.conf') - optionalConfig.withInputStream { - input -> confFile << input - } - } - } - } - - /** - * Find the corda JAR amongst the dependencies. - * - * @return A file representing the Corda JAR. - */ - private File verifyAndGetCordaJar() { - def maybeCordaJAR = project.configurations.runtime.filter { - it.toString().contains("corda-${project.corda_release_version}.jar") || it.toString().contains("corda-enterprise-${project.corda_release_version}.jar") - } - if (maybeCordaJAR.size() == 0) { - throw new RuntimeException("No Corda Capsule JAR found. Have you deployed the Corda project to Maven? Looked for \"corda-${project.corda_release_version}.jar\"") - } else { - def cordaJar = maybeCordaJAR.getSingleFile() - assert(cordaJar.isFile()) - return cordaJar - } - } - - /** - * Find the corda JAR amongst the dependencies - * - * @return A file representing the Corda webserver JAR - */ - private File verifyAndGetWebserverJar() { - def maybeJar = project.configurations.runtime.filter { - it.toString().contains("corda-webserver-${project.corda_release_version}.jar") - } - if (maybeJar.size() == 0) { - throw new RuntimeException("No Corda Webserver JAR found. Have you deployed the Corda project to Maven? Looked for \"corda-webserver-${project.corda_release_version}.jar\"") - } else { - def jar = maybeJar.getSingleFile() - assert(jar.isFile()) - return jar - } - } - - /** - * Gets a list of cordapps based on what dependent cordapps were specified. - * - * @return List of this node's cordapps. - */ - private Collection getCordappList() { - // Cordapps can sometimes contain a GString instance which fails the equality test with the Java string - List cordapps = this.cordapps.collect { it.toString() } - return project.configurations.cordapp.files { - cordapps.contains(it.group + ":" + it.name + ":" + it.version) - } - } -} diff --git a/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordform.kt b/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordform.kt new file mode 100644 index 0000000000..5193703c59 --- /dev/null +++ b/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordform.kt @@ -0,0 +1,198 @@ +package net.corda.plugins + +import groovy.lang.Closure +import net.corda.cordform.CordformDefinition +import net.corda.cordform.CordformNode +import org.apache.tools.ant.filters.FixCrLfFilter +import org.gradle.api.DefaultTask +import org.gradle.api.GradleException +import org.gradle.api.plugins.JavaPluginConvention +import org.gradle.api.tasks.SourceSet.MAIN_SOURCE_SET_NAME +import org.gradle.api.tasks.TaskAction +import java.io.File +import java.net.URLClassLoader +import java.nio.file.Path +import java.nio.file.Paths +import java.util.concurrent.TimeUnit + +/** + * Creates nodes based on the configuration of this task in the gradle configuration DSL. + * + * See documentation for examples. + */ +@Suppress("unused") +open class Cordform : DefaultTask() { + /** + * Optionally the name of a CordformDefinition subclass to which all configuration will be delegated. + */ + @Suppress("MemberVisibilityCanPrivate") + var definitionClass: String? = null + private var directory = Paths.get("build", "nodes") + private val nodes = mutableListOf() + + /** + * Set the directory to install nodes into. + * + * @param directory The directory the nodes will be installed into. + */ + fun directory(directory: String) { + this.directory = Paths.get(directory) + } + + /** + * Add a node configuration. + * + * @param configureClosure A node configuration that will be deployed. + */ + @Suppress("MemberVisibilityCanPrivate") + fun node(configureClosure: Closure) { + nodes += project.configure(Node(project), configureClosure) as Node + } + + /** + * Add a node configuration + * + * @param configureFunc A node configuration that will be deployed + */ + @Suppress("MemberVisibilityCanPrivate") + fun node(configureFunc: Node.() -> Any?): Node { + val node = Node(project).apply { configureFunc() } + nodes += node + return node + } + + /** + * Returns a node by name. + * + * @param name The name of the node as specified in the node configuration DSL. + * @return A node instance. + */ + private fun getNodeByName(name: String): Node? = nodes.firstOrNull { it.name == name } + + /** + * Installs the run script into the nodes directory. + */ + private fun installRunScript() { + project.copy { + it.apply { + from(Cordformation.getPluginFile(project, "net/corda/plugins/runnodes.jar")) + fileMode = Cordformation.executableFileMode + into("$directory/") + } + } + + project.copy { + it.apply { + from(Cordformation.getPluginFile(project, "net/corda/plugins/runnodes")) + // Replaces end of line with lf to avoid issues with the bash interpreter and Windows style line endings. + filter(mapOf("eol" to FixCrLfFilter.CrLf.newInstance("lf")), FixCrLfFilter::class.java) + fileMode = Cordformation.executableFileMode + into("$directory/") + } + } + + project.copy { + it.apply { + from(Cordformation.getPluginFile(project, "net/corda/plugins/runnodes.bat")) + into("$directory/") + } + } + } + + /** + * The definitionClass needn't be compiled until just before our build method, so we load it manually via sourceSets.main.runtimeClasspath. + */ + private fun loadCordformDefinition(): CordformDefinition { + val plugin = project.convention.getPlugin(JavaPluginConvention::class.java) + val classpath = plugin.sourceSets.getByName(MAIN_SOURCE_SET_NAME).runtimeClasspath + val urls = classpath.files.map { it.toURI().toURL() }.toTypedArray() + return URLClassLoader(urls, CordformDefinition::class.java.classLoader) + .loadClass(definitionClass) + .asSubclass(CordformDefinition::class.java) + .newInstance() + } + + /** + * This task action will create and install the nodes based on the node configurations added. + */ + @Suppress("unused") + @TaskAction + fun build() { + project.logger.info("Running Cordform task") + initializeConfiguration() + installRunScript() + nodes.forEach(Node::build) + generateAndInstallNodeInfos() + } + + private fun initializeConfiguration() { + if (definitionClass != null) { + val cd = loadCordformDefinition() + cd.nodeConfigurers.forEach { + val node = node { } + it.accept(node) + node.rootDir(directory) + } + cd.setup { nodeName -> project.projectDir.toPath().resolve(getNodeByName(nodeName)!!.nodeDir.toPath()) } + } else { + nodes.forEach { + it.rootDir(directory) + } + } + } + + private fun fullNodePath(node: Node): Path = project.projectDir.toPath().resolve(node.nodeDir.toPath()) + + private fun generateAndInstallNodeInfos() { + generateNodeInfos() + installNodeInfos() + } + + private fun generateNodeInfos() { + project.logger.info("Generating node infos") + val generateTimeoutSeconds = 60L + val processes = nodes.map { node -> + project.logger.info("Generating node info for ${fullNodePath(node)}") + val logDir = File(fullNodePath(node).toFile(), "logs") + logDir.mkdirs() // Directory may not exist at this point + Pair(node, ProcessBuilder("java", "-jar", Node.nodeJarName, "--just-generate-node-info") + .directory(fullNodePath(node).toFile()) + .redirectErrorStream(true) + // InheritIO causes hangs on windows due the gradle buffer also not being flushed. + // Must redirect to output or logger (node log is still written, this is just startup banner) + .redirectOutput(File(logDir, "generate-info-log.txt")) + .start()) + } + try { + processes.parallelStream().forEach { (node, process) -> + if (!process.waitFor(generateTimeoutSeconds, TimeUnit.SECONDS)) { + throw GradleException("Node took longer $generateTimeoutSeconds seconds than too to generate node info - see node log at ${fullNodePath(node)}/logs") + } else if (process.exitValue() != 0) { + throw GradleException("Node exited with ${process.exitValue()} when generating node infos - see node log at ${fullNodePath(node)}/logs") + } + } + } finally { + // This will be a no-op on success - abort remaining on failure + processes.forEach { + it.second.destroyForcibly() + } + } + } + + private fun installNodeInfos() { + project.logger.info("Installing node infos") + for (source in nodes) { + for (destination in nodes) { + if (source.nodeDir != destination.nodeDir) { + project.copy { + it.apply { + from(fullNodePath(source).toString()) + include("nodeInfo-*") + into(fullNodePath(destination).resolve(CordformNode.NODE_INFO_DIRECTORY).toString()) + } + } + } + } + } + } +} diff --git a/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordformation.kt b/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordformation.kt new file mode 100644 index 0000000000..b0e09ad2da --- /dev/null +++ b/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordformation.kt @@ -0,0 +1,33 @@ +package net.corda.plugins + +import org.gradle.api.Plugin +import org.gradle.api.Project +import java.io.File + +/** + * The Cordformation plugin deploys nodes to a directory in a state ready to be used by a developer for experimentation, + * testing, and debugging. It will prepopulate several fields in the configuration and create a simple node runner. + */ +class Cordformation : Plugin { + internal companion object { + /** + * Gets a resource file from this plugin's JAR file. + * + * @param project The project environment this plugin executes in. + * @param filePathInJar The file in the JAR, relative to root, you wish to access. + * @return A file handle to the file in the JAR. + */ + fun getPluginFile(project: Project, filePathInJar: String): File { + val archive: File? = project.rootProject.buildscript.configurations.single { it.name == "classpath" }.find { + it.name.contains("cordformation") + } + return project.rootProject.resources.text.fromArchiveEntry(archive, filePathInJar).asFile() + } + + val executableFileMode = "0755".toInt(8) + } + + override fun apply(project: Project) { + Utils.createCompileConfiguration("cordapp", project) + } +} diff --git a/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Node.kt b/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Node.kt new file mode 100644 index 0000000000..738a68c6d7 --- /dev/null +++ b/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Node.kt @@ -0,0 +1,284 @@ +package net.corda.plugins + +import com.typesafe.config.* +import net.corda.cordform.CordformNode +import org.bouncycastle.asn1.x500.X500Name +import org.bouncycastle.asn1.x500.style.BCStyle +import org.gradle.api.Project +import java.io.File +import java.nio.charset.StandardCharsets +import java.nio.file.Files +import java.nio.file.Path + +/** + * Represents a node that will be installed. + */ +class Node(private val project: Project) : CordformNode() { + companion object { + @JvmStatic + val nodeJarName = "corda.jar" + @JvmStatic + val webJarName = "corda-webserver.jar" + private val configFileProperty = "configFile" + } + + /** + * Set the list of CorDapps to install to the plugins directory. Each cordapp is a fully qualified Maven + * dependency name, eg: com.example:product-name:0.1 + * + * @note Your app will be installed by default and does not need to be included here. + * @note Type is any due to gradle's use of "GStrings" - each value will have "toString" called on it + */ + var cordapps = mutableListOf() + + private val releaseVersion = project.rootProject.ext("corda_release_version") + internal lateinit var nodeDir: File + + /** + * Sets whether this node will use HTTPS communication. + * + * @param isHttps True if this node uses HTTPS communication. + */ + fun https(isHttps: Boolean) { + config = config.withValue("useHTTPS", ConfigValueFactory.fromAnyRef(isHttps)) + } + + /** + * Sets the H2 port for this node + */ + fun h2Port(h2Port: Int) { + config = config.withValue("h2port", ConfigValueFactory.fromAnyRef(h2Port)) + } + + fun useTestClock(useTestClock: Boolean) { + config = config.withValue("useTestClock", ConfigValueFactory.fromAnyRef(useTestClock)) + } + + /** + * Set the HTTP web server port for this node. + * + * @param webPort The web port number for this node. + */ + fun webPort(webPort: Int) { + config = config.withValue("webAddress", + ConfigValueFactory.fromAnyRef("$DEFAULT_HOST:$webPort")) + } + + /** + * Set the network map address for this node. + * + * @warning This should not be directly set unless you know what you are doing. Use the networkMapName in the + * Cordform task instead. + * @param networkMapAddress Network map node address. + * @param networkMapLegalName Network map node legal name. + */ + fun networkMapAddress(networkMapAddress: String, networkMapLegalName: String) { + val networkMapService = mutableMapOf() + networkMapService.put("address", networkMapAddress) + networkMapService.put("legalName", networkMapLegalName) + config = config.withValue("networkMapService", ConfigValueFactory.fromMap(networkMapService)) + } + + /** + * Set the SSHD port for this node. + * + * @param sshdPort The SSHD port. + */ + fun sshdPort(sshdPort: Int) { + config = config.withValue("sshdAddress", + ConfigValueFactory.fromAnyRef("$DEFAULT_HOST:$sshdPort")) + } + + internal fun build() { + configureProperties() + installCordaJar() + if (config.hasPath("webAddress")) { + installWebserverJar() + } + installBuiltCordapp() + installCordapps() + installConfig() + appendOptionalConfig() + } + + /** + * Get the artemis address for this node. + * + * @return This node's P2P address. + */ + fun getP2PAddress(): String { + return config.getString("p2pAddress") + } + + internal fun rootDir(rootDir: Path) { + if(name == null) { + project.logger.error("Node has a null name - cannot create node") + throw IllegalStateException("Node has a null name - cannot create node") + } + + val dirName = try { + X500Name(name).getRDNs(BCStyle.O).first().first.value.toString() + } catch(_ : IllegalArgumentException) { + // Can't parse as an X500 name, use the full string + name + } + nodeDir = File(rootDir.toFile(), dirName.replace("\\s", "")) + } + + private fun configureProperties() { + config = config.withValue("rpcUsers", ConfigValueFactory.fromIterable(rpcUsers)) + if (notary != null) { + config = config.withValue("notary", ConfigValueFactory.fromMap(notary)) + } + if (extraConfig != null) { + config = config.withFallback(ConfigFactory.parseMap(extraConfig)) + } + } + + /** + * Installs the corda fat JAR to the node directory. + */ + private fun installCordaJar() { + val cordaJar = verifyAndGetCordaJar() + project.copy { + it.apply { + from(cordaJar) + into(nodeDir) + rename(cordaJar.name, nodeJarName) + fileMode = Cordformation.executableFileMode + } + } + } + + /** + * Installs the corda webserver JAR to the node directory + */ + private fun installWebserverJar() { + val webJar = verifyAndGetWebserverJar() + project.copy { + it.apply { + from(webJar) + into(nodeDir) + rename(webJar.name, webJarName) + } + } + } + + /** + * Installs this project's cordapp to this directory. + */ + private fun installBuiltCordapp() { + val cordappsDir = File(nodeDir, "cordapps") + project.copy { + it.apply { + from(project.tasks.getByName("jar")) + into(cordappsDir) + } + } + } + + /** + * Installs other cordapps to this node's cordapps directory. + */ + private fun installCordapps() { + val cordappsDir = File(nodeDir, "cordapps") + val cordapps = getCordappList() + project.copy { + it.apply { + from(cordapps) + into(cordappsDir) + } + } + } + + /** + * Installs the configuration file to this node's directory and detokenises it. + */ + private fun installConfig() { + val options = ConfigRenderOptions.defaults().setOriginComments(false).setComments(false).setFormatted(false).setJson(false) + val configFileText = config.root().render(options).split("\n").toList() + + // Need to write a temporary file first to use the project.copy, which resolves directories correctly. + val tmpDir = File(project.buildDir, "tmp") + val tmpConfFile = File(tmpDir, "node.conf") + Files.write(tmpConfFile.toPath(), configFileText, StandardCharsets.UTF_8) + + project.copy { + it.apply { + from(tmpConfFile) + into(nodeDir) + } + } + } + + /** + * Appends installed config file with properties from an optional file. + */ + private fun appendOptionalConfig() { + val optionalConfig: File? = when { + project.findProperty(configFileProperty) != null -> //provided by -PconfigFile command line property when running Gradle task + File(project.findProperty(configFileProperty) as String) + config.hasPath(configFileProperty) -> File(config.getString(configFileProperty)) + else -> null + } + + if (optionalConfig != null) { + if (!optionalConfig.exists()) { + project.logger.error("$configFileProperty '$optionalConfig' not found") + } else { + val confFile = File(project.buildDir.path + "/../" + nodeDir, "node.conf") + confFile.appendBytes(optionalConfig.readBytes()) + } + } + } + + /** + * Find the corda JAR amongst the dependencies. + * + * @return A file representing the Corda JAR. + */ + private fun verifyAndGetCordaJar(): File { + val maybeCordaJAR = project.configuration("runtime").filter { + it.toString().contains("corda-$releaseVersion.jar") || it.toString().contains("corda-enterprise-$releaseVersion.jar") + } + if (maybeCordaJAR.isEmpty) { + throw RuntimeException("No Corda Capsule JAR found. Have you deployed the Corda project to Maven? Looked for \"corda-$releaseVersion.jar\"") + } else { + val cordaJar = maybeCordaJAR.singleFile + assert(cordaJar.isFile) + return cordaJar + } + } + + /** + * Find the corda JAR amongst the dependencies + * + * @return A file representing the Corda webserver JAR + */ + private fun verifyAndGetWebserverJar(): File { + val maybeJar = project.configuration("runtime").filter { + it.toString().contains("corda-webserver-$releaseVersion.jar") + } + if (maybeJar.isEmpty) { + throw RuntimeException("No Corda Webserver JAR found. Have you deployed the Corda project to Maven? Looked for \"corda-webserver-$releaseVersion.jar\"") + } else { + val jar = maybeJar.singleFile + assert(jar.isFile) + return jar + } + } + + /** + * Gets a list of cordapps based on what dependent cordapps were specified. + * + * @return List of this node's cordapps. + */ + private fun getCordappList(): Collection { + // Cordapps can sometimes contain a GString instance which fails the equality test with the Java string + @Suppress("RemoveRedundantCallsOfConversionMethods") + val cordapps: List = cordapps.map { it.toString() } + return project.configuration("cordapp").files { + cordapps.contains(it.group + ":" + it.name + ":" + it.version) + } + } +} diff --git a/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt b/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt index 1776b64271..bc95502ee9 100644 --- a/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt +++ b/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt @@ -80,6 +80,7 @@ open class NodeStartup(val args: Array) { exitProcess(1) } + logger.info("Node exiting successfully") exitProcess(0) } diff --git a/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/BFTNotaryCordform.kt b/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/BFTNotaryCordform.kt index 92669b5714..b3e295d1cb 100644 --- a/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/BFTNotaryCordform.kt +++ b/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/BFTNotaryCordform.kt @@ -19,14 +19,14 @@ import net.corda.testing.internal.demorun.notary import net.corda.testing.internal.demorun.rpcUsers import net.corda.testing.internal.demorun.runNodes -fun main(args: Array) = BFTNotaryCordform.runNodes() +fun main(args: Array) = BFTNotaryCordform().runNodes() private val clusterSize = 4 // Minimum size that tolerates a faulty replica. private val notaryNames = createNotaryNames(clusterSize) // This is not the intended final design for how to use CordformDefinition, please treat this as experimental and DO // NOT use this as a design to copy. -object BFTNotaryCordform : CordformDefinition("build" / "notary-demo-nodes") { +class BFTNotaryCordform : CordformDefinition("build" / "notary-demo-nodes") { private val clusterName = CordaX500Name(BFTNonValidatingNotaryService.id, "BFT", "Zurich", "CH") init { diff --git a/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/Clean.kt b/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/Clean.kt index 1bab32c9a9..7ab6fff629 100644 --- a/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/Clean.kt +++ b/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/Clean.kt @@ -3,7 +3,7 @@ package net.corda.notarydemo import net.corda.testing.internal.demorun.clean fun main(args: Array) { - listOf(SingleNotaryCordform, RaftNotaryCordform, BFTNotaryCordform).forEach { + listOf(SingleNotaryCordform(), RaftNotaryCordform(), BFTNotaryCordform()).forEach { it.clean() } } diff --git a/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/CustomNotaryCordform.kt b/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/CustomNotaryCordform.kt index 900150da69..3cd43d7a0e 100644 --- a/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/CustomNotaryCordform.kt +++ b/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/CustomNotaryCordform.kt @@ -9,9 +9,9 @@ import net.corda.testing.BOB import net.corda.testing.DUMMY_NOTARY import net.corda.testing.internal.demorun.* -fun main(args: Array) = CustomNotaryCordform.runNodes() +fun main(args: Array) = CustomNotaryCordform().runNodes() -object CustomNotaryCordform : CordformDefinition("build" / "notary-demo-nodes") { +class CustomNotaryCordform : CordformDefinition("build" / "notary-demo-nodes") { init { node { name(ALICE.name) diff --git a/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/RaftNotaryCordform.kt b/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/RaftNotaryCordform.kt index 198867dcf1..ff128c6edf 100644 --- a/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/RaftNotaryCordform.kt +++ b/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/RaftNotaryCordform.kt @@ -14,7 +14,7 @@ import net.corda.testing.ALICE import net.corda.testing.BOB import net.corda.testing.internal.demorun.* -fun main(args: Array) = RaftNotaryCordform.runNodes() +fun main(args: Array) = RaftNotaryCordform().runNodes() internal fun createNotaryNames(clusterSize: Int) = (0 until clusterSize).map { CordaX500Name("Notary Service $it", "Zurich", "CH") } @@ -22,7 +22,7 @@ private val notaryNames = createNotaryNames(3) // This is not the intended final design for how to use CordformDefinition, please treat this as experimental and DO // NOT use this as a design to copy. -object RaftNotaryCordform : CordformDefinition("build" / "notary-demo-nodes") { +class RaftNotaryCordform : CordformDefinition("build" / "notary-demo-nodes") { private val clusterName = CordaX500Name(RaftValidatingNotaryService.id, "Raft", "Zurich", "CH") init { diff --git a/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/SingleNotaryCordform.kt b/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/SingleNotaryCordform.kt index 22cc63540d..8e349494b8 100644 --- a/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/SingleNotaryCordform.kt +++ b/samples/notary-demo/src/main/kotlin/net/corda/notarydemo/SingleNotaryCordform.kt @@ -17,13 +17,13 @@ import net.corda.testing.internal.demorun.notary import net.corda.testing.internal.demorun.rpcUsers import net.corda.testing.internal.demorun.runNodes -fun main(args: Array) = SingleNotaryCordform.runNodes() +fun main(args: Array) = SingleNotaryCordform().runNodes() val notaryDemoUser = User("demou", "demop", setOf(startFlowPermission(), startFlowPermission())) // This is not the intended final design for how to use CordformDefinition, please treat this as experimental and DO // NOT use this as a design to copy. -object SingleNotaryCordform : CordformDefinition("build" / "notary-demo-nodes") { +class SingleNotaryCordform : CordformDefinition("build" / "notary-demo-nodes") { init { node { name(ALICE.name) From 651bbdcf836978709f373751dd91ed57f7c90d7d Mon Sep 17 00:00:00 2001 From: Maksymilian Pawlak <120831+m4ksio@users.noreply.github.com> Date: Thu, 19 Oct 2017 16:13:33 +0100 Subject: [PATCH 19/94] Kotlin migration --- .../src/main/kotlin/net/corda/plugins/Node.kt | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Node.kt b/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Node.kt index 738a68c6d7..b0660a9245 100644 --- a/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Node.kt +++ b/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Node.kt @@ -3,6 +3,7 @@ package net.corda.plugins import com.typesafe.config.* import net.corda.cordform.CordformNode import org.bouncycastle.asn1.x500.X500Name +import org.bouncycastle.asn1.x500.RDN import org.bouncycastle.asn1.x500.style.BCStyle import org.gradle.api.Project import java.io.File @@ -117,7 +118,12 @@ class Node(private val project: Project) : CordformNode() { } val dirName = try { - X500Name(name).getRDNs(BCStyle.O).first().first.value.toString() + val o = X500Name(name).getRDNs(BCStyle.O) + if (o.size > 0) { + o.first().first.value.toString() + } else { + name + } } catch(_ : IllegalArgumentException) { // Can't parse as an X500 name, use the full string name From e6de0aa406ce8399728bc1df0eac739ff4955478 Mon Sep 17 00:00:00 2001 From: Katelyn Baker Date: Thu, 19 Oct 2017 13:05:09 +0100 Subject: [PATCH 20/94] Review Comments --- docs/source/changelog.rst | 2 +- .../net/corda/nodeapi/internal/serialization/amqp/Schema.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst index c652b586a5..bb2bce8461 100644 --- a/docs/source/changelog.rst +++ b/docs/source/changelog.rst @@ -63,7 +63,7 @@ UNRELEASED * A new function ``checkCommandVisibility(publicKey: PublicKey)`` has been added to ``FilteredTransaction`` to check if every command that a signer should receive (e.g. an Oracle) is indeed visible. -* Change the AMQP serialiser to use the oficially assigned R3 identifier rather than a placeholder +* Change the AMQP serialiser to use the oficially assigned R3 identifier rather than a placeholder. .. _changelog_v1: diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/Schema.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/Schema.kt index d3d767c4eb..cfb505ab6c 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/Schema.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/Schema.kt @@ -21,7 +21,7 @@ import net.corda.nodeapi.internal.serialization.carpenter.Schema as CarpenterSch /** * R3 AMQP assigned enterprise number * - * see [https://www.iana.org/assignments/enterprise-numbers/enterprise-numbers] + * see [here](https://www.iana.org/assignments/enterprise-numbers/enterprise-numbers) * * Repeated here for brevity: * 50530 - R3 - Mike Hearn - mike&r3.com From 2c84d07e8ed77aed0381ff6c3e10b8b0aaedfcac Mon Sep 17 00:00:00 2001 From: Chris Rankin Date: Thu, 19 Oct 2017 17:18:35 +0100 Subject: [PATCH 21/94] CORDA-704: Implement `@DoNotImplement` annotation (#1903) * Enhance the API Scanner plugin to monitor class annotations. * Implement @DoNotImplement annotation, and apply it. * Update API definition. * Update API change detection to handle @DoNotImplement. * Document the `@DoNotImplement` annotation. --- .ci/api-current.txt | 508 +++++++++--------- .ci/check-api-changes.sh | 10 +- .../net/corda/client/rpc/RPCConnection.kt | 2 + constants.properties | 2 +- .../kotlin/net/corda/core/DoNotImplement.kt | 18 + .../kotlin/net/corda/core/cordapp/Cordapp.kt | 6 +- .../net/corda/core/cordapp/CordappProvider.kt | 2 + .../net/corda/core/flows/FlowLogicRef.kt | 4 +- .../net/corda/core/flows/FlowSession.kt | 2 + .../net/corda/core/identity/AbstractParty.kt | 2 + .../net/corda/core/messaging/CordaRPCOps.kt | 1 - .../net/corda/core/messaging/FlowHandle.kt | 2 + .../kotlin/net/corda/core/messaging/RPCOps.kt | 3 + .../kotlin/net/corda/core/node/ServiceHub.kt | 4 +- .../core/node/services/AttachmentStorage.kt | 2 + .../node/services/ContractUpgradeService.kt | 2 + .../core/node/services/IdentityService.kt | 2 + .../node/services/KeyManagementService.kt | 2 + .../core/node/services/NetworkMapCache.kt | 2 + .../core/node/services/TransactionStorage.kt | 2 + .../services/TransactionVerifierService.kt | 2 + .../corda/core/node/services/VaultService.kt | 3 +- .../core/node/services/vault/QueryCriteria.kt | 2 + .../node/services/vault/QueryCriteriaUtils.kt | 3 + .../core/transactions/BaseTransaction.kt | 2 + .../transactions/TransactionWithSignatures.kt | 2 + docs/source/corda-api.rst | 11 + .../main/java/net/corda/plugins/ScanApi.java | 62 ++- .../statemachine/FlowLogicRefFactoryImpl.kt | 3 +- 29 files changed, 402 insertions(+), 266 deletions(-) create mode 100644 core/src/main/kotlin/net/corda/core/DoNotImplement.kt diff --git a/.ci/api-current.txt b/.ci/api-current.txt index 6b10e1b8b0..7c2e887573 100644 --- a/.ci/api-current.txt +++ b/.ci/api-current.txt @@ -1,4 +1,4 @@ -public class net.corda.core.CordaException extends java.lang.Exception implements net.corda.core.CordaThrowable +@net.corda.core.serialization.CordaSerializable public class net.corda.core.CordaException extends java.lang.Exception implements net.corda.core.CordaThrowable public () public (String) public (String, String, Throwable) @@ -14,7 +14,7 @@ public class net.corda.core.CordaException extends java.lang.Exception implement public void setMessage(String) public void setOriginalExceptionClassName(String) ## -public class net.corda.core.CordaRuntimeException extends java.lang.RuntimeException implements net.corda.core.CordaThrowable +@net.corda.core.serialization.CordaSerializable public class net.corda.core.CordaRuntimeException extends java.lang.RuntimeException implements net.corda.core.CordaThrowable public (String) public (String, String, Throwable) public (String, Throwable) @@ -29,7 +29,7 @@ public class net.corda.core.CordaRuntimeException extends java.lang.RuntimeExcep public void setMessage(String) public void setOriginalExceptionClassName(String) ## -public interface net.corda.core.CordaThrowable +@net.corda.core.serialization.CordaSerializable public interface net.corda.core.CordaThrowable public abstract void addSuppressed(Throwable[]) @org.jetbrains.annotations.Nullable public abstract String getOriginalExceptionClassName() @org.jetbrains.annotations.Nullable public abstract String getOriginalMessage() @@ -37,6 +37,8 @@ public interface net.corda.core.CordaThrowable public abstract void setMessage(String) public abstract void setOriginalExceptionClassName(String) ## +public @interface net.corda.core.DoNotImplement +## public final class net.corda.core.Utils extends java.lang.Object @org.jetbrains.annotations.NotNull public static final net.corda.core.concurrent.CordaFuture toFuture(rx.Observable) @org.jetbrains.annotations.NotNull public static final rx.Observable toObservable(net.corda.core.concurrent.CordaFuture) @@ -51,11 +53,11 @@ public interface net.corda.core.concurrent.CordaFuture extends java.util.concurr public abstract void then(kotlin.jvm.functions.Function1) @org.jetbrains.annotations.NotNull public abstract concurrent.CompletableFuture toCompletableFuture() ## -public final class net.corda.core.contracts.AlwaysAcceptAttachmentConstraint extends java.lang.Object implements net.corda.core.contracts.AttachmentConstraint +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.contracts.AlwaysAcceptAttachmentConstraint extends java.lang.Object implements net.corda.core.contracts.AttachmentConstraint public boolean isSatisfiedBy(net.corda.core.contracts.Attachment) public static final net.corda.core.contracts.AlwaysAcceptAttachmentConstraint INSTANCE ## -public final class net.corda.core.contracts.Amount extends java.lang.Object implements java.lang.Comparable +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.contracts.Amount extends java.lang.Object implements java.lang.Comparable public (long, Object) public (long, java.math.BigDecimal, Object) public int compareTo(net.corda.core.contracts.Amount) @@ -95,7 +97,7 @@ public static final class net.corda.core.contracts.Amount$Companion extends java @kotlin.jvm.JvmStatic @org.jetbrains.annotations.NotNull public final net.corda.core.contracts.Amount sumOrZero(Iterable, Object) @kotlin.jvm.JvmStatic @org.jetbrains.annotations.NotNull public final net.corda.core.contracts.Amount zero(Object) ## -public final class net.corda.core.contracts.AmountTransfer extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.contracts.AmountTransfer extends java.lang.Object public (long, Object, Object, Object) @org.jetbrains.annotations.NotNull public final List apply(List, Object) @org.jetbrains.annotations.NotNull public final net.corda.core.contracts.AmountTransfer copy(long, Object, Object, Object) @@ -119,24 +121,24 @@ public static final class net.corda.core.contracts.AmountTransfer$Companion exte @kotlin.jvm.JvmStatic @org.jetbrains.annotations.NotNull public final net.corda.core.contracts.AmountTransfer fromDecimal(java.math.BigDecimal, Object, Object, Object, java.math.RoundingMode) @kotlin.jvm.JvmStatic @org.jetbrains.annotations.NotNull public final net.corda.core.contracts.AmountTransfer zero(Object, Object, Object) ## -public interface net.corda.core.contracts.Attachment extends net.corda.core.contracts.NamedByHash +@net.corda.core.serialization.CordaSerializable public interface net.corda.core.contracts.Attachment extends net.corda.core.contracts.NamedByHash public abstract void extractFile(String, java.io.OutputStream) @org.jetbrains.annotations.NotNull public abstract List getSigners() @org.jetbrains.annotations.NotNull public abstract java.io.InputStream open() @org.jetbrains.annotations.NotNull public abstract jar.JarInputStream openAsJAR() ## -public interface net.corda.core.contracts.AttachmentConstraint +@net.corda.core.serialization.CordaSerializable public interface net.corda.core.contracts.AttachmentConstraint public abstract boolean isSatisfiedBy(net.corda.core.contracts.Attachment) ## -public final class net.corda.core.contracts.AttachmentResolutionException extends net.corda.core.flows.FlowException +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.contracts.AttachmentResolutionException extends net.corda.core.flows.FlowException public (net.corda.core.crypto.SecureHash) @org.jetbrains.annotations.NotNull public final net.corda.core.crypto.SecureHash getHash() ## -public final class net.corda.core.contracts.AutomaticHashConstraint extends java.lang.Object implements net.corda.core.contracts.AttachmentConstraint +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.contracts.AutomaticHashConstraint extends java.lang.Object implements net.corda.core.contracts.AttachmentConstraint public boolean isSatisfiedBy(net.corda.core.contracts.Attachment) public static final net.corda.core.contracts.AutomaticHashConstraint INSTANCE ## -public final class net.corda.core.contracts.Command extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.contracts.Command extends java.lang.Object public (net.corda.core.contracts.CommandData, java.security.PublicKey) public (net.corda.core.contracts.CommandData, List) @org.jetbrains.annotations.NotNull public final net.corda.core.contracts.CommandData component1() @@ -159,9 +161,9 @@ public final class net.corda.core.contracts.CommandAndState extends java.lang.Ob public int hashCode() public String toString() ## -public interface net.corda.core.contracts.CommandData +@net.corda.core.serialization.CordaSerializable public interface net.corda.core.contracts.CommandData ## -public final class net.corda.core.contracts.CommandWithParties extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.contracts.CommandWithParties extends java.lang.Object public (List, List, net.corda.core.contracts.CommandData) @org.jetbrains.annotations.NotNull public final List component1() @org.jetbrains.annotations.NotNull public final List component2() @@ -179,10 +181,10 @@ public final class net.corda.core.contracts.ComponentGroupEnum extends java.lang public static net.corda.core.contracts.ComponentGroupEnum valueOf(String) public static net.corda.core.contracts.ComponentGroupEnum[] values() ## -public interface net.corda.core.contracts.Contract +@net.corda.core.serialization.CordaSerializable public interface net.corda.core.contracts.Contract public abstract void verify(net.corda.core.transactions.LedgerTransaction) ## -public final class net.corda.core.contracts.ContractAttachment extends java.lang.Object implements net.corda.core.contracts.Attachment +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.contracts.ContractAttachment extends java.lang.Object implements net.corda.core.contracts.Attachment public (net.corda.core.contracts.Attachment, String) public void extractFile(String, java.io.OutputStream) @org.jetbrains.annotations.NotNull public final net.corda.core.contracts.Attachment getAttachment() @@ -192,19 +194,19 @@ public final class net.corda.core.contracts.ContractAttachment extends java.lang @org.jetbrains.annotations.NotNull public java.io.InputStream open() @org.jetbrains.annotations.NotNull public jar.JarInputStream openAsJAR() ## -public interface net.corda.core.contracts.ContractState +@net.corda.core.serialization.CordaSerializable public interface net.corda.core.contracts.ContractState @org.jetbrains.annotations.NotNull public abstract List getParticipants() ## public final class net.corda.core.contracts.ContractsDSL extends java.lang.Object @org.jetbrains.annotations.NotNull public static final net.corda.core.contracts.CommandWithParties requireSingleCommand(Collection, Class) public static final Object requireThat(kotlin.jvm.functions.Function1) ## -public interface net.corda.core.contracts.FungibleAsset extends net.corda.core.contracts.OwnableState +@net.corda.core.serialization.CordaSerializable public interface net.corda.core.contracts.FungibleAsset extends net.corda.core.contracts.OwnableState @org.jetbrains.annotations.NotNull public abstract net.corda.core.contracts.Amount getAmount() @org.jetbrains.annotations.NotNull public abstract Collection getExitKeys() @org.jetbrains.annotations.NotNull public abstract net.corda.core.contracts.FungibleAsset withNewOwnerAndAmount(net.corda.core.contracts.Amount, net.corda.core.identity.AbstractParty) ## -public final class net.corda.core.contracts.HashAttachmentConstraint extends java.lang.Object implements net.corda.core.contracts.AttachmentConstraint +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.contracts.HashAttachmentConstraint extends java.lang.Object implements net.corda.core.contracts.AttachmentConstraint public (net.corda.core.crypto.SecureHash) @org.jetbrains.annotations.NotNull public final net.corda.core.crypto.SecureHash component1() @org.jetbrains.annotations.NotNull public final net.corda.core.contracts.HashAttachmentConstraint copy(net.corda.core.crypto.SecureHash) @@ -214,11 +216,11 @@ public final class net.corda.core.contracts.HashAttachmentConstraint extends jav public boolean isSatisfiedBy(net.corda.core.contracts.Attachment) public String toString() ## -public final class net.corda.core.contracts.InsufficientBalanceException extends net.corda.core.flows.FlowException +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.contracts.InsufficientBalanceException extends net.corda.core.flows.FlowException public (net.corda.core.contracts.Amount) @org.jetbrains.annotations.NotNull public final net.corda.core.contracts.Amount getAmountMissing() ## -public final class net.corda.core.contracts.Issued extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.contracts.Issued extends java.lang.Object public (net.corda.core.contracts.PartyAndReference, Object) @org.jetbrains.annotations.NotNull public final net.corda.core.contracts.PartyAndReference component1() @org.jetbrains.annotations.NotNull public final Object component2() @@ -232,20 +234,20 @@ public final class net.corda.core.contracts.Issued extends java.lang.Object public @interface net.corda.core.contracts.LegalProseReference public abstract String uri() ## -public interface net.corda.core.contracts.LinearState extends net.corda.core.contracts.ContractState +@net.corda.core.serialization.CordaSerializable public interface net.corda.core.contracts.LinearState extends net.corda.core.contracts.ContractState @org.jetbrains.annotations.NotNull public abstract net.corda.core.contracts.UniqueIdentifier getLinearId() ## -public interface net.corda.core.contracts.MoveCommand extends net.corda.core.contracts.CommandData +@net.corda.core.serialization.CordaSerializable public interface net.corda.core.contracts.MoveCommand extends net.corda.core.contracts.CommandData @org.jetbrains.annotations.Nullable public abstract Class getContract() ## public interface net.corda.core.contracts.NamedByHash @org.jetbrains.annotations.NotNull public abstract net.corda.core.crypto.SecureHash getId() ## -public interface net.corda.core.contracts.OwnableState extends net.corda.core.contracts.ContractState +@net.corda.core.serialization.CordaSerializable public interface net.corda.core.contracts.OwnableState extends net.corda.core.contracts.ContractState @org.jetbrains.annotations.NotNull public abstract net.corda.core.identity.AbstractParty getOwner() @org.jetbrains.annotations.NotNull public abstract net.corda.core.contracts.CommandAndState withNewOwner(net.corda.core.identity.AbstractParty) ## -public final class net.corda.core.contracts.PartyAndReference extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.contracts.PartyAndReference extends java.lang.Object public (net.corda.core.identity.AbstractParty, net.corda.core.utilities.OpaqueBytes) @org.jetbrains.annotations.NotNull public final net.corda.core.identity.AbstractParty component1() @org.jetbrains.annotations.NotNull public final net.corda.core.utilities.OpaqueBytes component2() @@ -256,7 +258,7 @@ public final class net.corda.core.contracts.PartyAndReference extends java.lang. public int hashCode() @org.jetbrains.annotations.NotNull public String toString() ## -public final class net.corda.core.contracts.PrivacySalt extends net.corda.core.utilities.OpaqueBytes +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.contracts.PrivacySalt extends net.corda.core.utilities.OpaqueBytes public () public (byte[]) ## @@ -264,7 +266,7 @@ public final class net.corda.core.contracts.Requirements extends java.lang.Objec public final void using(String, boolean) public static final net.corda.core.contracts.Requirements INSTANCE ## -public interface net.corda.core.contracts.SchedulableState extends net.corda.core.contracts.ContractState +@net.corda.core.serialization.CordaSerializable public interface net.corda.core.contracts.SchedulableState extends net.corda.core.contracts.ContractState @org.jetbrains.annotations.Nullable public abstract net.corda.core.contracts.ScheduledActivity nextScheduledActivity(net.corda.core.contracts.StateRef, net.corda.core.flows.FlowLogicRefFactory) ## public interface net.corda.core.contracts.Scheduled @@ -316,7 +318,7 @@ public final class net.corda.core.contracts.StateAndContract extends java.lang.O public int hashCode() public String toString() ## -public final class net.corda.core.contracts.StateAndRef extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.contracts.StateAndRef extends java.lang.Object public (net.corda.core.contracts.TransactionState, net.corda.core.contracts.StateRef) @org.jetbrains.annotations.NotNull public final net.corda.core.contracts.TransactionState component1() @org.jetbrains.annotations.NotNull public final net.corda.core.contracts.StateRef component2() @@ -327,7 +329,7 @@ public final class net.corda.core.contracts.StateAndRef extends java.lang.Object public int hashCode() public String toString() ## -public final class net.corda.core.contracts.StateRef extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.contracts.StateRef extends java.lang.Object public (net.corda.core.crypto.SecureHash, int) @org.jetbrains.annotations.NotNull public final net.corda.core.crypto.SecureHash component1() public final int component2() @@ -342,7 +344,7 @@ public final class net.corda.core.contracts.Structures extends java.lang.Object @org.jetbrains.annotations.NotNull public static final net.corda.core.crypto.SecureHash hash(net.corda.core.contracts.ContractState) @org.jetbrains.annotations.NotNull public static final net.corda.core.contracts.Amount withoutIssuer(net.corda.core.contracts.Amount) ## -public abstract class net.corda.core.contracts.TimeWindow extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public abstract class net.corda.core.contracts.TimeWindow extends java.lang.Object public () @kotlin.jvm.JvmStatic @org.jetbrains.annotations.NotNull public static final net.corda.core.contracts.TimeWindow between(java.time.Instant, java.time.Instant) public abstract boolean contains(java.time.Instant) @@ -365,11 +367,11 @@ public static final class net.corda.core.contracts.TimeWindow$Companion extends public interface net.corda.core.contracts.TokenizableAssetInfo @org.jetbrains.annotations.NotNull public abstract java.math.BigDecimal getDisplayTokenSize() ## -public final class net.corda.core.contracts.TransactionResolutionException extends net.corda.core.flows.FlowException +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.contracts.TransactionResolutionException extends net.corda.core.flows.FlowException public (net.corda.core.crypto.SecureHash) @org.jetbrains.annotations.NotNull public final net.corda.core.crypto.SecureHash getHash() ## -public final class net.corda.core.contracts.TransactionState extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.contracts.TransactionState extends java.lang.Object public (net.corda.core.contracts.ContractState, String, net.corda.core.identity.Party) public (net.corda.core.contracts.ContractState, String, net.corda.core.identity.Party, Integer) public (net.corda.core.contracts.ContractState, String, net.corda.core.identity.Party, Integer, net.corda.core.contracts.AttachmentConstraint) @@ -390,51 +392,51 @@ public final class net.corda.core.contracts.TransactionState extends java.lang.O ## public final class net.corda.core.contracts.TransactionStateKt extends java.lang.Object ## -public abstract class net.corda.core.contracts.TransactionVerificationException extends net.corda.core.flows.FlowException +@net.corda.core.serialization.CordaSerializable public abstract class net.corda.core.contracts.TransactionVerificationException extends net.corda.core.flows.FlowException @org.jetbrains.annotations.NotNull public final net.corda.core.crypto.SecureHash getTxId() ## -public static final class net.corda.core.contracts.TransactionVerificationException$ContractConstraintRejection extends net.corda.core.contracts.TransactionVerificationException +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.contracts.TransactionVerificationException$ContractConstraintRejection extends net.corda.core.contracts.TransactionVerificationException public (net.corda.core.crypto.SecureHash, String) ## -public static final class net.corda.core.contracts.TransactionVerificationException$ContractCreationError extends net.corda.core.contracts.TransactionVerificationException +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.contracts.TransactionVerificationException$ContractCreationError extends net.corda.core.contracts.TransactionVerificationException public (net.corda.core.crypto.SecureHash, String, Throwable) ## -public static final class net.corda.core.contracts.TransactionVerificationException$ContractRejection extends net.corda.core.contracts.TransactionVerificationException +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.contracts.TransactionVerificationException$ContractRejection extends net.corda.core.contracts.TransactionVerificationException public (net.corda.core.crypto.SecureHash, net.corda.core.contracts.Contract, Throwable) ## -public static final class net.corda.core.contracts.TransactionVerificationException$Direction extends java.lang.Enum +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.contracts.TransactionVerificationException$Direction extends java.lang.Enum protected (String, int) public static net.corda.core.contracts.TransactionVerificationException$Direction valueOf(String) public static net.corda.core.contracts.TransactionVerificationException$Direction[] values() ## -public static final class net.corda.core.contracts.TransactionVerificationException$DuplicateInputStates extends net.corda.core.contracts.TransactionVerificationException +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.contracts.TransactionVerificationException$DuplicateInputStates extends net.corda.core.contracts.TransactionVerificationException public (net.corda.core.crypto.SecureHash, net.corda.core.utilities.NonEmptySet) @org.jetbrains.annotations.NotNull public final net.corda.core.utilities.NonEmptySet getDuplicates() ## -public static final class net.corda.core.contracts.TransactionVerificationException$InvalidNotaryChange extends net.corda.core.contracts.TransactionVerificationException +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.contracts.TransactionVerificationException$InvalidNotaryChange extends net.corda.core.contracts.TransactionVerificationException public (net.corda.core.crypto.SecureHash) ## -public static final class net.corda.core.contracts.TransactionVerificationException$MissingAttachmentRejection extends net.corda.core.contracts.TransactionVerificationException +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.contracts.TransactionVerificationException$MissingAttachmentRejection extends net.corda.core.contracts.TransactionVerificationException public (net.corda.core.crypto.SecureHash, String) ## -public static final class net.corda.core.contracts.TransactionVerificationException$MoreThanOneNotary extends net.corda.core.contracts.TransactionVerificationException +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.contracts.TransactionVerificationException$MoreThanOneNotary extends net.corda.core.contracts.TransactionVerificationException public (net.corda.core.crypto.SecureHash) ## -public static final class net.corda.core.contracts.TransactionVerificationException$NotaryChangeInWrongTransactionType extends net.corda.core.contracts.TransactionVerificationException +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.contracts.TransactionVerificationException$NotaryChangeInWrongTransactionType extends net.corda.core.contracts.TransactionVerificationException public (net.corda.core.crypto.SecureHash, net.corda.core.identity.Party, net.corda.core.identity.Party) ## -public static final class net.corda.core.contracts.TransactionVerificationException$SignersMissing extends net.corda.core.contracts.TransactionVerificationException +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.contracts.TransactionVerificationException$SignersMissing extends net.corda.core.contracts.TransactionVerificationException public (net.corda.core.crypto.SecureHash, List) ## -public static final class net.corda.core.contracts.TransactionVerificationException$TransactionMissingEncumbranceException extends net.corda.core.contracts.TransactionVerificationException +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.contracts.TransactionVerificationException$TransactionMissingEncumbranceException extends net.corda.core.contracts.TransactionVerificationException public (net.corda.core.crypto.SecureHash, int, net.corda.core.contracts.TransactionVerificationException$Direction) ## -public abstract class net.corda.core.contracts.TypeOnlyCommandData extends java.lang.Object implements net.corda.core.contracts.CommandData +@net.corda.core.serialization.CordaSerializable public abstract class net.corda.core.contracts.TypeOnlyCommandData extends java.lang.Object implements net.corda.core.contracts.CommandData public () public boolean equals(Object) public int hashCode() ## -public final class net.corda.core.contracts.UniqueIdentifier extends java.lang.Object implements java.lang.Comparable +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.contracts.UniqueIdentifier extends java.lang.Object implements java.lang.Comparable public () public (String, UUID) public int compareTo(net.corda.core.contracts.UniqueIdentifier) @@ -451,11 +453,11 @@ public final class net.corda.core.contracts.UniqueIdentifier extends java.lang.O public static final class net.corda.core.contracts.UniqueIdentifier$Companion extends java.lang.Object @org.jetbrains.annotations.NotNull public final net.corda.core.contracts.UniqueIdentifier fromString(String) ## -public interface net.corda.core.contracts.UpgradedContract extends net.corda.core.contracts.Contract +@net.corda.core.serialization.CordaSerializable public interface net.corda.core.contracts.UpgradedContract extends net.corda.core.contracts.Contract @org.jetbrains.annotations.NotNull public abstract String getLegacyContract() @org.jetbrains.annotations.NotNull public abstract net.corda.core.contracts.ContractState upgrade(net.corda.core.contracts.ContractState) ## -public interface net.corda.core.cordapp.Cordapp +@net.corda.core.DoNotImplement public interface net.corda.core.cordapp.Cordapp @org.jetbrains.annotations.NotNull public abstract List getContractClassNames() @org.jetbrains.annotations.NotNull public abstract List getCordappClasses() @org.jetbrains.annotations.NotNull public abstract Set getCustomSchemas() @@ -474,7 +476,7 @@ public final class net.corda.core.cordapp.CordappContext extends java.lang.Objec @org.jetbrains.annotations.NotNull public final ClassLoader getClassLoader() @org.jetbrains.annotations.NotNull public final net.corda.core.cordapp.Cordapp getCordapp() ## -public interface net.corda.core.cordapp.CordappProvider +@net.corda.core.DoNotImplement public interface net.corda.core.cordapp.CordappProvider @org.jetbrains.annotations.NotNull public abstract net.corda.core.cordapp.CordappContext getAppContext() @org.jetbrains.annotations.Nullable public abstract net.corda.core.crypto.SecureHash getContractAttachmentID(String) ## @@ -489,7 +491,7 @@ public class net.corda.core.crypto.Base58 extends java.lang.Object public static java.math.BigInteger decodeToBigInteger(String) public static String encode(byte[]) ## -public final class net.corda.core.crypto.CompositeKey extends java.lang.Object implements java.security.PublicKey +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.crypto.CompositeKey extends java.lang.Object implements java.security.PublicKey public final void checkValidity() public boolean equals(Object) @org.jetbrains.annotations.NotNull public String getAlgorithm() @@ -515,7 +517,7 @@ public static final class net.corda.core.crypto.CompositeKey$Companion extends j @org.jetbrains.annotations.NotNull public final java.security.PublicKey getInstance(org.bouncycastle.asn1.ASN1Primitive) @org.jetbrains.annotations.NotNull public final java.security.PublicKey getInstance(byte[]) ## -public static final class net.corda.core.crypto.CompositeKey$NodeAndWeight extends org.bouncycastle.asn1.ASN1Object implements java.lang.Comparable +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.crypto.CompositeKey$NodeAndWeight extends org.bouncycastle.asn1.ASN1Object implements java.lang.Comparable public (java.security.PublicKey, int) public int compareTo(net.corda.core.crypto.CompositeKey$NodeAndWeight) @org.jetbrains.annotations.NotNull public final java.security.PublicKey component1() @@ -565,7 +567,7 @@ public static final class net.corda.core.crypto.CompositeSignature$State extends public int hashCode() public String toString() ## -public final class net.corda.core.crypto.CompositeSignaturesWithKeys extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.crypto.CompositeSignaturesWithKeys extends java.lang.Object public (List) @org.jetbrains.annotations.NotNull public final List component1() @org.jetbrains.annotations.NotNull public final net.corda.core.crypto.CompositeSignaturesWithKeys copy(List) @@ -664,10 +666,10 @@ public final class net.corda.core.crypto.CryptoUtils extends java.lang.Object public static final boolean verify(java.security.PublicKey, byte[], net.corda.core.crypto.DigitalSignature) public static final boolean verify(java.security.PublicKey, byte[], byte[]) ## -public class net.corda.core.crypto.DigitalSignature extends net.corda.core.utilities.OpaqueBytes +@net.corda.core.serialization.CordaSerializable public class net.corda.core.crypto.DigitalSignature extends net.corda.core.utilities.OpaqueBytes public (byte[]) ## -public static class net.corda.core.crypto.DigitalSignature$WithKey extends net.corda.core.crypto.DigitalSignature +@net.corda.core.serialization.CordaSerializable public static class net.corda.core.crypto.DigitalSignature$WithKey extends net.corda.core.crypto.DigitalSignature public (java.security.PublicKey, byte[]) @org.jetbrains.annotations.NotNull public final java.security.PublicKey getBy() public final boolean isValid(byte[]) @@ -703,7 +705,7 @@ public static final class net.corda.core.crypto.MerkleTree$Node extends net.cord public int hashCode() public String toString() ## -public final class net.corda.core.crypto.MerkleTreeException extends net.corda.core.CordaException +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.crypto.MerkleTreeException extends net.corda.core.CordaException public (String) @org.jetbrains.annotations.NotNull public final String getReason() ## @@ -712,7 +714,7 @@ public final class net.corda.core.crypto.NullKeys extends java.lang.Object @org.jetbrains.annotations.NotNull public final net.corda.core.crypto.TransactionSignature getNULL_SIGNATURE() public static final net.corda.core.crypto.NullKeys INSTANCE ## -public static final class net.corda.core.crypto.NullKeys$NullPublicKey extends java.lang.Object implements java.security.PublicKey, java.lang.Comparable +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.crypto.NullKeys$NullPublicKey extends java.lang.Object implements java.security.PublicKey, java.lang.Comparable public int compareTo(java.security.PublicKey) @org.jetbrains.annotations.NotNull public String getAlgorithm() @org.jetbrains.annotations.NotNull public byte[] getEncoded() @@ -720,7 +722,7 @@ public static final class net.corda.core.crypto.NullKeys$NullPublicKey extends j @org.jetbrains.annotations.NotNull public String toString() public static final net.corda.core.crypto.NullKeys$NullPublicKey INSTANCE ## -public final class net.corda.core.crypto.PartialMerkleTree extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.crypto.PartialMerkleTree extends java.lang.Object public (net.corda.core.crypto.PartialMerkleTree$PartialTree) @org.jetbrains.annotations.NotNull public final net.corda.core.crypto.PartialMerkleTree$PartialTree getRoot() public final boolean verify(net.corda.core.crypto.SecureHash, List) @@ -730,9 +732,9 @@ public static final class net.corda.core.crypto.PartialMerkleTree$Companion exte @org.jetbrains.annotations.NotNull public final net.corda.core.crypto.PartialMerkleTree build(net.corda.core.crypto.MerkleTree, List) @org.jetbrains.annotations.NotNull public final net.corda.core.crypto.SecureHash rootAndUsedHashes(net.corda.core.crypto.PartialMerkleTree$PartialTree, List) ## -public abstract static class net.corda.core.crypto.PartialMerkleTree$PartialTree extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public abstract static class net.corda.core.crypto.PartialMerkleTree$PartialTree extends java.lang.Object ## -public static final class net.corda.core.crypto.PartialMerkleTree$PartialTree$IncludedLeaf extends net.corda.core.crypto.PartialMerkleTree$PartialTree +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.crypto.PartialMerkleTree$PartialTree$IncludedLeaf extends net.corda.core.crypto.PartialMerkleTree$PartialTree public (net.corda.core.crypto.SecureHash) @org.jetbrains.annotations.NotNull public final net.corda.core.crypto.SecureHash component1() @org.jetbrains.annotations.NotNull public final net.corda.core.crypto.PartialMerkleTree$PartialTree$IncludedLeaf copy(net.corda.core.crypto.SecureHash) @@ -741,7 +743,7 @@ public static final class net.corda.core.crypto.PartialMerkleTree$PartialTree$In public int hashCode() public String toString() ## -public static final class net.corda.core.crypto.PartialMerkleTree$PartialTree$Leaf extends net.corda.core.crypto.PartialMerkleTree$PartialTree +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.crypto.PartialMerkleTree$PartialTree$Leaf extends net.corda.core.crypto.PartialMerkleTree$PartialTree public (net.corda.core.crypto.SecureHash) @org.jetbrains.annotations.NotNull public final net.corda.core.crypto.SecureHash component1() @org.jetbrains.annotations.NotNull public final net.corda.core.crypto.PartialMerkleTree$PartialTree$Leaf copy(net.corda.core.crypto.SecureHash) @@ -750,7 +752,7 @@ public static final class net.corda.core.crypto.PartialMerkleTree$PartialTree$Le public int hashCode() public String toString() ## -public static final class net.corda.core.crypto.PartialMerkleTree$PartialTree$Node extends net.corda.core.crypto.PartialMerkleTree$PartialTree +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.crypto.PartialMerkleTree$PartialTree$Node extends net.corda.core.crypto.PartialMerkleTree$PartialTree public (net.corda.core.crypto.PartialMerkleTree$PartialTree, net.corda.core.crypto.PartialMerkleTree$PartialTree) @org.jetbrains.annotations.NotNull public final net.corda.core.crypto.PartialMerkleTree$PartialTree component1() @org.jetbrains.annotations.NotNull public final net.corda.core.crypto.PartialMerkleTree$PartialTree component2() @@ -761,7 +763,7 @@ public static final class net.corda.core.crypto.PartialMerkleTree$PartialTree$No public int hashCode() public String toString() ## -public abstract class net.corda.core.crypto.SecureHash extends net.corda.core.utilities.OpaqueBytes +@net.corda.core.serialization.CordaSerializable public abstract class net.corda.core.crypto.SecureHash extends net.corda.core.utilities.OpaqueBytes @org.jetbrains.annotations.NotNull public final net.corda.core.crypto.SecureHash$SHA256 hashConcat(net.corda.core.crypto.SecureHash) @kotlin.jvm.JvmStatic @org.jetbrains.annotations.NotNull public static final net.corda.core.crypto.SecureHash$SHA256 parse(String) @org.jetbrains.annotations.NotNull public final String prefixChars(int) @@ -781,14 +783,14 @@ public static final class net.corda.core.crypto.SecureHash$Companion extends jav @kotlin.jvm.JvmStatic @org.jetbrains.annotations.NotNull public final net.corda.core.crypto.SecureHash$SHA256 sha256(byte[]) @kotlin.jvm.JvmStatic @org.jetbrains.annotations.NotNull public final net.corda.core.crypto.SecureHash$SHA256 sha256Twice(byte[]) ## -public static final class net.corda.core.crypto.SecureHash$SHA256 extends net.corda.core.crypto.SecureHash +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.crypto.SecureHash$SHA256 extends net.corda.core.crypto.SecureHash public (byte[]) ## public final class net.corda.core.crypto.SecureHashKt extends java.lang.Object @org.jetbrains.annotations.NotNull public static final net.corda.core.crypto.SecureHash$SHA256 sha256(net.corda.core.utilities.OpaqueBytes) @org.jetbrains.annotations.NotNull public static final net.corda.core.crypto.SecureHash$SHA256 sha256(byte[]) ## -public final class net.corda.core.crypto.SignableData extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.crypto.SignableData extends java.lang.Object public (net.corda.core.crypto.SecureHash, net.corda.core.crypto.SignatureMetadata) @org.jetbrains.annotations.NotNull public final net.corda.core.crypto.SecureHash component1() @org.jetbrains.annotations.NotNull public final net.corda.core.crypto.SignatureMetadata component2() @@ -799,7 +801,7 @@ public final class net.corda.core.crypto.SignableData extends java.lang.Object public int hashCode() public String toString() ## -public final class net.corda.core.crypto.SignatureMetadata extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.crypto.SignatureMetadata extends java.lang.Object public (int, int) public final int component1() public final int component2() @@ -837,14 +839,14 @@ public final class net.corda.core.crypto.SignatureScheme extends java.lang.Objec public int hashCode() public String toString() ## -public class net.corda.core.crypto.SignedData extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public class net.corda.core.crypto.SignedData extends java.lang.Object public (net.corda.core.serialization.SerializedBytes, net.corda.core.crypto.DigitalSignature$WithKey) @org.jetbrains.annotations.NotNull public final net.corda.core.serialization.SerializedBytes getRaw() @org.jetbrains.annotations.NotNull public final net.corda.core.crypto.DigitalSignature$WithKey getSig() @org.jetbrains.annotations.NotNull public final Object verified() protected void verifyData(Object) ## -public final class net.corda.core.crypto.TransactionSignature extends net.corda.core.crypto.DigitalSignature +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.crypto.TransactionSignature extends net.corda.core.crypto.DigitalSignature public (byte[], java.security.PublicKey, net.corda.core.crypto.SignatureMetadata) public boolean equals(Object) @org.jetbrains.annotations.NotNull public final java.security.PublicKey getBy() @@ -868,10 +870,10 @@ public abstract static class net.corda.core.flows.AbstractStateReplacementFlow$A public static final class net.corda.core.flows.AbstractStateReplacementFlow$Acceptor$Companion extends java.lang.Object @org.jetbrains.annotations.NotNull public final net.corda.core.utilities.ProgressTracker tracker() ## -public static final class net.corda.core.flows.AbstractStateReplacementFlow$Acceptor$Companion$APPROVING extends net.corda.core.utilities.ProgressTracker$Step +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.flows.AbstractStateReplacementFlow$Acceptor$Companion$APPROVING extends net.corda.core.utilities.ProgressTracker$Step public static final net.corda.core.flows.AbstractStateReplacementFlow$Acceptor$Companion$APPROVING INSTANCE ## -public static final class net.corda.core.flows.AbstractStateReplacementFlow$Acceptor$Companion$VERIFYING extends net.corda.core.utilities.ProgressTracker$Step +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.flows.AbstractStateReplacementFlow$Acceptor$Companion$VERIFYING extends net.corda.core.utilities.ProgressTracker$Step public static final net.corda.core.flows.AbstractStateReplacementFlow$Acceptor$Companion$VERIFYING INSTANCE ## public abstract static class net.corda.core.flows.AbstractStateReplacementFlow$Instigator extends net.corda.core.flows.FlowLogic @@ -887,13 +889,13 @@ public abstract static class net.corda.core.flows.AbstractStateReplacementFlow$I public static final class net.corda.core.flows.AbstractStateReplacementFlow$Instigator$Companion extends java.lang.Object @org.jetbrains.annotations.NotNull public final net.corda.core.utilities.ProgressTracker tracker() ## -public static final class net.corda.core.flows.AbstractStateReplacementFlow$Instigator$Companion$NOTARY extends net.corda.core.utilities.ProgressTracker$Step +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.flows.AbstractStateReplacementFlow$Instigator$Companion$NOTARY extends net.corda.core.utilities.ProgressTracker$Step public static final net.corda.core.flows.AbstractStateReplacementFlow$Instigator$Companion$NOTARY INSTANCE ## -public static final class net.corda.core.flows.AbstractStateReplacementFlow$Instigator$Companion$SIGNING extends net.corda.core.utilities.ProgressTracker$Step +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.flows.AbstractStateReplacementFlow$Instigator$Companion$SIGNING extends net.corda.core.utilities.ProgressTracker$Step public static final net.corda.core.flows.AbstractStateReplacementFlow$Instigator$Companion$SIGNING INSTANCE ## -public static final class net.corda.core.flows.AbstractStateReplacementFlow$Proposal extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.flows.AbstractStateReplacementFlow$Proposal extends java.lang.Object public (net.corda.core.contracts.StateRef, Object) @org.jetbrains.annotations.NotNull public final net.corda.core.contracts.StateRef component1() public final Object component2() @@ -913,7 +915,7 @@ public static final class net.corda.core.flows.AbstractStateReplacementFlow$Upgr public int hashCode() public String toString() ## -public final class net.corda.core.flows.CollectSignatureFlow extends net.corda.core.flows.FlowLogic +@co.paralleluniverse.fibers.Suspendable public final class net.corda.core.flows.CollectSignatureFlow extends net.corda.core.flows.FlowLogic public (net.corda.core.transactions.SignedTransaction, net.corda.core.flows.FlowSession, List) @co.paralleluniverse.fibers.Suspendable @org.jetbrains.annotations.NotNull public List call() @org.jetbrains.annotations.NotNull public final net.corda.core.transactions.SignedTransaction getPartiallySignedTx() @@ -936,26 +938,26 @@ public final class net.corda.core.flows.CollectSignaturesFlow extends net.corda. public static final class net.corda.core.flows.CollectSignaturesFlow$Companion extends java.lang.Object @kotlin.jvm.JvmStatic @org.jetbrains.annotations.NotNull public final net.corda.core.utilities.ProgressTracker tracker() ## -public static final class net.corda.core.flows.CollectSignaturesFlow$Companion$COLLECTING extends net.corda.core.utilities.ProgressTracker$Step +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.flows.CollectSignaturesFlow$Companion$COLLECTING extends net.corda.core.utilities.ProgressTracker$Step public static final net.corda.core.flows.CollectSignaturesFlow$Companion$COLLECTING INSTANCE ## -public static final class net.corda.core.flows.CollectSignaturesFlow$Companion$VERIFYING extends net.corda.core.utilities.ProgressTracker$Step +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.flows.CollectSignaturesFlow$Companion$VERIFYING extends net.corda.core.utilities.ProgressTracker$Step public static final net.corda.core.flows.CollectSignaturesFlow$Companion$VERIFYING INSTANCE ## public final class net.corda.core.flows.ContractUpgradeFlow extends java.lang.Object public static final net.corda.core.flows.ContractUpgradeFlow INSTANCE ## -public static final class net.corda.core.flows.ContractUpgradeFlow$Authorise extends net.corda.core.flows.FlowLogic +@net.corda.core.flows.StartableByRPC public static final class net.corda.core.flows.ContractUpgradeFlow$Authorise extends net.corda.core.flows.FlowLogic public (net.corda.core.contracts.StateAndRef, Class) @co.paralleluniverse.fibers.Suspendable @org.jetbrains.annotations.Nullable public Void call() @org.jetbrains.annotations.NotNull public final net.corda.core.contracts.StateAndRef getStateAndRef() ## -public static final class net.corda.core.flows.ContractUpgradeFlow$Deauthorise extends net.corda.core.flows.FlowLogic +@net.corda.core.flows.StartableByRPC public static final class net.corda.core.flows.ContractUpgradeFlow$Deauthorise extends net.corda.core.flows.FlowLogic public (net.corda.core.contracts.StateRef) @co.paralleluniverse.fibers.Suspendable @org.jetbrains.annotations.Nullable public Void call() @org.jetbrains.annotations.NotNull public final net.corda.core.contracts.StateRef getStateRef() ## -public static final class net.corda.core.flows.ContractUpgradeFlow$Initiate extends net.corda.core.flows.AbstractStateReplacementFlow$Instigator +@net.corda.core.flows.InitiatingFlow @net.corda.core.flows.StartableByRPC public static final class net.corda.core.flows.ContractUpgradeFlow$Initiate extends net.corda.core.flows.AbstractStateReplacementFlow$Instigator public (net.corda.core.contracts.StateAndRef, Class) @co.paralleluniverse.fibers.Suspendable @org.jetbrains.annotations.NotNull protected net.corda.core.flows.AbstractStateReplacementFlow$UpgradeTx assembleTx() ## @@ -966,7 +968,7 @@ public abstract class net.corda.core.flows.DataVendingFlow extends net.corda.cor @co.paralleluniverse.fibers.Suspendable @org.jetbrains.annotations.NotNull protected net.corda.core.utilities.UntrustworthyData sendPayloadAndReceiveDataRequest(net.corda.core.flows.FlowSession, Object) @co.paralleluniverse.fibers.Suspendable protected void verifyDataRequest(net.corda.core.internal.FetchDataFlow$Request$Data) ## -public final class net.corda.core.flows.FinalityFlow extends net.corda.core.flows.FlowLogic +@net.corda.core.flows.InitiatingFlow public final class net.corda.core.flows.FinalityFlow extends net.corda.core.flows.FlowLogic public (net.corda.core.transactions.SignedTransaction) public (net.corda.core.transactions.SignedTransaction, Set) public (net.corda.core.transactions.SignedTransaction, Set, net.corda.core.utilities.ProgressTracker) @@ -980,20 +982,20 @@ public final class net.corda.core.flows.FinalityFlow extends net.corda.core.flow public static final class net.corda.core.flows.FinalityFlow$Companion extends java.lang.Object @kotlin.jvm.JvmStatic @org.jetbrains.annotations.NotNull public final net.corda.core.utilities.ProgressTracker tracker() ## -public static final class net.corda.core.flows.FinalityFlow$Companion$BROADCASTING extends net.corda.core.utilities.ProgressTracker$Step +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.flows.FinalityFlow$Companion$BROADCASTING extends net.corda.core.utilities.ProgressTracker$Step public static final net.corda.core.flows.FinalityFlow$Companion$BROADCASTING INSTANCE ## -public static final class net.corda.core.flows.FinalityFlow$Companion$NOTARISING extends net.corda.core.utilities.ProgressTracker$Step +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.flows.FinalityFlow$Companion$NOTARISING extends net.corda.core.utilities.ProgressTracker$Step @org.jetbrains.annotations.NotNull public net.corda.core.utilities.ProgressTracker childProgressTracker() public static final net.corda.core.flows.FinalityFlow$Companion$NOTARISING INSTANCE ## -public class net.corda.core.flows.FlowException extends net.corda.core.CordaException +@net.corda.core.serialization.CordaSerializable public class net.corda.core.flows.FlowException extends net.corda.core.CordaException public () public (String) public (String, Throwable) public (Throwable) ## -public final class net.corda.core.flows.FlowInfo extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.flows.FlowInfo extends java.lang.Object public (int, String) public final int component1() @org.jetbrains.annotations.NotNull public final String component2() @@ -1004,9 +1006,9 @@ public final class net.corda.core.flows.FlowInfo extends java.lang.Object public int hashCode() public String toString() ## -public abstract class net.corda.core.flows.FlowInitiator extends java.lang.Object implements java.security.Principal +@net.corda.core.serialization.CordaSerializable public abstract class net.corda.core.flows.FlowInitiator extends java.lang.Object implements java.security.Principal ## -public static final class net.corda.core.flows.FlowInitiator$Peer extends net.corda.core.flows.FlowInitiator +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.flows.FlowInitiator$Peer extends net.corda.core.flows.FlowInitiator public (net.corda.core.identity.Party) @org.jetbrains.annotations.NotNull public final net.corda.core.identity.Party component1() @org.jetbrains.annotations.NotNull public final net.corda.core.flows.FlowInitiator$Peer copy(net.corda.core.identity.Party) @@ -1016,7 +1018,7 @@ public static final class net.corda.core.flows.FlowInitiator$Peer extends net.co public int hashCode() public String toString() ## -public static final class net.corda.core.flows.FlowInitiator$RPC extends net.corda.core.flows.FlowInitiator +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.flows.FlowInitiator$RPC extends net.corda.core.flows.FlowInitiator public (String) @org.jetbrains.annotations.NotNull public final String component1() @org.jetbrains.annotations.NotNull public final net.corda.core.flows.FlowInitiator$RPC copy(String) @@ -1026,7 +1028,7 @@ public static final class net.corda.core.flows.FlowInitiator$RPC extends net.cor public int hashCode() public String toString() ## -public static final class net.corda.core.flows.FlowInitiator$Scheduled extends net.corda.core.flows.FlowInitiator +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.flows.FlowInitiator$Scheduled extends net.corda.core.flows.FlowInitiator public (net.corda.core.contracts.ScheduledStateRef) @org.jetbrains.annotations.NotNull public final net.corda.core.contracts.ScheduledStateRef component1() @org.jetbrains.annotations.NotNull public final net.corda.core.flows.FlowInitiator$Scheduled copy(net.corda.core.contracts.ScheduledStateRef) @@ -1036,7 +1038,7 @@ public static final class net.corda.core.flows.FlowInitiator$Scheduled extends n public int hashCode() public String toString() ## -public static final class net.corda.core.flows.FlowInitiator$Shell extends net.corda.core.flows.FlowInitiator +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.flows.FlowInitiator$Shell extends net.corda.core.flows.FlowInitiator @org.jetbrains.annotations.NotNull public String getName() public static final net.corda.core.flows.FlowInitiator$Shell INSTANCE ## @@ -1064,11 +1066,11 @@ public abstract class net.corda.core.flows.FlowLogic extends java.lang.Object @org.jetbrains.annotations.Nullable public final net.corda.core.messaging.DataFeed track() @co.paralleluniverse.fibers.Suspendable @org.jetbrains.annotations.NotNull public final net.corda.core.transactions.SignedTransaction waitForLedgerCommit(net.corda.core.crypto.SecureHash) ## -public interface net.corda.core.flows.FlowLogicRef +@net.corda.core.serialization.CordaSerializable @net.corda.core.DoNotImplement public interface net.corda.core.flows.FlowLogicRef ## -public interface net.corda.core.flows.FlowLogicRefFactory +@net.corda.core.DoNotImplement public interface net.corda.core.flows.FlowLogicRefFactory ## -public abstract class net.corda.core.flows.FlowSession extends java.lang.Object +@net.corda.core.DoNotImplement public abstract class net.corda.core.flows.FlowSession extends java.lang.Object public () @org.jetbrains.annotations.NotNull public abstract net.corda.core.identity.Party getCounterparty() @co.paralleluniverse.fibers.Suspendable @org.jetbrains.annotations.NotNull public abstract net.corda.core.flows.FlowInfo getCounterpartyFlowInfo() @@ -1100,7 +1102,7 @@ public static final class net.corda.core.flows.FlowStackSnapshot$Frame extends j public int hashCode() @org.jetbrains.annotations.NotNull public String toString() ## -public final class net.corda.core.flows.IllegalFlowLogicException extends java.lang.IllegalArgumentException +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.flows.IllegalFlowLogicException extends java.lang.IllegalArgumentException public (Class, String) ## public @interface net.corda.core.flows.InitiatedBy @@ -1109,13 +1111,13 @@ public @interface net.corda.core.flows.InitiatedBy public @interface net.corda.core.flows.InitiatingFlow public abstract int version() ## -public final class net.corda.core.flows.NotaryChangeFlow extends net.corda.core.flows.AbstractStateReplacementFlow$Instigator +@net.corda.core.flows.InitiatingFlow public final class net.corda.core.flows.NotaryChangeFlow extends net.corda.core.flows.AbstractStateReplacementFlow$Instigator public (net.corda.core.contracts.StateAndRef, net.corda.core.identity.Party, net.corda.core.utilities.ProgressTracker) @org.jetbrains.annotations.NotNull protected net.corda.core.flows.AbstractStateReplacementFlow$UpgradeTx assembleTx() ## -public abstract class net.corda.core.flows.NotaryError extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public abstract class net.corda.core.flows.NotaryError extends java.lang.Object ## -public static final class net.corda.core.flows.NotaryError$Conflict extends net.corda.core.flows.NotaryError +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.flows.NotaryError$Conflict extends net.corda.core.flows.NotaryError public (net.corda.core.crypto.SecureHash, net.corda.core.crypto.SignedData) @org.jetbrains.annotations.NotNull public final net.corda.core.crypto.SecureHash component1() @org.jetbrains.annotations.NotNull public final net.corda.core.crypto.SignedData component2() @@ -1126,10 +1128,10 @@ public static final class net.corda.core.flows.NotaryError$Conflict extends net. public int hashCode() @org.jetbrains.annotations.NotNull public String toString() ## -public static final class net.corda.core.flows.NotaryError$TimeWindowInvalid extends net.corda.core.flows.NotaryError +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.flows.NotaryError$TimeWindowInvalid extends net.corda.core.flows.NotaryError public static final net.corda.core.flows.NotaryError$TimeWindowInvalid INSTANCE ## -public static final class net.corda.core.flows.NotaryError$TransactionInvalid extends net.corda.core.flows.NotaryError +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.flows.NotaryError$TransactionInvalid extends net.corda.core.flows.NotaryError public (Throwable) @org.jetbrains.annotations.NotNull public final Throwable component1() @org.jetbrains.annotations.NotNull public final net.corda.core.flows.NotaryError$TransactionInvalid copy(Throwable) @@ -1138,17 +1140,17 @@ public static final class net.corda.core.flows.NotaryError$TransactionInvalid ex public int hashCode() @org.jetbrains.annotations.NotNull public String toString() ## -public static final class net.corda.core.flows.NotaryError$WrongNotary extends net.corda.core.flows.NotaryError +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.flows.NotaryError$WrongNotary extends net.corda.core.flows.NotaryError public static final net.corda.core.flows.NotaryError$WrongNotary INSTANCE ## -public final class net.corda.core.flows.NotaryException extends net.corda.core.flows.FlowException +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.flows.NotaryException extends net.corda.core.flows.FlowException public (net.corda.core.flows.NotaryError) @org.jetbrains.annotations.NotNull public final net.corda.core.flows.NotaryError getError() ## public final class net.corda.core.flows.NotaryFlow extends java.lang.Object public () ## -public static class net.corda.core.flows.NotaryFlow$Client extends net.corda.core.flows.FlowLogic +@net.corda.core.flows.InitiatingFlow public static class net.corda.core.flows.NotaryFlow$Client extends net.corda.core.flows.FlowLogic public (net.corda.core.transactions.SignedTransaction) public (net.corda.core.transactions.SignedTransaction, net.corda.core.utilities.ProgressTracker) @co.paralleluniverse.fibers.Suspendable @org.jetbrains.annotations.NotNull public List call() @@ -1158,10 +1160,10 @@ public static class net.corda.core.flows.NotaryFlow$Client extends net.corda.cor public static final class net.corda.core.flows.NotaryFlow$Client$Companion extends java.lang.Object @org.jetbrains.annotations.NotNull public final net.corda.core.utilities.ProgressTracker tracker() ## -public static final class net.corda.core.flows.NotaryFlow$Client$Companion$REQUESTING extends net.corda.core.utilities.ProgressTracker$Step +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.flows.NotaryFlow$Client$Companion$REQUESTING extends net.corda.core.utilities.ProgressTracker$Step public static final net.corda.core.flows.NotaryFlow$Client$Companion$REQUESTING INSTANCE ## -public static final class net.corda.core.flows.NotaryFlow$Client$Companion$VALIDATING extends net.corda.core.utilities.ProgressTracker$Step +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.flows.NotaryFlow$Client$Companion$VALIDATING extends net.corda.core.utilities.ProgressTracker$Step public static final net.corda.core.flows.NotaryFlow$Client$Companion$VALIDATING INSTANCE ## public abstract static class net.corda.core.flows.NotaryFlow$Service extends net.corda.core.flows.FlowLogic @@ -1201,13 +1203,13 @@ public abstract class net.corda.core.flows.SignTransactionFlow extends net.corda public static final class net.corda.core.flows.SignTransactionFlow$Companion extends java.lang.Object @kotlin.jvm.JvmStatic @org.jetbrains.annotations.NotNull public final net.corda.core.utilities.ProgressTracker tracker() ## -public static final class net.corda.core.flows.SignTransactionFlow$Companion$RECEIVING extends net.corda.core.utilities.ProgressTracker$Step +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.flows.SignTransactionFlow$Companion$RECEIVING extends net.corda.core.utilities.ProgressTracker$Step public static final net.corda.core.flows.SignTransactionFlow$Companion$RECEIVING INSTANCE ## -public static final class net.corda.core.flows.SignTransactionFlow$Companion$SIGNING extends net.corda.core.utilities.ProgressTracker$Step +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.flows.SignTransactionFlow$Companion$SIGNING extends net.corda.core.utilities.ProgressTracker$Step public static final net.corda.core.flows.SignTransactionFlow$Companion$SIGNING INSTANCE ## -public static final class net.corda.core.flows.SignTransactionFlow$Companion$VERIFYING extends net.corda.core.utilities.ProgressTracker$Step +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.flows.SignTransactionFlow$Companion$VERIFYING extends net.corda.core.utilities.ProgressTracker$Step public static final net.corda.core.flows.SignTransactionFlow$Companion$VERIFYING INSTANCE ## public final class net.corda.core.flows.StackFrameDataToken extends java.lang.Object @@ -1221,7 +1223,7 @@ public final class net.corda.core.flows.StackFrameDataToken extends java.lang.Ob ## public @interface net.corda.core.flows.StartableByRPC ## -public final class net.corda.core.flows.StateMachineRunId extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.flows.StateMachineRunId extends java.lang.Object public (UUID) @org.jetbrains.annotations.NotNull public final UUID component1() @org.jetbrains.annotations.NotNull public final net.corda.core.flows.StateMachineRunId copy(UUID) @@ -1234,7 +1236,7 @@ public final class net.corda.core.flows.StateMachineRunId extends java.lang.Obje public static final class net.corda.core.flows.StateMachineRunId$Companion extends java.lang.Object @org.jetbrains.annotations.NotNull public final net.corda.core.flows.StateMachineRunId createRandom() ## -public class net.corda.core.flows.StateReplacementException extends net.corda.core.flows.FlowException +@net.corda.core.serialization.CordaSerializable public class net.corda.core.flows.StateReplacementException extends net.corda.core.flows.FlowException public () public (String) public (String, Throwable) @@ -1254,11 +1256,11 @@ public final class net.corda.core.flows.TransactionParts extends java.lang.Objec public int hashCode() public String toString() ## -public final class net.corda.core.flows.UnexpectedFlowEndException extends net.corda.core.CordaRuntimeException +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.flows.UnexpectedFlowEndException extends net.corda.core.CordaRuntimeException public (String) public (String, Throwable) ## -public abstract class net.corda.core.identity.AbstractParty extends java.lang.Object +@net.corda.core.serialization.CordaSerializable @net.corda.core.DoNotImplement public abstract class net.corda.core.identity.AbstractParty extends java.lang.Object public (java.security.PublicKey) public boolean equals(Object) @org.jetbrains.annotations.NotNull public final java.security.PublicKey getOwningKey() @@ -1266,13 +1268,13 @@ public abstract class net.corda.core.identity.AbstractParty extends java.lang.Ob @org.jetbrains.annotations.Nullable public abstract net.corda.core.identity.CordaX500Name nameOrNull() @org.jetbrains.annotations.NotNull public abstract net.corda.core.contracts.PartyAndReference ref(net.corda.core.utilities.OpaqueBytes) ## -public final class net.corda.core.identity.AnonymousParty extends net.corda.core.identity.AbstractParty +@net.corda.core.serialization.CordaSerializable @net.corda.core.DoNotImplement public final class net.corda.core.identity.AnonymousParty extends net.corda.core.identity.AbstractParty public (java.security.PublicKey) @org.jetbrains.annotations.Nullable public net.corda.core.identity.CordaX500Name nameOrNull() @org.jetbrains.annotations.NotNull public net.corda.core.contracts.PartyAndReference ref(net.corda.core.utilities.OpaqueBytes) @org.jetbrains.annotations.NotNull public String toString() ## -public final class net.corda.core.identity.CordaX500Name extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.identity.CordaX500Name extends java.lang.Object public (String, String, String) public (String, String, String, String) public (String, String, String, String, String, String) @@ -1315,7 +1317,7 @@ public final class net.corda.core.identity.IdentityUtils extends java.lang.Objec @org.jetbrains.annotations.NotNull public static final Map groupPublicKeysByWellKnownParty(net.corda.core.node.ServiceHub, Collection) @org.jetbrains.annotations.NotNull public static final Map groupPublicKeysByWellKnownParty(net.corda.core.node.ServiceHub, Collection, boolean) ## -public final class net.corda.core.identity.Party extends net.corda.core.identity.AbstractParty +@net.corda.core.serialization.CordaSerializable @net.corda.core.DoNotImplement public final class net.corda.core.identity.Party extends net.corda.core.identity.AbstractParty public (java.security.cert.X509Certificate) public (net.corda.core.identity.CordaX500Name, java.security.PublicKey) @org.jetbrains.annotations.NotNull public final net.corda.core.identity.AnonymousParty anonymise() @@ -1324,7 +1326,7 @@ public final class net.corda.core.identity.Party extends net.corda.core.identity @org.jetbrains.annotations.NotNull public net.corda.core.contracts.PartyAndReference ref(net.corda.core.utilities.OpaqueBytes) @org.jetbrains.annotations.NotNull public String toString() ## -public final class net.corda.core.identity.PartyAndCertificate extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.identity.PartyAndCertificate extends java.lang.Object public (java.security.cert.CertPath) @org.jetbrains.annotations.NotNull public final net.corda.core.identity.Party component1() @org.jetbrains.annotations.NotNull public final java.security.cert.X509Certificate component2() @@ -1338,9 +1340,9 @@ public final class net.corda.core.identity.PartyAndCertificate extends java.lang @org.jetbrains.annotations.NotNull public String toString() @org.jetbrains.annotations.NotNull public final java.security.cert.PKIXCertPathValidatorResult verify(java.security.cert.TrustAnchor) ## -public interface net.corda.core.messaging.AllPossibleRecipients extends net.corda.core.messaging.MessageRecipients +@net.corda.core.serialization.CordaSerializable public interface net.corda.core.messaging.AllPossibleRecipients extends net.corda.core.messaging.MessageRecipients ## -public interface net.corda.core.messaging.CordaRPCOps extends net.corda.core.messaging.RPCOps +@net.corda.core.DoNotImplement public interface net.corda.core.messaging.CordaRPCOps extends net.corda.core.messaging.RPCOps public abstract void addVaultTransactionNote(net.corda.core.crypto.SecureHash, String) public abstract boolean attachmentExists(net.corda.core.crypto.SecureHash) public abstract void clearNetworkMapCache() @@ -1380,7 +1382,7 @@ public interface net.corda.core.messaging.CordaRPCOps extends net.corda.core.mes ## public final class net.corda.core.messaging.CordaRPCOpsKt extends java.lang.Object ## -public final class net.corda.core.messaging.DataFeed extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.messaging.DataFeed extends java.lang.Object public (Object, rx.Observable) public final Object component1() @org.jetbrains.annotations.NotNull public final rx.Observable component2() @@ -1391,12 +1393,12 @@ public final class net.corda.core.messaging.DataFeed extends java.lang.Object public int hashCode() public String toString() ## -public interface net.corda.core.messaging.FlowHandle extends java.lang.AutoCloseable +@net.corda.core.DoNotImplement public interface net.corda.core.messaging.FlowHandle extends java.lang.AutoCloseable public abstract void close() @org.jetbrains.annotations.NotNull public abstract net.corda.core.flows.StateMachineRunId getId() @org.jetbrains.annotations.NotNull public abstract net.corda.core.concurrent.CordaFuture getReturnValue() ## -public final class net.corda.core.messaging.FlowHandleImpl extends java.lang.Object implements net.corda.core.messaging.FlowHandle +@net.corda.core.serialization.CordaSerializable @net.corda.core.DoNotImplement public final class net.corda.core.messaging.FlowHandleImpl extends java.lang.Object implements net.corda.core.messaging.FlowHandle public (net.corda.core.flows.StateMachineRunId, net.corda.core.concurrent.CordaFuture) public void close() @org.jetbrains.annotations.NotNull public final net.corda.core.flows.StateMachineRunId component1() @@ -1408,11 +1410,11 @@ public final class net.corda.core.messaging.FlowHandleImpl extends java.lang.Obj public int hashCode() public String toString() ## -public interface net.corda.core.messaging.FlowProgressHandle extends net.corda.core.messaging.FlowHandle +@net.corda.core.DoNotImplement public interface net.corda.core.messaging.FlowProgressHandle extends net.corda.core.messaging.FlowHandle public abstract void close() @org.jetbrains.annotations.NotNull public abstract rx.Observable getProgress() ## -public final class net.corda.core.messaging.FlowProgressHandleImpl extends java.lang.Object implements net.corda.core.messaging.FlowProgressHandle +@net.corda.core.serialization.CordaSerializable @net.corda.core.DoNotImplement public final class net.corda.core.messaging.FlowProgressHandleImpl extends java.lang.Object implements net.corda.core.messaging.FlowProgressHandle public (net.corda.core.flows.StateMachineRunId, net.corda.core.concurrent.CordaFuture, rx.Observable) public void close() @org.jetbrains.annotations.NotNull public final net.corda.core.flows.StateMachineRunId component1() @@ -1426,18 +1428,18 @@ public final class net.corda.core.messaging.FlowProgressHandleImpl extends java. public int hashCode() public String toString() ## -public interface net.corda.core.messaging.MessageRecipientGroup extends net.corda.core.messaging.MessageRecipients +@net.corda.core.serialization.CordaSerializable public interface net.corda.core.messaging.MessageRecipientGroup extends net.corda.core.messaging.MessageRecipients ## -public interface net.corda.core.messaging.MessageRecipients +@net.corda.core.serialization.CordaSerializable public interface net.corda.core.messaging.MessageRecipients ## -public interface net.corda.core.messaging.RPCOps +@net.corda.core.DoNotImplement public interface net.corda.core.messaging.RPCOps public abstract int getProtocolVersion() ## public @interface net.corda.core.messaging.RPCReturnsObservables ## -public interface net.corda.core.messaging.SingleMessageRecipient extends net.corda.core.messaging.MessageRecipients +@net.corda.core.serialization.CordaSerializable public interface net.corda.core.messaging.SingleMessageRecipient extends net.corda.core.messaging.MessageRecipients ## -public final class net.corda.core.messaging.StateMachineInfo extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.messaging.StateMachineInfo extends java.lang.Object public (net.corda.core.flows.StateMachineRunId, String, net.corda.core.flows.FlowInitiator, net.corda.core.messaging.DataFeed) @org.jetbrains.annotations.NotNull public final net.corda.core.flows.StateMachineRunId component1() @org.jetbrains.annotations.NotNull public final String component2() @@ -1452,7 +1454,7 @@ public final class net.corda.core.messaging.StateMachineInfo extends java.lang.O public int hashCode() @org.jetbrains.annotations.NotNull public String toString() ## -public final class net.corda.core.messaging.StateMachineTransactionMapping extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.messaging.StateMachineTransactionMapping extends java.lang.Object public (net.corda.core.flows.StateMachineRunId, net.corda.core.crypto.SecureHash) @org.jetbrains.annotations.NotNull public final net.corda.core.flows.StateMachineRunId component1() @org.jetbrains.annotations.NotNull public final net.corda.core.crypto.SecureHash component2() @@ -1463,10 +1465,10 @@ public final class net.corda.core.messaging.StateMachineTransactionMapping exten public int hashCode() public String toString() ## -public abstract class net.corda.core.messaging.StateMachineUpdate extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public abstract class net.corda.core.messaging.StateMachineUpdate extends java.lang.Object @org.jetbrains.annotations.NotNull public abstract net.corda.core.flows.StateMachineRunId getId() ## -public static final class net.corda.core.messaging.StateMachineUpdate$Added extends net.corda.core.messaging.StateMachineUpdate +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.messaging.StateMachineUpdate$Added extends net.corda.core.messaging.StateMachineUpdate public (net.corda.core.messaging.StateMachineInfo) @org.jetbrains.annotations.NotNull public final net.corda.core.messaging.StateMachineInfo component1() @org.jetbrains.annotations.NotNull public final net.corda.core.messaging.StateMachineUpdate$Added copy(net.corda.core.messaging.StateMachineInfo) @@ -1476,7 +1478,7 @@ public static final class net.corda.core.messaging.StateMachineUpdate$Added exte public int hashCode() public String toString() ## -public static final class net.corda.core.messaging.StateMachineUpdate$Removed extends net.corda.core.messaging.StateMachineUpdate +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.messaging.StateMachineUpdate$Removed extends net.corda.core.messaging.StateMachineUpdate public (net.corda.core.flows.StateMachineRunId, net.corda.core.utilities.Try) @org.jetbrains.annotations.NotNull public final net.corda.core.flows.StateMachineRunId component1() @org.jetbrains.annotations.NotNull public final net.corda.core.utilities.Try component2() @@ -1487,11 +1489,11 @@ public static final class net.corda.core.messaging.StateMachineUpdate$Removed ex public int hashCode() public String toString() ## -public interface net.corda.core.node.AppServiceHub extends net.corda.core.node.ServiceHub +@net.corda.core.DoNotImplement public interface net.corda.core.node.AppServiceHub extends net.corda.core.node.ServiceHub @org.jetbrains.annotations.NotNull public abstract net.corda.core.messaging.FlowHandle startFlow(net.corda.core.flows.FlowLogic) @org.jetbrains.annotations.NotNull public abstract net.corda.core.messaging.FlowProgressHandle startTrackedFlow(net.corda.core.flows.FlowLogic) ## -public final class net.corda.core.node.NodeInfo extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.node.NodeInfo extends java.lang.Object public (List, List, int, long) @org.jetbrains.annotations.NotNull public final List component1() @org.jetbrains.annotations.NotNull public final List component2() @@ -1508,7 +1510,7 @@ public final class net.corda.core.node.NodeInfo extends java.lang.Object public final boolean isLegalIdentity(net.corda.core.identity.Party) public String toString() ## -public interface net.corda.core.node.ServiceHub extends net.corda.core.node.ServicesForResolution +@net.corda.core.DoNotImplement public interface net.corda.core.node.ServiceHub extends net.corda.core.node.ServicesForResolution @org.jetbrains.annotations.NotNull public abstract net.corda.core.transactions.SignedTransaction addSignature(net.corda.core.transactions.SignedTransaction) @org.jetbrains.annotations.NotNull public abstract net.corda.core.transactions.SignedTransaction addSignature(net.corda.core.transactions.SignedTransaction, java.security.PublicKey) @org.jetbrains.annotations.NotNull public abstract net.corda.core.serialization.SerializeAsToken cordaService(Class) @@ -1532,28 +1534,28 @@ public interface net.corda.core.node.ServiceHub extends net.corda.core.node.Serv @org.jetbrains.annotations.NotNull public abstract net.corda.core.transactions.SignedTransaction signInitialTransaction(net.corda.core.transactions.TransactionBuilder, java.security.PublicKey) @org.jetbrains.annotations.NotNull public abstract net.corda.core.contracts.StateAndRef toStateAndRef(net.corda.core.contracts.StateRef) ## -public interface net.corda.core.node.ServicesForResolution extends net.corda.core.node.StateLoader +@net.corda.core.DoNotImplement public interface net.corda.core.node.ServicesForResolution extends net.corda.core.node.StateLoader @org.jetbrains.annotations.NotNull public abstract net.corda.core.node.services.AttachmentStorage getAttachments() @org.jetbrains.annotations.NotNull public abstract net.corda.core.cordapp.CordappProvider getCordappProvider() @org.jetbrains.annotations.NotNull public abstract net.corda.core.node.services.IdentityService getIdentityService() ## -public interface net.corda.core.node.StateLoader +@net.corda.core.DoNotImplement public interface net.corda.core.node.StateLoader @org.jetbrains.annotations.NotNull public abstract net.corda.core.contracts.TransactionState loadState(net.corda.core.contracts.StateRef) ## -public interface net.corda.core.node.services.AttachmentStorage +@net.corda.core.DoNotImplement public interface net.corda.core.node.services.AttachmentStorage @org.jetbrains.annotations.NotNull public abstract net.corda.core.crypto.SecureHash importAttachment(java.io.InputStream) @org.jetbrains.annotations.Nullable public abstract net.corda.core.contracts.Attachment openAttachment(net.corda.core.crypto.SecureHash) ## public final class net.corda.core.node.services.AttachmentStorageKt extends java.lang.Object ## -public interface net.corda.core.node.services.ContractUpgradeService +@net.corda.core.DoNotImplement public interface net.corda.core.node.services.ContractUpgradeService @org.jetbrains.annotations.Nullable public abstract String getAuthorisedContractUpgrade(net.corda.core.contracts.StateRef) public abstract void removeAuthorisedContractUpgrade(net.corda.core.contracts.StateRef) public abstract void storeAuthorisedContractUpgrade(net.corda.core.contracts.StateRef, Class) ## public @interface net.corda.core.node.services.CordaService ## -public interface net.corda.core.node.services.IdentityService +@net.corda.core.DoNotImplement public interface net.corda.core.node.services.IdentityService public abstract void assertOwnership(net.corda.core.identity.Party, net.corda.core.identity.AnonymousParty) @org.jetbrains.annotations.Nullable public abstract net.corda.core.identity.PartyAndCertificate certificateFromKey(java.security.PublicKey) @org.jetbrains.annotations.NotNull public abstract Iterable getAllIdentities() @@ -1568,7 +1570,7 @@ public interface net.corda.core.node.services.IdentityService @org.jetbrains.annotations.Nullable public abstract net.corda.core.identity.Party wellKnownPartyFromAnonymous(net.corda.core.identity.AbstractParty) @org.jetbrains.annotations.Nullable public abstract net.corda.core.identity.Party wellKnownPartyFromX500Name(net.corda.core.identity.CordaX500Name) ## -public interface net.corda.core.node.services.KeyManagementService +@net.corda.core.DoNotImplement public interface net.corda.core.node.services.KeyManagementService @org.jetbrains.annotations.NotNull public abstract Iterable filterMyKeys(Iterable) @co.paralleluniverse.fibers.Suspendable @org.jetbrains.annotations.NotNull public abstract java.security.PublicKey freshKey() @co.paralleluniverse.fibers.Suspendable @org.jetbrains.annotations.NotNull public abstract net.corda.core.identity.PartyAndCertificate freshKeyAndCert(net.corda.core.identity.PartyAndCertificate, boolean) @@ -1576,13 +1578,13 @@ public interface net.corda.core.node.services.KeyManagementService @co.paralleluniverse.fibers.Suspendable @org.jetbrains.annotations.NotNull public abstract net.corda.core.crypto.TransactionSignature sign(net.corda.core.crypto.SignableData, java.security.PublicKey) @co.paralleluniverse.fibers.Suspendable @org.jetbrains.annotations.NotNull public abstract net.corda.core.crypto.DigitalSignature$WithKey sign(byte[], java.security.PublicKey) ## -public interface net.corda.core.node.services.NetworkMapCache extends net.corda.core.node.services.NetworkMapCacheBase +@net.corda.core.DoNotImplement public interface net.corda.core.node.services.NetworkMapCache extends net.corda.core.node.services.NetworkMapCacheBase @org.jetbrains.annotations.Nullable public abstract net.corda.core.node.NodeInfo getNodeByLegalIdentity(net.corda.core.identity.AbstractParty) ## -public abstract static class net.corda.core.node.services.NetworkMapCache$MapChange extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public abstract static class net.corda.core.node.services.NetworkMapCache$MapChange extends java.lang.Object @org.jetbrains.annotations.NotNull public abstract net.corda.core.node.NodeInfo getNode() ## -public static final class net.corda.core.node.services.NetworkMapCache$MapChange$Added extends net.corda.core.node.services.NetworkMapCache$MapChange +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.node.services.NetworkMapCache$MapChange$Added extends net.corda.core.node.services.NetworkMapCache$MapChange public (net.corda.core.node.NodeInfo) @org.jetbrains.annotations.NotNull public final net.corda.core.node.NodeInfo component1() @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.NetworkMapCache$MapChange$Added copy(net.corda.core.node.NodeInfo) @@ -1591,7 +1593,7 @@ public static final class net.corda.core.node.services.NetworkMapCache$MapChange public int hashCode() public String toString() ## -public static final class net.corda.core.node.services.NetworkMapCache$MapChange$Modified extends net.corda.core.node.services.NetworkMapCache$MapChange +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.node.services.NetworkMapCache$MapChange$Modified extends net.corda.core.node.services.NetworkMapCache$MapChange public (net.corda.core.node.NodeInfo, net.corda.core.node.NodeInfo) @org.jetbrains.annotations.NotNull public final net.corda.core.node.NodeInfo component1() @org.jetbrains.annotations.NotNull public final net.corda.core.node.NodeInfo component2() @@ -1602,7 +1604,7 @@ public static final class net.corda.core.node.services.NetworkMapCache$MapChange public int hashCode() public String toString() ## -public static final class net.corda.core.node.services.NetworkMapCache$MapChange$Removed extends net.corda.core.node.services.NetworkMapCache$MapChange +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.node.services.NetworkMapCache$MapChange$Removed extends net.corda.core.node.services.NetworkMapCache$MapChange public (net.corda.core.node.NodeInfo) @org.jetbrains.annotations.NotNull public final net.corda.core.node.NodeInfo component1() @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.NetworkMapCache$MapChange$Removed copy(net.corda.core.node.NodeInfo) @@ -1611,7 +1613,7 @@ public static final class net.corda.core.node.services.NetworkMapCache$MapChange public int hashCode() public String toString() ## -public interface net.corda.core.node.services.NetworkMapCacheBase +@net.corda.core.DoNotImplement public interface net.corda.core.node.services.NetworkMapCacheBase public abstract void clearNetworkMapCache() @org.jetbrains.annotations.NotNull public abstract List getAllNodes() @org.jetbrains.annotations.NotNull public abstract rx.Observable getChanged() @@ -1629,7 +1631,7 @@ public interface net.corda.core.node.services.NetworkMapCacheBase public abstract boolean isValidatingNotary(net.corda.core.identity.Party) @org.jetbrains.annotations.NotNull public abstract net.corda.core.messaging.DataFeed track() ## -public abstract class net.corda.core.node.services.NotaryService extends net.corda.core.serialization.SingletonSerializeAsToken +@net.corda.core.serialization.CordaSerializable public abstract class net.corda.core.node.services.NotaryService extends net.corda.core.serialization.SingletonSerializeAsToken public () @org.jetbrains.annotations.NotNull public abstract net.corda.core.flows.FlowLogic createServiceFlow(net.corda.core.flows.FlowSession) @org.jetbrains.annotations.NotNull public abstract java.security.PublicKey getNotaryIdentityKey() @@ -1660,7 +1662,7 @@ public static final class net.corda.core.node.services.PartyInfo$SingleNode exte public int hashCode() public String toString() ## -public final class net.corda.core.node.services.StatesNotAvailableException extends net.corda.core.flows.FlowException +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.node.services.StatesNotAvailableException extends net.corda.core.flows.FlowException public (String, Throwable) @org.jetbrains.annotations.Nullable public Throwable getCause() @org.jetbrains.annotations.Nullable public String getMessage() @@ -1672,15 +1674,15 @@ public final class net.corda.core.node.services.TimeWindowChecker extends java.l @org.jetbrains.annotations.NotNull public final java.time.Clock getClock() public final boolean isValid(net.corda.core.contracts.TimeWindow) ## -public interface net.corda.core.node.services.TransactionStorage +@net.corda.core.DoNotImplement public interface net.corda.core.node.services.TransactionStorage @org.jetbrains.annotations.Nullable public abstract net.corda.core.transactions.SignedTransaction getTransaction(net.corda.core.crypto.SecureHash) @org.jetbrains.annotations.NotNull public abstract rx.Observable getUpdates() @org.jetbrains.annotations.NotNull public abstract net.corda.core.messaging.DataFeed track() ## -public interface net.corda.core.node.services.TransactionVerifierService +@net.corda.core.DoNotImplement public interface net.corda.core.node.services.TransactionVerifierService @org.jetbrains.annotations.NotNull public abstract net.corda.core.concurrent.CordaFuture verify(net.corda.core.transactions.LedgerTransaction) ## -public abstract class net.corda.core.node.services.TrustedAuthorityNotaryService extends net.corda.core.node.services.NotaryService +@net.corda.core.serialization.CordaSerializable public abstract class net.corda.core.node.services.TrustedAuthorityNotaryService extends net.corda.core.node.services.NotaryService public () public final void commitInputStates(List, net.corda.core.crypto.SecureHash, net.corda.core.identity.Party) @org.jetbrains.annotations.NotNull protected org.slf4j.Logger getLog() @@ -1690,14 +1692,14 @@ public abstract class net.corda.core.node.services.TrustedAuthorityNotaryService @org.jetbrains.annotations.NotNull public final net.corda.core.crypto.DigitalSignature$WithKey sign(byte[]) public final void validateTimeWindow(net.corda.core.contracts.TimeWindow) ## -public final class net.corda.core.node.services.UniquenessException extends net.corda.core.CordaException +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.node.services.UniquenessException extends net.corda.core.CordaException public (net.corda.core.node.services.UniquenessProvider$Conflict) @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.UniquenessProvider$Conflict getError() ## public interface net.corda.core.node.services.UniquenessProvider public abstract void commit(List, net.corda.core.crypto.SecureHash, net.corda.core.identity.Party) ## -public static final class net.corda.core.node.services.UniquenessProvider$Conflict extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.node.services.UniquenessProvider$Conflict extends java.lang.Object public (Map) @org.jetbrains.annotations.NotNull public final Map component1() @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.UniquenessProvider$Conflict copy(Map) @@ -1706,7 +1708,7 @@ public static final class net.corda.core.node.services.UniquenessProvider$Confli public int hashCode() public String toString() ## -public static final class net.corda.core.node.services.UniquenessProvider$ConsumingTx extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.node.services.UniquenessProvider$ConsumingTx extends java.lang.Object public (net.corda.core.crypto.SecureHash, int, net.corda.core.identity.Party) @org.jetbrains.annotations.NotNull public final net.corda.core.crypto.SecureHash component1() public final int component2() @@ -1719,10 +1721,10 @@ public static final class net.corda.core.node.services.UniquenessProvider$Consum public int hashCode() public String toString() ## -public final class net.corda.core.node.services.UnknownAnonymousPartyException extends net.corda.core.CordaException +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.node.services.UnknownAnonymousPartyException extends net.corda.core.CordaException public (String) ## -public final class net.corda.core.node.services.Vault extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.node.services.Vault extends java.lang.Object public (Iterable) @org.jetbrains.annotations.NotNull public final Iterable getStates() public static final net.corda.core.node.services.Vault$Companion Companion @@ -1731,7 +1733,7 @@ public static final class net.corda.core.node.services.Vault$Companion extends j @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.Vault$Update getNoNotaryUpdate() @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.Vault$Update getNoUpdate() ## -public static final class net.corda.core.node.services.Vault$Page extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.node.services.Vault$Page extends java.lang.Object public (List, List, long, net.corda.core.node.services.Vault$StateStatus, List) @org.jetbrains.annotations.NotNull public final List component1() @org.jetbrains.annotations.NotNull public final List component2() @@ -1748,7 +1750,7 @@ public static final class net.corda.core.node.services.Vault$Page extends java.l public int hashCode() public String toString() ## -public static final class net.corda.core.node.services.Vault$StateMetadata extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.node.services.Vault$StateMetadata extends java.lang.Object public (net.corda.core.contracts.StateRef, String, java.time.Instant, java.time.Instant, net.corda.core.node.services.Vault$StateStatus, net.corda.core.identity.AbstractParty, String, java.time.Instant) @org.jetbrains.annotations.NotNull public final net.corda.core.contracts.StateRef component1() @org.jetbrains.annotations.NotNull public final String component2() @@ -1771,12 +1773,12 @@ public static final class net.corda.core.node.services.Vault$StateMetadata exten public int hashCode() public String toString() ## -public static final class net.corda.core.node.services.Vault$StateStatus extends java.lang.Enum +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.node.services.Vault$StateStatus extends java.lang.Enum protected (String, int) public static net.corda.core.node.services.Vault$StateStatus valueOf(String) public static net.corda.core.node.services.Vault$StateStatus[] values() ## -public static final class net.corda.core.node.services.Vault$Update extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.node.services.Vault$Update extends java.lang.Object public (Set, Set, UUID, net.corda.core.node.services.Vault$UpdateType) @org.jetbrains.annotations.NotNull public final Set component1() @org.jetbrains.annotations.NotNull public final Set component2() @@ -1794,15 +1796,15 @@ public static final class net.corda.core.node.services.Vault$Update extends java @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.Vault$Update plus(net.corda.core.node.services.Vault$Update) @org.jetbrains.annotations.NotNull public String toString() ## -public static final class net.corda.core.node.services.Vault$UpdateType extends java.lang.Enum +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.node.services.Vault$UpdateType extends java.lang.Enum protected (String, int) public static net.corda.core.node.services.Vault$UpdateType valueOf(String) public static net.corda.core.node.services.Vault$UpdateType[] values() ## -public final class net.corda.core.node.services.VaultQueryException extends net.corda.core.flows.FlowException +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.node.services.VaultQueryException extends net.corda.core.flows.FlowException public (String) ## -public interface net.corda.core.node.services.VaultService +@net.corda.core.DoNotImplement public interface net.corda.core.node.services.VaultService @org.jetbrains.annotations.NotNull public abstract net.corda.core.node.services.Vault$Page _queryBy(net.corda.core.node.services.vault.QueryCriteria, net.corda.core.node.services.vault.PageSpecification, net.corda.core.node.services.vault.Sort, Class) @org.jetbrains.annotations.NotNull public abstract net.corda.core.messaging.DataFeed _trackBy(net.corda.core.node.services.vault.QueryCriteria, net.corda.core.node.services.vault.PageSpecification, net.corda.core.node.services.vault.Sort, Class) public abstract void addNoteToTransaction(net.corda.core.crypto.SecureHash, String) @@ -1826,22 +1828,22 @@ public interface net.corda.core.node.services.VaultService ## public final class net.corda.core.node.services.VaultServiceKt extends java.lang.Object ## -public final class net.corda.core.node.services.vault.AggregateFunctionType extends java.lang.Enum +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.node.services.vault.AggregateFunctionType extends java.lang.Enum protected (String, int) public static net.corda.core.node.services.vault.AggregateFunctionType valueOf(String) public static net.corda.core.node.services.vault.AggregateFunctionType[] values() ## -public final class net.corda.core.node.services.vault.BinaryComparisonOperator extends java.lang.Enum implements net.corda.core.node.services.vault.Operator +@net.corda.core.serialization.CordaSerializable @net.corda.core.DoNotImplement public final class net.corda.core.node.services.vault.BinaryComparisonOperator extends java.lang.Enum implements net.corda.core.node.services.vault.Operator protected (String, int) public static net.corda.core.node.services.vault.BinaryComparisonOperator valueOf(String) public static net.corda.core.node.services.vault.BinaryComparisonOperator[] values() ## -public final class net.corda.core.node.services.vault.BinaryLogicalOperator extends java.lang.Enum implements net.corda.core.node.services.vault.Operator +@net.corda.core.serialization.CordaSerializable @net.corda.core.DoNotImplement public final class net.corda.core.node.services.vault.BinaryLogicalOperator extends java.lang.Enum implements net.corda.core.node.services.vault.Operator protected (String, int) public static net.corda.core.node.services.vault.BinaryLogicalOperator valueOf(String) public static net.corda.core.node.services.vault.BinaryLogicalOperator[] values() ## -public final class net.corda.core.node.services.vault.Builder extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.node.services.vault.Builder extends java.lang.Object @kotlin.jvm.JvmStatic @org.jetbrains.annotations.NotNull public static final net.corda.core.node.services.vault.CriteriaExpression$AggregateFunctionExpression avg(reflect.Field) @kotlin.jvm.JvmStatic @org.jetbrains.annotations.NotNull public static final net.corda.core.node.services.vault.CriteriaExpression$AggregateFunctionExpression avg(reflect.Field, List) @kotlin.jvm.JvmStatic @org.jetbrains.annotations.NotNull public static final net.corda.core.node.services.vault.CriteriaExpression$AggregateFunctionExpression avg(reflect.Field, List, net.corda.core.node.services.vault.Sort$Direction) @@ -1904,21 +1906,21 @@ public final class net.corda.core.node.services.vault.Builder extends java.lang. @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.vault.CriteriaExpression$AggregateFunctionExpression sum(kotlin.reflect.KProperty1, List, net.corda.core.node.services.vault.Sort$Direction) public static final net.corda.core.node.services.vault.Builder INSTANCE ## -public final class net.corda.core.node.services.vault.CollectionOperator extends java.lang.Enum implements net.corda.core.node.services.vault.Operator +@net.corda.core.serialization.CordaSerializable @net.corda.core.DoNotImplement public final class net.corda.core.node.services.vault.CollectionOperator extends java.lang.Enum implements net.corda.core.node.services.vault.Operator protected (String, int) public static net.corda.core.node.services.vault.CollectionOperator valueOf(String) public static net.corda.core.node.services.vault.CollectionOperator[] values() ## -public final class net.corda.core.node.services.vault.Column extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.node.services.vault.Column extends java.lang.Object public (String, Class) public (reflect.Field) public (kotlin.reflect.KProperty1) @org.jetbrains.annotations.NotNull public final Class getDeclaringClass() @org.jetbrains.annotations.NotNull public final String getName() ## -public abstract class net.corda.core.node.services.vault.ColumnPredicate extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public abstract class net.corda.core.node.services.vault.ColumnPredicate extends java.lang.Object ## -public static final class net.corda.core.node.services.vault.ColumnPredicate$AggregateFunction extends net.corda.core.node.services.vault.ColumnPredicate +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.node.services.vault.ColumnPredicate$AggregateFunction extends net.corda.core.node.services.vault.ColumnPredicate public (net.corda.core.node.services.vault.AggregateFunctionType) @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.vault.AggregateFunctionType component1() @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.vault.ColumnPredicate$AggregateFunction copy(net.corda.core.node.services.vault.AggregateFunctionType) @@ -1927,7 +1929,7 @@ public static final class net.corda.core.node.services.vault.ColumnPredicate$Agg public int hashCode() public String toString() ## -public static final class net.corda.core.node.services.vault.ColumnPredicate$Between extends net.corda.core.node.services.vault.ColumnPredicate +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.node.services.vault.ColumnPredicate$Between extends net.corda.core.node.services.vault.ColumnPredicate public (Comparable, Comparable) @org.jetbrains.annotations.NotNull public final Comparable component1() @org.jetbrains.annotations.NotNull public final Comparable component2() @@ -1938,7 +1940,7 @@ public static final class net.corda.core.node.services.vault.ColumnPredicate$Bet public int hashCode() public String toString() ## -public static final class net.corda.core.node.services.vault.ColumnPredicate$BinaryComparison extends net.corda.core.node.services.vault.ColumnPredicate +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.node.services.vault.ColumnPredicate$BinaryComparison extends net.corda.core.node.services.vault.ColumnPredicate public (net.corda.core.node.services.vault.BinaryComparisonOperator, Comparable) @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.vault.BinaryComparisonOperator component1() @org.jetbrains.annotations.NotNull public final Comparable component2() @@ -1949,7 +1951,7 @@ public static final class net.corda.core.node.services.vault.ColumnPredicate$Bin public int hashCode() public String toString() ## -public static final class net.corda.core.node.services.vault.ColumnPredicate$CollectionExpression extends net.corda.core.node.services.vault.ColumnPredicate +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.node.services.vault.ColumnPredicate$CollectionExpression extends net.corda.core.node.services.vault.ColumnPredicate public (net.corda.core.node.services.vault.CollectionOperator, Collection) @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.vault.CollectionOperator component1() @org.jetbrains.annotations.NotNull public final Collection component2() @@ -1960,7 +1962,7 @@ public static final class net.corda.core.node.services.vault.ColumnPredicate$Col public int hashCode() public String toString() ## -public static final class net.corda.core.node.services.vault.ColumnPredicate$EqualityComparison extends net.corda.core.node.services.vault.ColumnPredicate +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.node.services.vault.ColumnPredicate$EqualityComparison extends net.corda.core.node.services.vault.ColumnPredicate public (net.corda.core.node.services.vault.EqualityComparisonOperator, Object) @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.vault.EqualityComparisonOperator component1() public final Object component2() @@ -1971,7 +1973,7 @@ public static final class net.corda.core.node.services.vault.ColumnPredicate$Equ public int hashCode() public String toString() ## -public static final class net.corda.core.node.services.vault.ColumnPredicate$Likeness extends net.corda.core.node.services.vault.ColumnPredicate +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.node.services.vault.ColumnPredicate$Likeness extends net.corda.core.node.services.vault.ColumnPredicate public (net.corda.core.node.services.vault.LikenessOperator, String) @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.vault.LikenessOperator component1() @org.jetbrains.annotations.NotNull public final String component2() @@ -1982,7 +1984,7 @@ public static final class net.corda.core.node.services.vault.ColumnPredicate$Lik public int hashCode() public String toString() ## -public static final class net.corda.core.node.services.vault.ColumnPredicate$NullExpression extends net.corda.core.node.services.vault.ColumnPredicate +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.node.services.vault.ColumnPredicate$NullExpression extends net.corda.core.node.services.vault.ColumnPredicate public (net.corda.core.node.services.vault.NullOperator) @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.vault.NullOperator component1() @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.vault.ColumnPredicate$NullExpression copy(net.corda.core.node.services.vault.NullOperator) @@ -1991,9 +1993,9 @@ public static final class net.corda.core.node.services.vault.ColumnPredicate$Nul public int hashCode() public String toString() ## -public abstract class net.corda.core.node.services.vault.CriteriaExpression extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public abstract class net.corda.core.node.services.vault.CriteriaExpression extends java.lang.Object ## -public static final class net.corda.core.node.services.vault.CriteriaExpression$AggregateFunctionExpression extends net.corda.core.node.services.vault.CriteriaExpression +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.node.services.vault.CriteriaExpression$AggregateFunctionExpression extends net.corda.core.node.services.vault.CriteriaExpression public (net.corda.core.node.services.vault.Column, net.corda.core.node.services.vault.ColumnPredicate, List, net.corda.core.node.services.vault.Sort$Direction) @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.vault.Column component1() @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.vault.ColumnPredicate component2() @@ -2008,7 +2010,7 @@ public static final class net.corda.core.node.services.vault.CriteriaExpression$ public int hashCode() public String toString() ## -public static final class net.corda.core.node.services.vault.CriteriaExpression$BinaryLogical extends net.corda.core.node.services.vault.CriteriaExpression +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.node.services.vault.CriteriaExpression$BinaryLogical extends net.corda.core.node.services.vault.CriteriaExpression public (net.corda.core.node.services.vault.CriteriaExpression, net.corda.core.node.services.vault.CriteriaExpression, net.corda.core.node.services.vault.BinaryLogicalOperator) @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.vault.CriteriaExpression component1() @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.vault.CriteriaExpression component2() @@ -2021,7 +2023,7 @@ public static final class net.corda.core.node.services.vault.CriteriaExpression$ public int hashCode() public String toString() ## -public static final class net.corda.core.node.services.vault.CriteriaExpression$ColumnPredicateExpression extends net.corda.core.node.services.vault.CriteriaExpression +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.node.services.vault.CriteriaExpression$ColumnPredicateExpression extends net.corda.core.node.services.vault.CriteriaExpression public (net.corda.core.node.services.vault.Column, net.corda.core.node.services.vault.ColumnPredicate) @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.vault.Column component1() @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.vault.ColumnPredicate component2() @@ -2032,7 +2034,7 @@ public static final class net.corda.core.node.services.vault.CriteriaExpression$ public int hashCode() public String toString() ## -public static final class net.corda.core.node.services.vault.CriteriaExpression$Not extends net.corda.core.node.services.vault.CriteriaExpression +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.node.services.vault.CriteriaExpression$Not extends net.corda.core.node.services.vault.CriteriaExpression public (net.corda.core.node.services.vault.CriteriaExpression) @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.vault.CriteriaExpression component1() @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.vault.CriteriaExpression$Not copy(net.corda.core.node.services.vault.CriteriaExpression) @@ -2041,12 +2043,12 @@ public static final class net.corda.core.node.services.vault.CriteriaExpression$ public int hashCode() public String toString() ## -public final class net.corda.core.node.services.vault.EqualityComparisonOperator extends java.lang.Enum implements net.corda.core.node.services.vault.Operator +@net.corda.core.serialization.CordaSerializable @net.corda.core.DoNotImplement public final class net.corda.core.node.services.vault.EqualityComparisonOperator extends java.lang.Enum implements net.corda.core.node.services.vault.Operator protected (String, int) public static net.corda.core.node.services.vault.EqualityComparisonOperator valueOf(String) public static net.corda.core.node.services.vault.EqualityComparisonOperator[] values() ## -public interface net.corda.core.node.services.vault.IQueryCriteriaParser +@net.corda.core.DoNotImplement public interface net.corda.core.node.services.vault.IQueryCriteriaParser @org.jetbrains.annotations.NotNull public abstract Collection parse(net.corda.core.node.services.vault.QueryCriteria, net.corda.core.node.services.vault.Sort) @org.jetbrains.annotations.NotNull public abstract Collection parseAnd(net.corda.core.node.services.vault.QueryCriteria, net.corda.core.node.services.vault.QueryCriteria) @org.jetbrains.annotations.NotNull public abstract Collection parseCriteria(net.corda.core.node.services.vault.QueryCriteria$CommonQueryCriteria) @@ -2056,19 +2058,19 @@ public interface net.corda.core.node.services.vault.IQueryCriteriaParser @org.jetbrains.annotations.NotNull public abstract Collection parseCriteria(net.corda.core.node.services.vault.QueryCriteria$VaultQueryCriteria) @org.jetbrains.annotations.NotNull public abstract Collection parseOr(net.corda.core.node.services.vault.QueryCriteria, net.corda.core.node.services.vault.QueryCriteria) ## -public final class net.corda.core.node.services.vault.LikenessOperator extends java.lang.Enum implements net.corda.core.node.services.vault.Operator +@net.corda.core.serialization.CordaSerializable @net.corda.core.DoNotImplement public final class net.corda.core.node.services.vault.LikenessOperator extends java.lang.Enum implements net.corda.core.node.services.vault.Operator protected (String, int) public static net.corda.core.node.services.vault.LikenessOperator valueOf(String) public static net.corda.core.node.services.vault.LikenessOperator[] values() ## -public final class net.corda.core.node.services.vault.NullOperator extends java.lang.Enum implements net.corda.core.node.services.vault.Operator +@net.corda.core.serialization.CordaSerializable @net.corda.core.DoNotImplement public final class net.corda.core.node.services.vault.NullOperator extends java.lang.Enum implements net.corda.core.node.services.vault.Operator protected (String, int) public static net.corda.core.node.services.vault.NullOperator valueOf(String) public static net.corda.core.node.services.vault.NullOperator[] values() ## -public interface net.corda.core.node.services.vault.Operator +@net.corda.core.serialization.CordaSerializable @net.corda.core.DoNotImplement public interface net.corda.core.node.services.vault.Operator ## -public final class net.corda.core.node.services.vault.PageSpecification extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.node.services.vault.PageSpecification extends java.lang.Object public () public (int, int) public final int component1() @@ -2081,18 +2083,18 @@ public final class net.corda.core.node.services.vault.PageSpecification extends public final boolean isDefault() public String toString() ## -public abstract class net.corda.core.node.services.vault.QueryCriteria extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public abstract class net.corda.core.node.services.vault.QueryCriteria extends java.lang.Object @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.vault.QueryCriteria and(net.corda.core.node.services.vault.QueryCriteria) @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.vault.QueryCriteria or(net.corda.core.node.services.vault.QueryCriteria) @org.jetbrains.annotations.NotNull public abstract Collection visit(net.corda.core.node.services.vault.IQueryCriteriaParser) ## -public abstract static class net.corda.core.node.services.vault.QueryCriteria$CommonQueryCriteria extends net.corda.core.node.services.vault.QueryCriteria +@net.corda.core.serialization.CordaSerializable public abstract static class net.corda.core.node.services.vault.QueryCriteria$CommonQueryCriteria extends net.corda.core.node.services.vault.QueryCriteria public () @org.jetbrains.annotations.Nullable public abstract Set getContractStateTypes() @org.jetbrains.annotations.NotNull public abstract net.corda.core.node.services.Vault$StateStatus getStatus() @org.jetbrains.annotations.NotNull public Collection visit(net.corda.core.node.services.vault.IQueryCriteriaParser) ## -public static final class net.corda.core.node.services.vault.QueryCriteria$FungibleAssetQueryCriteria extends net.corda.core.node.services.vault.QueryCriteria$CommonQueryCriteria +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.node.services.vault.QueryCriteria$FungibleAssetQueryCriteria extends net.corda.core.node.services.vault.QueryCriteria$CommonQueryCriteria public () public (List) public (List, List) @@ -2121,7 +2123,7 @@ public static final class net.corda.core.node.services.vault.QueryCriteria$Fungi public String toString() @org.jetbrains.annotations.NotNull public Collection visit(net.corda.core.node.services.vault.IQueryCriteriaParser) ## -public static final class net.corda.core.node.services.vault.QueryCriteria$LinearStateQueryCriteria extends net.corda.core.node.services.vault.QueryCriteria$CommonQueryCriteria +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.node.services.vault.QueryCriteria$LinearStateQueryCriteria extends net.corda.core.node.services.vault.QueryCriteria$CommonQueryCriteria public () public (List) public (List, List) @@ -2145,7 +2147,7 @@ public static final class net.corda.core.node.services.vault.QueryCriteria$Linea public String toString() @org.jetbrains.annotations.NotNull public Collection visit(net.corda.core.node.services.vault.IQueryCriteriaParser) ## -public static final class net.corda.core.node.services.vault.QueryCriteria$SoftLockingCondition extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.node.services.vault.QueryCriteria$SoftLockingCondition extends java.lang.Object public (net.corda.core.node.services.vault.QueryCriteria$SoftLockingType, List) @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.vault.QueryCriteria$SoftLockingType component1() @org.jetbrains.annotations.NotNull public final List component2() @@ -2156,12 +2158,12 @@ public static final class net.corda.core.node.services.vault.QueryCriteria$SoftL public int hashCode() public String toString() ## -public static final class net.corda.core.node.services.vault.QueryCriteria$SoftLockingType extends java.lang.Enum +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.node.services.vault.QueryCriteria$SoftLockingType extends java.lang.Enum protected (String, int) public static net.corda.core.node.services.vault.QueryCriteria$SoftLockingType valueOf(String) public static net.corda.core.node.services.vault.QueryCriteria$SoftLockingType[] values() ## -public static final class net.corda.core.node.services.vault.QueryCriteria$TimeCondition extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.node.services.vault.QueryCriteria$TimeCondition extends java.lang.Object public (net.corda.core.node.services.vault.QueryCriteria$TimeInstantType, net.corda.core.node.services.vault.ColumnPredicate) @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.vault.QueryCriteria$TimeInstantType component1() @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.vault.ColumnPredicate component2() @@ -2172,12 +2174,12 @@ public static final class net.corda.core.node.services.vault.QueryCriteria$TimeC public int hashCode() public String toString() ## -public static final class net.corda.core.node.services.vault.QueryCriteria$TimeInstantType extends java.lang.Enum +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.node.services.vault.QueryCriteria$TimeInstantType extends java.lang.Enum protected (String, int) public static net.corda.core.node.services.vault.QueryCriteria$TimeInstantType valueOf(String) public static net.corda.core.node.services.vault.QueryCriteria$TimeInstantType[] values() ## -public static final class net.corda.core.node.services.vault.QueryCriteria$VaultCustomQueryCriteria extends net.corda.core.node.services.vault.QueryCriteria$CommonQueryCriteria +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.node.services.vault.QueryCriteria$VaultCustomQueryCriteria extends net.corda.core.node.services.vault.QueryCriteria$CommonQueryCriteria public (net.corda.core.node.services.vault.CriteriaExpression) public (net.corda.core.node.services.vault.CriteriaExpression, net.corda.core.node.services.Vault$StateStatus) public (net.corda.core.node.services.vault.CriteriaExpression, net.corda.core.node.services.Vault$StateStatus, Set) @@ -2193,7 +2195,7 @@ public static final class net.corda.core.node.services.vault.QueryCriteria$Vault public String toString() @org.jetbrains.annotations.NotNull public Collection visit(net.corda.core.node.services.vault.IQueryCriteriaParser) ## -public static final class net.corda.core.node.services.vault.QueryCriteria$VaultQueryCriteria extends net.corda.core.node.services.vault.QueryCriteria$CommonQueryCriteria +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.node.services.vault.QueryCriteria$VaultQueryCriteria extends net.corda.core.node.services.vault.QueryCriteria$CommonQueryCriteria public () public (net.corda.core.node.services.Vault$StateStatus) public (net.corda.core.node.services.Vault$StateStatus, Set) @@ -2228,7 +2230,7 @@ public final class net.corda.core.node.services.vault.QueryCriteriaUtils extends public static final int DEFAULT_PAGE_SIZE = 200 public static final int MAX_PAGE_SIZE = 2147483647 ## -public final class net.corda.core.node.services.vault.Sort extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.node.services.vault.Sort extends java.lang.Object public (Collection) @org.jetbrains.annotations.NotNull public final Collection component1() @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.vault.Sort copy(Collection) @@ -2237,33 +2239,33 @@ public final class net.corda.core.node.services.vault.Sort extends java.lang.Obj public int hashCode() public String toString() ## -public static interface net.corda.core.node.services.vault.Sort$Attribute +@net.corda.core.serialization.CordaSerializable @net.corda.core.DoNotImplement public static interface net.corda.core.node.services.vault.Sort$Attribute ## -public static final class net.corda.core.node.services.vault.Sort$CommonStateAttribute extends java.lang.Enum implements net.corda.core.node.services.vault.Sort$Attribute +@net.corda.core.serialization.CordaSerializable @net.corda.core.DoNotImplement public static final class net.corda.core.node.services.vault.Sort$CommonStateAttribute extends java.lang.Enum implements net.corda.core.node.services.vault.Sort$Attribute protected (String, int, String, String) @org.jetbrains.annotations.Nullable public final String getAttributeChild() @org.jetbrains.annotations.NotNull public final String getAttributeParent() public static net.corda.core.node.services.vault.Sort$CommonStateAttribute valueOf(String) public static net.corda.core.node.services.vault.Sort$CommonStateAttribute[] values() ## -public static final class net.corda.core.node.services.vault.Sort$Direction extends java.lang.Enum +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.node.services.vault.Sort$Direction extends java.lang.Enum protected (String, int) public static net.corda.core.node.services.vault.Sort$Direction valueOf(String) public static net.corda.core.node.services.vault.Sort$Direction[] values() ## -public static final class net.corda.core.node.services.vault.Sort$FungibleStateAttribute extends java.lang.Enum implements net.corda.core.node.services.vault.Sort$Attribute +@net.corda.core.serialization.CordaSerializable @net.corda.core.DoNotImplement public static final class net.corda.core.node.services.vault.Sort$FungibleStateAttribute extends java.lang.Enum implements net.corda.core.node.services.vault.Sort$Attribute protected (String, int, String) @org.jetbrains.annotations.NotNull public final String getAttributeName() public static net.corda.core.node.services.vault.Sort$FungibleStateAttribute valueOf(String) public static net.corda.core.node.services.vault.Sort$FungibleStateAttribute[] values() ## -public static final class net.corda.core.node.services.vault.Sort$LinearStateAttribute extends java.lang.Enum implements net.corda.core.node.services.vault.Sort$Attribute +@net.corda.core.serialization.CordaSerializable @net.corda.core.DoNotImplement public static final class net.corda.core.node.services.vault.Sort$LinearStateAttribute extends java.lang.Enum implements net.corda.core.node.services.vault.Sort$Attribute protected (String, int, String) @org.jetbrains.annotations.NotNull public final String getAttributeName() public static net.corda.core.node.services.vault.Sort$LinearStateAttribute valueOf(String) public static net.corda.core.node.services.vault.Sort$LinearStateAttribute[] values() ## -public static final class net.corda.core.node.services.vault.Sort$SortColumn extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.node.services.vault.Sort$SortColumn extends java.lang.Object public (net.corda.core.node.services.vault.SortAttribute, net.corda.core.node.services.vault.Sort$Direction) @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.vault.SortAttribute component1() @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.vault.Sort$Direction component2() @@ -2274,15 +2276,15 @@ public static final class net.corda.core.node.services.vault.Sort$SortColumn ext public int hashCode() public String toString() ## -public static final class net.corda.core.node.services.vault.Sort$VaultStateAttribute extends java.lang.Enum implements net.corda.core.node.services.vault.Sort$Attribute +@net.corda.core.serialization.CordaSerializable @net.corda.core.DoNotImplement public static final class net.corda.core.node.services.vault.Sort$VaultStateAttribute extends java.lang.Enum implements net.corda.core.node.services.vault.Sort$Attribute protected (String, int, String) @org.jetbrains.annotations.NotNull public final String getAttributeName() public static net.corda.core.node.services.vault.Sort$VaultStateAttribute valueOf(String) public static net.corda.core.node.services.vault.Sort$VaultStateAttribute[] values() ## -public abstract class net.corda.core.node.services.vault.SortAttribute extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public abstract class net.corda.core.node.services.vault.SortAttribute extends java.lang.Object ## -public static final class net.corda.core.node.services.vault.SortAttribute$Custom extends net.corda.core.node.services.vault.SortAttribute +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.node.services.vault.SortAttribute$Custom extends net.corda.core.node.services.vault.SortAttribute public (Class, String) @org.jetbrains.annotations.NotNull public final Class component1() @org.jetbrains.annotations.NotNull public final String component2() @@ -2293,7 +2295,7 @@ public static final class net.corda.core.node.services.vault.SortAttribute$Custo public int hashCode() public String toString() ## -public static final class net.corda.core.node.services.vault.SortAttribute$Standard extends net.corda.core.node.services.vault.SortAttribute +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.node.services.vault.SortAttribute$Standard extends net.corda.core.node.services.vault.SortAttribute public (net.corda.core.node.services.vault.Sort$Attribute) @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.vault.Sort$Attribute component1() @org.jetbrains.annotations.NotNull public final net.corda.core.node.services.vault.SortAttribute$Standard copy(net.corda.core.node.services.vault.Sort$Attribute) @@ -2308,7 +2310,7 @@ public final class net.corda.core.schemas.CommonSchema extends java.lang.Object public final class net.corda.core.schemas.CommonSchemaV1 extends net.corda.core.schemas.MappedSchema public static final net.corda.core.schemas.CommonSchemaV1 INSTANCE ## -public static class net.corda.core.schemas.CommonSchemaV1$FungibleState extends net.corda.core.schemas.PersistentState +@javax.persistence.MappedSuperclass @net.corda.core.serialization.CordaSerializable public static class net.corda.core.schemas.CommonSchemaV1$FungibleState extends net.corda.core.schemas.PersistentState public (Set, net.corda.core.identity.AbstractParty, long, net.corda.core.identity.AbstractParty, byte[]) @org.jetbrains.annotations.NotNull public final net.corda.core.identity.AbstractParty getIssuer() @org.jetbrains.annotations.NotNull public final byte[] getIssuerRef() @@ -2321,7 +2323,7 @@ public static class net.corda.core.schemas.CommonSchemaV1$FungibleState extends public final void setParticipants(Set) public final void setQuantity(long) ## -public static class net.corda.core.schemas.CommonSchemaV1$LinearState extends net.corda.core.schemas.PersistentState +@javax.persistence.MappedSuperclass @net.corda.core.serialization.CordaSerializable public static class net.corda.core.schemas.CommonSchemaV1$LinearState extends net.corda.core.schemas.PersistentState public (Set, String, UUID) public (net.corda.core.contracts.UniqueIdentifier, Set) @org.jetbrains.annotations.Nullable public final String getExternalId() @@ -2344,7 +2346,7 @@ public final class net.corda.core.schemas.NodeInfoSchema extends java.lang.Objec public final class net.corda.core.schemas.NodeInfoSchemaV1 extends net.corda.core.schemas.MappedSchema public static final net.corda.core.schemas.NodeInfoSchemaV1 INSTANCE ## -public static final class net.corda.core.schemas.NodeInfoSchemaV1$DBHostAndPort extends java.lang.Object +@javax.persistence.Entity public static final class net.corda.core.schemas.NodeInfoSchemaV1$DBHostAndPort extends java.lang.Object public () public (net.corda.core.schemas.NodeInfoSchemaV1$PKHostAndPort) @org.jetbrains.annotations.NotNull public final net.corda.core.schemas.NodeInfoSchemaV1$DBHostAndPort copy(net.corda.core.schemas.NodeInfoSchemaV1$PKHostAndPort) @@ -2357,7 +2359,7 @@ public static final class net.corda.core.schemas.NodeInfoSchemaV1$DBHostAndPort public static final class net.corda.core.schemas.NodeInfoSchemaV1$DBHostAndPort$Companion extends java.lang.Object @org.jetbrains.annotations.NotNull public final net.corda.core.schemas.NodeInfoSchemaV1$DBHostAndPort fromHostAndPort(net.corda.core.utilities.NetworkHostAndPort) ## -public static final class net.corda.core.schemas.NodeInfoSchemaV1$DBPartyAndCertificate extends java.lang.Object +@javax.persistence.Entity @javax.persistence.Table public static final class net.corda.core.schemas.NodeInfoSchemaV1$DBPartyAndCertificate extends java.lang.Object public () public (String, String, byte[], boolean, Set) public (net.corda.core.identity.PartyAndCertificate, boolean) @@ -2375,7 +2377,7 @@ public static final class net.corda.core.schemas.NodeInfoSchemaV1$DBPartyAndCert @org.jetbrains.annotations.NotNull public final net.corda.core.identity.PartyAndCertificate toLegalIdentityAndCert() public String toString() ## -public static final class net.corda.core.schemas.NodeInfoSchemaV1$PKHostAndPort extends java.lang.Object implements java.io.Serializable +@javax.persistence.Embeddable public static final class net.corda.core.schemas.NodeInfoSchemaV1$PKHostAndPort extends java.lang.Object implements java.io.Serializable public () public (String, Integer) @org.jetbrains.annotations.Nullable public final String component1() @@ -2387,7 +2389,7 @@ public static final class net.corda.core.schemas.NodeInfoSchemaV1$PKHostAndPort public int hashCode() public String toString() ## -public static final class net.corda.core.schemas.NodeInfoSchemaV1$PersistentNodeInfo extends java.lang.Object +@javax.persistence.Entity @javax.persistence.Table public static final class net.corda.core.schemas.NodeInfoSchemaV1$PersistentNodeInfo extends java.lang.Object public () public (int, List, List, int, long) @org.jetbrains.annotations.NotNull public final List getAddresses() @@ -2398,13 +2400,13 @@ public static final class net.corda.core.schemas.NodeInfoSchemaV1$PersistentNode public final void setId(int) @org.jetbrains.annotations.NotNull public final net.corda.core.node.NodeInfo toNodeInfo() ## -public class net.corda.core.schemas.PersistentState extends java.lang.Object implements net.corda.core.schemas.StatePersistable +@javax.persistence.MappedSuperclass @net.corda.core.serialization.CordaSerializable public class net.corda.core.schemas.PersistentState extends java.lang.Object implements net.corda.core.schemas.StatePersistable public () public (net.corda.core.schemas.PersistentStateRef) @org.jetbrains.annotations.Nullable public final net.corda.core.schemas.PersistentStateRef getStateRef() public final void setStateRef(net.corda.core.schemas.PersistentStateRef) ## -public final class net.corda.core.schemas.PersistentStateRef extends java.lang.Object implements java.io.Serializable +@javax.persistence.Embeddable public final class net.corda.core.schemas.PersistentStateRef extends java.lang.Object implements java.io.Serializable public () public (String, Integer) public (net.corda.core.contracts.StateRef) @@ -2419,7 +2421,7 @@ public final class net.corda.core.schemas.PersistentStateRef extends java.lang.O public final void setTxId(String) public String toString() ## -public interface net.corda.core.schemas.QueryableState extends net.corda.core.contracts.ContractState +@net.corda.core.serialization.CordaSerializable public interface net.corda.core.schemas.QueryableState extends net.corda.core.contracts.ContractState @org.jetbrains.annotations.NotNull public abstract net.corda.core.schemas.PersistentState generateMappedObject(net.corda.core.schemas.MappedSchema) @org.jetbrains.annotations.NotNull public abstract Iterable supportedSchemas() ## @@ -2433,7 +2435,7 @@ public @interface net.corda.core.serialization.CordaSerializable public @interface net.corda.core.serialization.DeprecatedConstructorForDeserialization public abstract int version() ## -public final class net.corda.core.serialization.MissingAttachmentsException extends net.corda.core.CordaException +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.serialization.MissingAttachmentsException extends net.corda.core.CordaException public (List) @org.jetbrains.annotations.NotNull public final List getIds() ## @@ -2506,7 +2508,7 @@ public interface net.corda.core.serialization.SerializationToken public interface net.corda.core.serialization.SerializationWhitelist @org.jetbrains.annotations.NotNull public abstract List getWhitelist() ## -public interface net.corda.core.serialization.SerializeAsToken +@net.corda.core.serialization.CordaSerializable public interface net.corda.core.serialization.SerializeAsToken @org.jetbrains.annotations.NotNull public abstract net.corda.core.serialization.SerializationToken toToken(net.corda.core.serialization.SerializeAsTokenContext) ## public interface net.corda.core.serialization.SerializeAsTokenContext @@ -2514,7 +2516,7 @@ public interface net.corda.core.serialization.SerializeAsTokenContext @org.jetbrains.annotations.NotNull public abstract net.corda.core.serialization.SerializeAsToken getSingleton(String) public abstract void putSingleton(net.corda.core.serialization.SerializeAsToken) ## -public final class net.corda.core.serialization.SerializedBytes extends net.corda.core.utilities.OpaqueBytes +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.serialization.SerializedBytes extends net.corda.core.utilities.OpaqueBytes public (byte[]) @org.jetbrains.annotations.NotNull public final net.corda.core.crypto.SecureHash getHash() ## @@ -2526,11 +2528,11 @@ public final class net.corda.core.serialization.SingletonSerializationToken exte public static final class net.corda.core.serialization.SingletonSerializationToken$Companion extends java.lang.Object @org.jetbrains.annotations.NotNull public final net.corda.core.serialization.SingletonSerializationToken singletonSerializationToken(Class) ## -public abstract class net.corda.core.serialization.SingletonSerializeAsToken extends java.lang.Object implements net.corda.core.serialization.SerializeAsToken +@net.corda.core.serialization.CordaSerializable public abstract class net.corda.core.serialization.SingletonSerializeAsToken extends java.lang.Object implements net.corda.core.serialization.SerializeAsToken public () @org.jetbrains.annotations.NotNull public net.corda.core.serialization.SingletonSerializationToken toToken(net.corda.core.serialization.SerializeAsTokenContext) ## -public abstract class net.corda.core.transactions.BaseTransaction extends java.lang.Object implements net.corda.core.contracts.NamedByHash +@net.corda.core.DoNotImplement public abstract class net.corda.core.transactions.BaseTransaction extends java.lang.Object implements net.corda.core.contracts.NamedByHash public () protected void checkBaseInvariants() @org.jetbrains.annotations.NotNull public final List filterOutRefs(Class, function.Predicate) @@ -2548,21 +2550,21 @@ public abstract class net.corda.core.transactions.BaseTransaction extends java.l @org.jetbrains.annotations.NotNull public final List outputsOfType(Class) @org.jetbrains.annotations.NotNull public String toString() ## -public class net.corda.core.transactions.ComponentGroup extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public class net.corda.core.transactions.ComponentGroup extends java.lang.Object public (int, List) @org.jetbrains.annotations.NotNull public List getComponents() public int getGroupIndex() ## -public final class net.corda.core.transactions.ComponentVisibilityException extends net.corda.core.CordaException +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.transactions.ComponentVisibilityException extends net.corda.core.CordaException public (net.corda.core.crypto.SecureHash, String) @org.jetbrains.annotations.NotNull public final net.corda.core.crypto.SecureHash getId() @org.jetbrains.annotations.NotNull public final String getReason() ## -public abstract class net.corda.core.transactions.CoreTransaction extends net.corda.core.transactions.BaseTransaction +@net.corda.core.DoNotImplement public abstract class net.corda.core.transactions.CoreTransaction extends net.corda.core.transactions.BaseTransaction public () @org.jetbrains.annotations.NotNull public abstract List getInputs() ## -public final class net.corda.core.transactions.FilteredComponentGroup extends net.corda.core.transactions.ComponentGroup +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.transactions.FilteredComponentGroup extends net.corda.core.transactions.ComponentGroup public (int, List, List, net.corda.core.crypto.PartialMerkleTree) public final int component1() @org.jetbrains.annotations.NotNull public final List component2() @@ -2577,7 +2579,7 @@ public final class net.corda.core.transactions.FilteredComponentGroup extends ne public int hashCode() public String toString() ## -public final class net.corda.core.transactions.FilteredTransaction extends net.corda.core.transactions.TraversableTransaction +@net.corda.core.serialization.CordaSerializable @net.corda.core.DoNotImplement public final class net.corda.core.transactions.FilteredTransaction extends net.corda.core.transactions.TraversableTransaction @kotlin.jvm.JvmStatic @org.jetbrains.annotations.NotNull public static final net.corda.core.transactions.FilteredTransaction buildFilteredTransaction(net.corda.core.transactions.WireTransaction, function.Predicate) public final void checkAllComponentsVisible(net.corda.core.contracts.ComponentGroupEnum) public final boolean checkWithFun(kotlin.jvm.functions.Function1) @@ -2590,17 +2592,17 @@ public final class net.corda.core.transactions.FilteredTransaction extends net.c public static final class net.corda.core.transactions.FilteredTransaction$Companion extends java.lang.Object @kotlin.jvm.JvmStatic @org.jetbrains.annotations.NotNull public final net.corda.core.transactions.FilteredTransaction buildFilteredTransaction(net.corda.core.transactions.WireTransaction, function.Predicate) ## -public final class net.corda.core.transactions.FilteredTransactionVerificationException extends net.corda.core.CordaException +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.transactions.FilteredTransactionVerificationException extends net.corda.core.CordaException public (net.corda.core.crypto.SecureHash, String) @org.jetbrains.annotations.NotNull public final net.corda.core.crypto.SecureHash getId() @org.jetbrains.annotations.NotNull public final String getReason() ## -public abstract class net.corda.core.transactions.FullTransaction extends net.corda.core.transactions.BaseTransaction +@net.corda.core.DoNotImplement public abstract class net.corda.core.transactions.FullTransaction extends net.corda.core.transactions.BaseTransaction public () protected void checkBaseInvariants() @org.jetbrains.annotations.NotNull public abstract List getInputs() ## -public final class net.corda.core.transactions.LedgerTransaction extends net.corda.core.transactions.FullTransaction +@net.corda.core.serialization.CordaSerializable @net.corda.core.DoNotImplement public final class net.corda.core.transactions.LedgerTransaction extends net.corda.core.transactions.FullTransaction public (List, List, List, List, net.corda.core.crypto.SecureHash, net.corda.core.identity.Party, net.corda.core.contracts.TimeWindow, net.corda.core.contracts.PrivacySalt) @org.jetbrains.annotations.NotNull public final List commandsOfType(Class) @org.jetbrains.annotations.NotNull public final List component1() @@ -2653,11 +2655,11 @@ public static final class net.corda.core.transactions.LedgerTransaction$InOutGro public int hashCode() public String toString() ## -public final class net.corda.core.transactions.MissingContractAttachments extends net.corda.core.flows.FlowException +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.transactions.MissingContractAttachments extends net.corda.core.flows.FlowException public (List) @org.jetbrains.annotations.NotNull public final List getStates() ## -public final class net.corda.core.transactions.NotaryChangeLedgerTransaction extends net.corda.core.transactions.FullTransaction implements net.corda.core.transactions.TransactionWithSignatures +@net.corda.core.DoNotImplement public final class net.corda.core.transactions.NotaryChangeLedgerTransaction extends net.corda.core.transactions.FullTransaction implements net.corda.core.transactions.TransactionWithSignatures public (List, net.corda.core.identity.Party, net.corda.core.identity.Party, net.corda.core.crypto.SecureHash, List) public void checkSignaturesAreValid() @org.jetbrains.annotations.NotNull public final List component1() @@ -2680,7 +2682,7 @@ public final class net.corda.core.transactions.NotaryChangeLedgerTransaction ext public String toString() public void verifyRequiredSignatures() ## -public final class net.corda.core.transactions.NotaryChangeWireTransaction extends net.corda.core.transactions.CoreTransaction +@net.corda.core.serialization.CordaSerializable @net.corda.core.DoNotImplement public final class net.corda.core.transactions.NotaryChangeWireTransaction extends net.corda.core.transactions.CoreTransaction public (List, net.corda.core.identity.Party, net.corda.core.identity.Party) @org.jetbrains.annotations.NotNull public final List component1() @org.jetbrains.annotations.NotNull public final net.corda.core.identity.Party component2() @@ -2696,7 +2698,7 @@ public final class net.corda.core.transactions.NotaryChangeWireTransaction exten @org.jetbrains.annotations.NotNull public final net.corda.core.transactions.NotaryChangeLedgerTransaction resolve(net.corda.core.node.ServiceHub, List) public String toString() ## -public final class net.corda.core.transactions.SignedTransaction extends java.lang.Object implements net.corda.core.transactions.TransactionWithSignatures +@net.corda.core.serialization.CordaSerializable @net.corda.core.DoNotImplement public final class net.corda.core.transactions.SignedTransaction extends java.lang.Object implements net.corda.core.transactions.TransactionWithSignatures public (net.corda.core.serialization.SerializedBytes, List) public (net.corda.core.transactions.CoreTransaction, List) @org.jetbrains.annotations.NotNull public final net.corda.core.transactions.FilteredTransaction buildFilteredTransaction(function.Predicate) @@ -2731,7 +2733,7 @@ public final class net.corda.core.transactions.SignedTransaction extends java.la @org.jetbrains.annotations.NotNull public final net.corda.core.transactions.SignedTransaction withAdditionalSignatures(Iterable) public static final net.corda.core.transactions.SignedTransaction$Companion Companion ## -public static final class net.corda.core.transactions.SignedTransaction$SignaturesMissingException extends java.security.SignatureException implements net.corda.core.CordaThrowable, net.corda.core.contracts.NamedByHash +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.transactions.SignedTransaction$SignaturesMissingException extends java.security.SignatureException implements net.corda.core.CordaThrowable, net.corda.core.contracts.NamedByHash public (Set, List, net.corda.core.crypto.SecureHash) public void addSuppressed(Throwable[]) @org.jetbrains.annotations.NotNull public final List getDescriptions() @@ -2782,7 +2784,7 @@ public class net.corda.core.transactions.TransactionBuilder extends java.lang.Ob @org.jetbrains.annotations.NotNull public final net.corda.core.transactions.WireTransaction toWireTransaction(net.corda.core.node.ServicesForResolution) public final void verify(net.corda.core.node.ServiceHub) ## -public interface net.corda.core.transactions.TransactionWithSignatures extends net.corda.core.contracts.NamedByHash +@net.corda.core.DoNotImplement public interface net.corda.core.transactions.TransactionWithSignatures extends net.corda.core.contracts.NamedByHash public abstract void checkSignaturesAreValid() @org.jetbrains.annotations.NotNull public abstract List getKeyDescriptions(Set) @org.jetbrains.annotations.NotNull public abstract Set getMissingSigners() @@ -2790,7 +2792,7 @@ public interface net.corda.core.transactions.TransactionWithSignatures extends n @org.jetbrains.annotations.NotNull public abstract List getSigs() public abstract void verifyRequiredSignatures() ## -public abstract class net.corda.core.transactions.TraversableTransaction extends net.corda.core.transactions.CoreTransaction +@net.corda.core.DoNotImplement public abstract class net.corda.core.transactions.TraversableTransaction extends net.corda.core.transactions.CoreTransaction public (List) @org.jetbrains.annotations.NotNull public final List getAttachments() @org.jetbrains.annotations.NotNull public final List getAvailableComponentGroups() @@ -2801,7 +2803,7 @@ public abstract class net.corda.core.transactions.TraversableTransaction extends @org.jetbrains.annotations.NotNull public List getOutputs() @org.jetbrains.annotations.Nullable public final net.corda.core.contracts.TimeWindow getTimeWindow() ## -public final class net.corda.core.transactions.WireTransaction extends net.corda.core.transactions.TraversableTransaction +@net.corda.core.serialization.CordaSerializable @net.corda.core.DoNotImplement public final class net.corda.core.transactions.WireTransaction extends net.corda.core.transactions.TraversableTransaction @kotlin.Deprecated public (List, List, List, List, net.corda.core.identity.Party, net.corda.core.contracts.TimeWindow, net.corda.core.contracts.PrivacySalt) public (List, net.corda.core.contracts.PrivacySalt) @org.jetbrains.annotations.NotNull public final net.corda.core.transactions.FilteredTransaction buildFilteredTransaction(function.Predicate) @@ -2825,7 +2827,7 @@ public final class net.corda.core.utilities.ByteArrays extends java.lang.Object @org.jetbrains.annotations.NotNull public static final net.corda.core.utilities.ByteSequence sequence(byte[], int, int) @org.jetbrains.annotations.NotNull public static final String toHexString(byte[]) ## -public abstract class net.corda.core.utilities.ByteSequence extends java.lang.Object implements java.lang.Comparable +@net.corda.core.serialization.CordaSerializable public abstract class net.corda.core.utilities.ByteSequence extends java.lang.Object implements java.lang.Comparable public int compareTo(net.corda.core.utilities.ByteSequence) @org.jetbrains.annotations.NotNull public final net.corda.core.utilities.ByteSequence copy() public boolean equals(Object) @@ -2881,7 +2883,7 @@ public final class net.corda.core.utilities.KotlinUtilsKt extends java.lang.Obje public static final void trace(org.slf4j.Logger, kotlin.jvm.functions.Function0) @org.jetbrains.annotations.NotNull public static final net.corda.core.utilities.PropertyDelegate transient(kotlin.jvm.functions.Function0) ## -public final class net.corda.core.utilities.NetworkHostAndPort extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.utilities.NetworkHostAndPort extends java.lang.Object public (String, int) @org.jetbrains.annotations.NotNull public final String component1() public final int component2() @@ -2935,7 +2937,7 @@ public static final class net.corda.core.utilities.NonEmptySet$iterator$1 extend public Object next() public void remove() ## -public class net.corda.core.utilities.OpaqueBytes extends net.corda.core.utilities.ByteSequence +@net.corda.core.serialization.CordaSerializable public class net.corda.core.utilities.OpaqueBytes extends net.corda.core.utilities.ByteSequence public (byte[]) @org.jetbrains.annotations.NotNull public final byte[] getBytes() public int getOffset() @@ -2944,13 +2946,13 @@ public class net.corda.core.utilities.OpaqueBytes extends net.corda.core.utiliti ## public static final class net.corda.core.utilities.OpaqueBytes$Companion extends java.lang.Object ## -public final class net.corda.core.utilities.OpaqueBytesSubSequence extends net.corda.core.utilities.ByteSequence +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.utilities.OpaqueBytesSubSequence extends net.corda.core.utilities.ByteSequence public (byte[], int, int) @org.jetbrains.annotations.NotNull public byte[] getBytes() public int getOffset() public int getSize() ## -public final class net.corda.core.utilities.ProgressTracker extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public final class net.corda.core.utilities.ProgressTracker extends java.lang.Object public final void endWithError(Throwable) @org.jetbrains.annotations.NotNull public final List getAllSteps() @org.jetbrains.annotations.NotNull public final rx.Observable getChanges() @@ -2966,9 +2968,9 @@ public final class net.corda.core.utilities.ProgressTracker extends java.lang.Ob public final void setChildProgressTracker(net.corda.core.utilities.ProgressTracker$Step, net.corda.core.utilities.ProgressTracker) public final void setCurrentStep(net.corda.core.utilities.ProgressTracker$Step) ## -public abstract static class net.corda.core.utilities.ProgressTracker$Change extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public abstract static class net.corda.core.utilities.ProgressTracker$Change extends java.lang.Object ## -public static final class net.corda.core.utilities.ProgressTracker$Change$Position extends net.corda.core.utilities.ProgressTracker$Change +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.utilities.ProgressTracker$Change$Position extends net.corda.core.utilities.ProgressTracker$Change public (net.corda.core.utilities.ProgressTracker, net.corda.core.utilities.ProgressTracker$Step) @org.jetbrains.annotations.NotNull public final net.corda.core.utilities.ProgressTracker component1() @org.jetbrains.annotations.NotNull public final net.corda.core.utilities.ProgressTracker$Step component2() @@ -2979,7 +2981,7 @@ public static final class net.corda.core.utilities.ProgressTracker$Change$Positi public int hashCode() @org.jetbrains.annotations.NotNull public String toString() ## -public static final class net.corda.core.utilities.ProgressTracker$Change$Rendering extends net.corda.core.utilities.ProgressTracker$Change +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.utilities.ProgressTracker$Change$Rendering extends net.corda.core.utilities.ProgressTracker$Change public (net.corda.core.utilities.ProgressTracker, net.corda.core.utilities.ProgressTracker$Step) @org.jetbrains.annotations.NotNull public final net.corda.core.utilities.ProgressTracker component1() @org.jetbrains.annotations.NotNull public final net.corda.core.utilities.ProgressTracker$Step component2() @@ -2990,7 +2992,7 @@ public static final class net.corda.core.utilities.ProgressTracker$Change$Render public int hashCode() @org.jetbrains.annotations.NotNull public String toString() ## -public static final class net.corda.core.utilities.ProgressTracker$Change$Structural extends net.corda.core.utilities.ProgressTracker$Change +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.utilities.ProgressTracker$Change$Structural extends net.corda.core.utilities.ProgressTracker$Change public (net.corda.core.utilities.ProgressTracker, net.corda.core.utilities.ProgressTracker$Step) @org.jetbrains.annotations.NotNull public final net.corda.core.utilities.ProgressTracker component1() @org.jetbrains.annotations.NotNull public final net.corda.core.utilities.ProgressTracker$Step component2() @@ -3001,25 +3003,25 @@ public static final class net.corda.core.utilities.ProgressTracker$Change$Struct public int hashCode() @org.jetbrains.annotations.NotNull public String toString() ## -public static final class net.corda.core.utilities.ProgressTracker$DONE extends net.corda.core.utilities.ProgressTracker$Step +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.utilities.ProgressTracker$DONE extends net.corda.core.utilities.ProgressTracker$Step public boolean equals(Object) public static final net.corda.core.utilities.ProgressTracker$DONE INSTANCE ## -public static class net.corda.core.utilities.ProgressTracker$Step extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public static class net.corda.core.utilities.ProgressTracker$Step extends java.lang.Object public (String) @org.jetbrains.annotations.Nullable public net.corda.core.utilities.ProgressTracker childProgressTracker() @org.jetbrains.annotations.NotNull public rx.Observable getChanges() @org.jetbrains.annotations.NotNull public Map getExtraAuditData() @org.jetbrains.annotations.NotNull public String getLabel() ## -public static final class net.corda.core.utilities.ProgressTracker$UNSTARTED extends net.corda.core.utilities.ProgressTracker$Step +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.utilities.ProgressTracker$UNSTARTED extends net.corda.core.utilities.ProgressTracker$Step public boolean equals(Object) public static final net.corda.core.utilities.ProgressTracker$UNSTARTED INSTANCE ## public interface net.corda.core.utilities.PropertyDelegate public abstract Object getValue(Object, kotlin.reflect.KProperty) ## -public abstract class net.corda.core.utilities.Try extends java.lang.Object +@net.corda.core.serialization.CordaSerializable public abstract class net.corda.core.utilities.Try extends java.lang.Object @org.jetbrains.annotations.NotNull public final net.corda.core.utilities.Try combine(net.corda.core.utilities.Try, kotlin.jvm.functions.Function2) @org.jetbrains.annotations.NotNull public final net.corda.core.utilities.Try flatMap(kotlin.jvm.functions.Function1) public abstract Object getOrThrow() @@ -3032,7 +3034,7 @@ public abstract class net.corda.core.utilities.Try extends java.lang.Object public static final class net.corda.core.utilities.Try$Companion extends java.lang.Object @kotlin.jvm.JvmStatic @org.jetbrains.annotations.NotNull public final net.corda.core.utilities.Try on(kotlin.jvm.functions.Function0) ## -public static final class net.corda.core.utilities.Try$Failure extends net.corda.core.utilities.Try +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.utilities.Try$Failure extends net.corda.core.utilities.Try public (Throwable) @org.jetbrains.annotations.NotNull public final Throwable component1() @org.jetbrains.annotations.NotNull public final net.corda.core.utilities.Try$Failure copy(Throwable) @@ -3044,7 +3046,7 @@ public static final class net.corda.core.utilities.Try$Failure extends net.corda public boolean isSuccess() @org.jetbrains.annotations.NotNull public String toString() ## -public static final class net.corda.core.utilities.Try$Success extends net.corda.core.utilities.Try +@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.utilities.Try$Success extends net.corda.core.utilities.Try public (Object) public final Object component1() @org.jetbrains.annotations.NotNull public final net.corda.core.utilities.Try$Success copy(Object) @@ -3196,7 +3198,7 @@ public abstract static class net.corda.client.jackson.JacksonSupport$WireTransac @com.fasterxml.jackson.annotation.JsonIgnore @org.jetbrains.annotations.NotNull public abstract net.corda.core.crypto.MerkleTree getMerkleTree() @com.fasterxml.jackson.annotation.JsonIgnore @org.jetbrains.annotations.NotNull public abstract List getOutputStates() ## -public class net.corda.client.jackson.StringToMethodCallParser extends java.lang.Object +@javax.annotation.concurrent.ThreadSafe public class net.corda.client.jackson.StringToMethodCallParser extends java.lang.Object public (Class) public (Class, com.fasterxml.jackson.databind.ObjectMapper) public (kotlin.reflect.KClass) @@ -3268,7 +3270,7 @@ public final class net.corda.client.rpc.CordaRPCConnection extends java.lang.Obj public final class net.corda.client.rpc.PermissionException extends net.corda.core.CordaRuntimeException public (String) ## -public interface net.corda.client.rpc.RPCConnection extends java.io.Closeable +@net.corda.core.DoNotImplement public interface net.corda.client.rpc.RPCConnection extends java.io.Closeable public abstract void forceClose() @org.jetbrains.annotations.NotNull public abstract net.corda.core.messaging.RPCOps getProxy() public abstract int getServerProtocolVersion() diff --git a/.ci/check-api-changes.sh b/.ci/check-api-changes.sh index e9ac25cf6d..9621f90fce 100755 --- a/.ci/check-api-changes.sh +++ b/.ci/check-api-changes.sh @@ -30,7 +30,15 @@ if [ $removalCount -gt 0 ]; then fi # Adding new abstract methods could also break the API. -newAbstracts=$(echo "$diffContents" | grep "^+" | grep "\(public\|protected\) abstract") +# However, first exclude anything with the @DoNotImplement annotation. + +function forUserImpl() { + awk '/DoNotImplement/,/^##/{ next }{ print }' $1 +} + +userDiffContents=`diff -u <(forUserImpl $apiCurrent) <(forUserImpl $APIHOME/../build/api/api-corda-*.txt) | tail --lines=+3` + +newAbstracts=$(echo "$userDiffContents" | grep "^+" | grep "\(public\|protected\) abstract") abstractCount=`grep -v "^$" < : Closeable { /** * Holds a synthetic class that automatically forwards method calls to the server, and returns the response. diff --git a/constants.properties b/constants.properties index 37f3b33003..c85d51a756 100644 --- a/constants.properties +++ b/constants.properties @@ -1,4 +1,4 @@ -gradlePluginsVersion=2.0.5 +gradlePluginsVersion=2.0.6 kotlinVersion=1.1.50 guavaVersion=21.0 bouncycastleVersion=1.57 diff --git a/core/src/main/kotlin/net/corda/core/DoNotImplement.kt b/core/src/main/kotlin/net/corda/core/DoNotImplement.kt new file mode 100644 index 0000000000..6800a92f0c --- /dev/null +++ b/core/src/main/kotlin/net/corda/core/DoNotImplement.kt @@ -0,0 +1,18 @@ +package net.corda.core + +import java.lang.annotation.Inherited + +/** + * This annotation is for interfaces and abstract classes that provide Corda functionality + * to user applications. Future versions of Corda may add new methods to such interfaces and + * classes, but will not remove or modify existing methods. + * + * Adding new methods does not break Corda's API compatibility guarantee because applications + * should not implement or extend anything annotated with [DoNotImplement]. These classes are + * only meant to be implemented by Corda itself. + */ +@Retention(AnnotationRetention.BINARY) +@Target(AnnotationTarget.CLASS) +@MustBeDocumented +@Inherited +annotation class DoNotImplement \ No newline at end of file diff --git a/core/src/main/kotlin/net/corda/core/cordapp/Cordapp.kt b/core/src/main/kotlin/net/corda/core/cordapp/Cordapp.kt index 8f7f5f8d56..f4fe71ead0 100644 --- a/core/src/main/kotlin/net/corda/core/cordapp/Cordapp.kt +++ b/core/src/main/kotlin/net/corda/core/cordapp/Cordapp.kt @@ -1,5 +1,6 @@ package net.corda.core.cordapp +import net.corda.core.DoNotImplement import net.corda.core.flows.FlowLogic import net.corda.core.schemas.MappedSchema import net.corda.core.serialization.SerializationWhitelist @@ -17,13 +18,14 @@ import java.net.URL * @property contractClassNames List of contracts * @property initiatedFlows List of initiatable flow classes * @property rpcFlows List of RPC initiable flows classes - * @property serviceFlows List of [CordaService] initiable flows classes + * @property serviceFlows List of [net.corda.core.node.services.CordaService] initiable flows classes * @property schedulableFlows List of flows startable by the scheduler - * @property servies List of RPC services + * @property services List of RPC services * @property serializationWhitelists List of Corda plugin registries * @property customSchemas List of custom schemas * @property jarPath The path to the JAR for this CorDapp */ +@DoNotImplement interface Cordapp { val name: String val contractClassNames: List diff --git a/core/src/main/kotlin/net/corda/core/cordapp/CordappProvider.kt b/core/src/main/kotlin/net/corda/core/cordapp/CordappProvider.kt index 289e606ac4..bf7864ee95 100644 --- a/core/src/main/kotlin/net/corda/core/cordapp/CordappProvider.kt +++ b/core/src/main/kotlin/net/corda/core/cordapp/CordappProvider.kt @@ -1,11 +1,13 @@ package net.corda.core.cordapp +import net.corda.core.DoNotImplement import net.corda.core.contracts.ContractClassName import net.corda.core.node.services.AttachmentId /** * Provides access to what the node knows about loaded applications. */ +@DoNotImplement interface CordappProvider { /** * Exposes the current CorDapp context which will contain information and configuration of the CorDapp that diff --git a/core/src/main/kotlin/net/corda/core/flows/FlowLogicRef.kt b/core/src/main/kotlin/net/corda/core/flows/FlowLogicRef.kt index 0b90909180..a3043709d0 100644 --- a/core/src/main/kotlin/net/corda/core/flows/FlowLogicRef.kt +++ b/core/src/main/kotlin/net/corda/core/flows/FlowLogicRef.kt @@ -1,6 +1,6 @@ package net.corda.core.flows -import net.corda.core.crypto.SecureHash +import net.corda.core.DoNotImplement import net.corda.core.serialization.CordaSerializable /** @@ -8,6 +8,7 @@ import net.corda.core.serialization.CordaSerializable * Typically this would be used from within the nextScheduledActivity method of a QueryableState to specify * the flow to run at the scheduled time. */ +@DoNotImplement interface FlowLogicRefFactory { fun create(flowClass: Class>, vararg args: Any?): FlowLogicRef } @@ -24,4 +25,5 @@ class IllegalFlowLogicException(type: Class<*>, msg: String) : IllegalArgumentEx */ // TODO: align this with the existing [FlowRef] in the bank-side API (probably replace some of the API classes) @CordaSerializable +@DoNotImplement interface FlowLogicRef \ No newline at end of file diff --git a/core/src/main/kotlin/net/corda/core/flows/FlowSession.kt b/core/src/main/kotlin/net/corda/core/flows/FlowSession.kt index f5589f96c4..ae49ea3ebb 100644 --- a/core/src/main/kotlin/net/corda/core/flows/FlowSession.kt +++ b/core/src/main/kotlin/net/corda/core/flows/FlowSession.kt @@ -1,6 +1,7 @@ package net.corda.core.flows import co.paralleluniverse.fibers.Suspendable +import net.corda.core.DoNotImplement import net.corda.core.identity.Party import net.corda.core.utilities.UntrustworthyData @@ -41,6 +42,7 @@ import net.corda.core.utilities.UntrustworthyData * will become * otherSideSession.send(something) */ +@DoNotImplement abstract class FlowSession { /** * The [Party] on the other side of this session. In the case of a session created by [FlowLogic.initiateFlow] diff --git a/core/src/main/kotlin/net/corda/core/identity/AbstractParty.kt b/core/src/main/kotlin/net/corda/core/identity/AbstractParty.kt index 8f03ed640b..a24096f973 100644 --- a/core/src/main/kotlin/net/corda/core/identity/AbstractParty.kt +++ b/core/src/main/kotlin/net/corda/core/identity/AbstractParty.kt @@ -1,5 +1,6 @@ package net.corda.core.identity +import net.corda.core.DoNotImplement import net.corda.core.contracts.PartyAndReference import net.corda.core.serialization.CordaSerializable import net.corda.core.utilities.OpaqueBytes @@ -10,6 +11,7 @@ import java.security.PublicKey * the party. In most cases [Party] or [AnonymousParty] should be used, depending on use-case. */ @CordaSerializable +@DoNotImplement abstract class AbstractParty(val owningKey: PublicKey) { /** Anonymised parties do not include any detail apart from owning key, so equality is dependent solely on the key */ override fun equals(other: Any?): Boolean = other === this || other is AbstractParty && other.owningKey == owningKey diff --git a/core/src/main/kotlin/net/corda/core/messaging/CordaRPCOps.kt b/core/src/main/kotlin/net/corda/core/messaging/CordaRPCOps.kt index d29cc7a193..3e87d4ba39 100644 --- a/core/src/main/kotlin/net/corda/core/messaging/CordaRPCOps.kt +++ b/core/src/main/kotlin/net/corda/core/messaging/CordaRPCOps.kt @@ -24,7 +24,6 @@ import rx.Observable import java.io.InputStream import java.security.PublicKey import java.time.Instant -import java.util.* @CordaSerializable data class StateMachineInfo( diff --git a/core/src/main/kotlin/net/corda/core/messaging/FlowHandle.kt b/core/src/main/kotlin/net/corda/core/messaging/FlowHandle.kt index 166825de0c..ee241d1924 100644 --- a/core/src/main/kotlin/net/corda/core/messaging/FlowHandle.kt +++ b/core/src/main/kotlin/net/corda/core/messaging/FlowHandle.kt @@ -1,5 +1,6 @@ package net.corda.core.messaging +import net.corda.core.DoNotImplement import net.corda.core.concurrent.CordaFuture import net.corda.core.flows.StateMachineRunId import net.corda.core.serialization.CordaSerializable @@ -11,6 +12,7 @@ import rx.Observable * @property id The started state machine's ID. * @property returnValue A [CordaFuture] of the flow's return value. */ +@DoNotImplement interface FlowHandle : AutoCloseable { val id: StateMachineRunId val returnValue: CordaFuture diff --git a/core/src/main/kotlin/net/corda/core/messaging/RPCOps.kt b/core/src/main/kotlin/net/corda/core/messaging/RPCOps.kt index 6a3eaa4c1d..e6aa0b79a4 100644 --- a/core/src/main/kotlin/net/corda/core/messaging/RPCOps.kt +++ b/core/src/main/kotlin/net/corda/core/messaging/RPCOps.kt @@ -1,9 +1,12 @@ package net.corda.core.messaging +import net.corda.core.DoNotImplement + /** * Base interface that all RPC servers must implement. Note: in Corda there's only one RPC interface. This base * interface is here in case we split the RPC system out into a separate library one day. */ +@DoNotImplement interface RPCOps { /** Returns the RPC protocol version. Exists since version 0 so guaranteed to be present. */ val protocolVersion: Int diff --git a/core/src/main/kotlin/net/corda/core/node/ServiceHub.kt b/core/src/main/kotlin/net/corda/core/node/ServiceHub.kt index 7114e6c843..ce08e56881 100644 --- a/core/src/main/kotlin/net/corda/core/node/ServiceHub.kt +++ b/core/src/main/kotlin/net/corda/core/node/ServiceHub.kt @@ -1,5 +1,6 @@ package net.corda.core.node +import net.corda.core.DoNotImplement import net.corda.core.contracts.* import net.corda.core.cordapp.CordappProvider import net.corda.core.crypto.Crypto @@ -19,6 +20,7 @@ import java.time.Clock /** * Part of [ServiceHub]. */ +@DoNotImplement interface StateLoader { /** * Given a [StateRef] loads the referenced transaction and looks up the specified output [ContractState]. @@ -164,7 +166,7 @@ interface ServiceHub : ServicesForResolution { @Throws(TransactionResolutionException::class) fun toStateAndRef(stateRef: StateRef): StateAndRef { val stx = validatedTransactions.getTransaction(stateRef.txhash) ?: throw TransactionResolutionException(stateRef.txhash) - return stx.resolveBaseTransaction(this).outRef(stateRef.index) + return stx.resolveBaseTransaction(this).outRef(stateRef.index) } private val legalIdentityKey: PublicKey get() = this.myInfo.legalIdentitiesAndCerts.first().owningKey diff --git a/core/src/main/kotlin/net/corda/core/node/services/AttachmentStorage.kt b/core/src/main/kotlin/net/corda/core/node/services/AttachmentStorage.kt index 4e038925c1..ed13d5edd5 100644 --- a/core/src/main/kotlin/net/corda/core/node/services/AttachmentStorage.kt +++ b/core/src/main/kotlin/net/corda/core/node/services/AttachmentStorage.kt @@ -1,5 +1,6 @@ package net.corda.core.node.services +import net.corda.core.DoNotImplement import net.corda.core.contracts.Attachment import net.corda.core.crypto.SecureHash import java.io.IOException @@ -11,6 +12,7 @@ typealias AttachmentId = SecureHash /** * An attachment store records potentially large binary objects, identified by their hash. */ +@DoNotImplement interface AttachmentStorage { /** * Returns a handle to a locally stored attachment, or null if it's not known. The handle can be used to open diff --git a/core/src/main/kotlin/net/corda/core/node/services/ContractUpgradeService.kt b/core/src/main/kotlin/net/corda/core/node/services/ContractUpgradeService.kt index 42d1689796..b489027fc6 100644 --- a/core/src/main/kotlin/net/corda/core/node/services/ContractUpgradeService.kt +++ b/core/src/main/kotlin/net/corda/core/node/services/ContractUpgradeService.kt @@ -1,5 +1,6 @@ package net.corda.core.node.services +import net.corda.core.DoNotImplement import net.corda.core.contracts.StateRef import net.corda.core.contracts.UpgradedContract import net.corda.core.flows.ContractUpgradeFlow @@ -9,6 +10,7 @@ import net.corda.core.flows.ContractUpgradeFlow * a specified and mutually agreed (amongst participants) contract version. * See also [ContractUpgradeFlow] to understand the workflow associated with contract upgrades. */ +@DoNotImplement interface ContractUpgradeService { /** Get contracts we would be willing to upgrade the suggested contract to. */ diff --git a/core/src/main/kotlin/net/corda/core/node/services/IdentityService.kt b/core/src/main/kotlin/net/corda/core/node/services/IdentityService.kt index 489e316e01..1aa8e35f24 100644 --- a/core/src/main/kotlin/net/corda/core/node/services/IdentityService.kt +++ b/core/src/main/kotlin/net/corda/core/node/services/IdentityService.kt @@ -1,6 +1,7 @@ package net.corda.core.node.services import net.corda.core.CordaException +import net.corda.core.DoNotImplement import net.corda.core.contracts.PartyAndReference import net.corda.core.identity.* import java.security.InvalidAlgorithmParameterException @@ -16,6 +17,7 @@ import java.security.cert.* * whereas confidential identities are distributed only on a need to know basis (typically between parties in * a transaction being built). See [NetworkMapCache] for retrieving well known identities from the network map. */ +@DoNotImplement interface IdentityService { val trustRoot: X509Certificate val trustAnchor: TrustAnchor diff --git a/core/src/main/kotlin/net/corda/core/node/services/KeyManagementService.kt b/core/src/main/kotlin/net/corda/core/node/services/KeyManagementService.kt index ed2d106bae..7b4801b112 100644 --- a/core/src/main/kotlin/net/corda/core/node/services/KeyManagementService.kt +++ b/core/src/main/kotlin/net/corda/core/node/services/KeyManagementService.kt @@ -1,6 +1,7 @@ package net.corda.core.node.services import co.paralleluniverse.fibers.Suspendable +import net.corda.core.DoNotImplement import net.corda.core.crypto.DigitalSignature import net.corda.core.crypto.SignableData import net.corda.core.crypto.TransactionSignature @@ -11,6 +12,7 @@ import java.security.PublicKey * The KMS is responsible for storing and using private keys to sign things. An implementation of this may, for example, * call out to a hardware security module that enforces various auditing and frequency-of-use requirements. */ +@DoNotImplement interface KeyManagementService { /** * Returns a snapshot of the current signing [PublicKey]s. diff --git a/core/src/main/kotlin/net/corda/core/node/services/NetworkMapCache.kt b/core/src/main/kotlin/net/corda/core/node/services/NetworkMapCache.kt index 7e6f959e4f..b5c415c578 100644 --- a/core/src/main/kotlin/net/corda/core/node/services/NetworkMapCache.kt +++ b/core/src/main/kotlin/net/corda/core/node/services/NetworkMapCache.kt @@ -1,5 +1,6 @@ package net.corda.core.node.services +import net.corda.core.DoNotImplement import net.corda.core.concurrent.CordaFuture import net.corda.core.identity.AbstractParty import net.corda.core.identity.CordaX500Name @@ -43,6 +44,7 @@ interface NetworkMapCache : NetworkMapCacheBase { } /** Subset of [NetworkMapCache] that doesn't depend on an [IdentityService]. */ +@DoNotImplement interface NetworkMapCacheBase { // DOCSTART 1 /** diff --git a/core/src/main/kotlin/net/corda/core/node/services/TransactionStorage.kt b/core/src/main/kotlin/net/corda/core/node/services/TransactionStorage.kt index 8173616eda..9b6b713ed2 100644 --- a/core/src/main/kotlin/net/corda/core/node/services/TransactionStorage.kt +++ b/core/src/main/kotlin/net/corda/core/node/services/TransactionStorage.kt @@ -1,5 +1,6 @@ package net.corda.core.node.services +import net.corda.core.DoNotImplement import net.corda.core.crypto.SecureHash import net.corda.core.messaging.DataFeed import net.corda.core.transactions.SignedTransaction @@ -8,6 +9,7 @@ import rx.Observable /** * Thread-safe storage of transactions. */ +@DoNotImplement interface TransactionStorage { /** * Return the transaction with the given [id], or null if no such transaction exists. diff --git a/core/src/main/kotlin/net/corda/core/node/services/TransactionVerifierService.kt b/core/src/main/kotlin/net/corda/core/node/services/TransactionVerifierService.kt index 8cc5c7af65..d72eec72f2 100644 --- a/core/src/main/kotlin/net/corda/core/node/services/TransactionVerifierService.kt +++ b/core/src/main/kotlin/net/corda/core/node/services/TransactionVerifierService.kt @@ -1,5 +1,6 @@ package net.corda.core.node.services +import net.corda.core.DoNotImplement import net.corda.core.concurrent.CordaFuture import net.corda.core.transactions.LedgerTransaction @@ -7,6 +8,7 @@ import net.corda.core.transactions.LedgerTransaction * Provides verification service. The implementation may be a simple in-memory verify() call or perhaps an IPC/RPC. * @suppress */ +@DoNotImplement interface TransactionVerifierService { /** * @param transaction The transaction to be verified. diff --git a/core/src/main/kotlin/net/corda/core/node/services/VaultService.kt b/core/src/main/kotlin/net/corda/core/node/services/VaultService.kt index 38545ac754..2440d80f77 100644 --- a/core/src/main/kotlin/net/corda/core/node/services/VaultService.kt +++ b/core/src/main/kotlin/net/corda/core/node/services/VaultService.kt @@ -1,6 +1,7 @@ package net.corda.core.node.services import co.paralleluniverse.fibers.Suspendable +import net.corda.core.DoNotImplement import net.corda.core.concurrent.CordaFuture import net.corda.core.contracts.* import net.corda.core.crypto.SecureHash @@ -14,7 +15,6 @@ import net.corda.core.serialization.CordaSerializable import net.corda.core.toFuture import net.corda.core.utilities.NonEmptySet import rx.Observable -import rx.subjects.PublishSubject import java.time.Instant import java.util.* @@ -151,6 +151,7 @@ class Vault(val states: Iterable>) { * * Note that transactions we've seen are held by the storage service, not the vault. */ +@DoNotImplement interface VaultService { /** * Prefer the use of [updates] unless you know why you want to use this instead. diff --git a/core/src/main/kotlin/net/corda/core/node/services/vault/QueryCriteria.kt b/core/src/main/kotlin/net/corda/core/node/services/vault/QueryCriteria.kt index 1e5eeb858f..208109f3b4 100644 --- a/core/src/main/kotlin/net/corda/core/node/services/vault/QueryCriteria.kt +++ b/core/src/main/kotlin/net/corda/core/node/services/vault/QueryCriteria.kt @@ -2,6 +2,7 @@ package net.corda.core.node.services.vault +import net.corda.core.DoNotImplement import net.corda.core.contracts.ContractState import net.corda.core.contracts.StateRef import net.corda.core.contracts.UniqueIdentifier @@ -144,6 +145,7 @@ sealed class QueryCriteria { infix fun or(criteria: QueryCriteria): QueryCriteria = OrComposition(this, criteria) } +@DoNotImplement interface IQueryCriteriaParser { fun parseCriteria(criteria: QueryCriteria.CommonQueryCriteria): Collection fun parseCriteria(criteria: QueryCriteria.FungibleAssetQueryCriteria): Collection diff --git a/core/src/main/kotlin/net/corda/core/node/services/vault/QueryCriteriaUtils.kt b/core/src/main/kotlin/net/corda/core/node/services/vault/QueryCriteriaUtils.kt index 85d7292061..098a5a07e2 100644 --- a/core/src/main/kotlin/net/corda/core/node/services/vault/QueryCriteriaUtils.kt +++ b/core/src/main/kotlin/net/corda/core/node/services/vault/QueryCriteriaUtils.kt @@ -2,6 +2,7 @@ package net.corda.core.node.services.vault +import net.corda.core.DoNotImplement import net.corda.core.internal.uncheckedCast import net.corda.core.schemas.PersistentState import net.corda.core.serialization.CordaSerializable @@ -10,6 +11,7 @@ import kotlin.reflect.KProperty1 import kotlin.reflect.jvm.javaGetter @CordaSerializable +@DoNotImplement interface Operator enum class BinaryLogicalOperator : Operator { @@ -138,6 +140,7 @@ data class Sort(val columns: Collection) { } @CordaSerializable + @DoNotImplement interface Attribute enum class CommonStateAttribute(val attributeParent: String, val attributeChild: String?) : Attribute { diff --git a/core/src/main/kotlin/net/corda/core/transactions/BaseTransaction.kt b/core/src/main/kotlin/net/corda/core/transactions/BaseTransaction.kt index 0d5a798d9f..14483543b2 100644 --- a/core/src/main/kotlin/net/corda/core/transactions/BaseTransaction.kt +++ b/core/src/main/kotlin/net/corda/core/transactions/BaseTransaction.kt @@ -1,5 +1,6 @@ package net.corda.core.transactions +import net.corda.core.DoNotImplement import net.corda.core.contracts.* import net.corda.core.identity.Party import net.corda.core.internal.castIfPossible @@ -10,6 +11,7 @@ import java.util.function.Predicate /** * An abstract class defining fields shared by all transaction types in the system. */ +@DoNotImplement abstract class BaseTransaction : NamedByHash { /** The inputs of this transaction. Note that in BaseTransaction subclasses the type of this list may change! */ abstract val inputs: List<*> diff --git a/core/src/main/kotlin/net/corda/core/transactions/TransactionWithSignatures.kt b/core/src/main/kotlin/net/corda/core/transactions/TransactionWithSignatures.kt index ab25d68064..6835e39686 100644 --- a/core/src/main/kotlin/net/corda/core/transactions/TransactionWithSignatures.kt +++ b/core/src/main/kotlin/net/corda/core/transactions/TransactionWithSignatures.kt @@ -1,5 +1,6 @@ package net.corda.core.transactions +import net.corda.core.DoNotImplement import net.corda.core.contracts.NamedByHash import net.corda.core.crypto.TransactionSignature import net.corda.core.crypto.isFulfilledBy @@ -10,6 +11,7 @@ import java.security.PublicKey import java.security.SignatureException /** An interface for transactions containing signatures, with logic for signature verification */ +@DoNotImplement interface TransactionWithSignatures : NamedByHash { val sigs: List diff --git a/docs/source/corda-api.rst b/docs/source/corda-api.rst index 8b5dd94053..bb973b0f05 100644 --- a/docs/source/corda-api.rst +++ b/docs/source/corda-api.rst @@ -91,3 +91,14 @@ The following modules are available but we do not commit to their stability or c Future releases will reject any CorDapps that use types from these packages. .. warning:: The web server module will be removed in future. You should call Corda nodes through RPC from your web server of choice e.g., Spring Boot, Vertx, Undertow. + +The ``@DoNotImplement`` annotation +---------------------------------- + +Certain interfaces and abstract classes within the Corda API have been annotated +as ``@DoNotImplement``. While we undertake not to remove or modify any of these classes' existing +functionality, the annotation is a warning that we may need to extend them in future versions of Corda. +Cordapp developers should therefore just use these classes "as is", and *not* attempt to extend or implement any of them themselves. + +This annotation is inherited by subclasses and subinterfaces. + diff --git a/gradle-plugins/api-scanner/src/main/java/net/corda/plugins/ScanApi.java b/gradle-plugins/api-scanner/src/main/java/net/corda/plugins/ScanApi.java index f0220add42..eb2b7e5599 100644 --- a/gradle-plugins/api-scanner/src/main/java/net/corda/plugins/ScanApi.java +++ b/gradle-plugins/api-scanner/src/main/java/net/corda/plugins/ScanApi.java @@ -16,6 +16,7 @@ import org.gradle.api.tasks.TaskAction; import java.io.*; import java.lang.annotation.Annotation; +import java.lang.annotation.Inherited; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; @@ -25,7 +26,7 @@ import java.net.URLClassLoader; import java.util.*; import java.util.stream.StreamSupport; -import static java.util.Collections.unmodifiableSet; +import static java.util.Collections.*; import static java.util.stream.Collectors.*; @SuppressWarnings("unused") @@ -228,9 +229,19 @@ public class ScanApi extends DefaultTask { private void writeClass(PrintWriter writer, ClassInfo classInfo, int modifiers) { if (classInfo.isAnnotation()) { + /* + * Annotation declaration. + */ writer.append(Modifier.toString(modifiers & INTERFACE_MASK)); writer.append(" @interface ").print(classInfo); } else if (classInfo.isStandardClass()) { + /* + * Class declaration. + */ + List annotationNames = toNames(readClassAnnotationsFor(classInfo)); + if (!annotationNames.isEmpty()) { + writer.append(asAnnotations(annotationNames)); + } writer.append(Modifier.toString(modifiers & CLASS_MASK)); writer.append(" class ").print(classInfo); Set superclasses = classInfo.getDirectSuperclasses(); @@ -242,6 +253,13 @@ public class ScanApi extends DefaultTask { writer.append(" implements ").print(stringOf(interfaces)); } } else { + /* + * Interface declaration. + */ + List annotationNames = toNames(readInterfaceAnnotationsFor(classInfo)); + if (!annotationNames.isEmpty()) { + writer.append(asAnnotations(annotationNames)); + } writer.append(Modifier.toString(modifiers & INTERFACE_MASK)); writer.append(" interface ").print(classInfo); Set superinterfaces = classInfo.getDirectSuperinterfaces(); @@ -253,7 +271,7 @@ public class ScanApi extends DefaultTask { } private void writeMethods(PrintWriter writer, List methods) { - Collections.sort(methods); + sort(methods); for (MethodInfo method : methods) { if (isVisible(method.getAccessFlags()) // Only public and protected methods && isValid(method.getAccessFlags(), METHOD_MASK) // Excludes bridge and synthetic methods @@ -264,7 +282,7 @@ public class ScanApi extends DefaultTask { } private void writeFields(PrintWriter output, List fields) { - Collections.sort(fields); + sort(fields); for (FieldInfo field : fields) { if (isVisible(field.getAccessFlags()) && isValid(field.getAccessFlags(), FIELD_MASK)) { output.append(" ").println(field); @@ -286,6 +304,36 @@ public class ScanApi extends DefaultTask { return 0; } + private List toNames(Collection classes) { + return classes.stream() + .map(ClassInfo::toString) + .filter(ScanApi::isApplicationClass) + .collect(toList()); + } + + private Set readClassAnnotationsFor(ClassInfo classInfo) { + Set annotations = new HashSet<>(classInfo.getAnnotations()); + annotations.addAll(selectInheritedAnnotations(classInfo.getSuperclasses())); + annotations.addAll(selectInheritedAnnotations(classInfo.getImplementedInterfaces())); + return annotations; + } + + private Set readInterfaceAnnotationsFor(ClassInfo classInfo) { + Set annotations = new HashSet<>(classInfo.getAnnotations()); + annotations.addAll(selectInheritedAnnotations(classInfo.getSuperinterfaces())); + return annotations; + } + + /** + * Returns those annotations which have themselves been annotated as "Inherited". + */ + private List selectInheritedAnnotations(Collection classes) { + return classes.stream() + .flatMap(cls -> cls.getAnnotations().stream()) + .filter(ann -> ann.hasMetaAnnotation(Inherited.class.getName())) + .collect(toList()); + } + private MethodInfo filterAnnotationsFor(MethodInfo method) { return new MethodInfo( method.getClassName(), @@ -319,6 +367,14 @@ public class ScanApi extends DefaultTask { return items.stream().map(ClassInfo::toString).collect(joining(", ")); } + private static String asAnnotations(Collection items) { + return items.stream().collect(joining(" @", "@", " ")); + } + + private static boolean isApplicationClass(String typeName) { + return !typeName.startsWith("java.") && !typeName.startsWith("kotlin."); + } + private static URL toURL(File file) throws MalformedURLException { return file.toURI().toURL(); } diff --git a/node/src/main/kotlin/net/corda/node/services/statemachine/FlowLogicRefFactoryImpl.kt b/node/src/main/kotlin/net/corda/node/services/statemachine/FlowLogicRefFactoryImpl.kt index a3c36fa3a4..f0f10ebed4 100644 --- a/node/src/main/kotlin/net/corda/node/services/statemachine/FlowLogicRefFactoryImpl.kt +++ b/node/src/main/kotlin/net/corda/node/services/statemachine/FlowLogicRefFactoryImpl.kt @@ -2,7 +2,6 @@ package net.corda.node.services.statemachine import net.corda.core.internal.VisibleForTesting import com.google.common.primitives.Primitives -import net.corda.core.cordapp.CordappContext import net.corda.core.flows.* import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.SingletonSerializeAsToken @@ -34,7 +33,7 @@ data class FlowLogicRefImpl internal constructor(val flowLogicClassName: String, */ object FlowLogicRefFactoryImpl : SingletonSerializeAsToken(), FlowLogicRefFactory { // TODO: Replace with a per app classloader/cordapp provider/cordapp loader - this will do for now - var classloader = javaClass.classLoader + var classloader: ClassLoader = javaClass.classLoader override fun create(flowClass: Class>, vararg args: Any?): FlowLogicRef { if (!flowClass.isAnnotationPresent(SchedulableFlow::class.java)) { From 342090db62ae40cef2be30b2ec4aa451b099d0b7 Mon Sep 17 00:00:00 2001 From: Tomas Tauber Date: Fri, 20 Oct 2017 05:17:54 +0800 Subject: [PATCH 22/94] Experimental support for PostgreSQL (#1525) * Cash selection refactoring such that 3d party DB providers are only required to implement Coin Selection SQL logic. * Re-added debug logging statement. * Updated to include PR review feedback from VK * Refactoring following rebase from master. * Fix broken JUnits following rebase. * Use JDBC ResultSet getBlob() and added custom serializer to address concern raised by tomtau in PR. * Fix failing JUnits. * Experimental support for PostgreSQL: CashSelection done using window functions * Moved postgresql version information into corda/build.gradle * Using a PreparedStatement in CashSelectionPostgreSQLImpl * Changed the PostgreSQL Cash Selection implementation to use the new refactored AbstractCashSelection --- build.gradle | 1 + docs/source/changelog.rst | 2 + .../selection/CashSelectionPostgreSQLImpl.kt | 83 +++++++++++++++++++ ...asset.cash.selection.AbstractCashSelection | 1 + node/build.gradle | 3 + .../persistence/HibernateConfiguration.kt | 1 + 6 files changed, 91 insertions(+) create mode 100644 finance/src/main/kotlin/net/corda/finance/contracts/asset/cash/selection/CashSelectionPostgreSQLImpl.kt diff --git a/build.gradle b/build.gradle index 70abd216ca..295f38750f 100644 --- a/build.gradle +++ b/build.gradle @@ -41,6 +41,7 @@ buildscript { ext.jansi_version = '1.14' ext.hibernate_version = '5.2.6.Final' ext.h2_version = '1.4.194' // Update docs if renamed or removed. + ext.postgresql_version = '42.1.4' ext.rxjava_version = '1.2.4' ext.dokka_version = '0.9.14' ext.eddsa_version = '0.2.0' diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst index e043e0c398..fc680cff1b 100644 --- a/docs/source/changelog.rst +++ b/docs/source/changelog.rst @@ -9,6 +9,8 @@ UNRELEASED * ``OpaqueBytes.bytes`` now returns a clone of its underlying ``ByteArray``, and has been redeclared as ``final``. This is a minor change to the public API, but is required to ensure that classes like ``SecureHash`` are immutable. +* Experimental support for PostgreSQL: CashSelection done using window functions + * ``FlowLogic`` now exposes a series of function called ``receiveAll(...)`` allowing to join ``receive(...)`` instructions. * Renamed "plugins" directory on nodes to "cordapps" diff --git a/finance/src/main/kotlin/net/corda/finance/contracts/asset/cash/selection/CashSelectionPostgreSQLImpl.kt b/finance/src/main/kotlin/net/corda/finance/contracts/asset/cash/selection/CashSelectionPostgreSQLImpl.kt new file mode 100644 index 0000000000..d8154336e3 --- /dev/null +++ b/finance/src/main/kotlin/net/corda/finance/contracts/asset/cash/selection/CashSelectionPostgreSQLImpl.kt @@ -0,0 +1,83 @@ +package net.corda.finance.contracts.asset.cash.selection + +import net.corda.core.contracts.Amount +import net.corda.core.identity.AbstractParty +import net.corda.core.identity.Party +import net.corda.core.utilities.OpaqueBytes +import net.corda.core.utilities.debug +import net.corda.core.utilities.loggerFor +import net.corda.core.utilities.toBase58String +import java.sql.Connection +import java.sql.DatabaseMetaData +import java.sql.ResultSet +import java.util.* + +class CashSelectionPostgreSQLImpl : AbstractCashSelection() { + + companion object { + val JDBC_DRIVER_NAME = "PostgreSQL JDBC Driver" + val log = loggerFor() + } + + override fun isCompatible(metadata: DatabaseMetaData): Boolean { + return metadata.driverName == JDBC_DRIVER_NAME + } + + override fun toString() = "${this::class.java} for $JDBC_DRIVER_NAME" + + // This is using PostgreSQL window functions for selecting a minimum set of rows that match a request amount of coins: + // 1) This may also be possible with user-defined functions (e.g. using PL/pgSQL) + // 2) The window function accumulated column (`total`) does not include the current row (starts from 0) and cannot + // appear in the WHERE clause, hence restricting row selection and adjusting the returned total in the outer query. + // 3) Currently (version 9.6), FOR UPDATE cannot be specified with window functions + override fun executeQuery(connection: Connection, amount: Amount, lockId: UUID, notary: Party?, + onlyFromIssuerParties: Set, withIssuerRefs: Set) : ResultSet { + val selectJoin = """SELECT nested.transaction_id, nested.output_index, nested.contract_state, nested.pennies, + nested.total+nested.pennies as total_pennies, nested.lock_id + FROM + (SELECT vs.transaction_id, vs.output_index, vs.contract_state, ccs.pennies, + coalesce((SUM(ccs.pennies) OVER (PARTITION BY 1 ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING)), 0) + AS total, vs.lock_id + FROM vault_states AS vs, contract_cash_states AS ccs + WHERE vs.transaction_id = ccs.transaction_id AND vs.output_index = ccs.output_index + AND vs.state_status = 0 + AND ccs.ccy_code = ? + AND (vs.lock_id = ? OR vs.lock_id is null) + """ + + (if (notary != null) + " AND vs.notary_name = ?" else "") + + (if (onlyFromIssuerParties.isNotEmpty()) + " AND ccs.issuer_key = ANY (?)" else "") + + (if (withIssuerRefs.isNotEmpty()) + " AND ccs.issuer_ref = ANY (?)" else "") + + """) + nested WHERE nested.total < ? + """ + + val statement = connection.prepareStatement(selectJoin) + statement.setString(1, amount.token.toString()) + statement.setString(2, lockId.toString()) + var paramOffset = 0 + if (notary != null) { + statement.setString(3, notary.name.toString()) + paramOffset += 1 + } + if (onlyFromIssuerParties.isNotEmpty()) { + val issuerKeys = connection.createArrayOf("VARCHAR", onlyFromIssuerParties.map + { it.owningKey.toBase58String() }.toTypedArray()) + statement.setArray(3 + paramOffset, issuerKeys) + paramOffset += 1 + } + if (withIssuerRefs.isNotEmpty()) { + val issuerRefs = connection.createArrayOf("BYTEA", withIssuerRefs.map + { it.bytes }.toTypedArray()) + statement.setArray(3 + paramOffset, issuerRefs) + paramOffset += 1 + } + statement.setLong(3 + paramOffset, amount.quantity) + log.debug { statement.toString() } + + return statement.executeQuery() + } + +} diff --git a/finance/src/main/resources/META-INF/services/net.corda.finance.contracts.asset.cash.selection.AbstractCashSelection b/finance/src/main/resources/META-INF/services/net.corda.finance.contracts.asset.cash.selection.AbstractCashSelection index 9ac2461b3c..a4feda21cf 100644 --- a/finance/src/main/resources/META-INF/services/net.corda.finance.contracts.asset.cash.selection.AbstractCashSelection +++ b/finance/src/main/resources/META-INF/services/net.corda.finance.contracts.asset.cash.selection.AbstractCashSelection @@ -1,2 +1,3 @@ net.corda.finance.contracts.asset.cash.selection.CashSelectionH2Impl net.corda.finance.contracts.asset.cash.selection.CashSelectionMySQLImpl +net.corda.finance.contracts.asset.cash.selection.CashSelectionPostgreSQLImpl \ No newline at end of file diff --git a/node/build.gradle b/node/build.gradle index 10bd5877ff..72f45ea297 100644 --- a/node/build.gradle +++ b/node/build.gradle @@ -137,6 +137,9 @@ dependencies { // For H2 database support in persistence compile "com.h2database:h2:$h2_version" + // For Postgres database support in persistence + compile "org.postgresql:postgresql:$postgresql_version" + // SQL connection pooling library compile "com.zaxxer:HikariCP:2.5.1" diff --git a/node/src/main/kotlin/net/corda/node/services/persistence/HibernateConfiguration.kt b/node/src/main/kotlin/net/corda/node/services/persistence/HibernateConfiguration.kt index 8fb0023b3d..589684ec01 100644 --- a/node/src/main/kotlin/net/corda/node/services/persistence/HibernateConfiguration.kt +++ b/node/src/main/kotlin/net/corda/node/services/persistence/HibernateConfiguration.kt @@ -93,6 +93,7 @@ class HibernateConfiguration(val schemaService: SchemaService, private val datab // during schema creation / update. class NodeDatabaseConnectionProvider : ConnectionProvider { override fun closeConnection(conn: Connection) { + conn.autoCommit = false val tx = DatabaseTransactionManager.current() tx.commit() tx.close() From 005ce349a7bee58c32d9fe10bdf6e074de5615ea Mon Sep 17 00:00:00 2001 From: Andrzej Cichocki Date: Fri, 20 Oct 2017 10:06:53 +0100 Subject: [PATCH 23/94] * Retire MockServiceHubInternal (#1909) * Introduce rigorousMock * Add test-utils and node-driver to generated documentation --- .../client/jackson/JacksonSupportTest.kt | 16 ++-- .../core/concurrent/ConcurrencyUtilsTest.kt | 6 +- .../concurrent/CordaFutureImplTest.kt | 10 ++- docs/build.gradle | 13 +-- .../internal/AttachmentsClassLoaderTests.kt | 11 +-- .../serialization/CordaClassResolverTests.kt | 26 +++--- .../serialization/SerializationTokenTest.kt | 5 +- .../node/services/BFTNotaryServiceTests.kt | 3 +- .../services/messaging/P2PSecurityTest.kt | 7 +- .../net/corda/node/InteractiveShellTest.kt | 4 +- .../cordapp/CordappProviderImplTests.kt | 3 +- .../events/NodeSchedulerServiceTest.kt | 75 +++++++++-------- .../messaging/ArtemisMessagingTests.kt | 3 +- .../vault/VaultSoftLockManagerTest.kt | 9 ++- .../NetworkisRegistrationHelperTest.kt | 12 ++- .../node/testing/MockServiceHubInternal.kt | 80 ------------------- .../kotlin/net/corda/testing/NodeTestUtils.kt | 49 +++++++----- .../kotlin/net/corda/testing/node/MockNode.kt | 11 +-- .../kotlin/net/corda/testing/CoreTestUtils.kt | 19 +++++ .../net/corda/demobench/pty/ZeroFilterTest.kt | 11 +-- 20 files changed, 166 insertions(+), 207 deletions(-) delete mode 100644 testing/node-driver/src/main/kotlin/net/corda/node/testing/MockServiceHubInternal.kt diff --git a/client/jackson/src/test/kotlin/net/corda/client/jackson/JacksonSupportTest.kt b/client/jackson/src/test/kotlin/net/corda/client/jackson/JacksonSupportTest.kt index d8595e6bd9..c9ee7af180 100644 --- a/client/jackson/src/test/kotlin/net/corda/client/jackson/JacksonSupportTest.kt +++ b/client/jackson/src/test/kotlin/net/corda/client/jackson/JacksonSupportTest.kt @@ -1,7 +1,7 @@ package net.corda.client.jackson import com.fasterxml.jackson.databind.SerializationFeature -import com.nhaarman.mockito_kotlin.mock +import com.nhaarman.mockito_kotlin.doReturn import com.nhaarman.mockito_kotlin.whenever import net.corda.core.contracts.Amount import net.corda.core.cordapp.CordappProvider @@ -9,10 +9,7 @@ import net.corda.core.crypto.* import net.corda.core.node.ServiceHub import net.corda.core.transactions.SignedTransaction import net.corda.finance.USD -import net.corda.testing.ALICE_PUBKEY -import net.corda.testing.DUMMY_NOTARY -import net.corda.testing.MINI_CORP -import net.corda.testing.TestDependencyInjectionBase +import net.corda.testing.* import net.corda.testing.contracts.DummyContract import org.junit.Before import org.junit.Test @@ -32,9 +29,9 @@ class JacksonSupportTest : TestDependencyInjectionBase() { @Before fun setup() { - services = mock() - cordappProvider = mock() - whenever(services.cordappProvider).thenReturn(cordappProvider) + services = rigorousMock() + cordappProvider = rigorousMock() + doReturn(cordappProvider).whenever(services).cordappProvider } @Test @@ -91,8 +88,7 @@ class JacksonSupportTest : TestDependencyInjectionBase() { @Test fun writeTransaction() { val attachmentRef = SecureHash.randomSHA256() - whenever(cordappProvider.getContractAttachmentID(DummyContract.PROGRAM_ID)) - .thenReturn(attachmentRef) + doReturn(attachmentRef).whenever(cordappProvider).getContractAttachmentID(DummyContract.PROGRAM_ID) fun makeDummyTx(): SignedTransaction { val wtx = DummyContract.generateInitial(1, DUMMY_NOTARY, MINI_CORP.ref(1)) .toWireTransaction(services) diff --git a/core/src/test/kotlin/net/corda/core/concurrent/ConcurrencyUtilsTest.kt b/core/src/test/kotlin/net/corda/core/concurrent/ConcurrencyUtilsTest.kt index a0b4f62453..830398964c 100644 --- a/core/src/test/kotlin/net/corda/core/concurrent/ConcurrencyUtilsTest.kt +++ b/core/src/test/kotlin/net/corda/core/concurrent/ConcurrencyUtilsTest.kt @@ -3,6 +3,7 @@ package net.corda.core.concurrent import com.nhaarman.mockito_kotlin.* import net.corda.core.internal.concurrent.openFuture import net.corda.core.utilities.getOrThrow +import net.corda.testing.rigorousMock import org.assertj.core.api.Assertions.assertThatThrownBy import org.junit.Test import org.slf4j.Logger @@ -16,7 +17,10 @@ class ConcurrencyUtilsTest { private val f1 = openFuture() private val f2 = openFuture() private var invocations = 0 - private val log = mock() + private val log = rigorousMock().also { + doNothing().whenever(it).error(any(), any()) + } + @Test fun `firstOf short circuit`() { // Order not significant in this case: diff --git a/core/src/test/kotlin/net/corda/core/internal/concurrent/CordaFutureImplTest.kt b/core/src/test/kotlin/net/corda/core/internal/concurrent/CordaFutureImplTest.kt index 8b9efe991a..6ca2319239 100644 --- a/core/src/test/kotlin/net/corda/core/internal/concurrent/CordaFutureImplTest.kt +++ b/core/src/test/kotlin/net/corda/core/internal/concurrent/CordaFutureImplTest.kt @@ -3,6 +3,7 @@ package net.corda.core.internal.concurrent import com.nhaarman.mockito_kotlin.* import net.corda.core.concurrent.CordaFuture import net.corda.core.utilities.getOrThrow +import net.corda.testing.rigorousMock import org.assertj.core.api.Assertions import org.junit.Test import org.slf4j.Logger @@ -31,7 +32,7 @@ class CordaFutureTest { fun `if a listener fails its throwable is logged`() { val f = CordaFutureImpl() val x = Exception() - val log = mock() + val log = rigorousMock() val flag = AtomicBoolean() f.thenImpl(log) { throw x } f.thenImpl(log) { flag.set(true) } // Must not be affected by failure of previous listener. @@ -57,7 +58,7 @@ class CordaFutureTest { Assertions.assertThatThrownBy { g.getOrThrow() }.isSameAs(x) } run { - val block = mock<(Any?) -> Any?>() + val block = rigorousMock<(Any?) -> Any?>() val f = CordaFutureImpl() val g = f.map(block) val x = Exception() @@ -90,7 +91,7 @@ class CordaFutureTest { Assertions.assertThatThrownBy { g.getOrThrow() }.isSameAs(x) } run { - val block = mock<(Any?) -> CordaFuture<*>>() + val block = rigorousMock<(Any?) -> CordaFuture<*>>() val f = CordaFutureImpl() val g = f.flatMap(block) val x = Exception() @@ -102,7 +103,8 @@ class CordaFutureTest { @Test fun `andForget works`() { - val log = mock() + val log = rigorousMock() + doNothing().whenever(log).error(any(), any()) val throwable = Exception("Boom") val executor = Executors.newSingleThreadExecutor() executor.fork { throw throwable }.andForget(log) diff --git a/docs/build.gradle b/docs/build.gradle index 542cc7380f..a0d60a371e 100644 --- a/docs/build.gradle +++ b/docs/build.gradle @@ -5,13 +5,17 @@ dependencies { compile rootProject } +ext { + // TODO: Add '../client/jfx/src/main/kotlin' and '../client/mock/src/main/kotlin' if we decide to make them into public API + dokkaSourceDirs = files('../core/src/main/kotlin', '../client/rpc/src/main/kotlin', '../finance/src/main/kotlin', '../client/jackson/src/main/kotlin', + '../testing/test-utils/src/main/kotlin', '../testing/node-driver/src/main/kotlin') +} + dokka { moduleName = 'corda' outputDirectory = file("${rootProject.rootDir}/docs/build/html/api/kotlin") processConfigurations = ['compile'] - // TODO: Re-add '../testing/node-driver/src/main/kotlin', '../testing/test-utils/src/main/kotlin' when they're API stable - // TODO: Add '../client/jfx/src/main/kotlin' and '../client/mock/src/main/kotlin' if we decide to make them into public API - sourceDirs = files('../core/src/main/kotlin', '../client/rpc/src/main/kotlin', '../finance/src/main/kotlin', '../client/jackson/src/main/kotlin') + sourceDirs = dokkaSourceDirs includes = ['packages.md'] jdkVersion = 8 @@ -31,8 +35,7 @@ task dokkaJavadoc(type: org.jetbrains.dokka.gradle.DokkaTask) { outputFormat = "javadoc" outputDirectory = file("${rootProject.rootDir}/docs/build/html/api/javadoc") processConfigurations = ['compile'] - // TODO: Make this a copy of the list above programmatically. - sourceDirs = files('../core/src/main/kotlin', '../client/rpc/src/main/kotlin', '../finance/src/main/kotlin', '../client/jackson/src/main/kotlin') + sourceDirs = dokkaSourceDirs includes = ['packages.md'] jdkVersion = 8 diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/AttachmentsClassLoaderTests.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/AttachmentsClassLoaderTests.kt index 5fd6b46f21..b2cd36ff1f 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/AttachmentsClassLoaderTests.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/AttachmentsClassLoaderTests.kt @@ -1,6 +1,6 @@ package net.corda.nodeapi.internal -import com.nhaarman.mockito_kotlin.mock +import com.nhaarman.mockito_kotlin.doReturn import com.nhaarman.mockito_kotlin.whenever import net.corda.core.contracts.* import net.corda.core.crypto.SecureHash @@ -17,10 +17,7 @@ import net.corda.nodeapi.DummyContractBackdoor import net.corda.nodeapi.internal.serialization.SerializeAsTokenContextImpl import net.corda.nodeapi.internal.serialization.attachmentsClassLoaderEnabledPropertyName import net.corda.nodeapi.internal.serialization.withTokenContext -import net.corda.testing.DUMMY_NOTARY -import net.corda.testing.MEGA_CORP -import net.corda.testing.TestDependencyInjectionBase -import net.corda.testing.kryoSpecific +import net.corda.testing.* import net.corda.testing.node.MockAttachmentStorage import net.corda.testing.node.MockServices import org.apache.commons.io.IOUtils @@ -41,8 +38,8 @@ class AttachmentsClassLoaderTests : TestDependencyInjectionBase() { private const val ISOLATED_CONTRACT_CLASS_NAME = "net.corda.finance.contracts.isolated.AnotherDummyContract" private fun SerializationContext.withAttachmentStorage(attachmentStorage: AttachmentStorage): SerializationContext { - val serviceHub = mock() - whenever(serviceHub.attachments).thenReturn(attachmentStorage) + val serviceHub = rigorousMock() + doReturn(attachmentStorage).whenever(serviceHub).attachments return this.withServiceHub(serviceHub) } diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/CordaClassResolverTests.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/CordaClassResolverTests.kt index cdd54295a6..04f4c69122 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/CordaClassResolverTests.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/CordaClassResolverTests.kt @@ -5,14 +5,13 @@ import com.esotericsoftware.kryo.io.Input import com.esotericsoftware.kryo.io.Output import com.esotericsoftware.kryo.util.DefaultClassResolver import com.esotericsoftware.kryo.util.MapReferenceResolver -import com.nhaarman.mockito_kotlin.mock -import com.nhaarman.mockito_kotlin.verify -import com.nhaarman.mockito_kotlin.whenever +import com.nhaarman.mockito_kotlin.* import net.corda.core.node.services.AttachmentStorage import net.corda.core.serialization.* import net.corda.nodeapi.internal.AttachmentsClassLoader import net.corda.nodeapi.internal.AttachmentsClassLoaderTests import net.corda.testing.node.MockAttachmentStorage +import net.corda.testing.rigorousMock import org.junit.Rule import org.junit.Test import org.junit.rules.ExpectedException @@ -241,10 +240,11 @@ class CordaClassResolverTests { @Test fun `Kotlin EmptyList registers as Java emptyList`() { val javaEmptyListClass = Collections.emptyList().javaClass - val kryo = mock() + val kryo = rigorousMock() val resolver = CordaClassResolver(allButBlacklistedContext).apply { setKryo(kryo) } - whenever(kryo.getDefaultSerializer(javaEmptyListClass)).thenReturn(DefaultSerializableSerializer()) - + doReturn(DefaultSerializableSerializer()).whenever(kryo).getDefaultSerializer(javaEmptyListClass) + doReturn(false).whenever(kryo).references + doReturn(false).whenever(kryo).references = any() val registration = resolver.registerImplicit(emptyListClass) assertNotNull(registration) assertEquals(javaEmptyListClass, registration.type) @@ -262,10 +262,11 @@ class CordaClassResolverTests { @Test fun `Kotlin EmptySet registers as Java emptySet`() { val javaEmptySetClass = Collections.emptySet().javaClass - val kryo = mock() + val kryo = rigorousMock() val resolver = CordaClassResolver(allButBlacklistedContext).apply { setKryo(kryo) } - whenever(kryo.getDefaultSerializer(javaEmptySetClass)).thenReturn(DefaultSerializableSerializer()) - + doReturn(DefaultSerializableSerializer()).whenever(kryo).getDefaultSerializer(javaEmptySetClass) + doReturn(false).whenever(kryo).references + doReturn(false).whenever(kryo).references = any() val registration = resolver.registerImplicit(emptySetClass) assertNotNull(registration) assertEquals(javaEmptySetClass, registration.type) @@ -283,10 +284,11 @@ class CordaClassResolverTests { @Test fun `Kotlin EmptyMap registers as Java emptyMap`() { val javaEmptyMapClass = Collections.emptyMap().javaClass - val kryo = mock() + val kryo = rigorousMock() val resolver = CordaClassResolver(allButBlacklistedContext).apply { setKryo(kryo) } - whenever(kryo.getDefaultSerializer(javaEmptyMapClass)).thenReturn(DefaultSerializableSerializer()) - + doReturn(DefaultSerializableSerializer()).whenever(kryo).getDefaultSerializer(javaEmptyMapClass) + doReturn(false).whenever(kryo).references + doReturn(false).whenever(kryo).references = any() val registration = resolver.registerImplicit(emptyMapClass) assertNotNull(registration) assertEquals(javaEmptyMapClass, registration.type) diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/SerializationTokenTest.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/SerializationTokenTest.kt index 909f672fde..64b2040642 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/SerializationTokenTest.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/SerializationTokenTest.kt @@ -3,10 +3,10 @@ package net.corda.nodeapi.internal.serialization import com.esotericsoftware.kryo.Kryo import com.esotericsoftware.kryo.KryoException import com.esotericsoftware.kryo.io.Output -import com.nhaarman.mockito_kotlin.mock import net.corda.core.serialization.* import net.corda.core.utilities.OpaqueBytes import net.corda.testing.TestDependencyInjectionBase +import net.corda.testing.rigorousMock import org.assertj.core.api.Assertions.assertThat import org.junit.Before import org.junit.Test @@ -35,8 +35,7 @@ class SerializationTokenTest : TestDependencyInjectionBase() { override fun equals(other: Any?) = other is LargeTokenizable && other.bytes.size == this.bytes.size } - private fun serializeAsTokenContext(toBeTokenized: Any) = SerializeAsTokenContextImpl(toBeTokenized, factory, context, mock()) - + private fun serializeAsTokenContext(toBeTokenized: Any) = SerializeAsTokenContextImpl(toBeTokenized, factory, context, rigorousMock()) @Test fun `write token and read tokenizable`() { val tokenizableBefore = LargeTokenizable() diff --git a/node/src/integration-test/kotlin/net/corda/node/services/BFTNotaryServiceTests.kt b/node/src/integration-test/kotlin/net/corda/node/services/BFTNotaryServiceTests.kt index 9a6a5c3112..735a926462 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/BFTNotaryServiceTests.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/BFTNotaryServiceTests.kt @@ -1,5 +1,6 @@ package net.corda.node.services +import com.nhaarman.mockito_kotlin.doReturn import com.nhaarman.mockito_kotlin.whenever import net.corda.core.contracts.AlwaysAcceptAttachmentConstraint import net.corda.core.contracts.ContractState @@ -58,7 +59,7 @@ class BFTNotaryServiceTests { replicaIds.forEach { replicaId -> mockNet.createNode(configOverrides = { val notary = NotaryConfig(validating = false, bftSMaRt = BFTSMaRtConfiguration(replicaId, clusterAddresses, exposeRaces = exposeRaces)) - whenever(it.notary).thenReturn(notary) + doReturn(notary).whenever(it).notary }) } mockNet.runNetwork() // Exchange initial network map registration messages. diff --git a/node/src/integration-test/kotlin/net/corda/services/messaging/P2PSecurityTest.kt b/node/src/integration-test/kotlin/net/corda/services/messaging/P2PSecurityTest.kt index 317252e531..07e396de2b 100644 --- a/node/src/integration-test/kotlin/net/corda/services/messaging/P2PSecurityTest.kt +++ b/node/src/integration-test/kotlin/net/corda/services/messaging/P2PSecurityTest.kt @@ -1,5 +1,6 @@ package net.corda.services.messaging +import com.nhaarman.mockito_kotlin.doReturn import com.nhaarman.mockito_kotlin.whenever import net.corda.core.concurrent.CordaFuture import net.corda.core.crypto.random63BitValue @@ -61,8 +62,8 @@ class P2PSecurityTest : NodeBasedTest() { val config = testNodeConfiguration( baseDirectory = baseDirectory(legalName), myLegalName = legalName).also { - whenever(it.networkMapService).thenReturn(NetworkMapInfo(networkMapNode.internals.configuration.p2pAddress, networkMapNode.info.chooseIdentity().name)) - whenever(it.activeMQServer).thenReturn(ActiveMqServerConfiguration(BridgeConfiguration(1001, 2, 3.4))) + doReturn(NetworkMapInfo(networkMapNode.internals.configuration.p2pAddress, networkMapNode.info.chooseIdentity().name)).whenever(it).networkMapService + doReturn(ActiveMqServerConfiguration(BridgeConfiguration(1001, 2, 3.4))).whenever(it).activeMQServer } config.configureWithDevSSLCertificate() // This creates the node's TLS cert with the CN as the legal name return SimpleNode(config, trustRoot = trustRoot).apply { start() } @@ -73,6 +74,6 @@ class P2PSecurityTest : NodeBasedTest() { val nodeInfo = NodeInfo(listOf(MOCK_HOST_AND_PORT), listOf(legalIdentity), 1, serial = 1) val registration = NodeRegistration(nodeInfo, System.currentTimeMillis(), AddOrRemove.ADD, Instant.MAX) val request = RegistrationRequest(registration.toWire(keyService, identity.public), network.myAddress) - return network.sendRequest(NetworkMapService.REGISTER_TOPIC, request, networkMapNode.network.myAddress) + return network.sendRequest(NetworkMapService.REGISTER_TOPIC, request, networkMapNode.network.myAddress) } } diff --git a/node/src/test/kotlin/net/corda/node/InteractiveShellTest.kt b/node/src/test/kotlin/net/corda/node/InteractiveShellTest.kt index 54117732cf..a1eb79aefb 100644 --- a/node/src/test/kotlin/net/corda/node/InteractiveShellTest.kt +++ b/node/src/test/kotlin/net/corda/node/InteractiveShellTest.kt @@ -1,7 +1,6 @@ package net.corda.node import com.fasterxml.jackson.dataformat.yaml.YAMLFactory -import com.nhaarman.mockito_kotlin.mock import net.corda.client.jackson.JacksonSupport import net.corda.core.contracts.Amount import net.corda.core.crypto.SecureHash @@ -17,6 +16,7 @@ import net.corda.testing.MEGA_CORP import net.corda.testing.MEGA_CORP_IDENTITY import net.corda.testing.node.MockServices import net.corda.testing.node.MockServices.Companion.makeTestIdentityService +import net.corda.testing.rigorousMock import org.junit.After import org.junit.Before import org.junit.Test @@ -83,5 +83,5 @@ class InteractiveShellTest { @Test fun party() = check("party: \"${MEGA_CORP.name}\"", MEGA_CORP.name.toString()) - class DummyFSM(val flowA: FlowA) : FlowStateMachine by mock() + class DummyFSM(val flowA: FlowA) : FlowStateMachine by rigorousMock() } diff --git a/node/src/test/kotlin/net/corda/node/internal/cordapp/CordappProviderImplTests.kt b/node/src/test/kotlin/net/corda/node/internal/cordapp/CordappProviderImplTests.kt index 9f7833f928..af830013d4 100644 --- a/node/src/test/kotlin/net/corda/node/internal/cordapp/CordappProviderImplTests.kt +++ b/node/src/test/kotlin/net/corda/node/internal/cordapp/CordappProviderImplTests.kt @@ -1,6 +1,5 @@ package net.corda.node.internal.cordapp -import com.nhaarman.mockito_kotlin.mock import net.corda.core.node.services.AttachmentStorage import net.corda.testing.node.MockAttachmentStorage import org.junit.Assert @@ -40,7 +39,7 @@ class CordappProviderImplTests { @Test fun `test that we find a cordapp class that is loaded into the store`() { val loader = CordappLoader.createDevMode(listOf(isolatedJAR)) - val provider = CordappProviderImpl(loader, mock()) + val provider = CordappProviderImpl(loader, attachmentStore) val className = "net.corda.finance.contracts.isolated.AnotherDummyContract" val expected = provider.cordapps.first() diff --git a/node/src/test/kotlin/net/corda/node/services/events/NodeSchedulerServiceTest.kt b/node/src/test/kotlin/net/corda/node/services/events/NodeSchedulerServiceTest.kt index 2040fb562b..53c7600ca3 100644 --- a/node/src/test/kotlin/net/corda/node/services/events/NodeSchedulerServiceTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/events/NodeSchedulerServiceTest.kt @@ -1,6 +1,8 @@ package net.corda.node.services.events import co.paralleluniverse.fibers.Suspendable +import com.codahale.metrics.MetricRegistry +import com.nhaarman.mockito_kotlin.* import net.corda.core.contracts.* import net.corda.core.flows.FlowLogic import net.corda.core.flows.FlowLogicRef @@ -8,36 +10,39 @@ import net.corda.core.flows.FlowLogicRefFactory import net.corda.core.identity.AbstractParty import net.corda.core.identity.CordaX500Name import net.corda.core.identity.Party +import net.corda.core.node.NodeInfo import net.corda.core.node.ServiceHub import net.corda.core.serialization.SingletonSerializeAsToken +import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.TransactionBuilder import net.corda.core.utilities.days import net.corda.node.internal.FlowStarterImpl +import net.corda.node.internal.StateLoaderImpl import net.corda.node.internal.cordapp.CordappLoader import net.corda.node.internal.cordapp.CordappProviderImpl -import net.corda.node.services.api.VaultServiceInternal +import net.corda.node.services.api.MonitoringService +import net.corda.node.services.api.ServiceHubInternal import net.corda.node.services.identity.InMemoryIdentityService +import net.corda.node.services.network.NetworkMapCacheImpl import net.corda.node.services.persistence.DBCheckpointStorage import net.corda.node.services.statemachine.FlowLogicRefFactoryImpl import net.corda.node.services.statemachine.StateMachineManager import net.corda.node.services.vault.NodeVaultService -import net.corda.node.testing.MockServiceHubInternal import net.corda.node.utilities.AffinityExecutor import net.corda.node.utilities.CordaPersistence import net.corda.node.utilities.configureDatabase import net.corda.testing.* import net.corda.testing.contracts.DummyContract -import net.corda.testing.node.InMemoryMessagingNetwork -import net.corda.testing.node.MockKeyManagementService +import net.corda.testing.node.* import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties import net.corda.testing.node.MockServices.Companion.makeTestDatabaseProperties import net.corda.testing.node.MockServices.Companion.makeTestIdentityService -import net.corda.testing.node.TestClock import org.assertj.core.api.Assertions.assertThat import org.junit.After import org.junit.Before import org.junit.Test import java.nio.file.Paths +import java.security.PublicKey import java.time.Clock import java.time.Instant import java.util.concurrent.CountDownLatch @@ -46,20 +51,26 @@ import java.util.concurrent.TimeUnit import kotlin.test.assertTrue class NodeSchedulerServiceTest : SingletonSerializeAsToken() { + companion object { + private val myInfo = NodeInfo(listOf(MOCK_HOST_AND_PORT), listOf(DUMMY_IDENTITY_1), 1, serial = 1L) + } + private val realClock: Clock = Clock.systemUTC() private val stoppedClock: Clock = Clock.fixed(realClock.instant(), realClock.zone) private val testClock = TestClock(stoppedClock) private val schedulerGatedExecutor = AffinityExecutor.Gate(true) - private lateinit var services: MockServiceHubInternal + abstract class Services : ServiceHubInternal, TestReference + private lateinit var services: Services private lateinit var scheduler: NodeSchedulerService private lateinit var smmExecutor: AffinityExecutor.ServiceAffinityExecutor private lateinit var database: CordaPersistence private lateinit var countDown: CountDownLatch private lateinit var smmHasRemovedAllFlows: CountDownLatch - + private lateinit var kms: MockKeyManagementService + private lateinit var mockSMM: StateMachineManager var calls: Int = 0 /** @@ -81,35 +92,35 @@ class NodeSchedulerServiceTest : SingletonSerializeAsToken() { val databaseProperties = makeTestDatabaseProperties() database = configureDatabase(dataSourceProps, databaseProperties, ::makeTestIdentityService) val identityService = InMemoryIdentityService(trustRoot = DEV_TRUST_ROOT) - val kms = MockKeyManagementService(identityService, ALICE_KEY) - + kms = MockKeyManagementService(identityService, ALICE_KEY) + val configuration = testNodeConfiguration(Paths.get("."), CordaX500Name("Alice", "London", "GB")) + val validatedTransactions = MockTransactionStorage() + val stateLoader = StateLoaderImpl(validatedTransactions) database.transaction { - val nullIdentity = CordaX500Name(organisation = "None", locality = "None", country = "GB") - val mockMessagingService = InMemoryMessagingNetwork(false).InMemoryMessaging( - false, - InMemoryMessagingNetwork.PeerHandle(0, nullIdentity), - AffinityExecutor.ServiceAffinityExecutor("test", 1), - database) - services = object : MockServiceHubInternal( - database, - testNodeConfiguration(Paths.get("."), CordaX500Name(organisation = "Alice", locality = "London", country = "GB")), - overrideClock = testClock, - keyManagement = kms, - network = mockMessagingService), TestReference { - override val vaultService: VaultServiceInternal = NodeVaultService(testClock, kms, stateLoader, database.hibernateConfig) - override val testReference = this@NodeSchedulerServiceTest - override val cordappProvider = CordappProviderImpl(CordappLoader.createWithTestPackages(listOf("net.corda.testing.contracts")), attachments) + services = rigorousMock().also { + doReturn(configuration).whenever(it).configuration + doReturn(MonitoringService(MetricRegistry())).whenever(it).monitoringService + doReturn(validatedTransactions).whenever(it).validatedTransactions + doReturn(NetworkMapCacheImpl(MockNetworkMapCache(database, configuration), identityService)).whenever(it).networkMapCache + doCallRealMethod().whenever(it).signInitialTransaction(any(), any()) + doReturn(myInfo).whenever(it).myInfo + doReturn(kms).whenever(it).keyManagementService + doReturn(CordappProviderImpl(CordappLoader.createWithTestPackages(listOf("net.corda.testing.contracts")), MockAttachmentStorage())).whenever(it).cordappProvider + doCallRealMethod().whenever(it).recordTransactions(any()) + doCallRealMethod().whenever(it).recordTransactions(any(), any()) + doCallRealMethod().whenever(it).recordTransactions(any(), any>()) + doReturn(NodeVaultService(testClock, kms, stateLoader, database.hibernateConfig)).whenever(it).vaultService + doReturn(this@NodeSchedulerServiceTest).whenever(it).testReference } smmExecutor = AffinityExecutor.ServiceAffinityExecutor("test", 1) - val mockSMM = StateMachineManager(services, DBCheckpointStorage(), smmExecutor, database) - scheduler = NodeSchedulerService(testClock, database, FlowStarterImpl(smmExecutor, mockSMM), services.stateLoader, schedulerGatedExecutor, serverThread = smmExecutor) + mockSMM = StateMachineManager(services, DBCheckpointStorage(), smmExecutor, database) + scheduler = NodeSchedulerService(testClock, database, FlowStarterImpl(smmExecutor, mockSMM), stateLoader, schedulerGatedExecutor, serverThread = smmExecutor) mockSMM.changes.subscribe { change -> if (change is StateMachineManager.Change.Removed && mockSMM.allStateMachines.isEmpty()) { smmHasRemovedAllFlows.countDown() } } mockSMM.start() - services.smm = mockSMM scheduler.start() } } @@ -117,7 +128,7 @@ class NodeSchedulerServiceTest : SingletonSerializeAsToken() { @After fun tearDown() { // We need to make sure the StateMachineManager is done before shutting down executors. - if (services.smm.allStateMachines.isNotEmpty()) { + if (mockSMM.allStateMachines.isNotEmpty()) { smmHasRemovedAllFlows.await() } smmExecutor.shutdown() @@ -126,7 +137,7 @@ class NodeSchedulerServiceTest : SingletonSerializeAsToken() { resetTestSerialization() } - class TestState(val flowLogicRef: FlowLogicRef, val instant: Instant, val myIdentity: Party) : LinearState, SchedulableState { + class TestState(private val flowLogicRef: FlowLogicRef, val instant: Instant, private val myIdentity: Party) : LinearState, SchedulableState { override val participants: List get() = listOf(myIdentity) @@ -137,7 +148,7 @@ class NodeSchedulerServiceTest : SingletonSerializeAsToken() { } } - class TestFlowLogic(val increment: Int = 1) : FlowLogic() { + class TestFlowLogic(private val increment: Int = 1) : FlowLogic() { @Suspendable override fun call() { (serviceHub as TestReference).testReference.calls += increment @@ -280,8 +291,8 @@ class NodeSchedulerServiceTest : SingletonSerializeAsToken() { var scheduledRef: ScheduledStateRef? = null database.transaction { apply { - val freshKey = services.keyManagementService.freshKey() - val state = TestState(FlowLogicRefFactoryImpl.createForRPC(TestFlowLogic::class.java, increment), instant, services.myInfo.chooseIdentity()) + val freshKey = kms.freshKey() + val state = TestState(FlowLogicRefFactoryImpl.createForRPC(TestFlowLogic::class.java, increment), instant, myInfo.chooseIdentity()) val builder = TransactionBuilder(null).apply { addOutputState(state, DummyContract.PROGRAM_ID, DUMMY_NOTARY) addCommand(Command(), freshKey) diff --git a/node/src/test/kotlin/net/corda/node/services/messaging/ArtemisMessagingTests.kt b/node/src/test/kotlin/net/corda/node/services/messaging/ArtemisMessagingTests.kt index b4504546bc..b7fdbd40eb 100644 --- a/node/src/test/kotlin/net/corda/node/services/messaging/ArtemisMessagingTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/messaging/ArtemisMessagingTests.kt @@ -1,7 +1,6 @@ package net.corda.node.services.messaging import com.codahale.metrics.MetricRegistry -import com.nhaarman.mockito_kotlin.mock import net.corda.core.concurrent.CordaFuture import net.corda.core.crypto.generateKeyPair import net.corda.core.internal.concurrent.doneFuture @@ -74,7 +73,7 @@ class ArtemisMessagingTests : TestDependencyInjectionBase() { LogHelper.setLevel(PersistentUniquenessProvider::class) database = configureDatabase(makeTestDataSourceProperties(), makeTestDatabaseProperties(), ::makeTestIdentityService) networkMapRegistrationFuture = doneFuture(Unit) - networkMapCache = NetworkMapCacheImpl(PersistentNetworkMapCache(database, config), mock()) + networkMapCache = NetworkMapCacheImpl(PersistentNetworkMapCache(database, config), rigorousMock()) } @After diff --git a/node/src/test/kotlin/net/corda/node/services/vault/VaultSoftLockManagerTest.kt b/node/src/test/kotlin/net/corda/node/services/vault/VaultSoftLockManagerTest.kt index d0d7562234..48a8347969 100644 --- a/node/src/test/kotlin/net/corda/node/services/vault/VaultSoftLockManagerTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/vault/VaultSoftLockManagerTest.kt @@ -1,9 +1,7 @@ package net.corda.node.services.vault import co.paralleluniverse.fibers.Suspendable -import com.nhaarman.mockito_kotlin.mock -import com.nhaarman.mockito_kotlin.verify -import com.nhaarman.mockito_kotlin.verifyNoMoreInteractions +import com.nhaarman.mockito_kotlin.* import net.corda.core.contracts.* import net.corda.core.flows.FinalityFlow import net.corda.core.flows.FlowLogic @@ -32,6 +30,7 @@ import net.corda.node.services.config.NodeConfiguration import net.corda.nodeapi.internal.ServiceInfo import net.corda.testing.chooseIdentity import net.corda.testing.node.MockNetwork +import net.corda.testing.rigorousMock import org.junit.After import org.junit.Test import java.math.BigInteger @@ -81,7 +80,9 @@ private class NodePair(private val mockNet: MockNetwork) { } class VaultSoftLockManagerTest { - private val mockVault: VaultServiceInternal = mock() + private val mockVault = rigorousMock().also { + doNothing().whenever(it).softLockRelease(any(), anyOrNull()) + } private val mockNet = MockNetwork(cordappPackages = listOf(ContractImpl::class.packageName), defaultFactory = object : MockNetwork.Factory { override fun create(config: NodeConfiguration, network: MockNetwork, networkMapAddr: SingleMessageRecipient?, id: Int, notaryIdentity: Pair?, entropyRoot: BigInteger): MockNetwork.MockNode { return object : MockNetwork.MockNode(config, network, networkMapAddr, id, notaryIdentity, entropyRoot) { diff --git a/node/src/test/kotlin/net/corda/node/utilities/registration/NetworkisRegistrationHelperTest.kt b/node/src/test/kotlin/net/corda/node/utilities/registration/NetworkisRegistrationHelperTest.kt index 54ad6a4310..8454a03cd9 100644 --- a/node/src/test/kotlin/net/corda/node/utilities/registration/NetworkisRegistrationHelperTest.kt +++ b/node/src/test/kotlin/net/corda/node/utilities/registration/NetworkisRegistrationHelperTest.kt @@ -1,8 +1,6 @@ package net.corda.node.utilities.registration -import com.nhaarman.mockito_kotlin.any -import com.nhaarman.mockito_kotlin.eq -import com.nhaarman.mockito_kotlin.mock +import com.nhaarman.mockito_kotlin.* import net.corda.core.crypto.Crypto import net.corda.core.crypto.SecureHash import net.corda.core.identity.CordaX500Name @@ -11,6 +9,7 @@ import net.corda.node.utilities.X509Utilities import net.corda.node.utilities.getX509Certificate import net.corda.node.utilities.loadKeyStore import net.corda.testing.ALICE +import net.corda.testing.rigorousMock import net.corda.testing.testNodeConfiguration import org.bouncycastle.asn1.x500.X500Name import org.bouncycastle.asn1.x500.style.BCStyle @@ -38,10 +37,9 @@ class NetworkRegistrationHelperTest { .map { CordaX500Name(commonName = it, organisation = "R3 Ltd", locality = "London", country = "GB") } val certs = identities.stream().map { X509Utilities.createSelfSignedCACertificate(it, Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)) } .map { it.cert }.toTypedArray() - - val certService: NetworkRegistrationService = mock { - on { submitRequest(any()) }.then { id } - on { retrieveCertificates(eq(id)) }.then { certs } + val certService = rigorousMock().also { + doReturn(id).whenever(it).submitRequest(any()) + doReturn(certs).whenever(it).retrieveCertificates(eq(id)) } val config = testNodeConfiguration( diff --git a/testing/node-driver/src/main/kotlin/net/corda/node/testing/MockServiceHubInternal.kt b/testing/node-driver/src/main/kotlin/net/corda/node/testing/MockServiceHubInternal.kt deleted file mode 100644 index 62fdc58934..0000000000 --- a/testing/node-driver/src/main/kotlin/net/corda/node/testing/MockServiceHubInternal.kt +++ /dev/null @@ -1,80 +0,0 @@ -package net.corda.node.testing - -import com.codahale.metrics.MetricRegistry -import net.corda.core.flows.FlowLogic -import net.corda.core.node.NodeInfo -import net.corda.core.node.StateLoader -import net.corda.core.node.services.* -import net.corda.core.serialization.SerializeAsToken -import net.corda.node.internal.InitiatedFlowFactory -import net.corda.node.internal.StateLoaderImpl -import net.corda.node.internal.cordapp.CordappLoader -import net.corda.node.internal.cordapp.CordappProviderImpl -import net.corda.node.internal.cordapp.CordappProviderInternal -import net.corda.node.serialization.NodeClock -import net.corda.node.services.api.* -import net.corda.node.services.config.NodeConfiguration -import net.corda.node.services.messaging.MessagingService -import net.corda.node.services.network.NetworkMapCacheImpl -import net.corda.node.services.statemachine.StateMachineManager -import net.corda.node.services.transactions.InMemoryTransactionVerifierService -import net.corda.node.utilities.CordaPersistence -import net.corda.testing.DUMMY_IDENTITY_1 -import net.corda.testing.MOCK_HOST_AND_PORT -import net.corda.testing.MOCK_IDENTITY_SERVICE -import net.corda.testing.node.MockAttachmentStorage -import net.corda.testing.node.MockNetworkMapCache -import net.corda.testing.node.MockStateMachineRecordedTransactionMappingStorage -import net.corda.testing.node.MockTransactionStorage -import java.nio.file.Paths -import java.sql.Connection -import java.time.Clock - -open class MockServiceHubInternal( - override val database: CordaPersistence, - override val configuration: NodeConfiguration, - val customVault: VaultServiceInternal? = null, - val keyManagement: KeyManagementService? = null, - val network: MessagingService? = null, - val identity: IdentityService? = MOCK_IDENTITY_SERVICE, - override val attachments: AttachmentStorage = MockAttachmentStorage(), - override val validatedTransactions: WritableTransactionStorage = MockTransactionStorage(), - override val stateMachineRecordedTransactionMapping: StateMachineRecordedTransactionMappingStorage = MockStateMachineRecordedTransactionMappingStorage(), - val mapCache: NetworkMapCacheInternal? = null, - val overrideClock: Clock? = NodeClock(), - val customContractUpgradeService: ContractUpgradeService? = null, - val customTransactionVerifierService: TransactionVerifierService? = InMemoryTransactionVerifierService(2), - override val cordappProvider: CordappProviderInternal = CordappProviderImpl(CordappLoader.createDefault(Paths.get(".")), attachments), - val stateLoader: StateLoaderImpl = StateLoaderImpl(validatedTransactions) -) : ServiceHubInternal, StateLoader by stateLoader { - override val transactionVerifierService: TransactionVerifierService - get() = customTransactionVerifierService ?: throw UnsupportedOperationException() - override val vaultService: VaultServiceInternal - get() = customVault ?: throw UnsupportedOperationException() - override val contractUpgradeService: ContractUpgradeService - get() = customContractUpgradeService ?: throw UnsupportedOperationException() - override val keyManagementService: KeyManagementService - get() = keyManagement ?: throw UnsupportedOperationException() - override val identityService: IdentityService - get() = identity ?: throw UnsupportedOperationException() - override val networkService: MessagingService - get() = network ?: throw UnsupportedOperationException() - override val networkMapCache: NetworkMapCacheInternal - get() = mapCache ?: NetworkMapCacheImpl(MockNetworkMapCache(database, configuration), identityService) - override val clock: Clock - get() = overrideClock ?: throw UnsupportedOperationException() - override val myInfo: NodeInfo - get() = NodeInfo(listOf(MOCK_HOST_AND_PORT), listOf(DUMMY_IDENTITY_1), 1, serial = 1L) // Required to get a dummy platformVersion when required for tests. - override val monitoringService: MonitoringService = MonitoringService(MetricRegistry()) - override val rpcFlows: List>> - get() = throw UnsupportedOperationException() - override val schemaService get() = throw UnsupportedOperationException() - override val auditService: AuditService = DummyAuditService() - - lateinit var smm: StateMachineManager - - override fun cordaService(type: Class): T = throw UnsupportedOperationException() - override fun getFlowFactory(initiatingFlowClass: Class>): InitiatedFlowFactory<*>? = null - - override fun jdbcSession(): Connection = database.createSession() -} diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/NodeTestUtils.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/NodeTestUtils.kt index 551a8075b8..aed33f1885 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/NodeTestUtils.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/NodeTestUtils.kt @@ -2,14 +2,17 @@ package net.corda.testing -import com.nhaarman.mockito_kotlin.spy +import com.nhaarman.mockito_kotlin.doCallRealMethod +import com.nhaarman.mockito_kotlin.doReturn import com.nhaarman.mockito_kotlin.whenever import net.corda.core.identity.CordaX500Name import net.corda.core.node.ServiceHub import net.corda.core.transactions.TransactionBuilder +import net.corda.node.services.config.CertChainPolicyConfig import net.corda.node.services.config.NodeConfiguration import net.corda.node.services.config.NotaryConfig import net.corda.node.services.config.VerifierType +import net.corda.nodeapi.User import net.corda.testing.node.MockServices import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties import net.corda.testing.node.MockServices.Companion.makeTestDatabaseProperties @@ -57,23 +60,29 @@ fun testNodeConfiguration( myLegalName: CordaX500Name, notaryConfig: NotaryConfig? = null): NodeConfiguration { abstract class MockableNodeConfiguration : NodeConfiguration // Otherwise Mockito is defeated by val getters. - - val nc = spy() - whenever(nc.baseDirectory).thenReturn(baseDirectory) - whenever(nc.myLegalName).thenReturn(myLegalName) - whenever(nc.minimumPlatformVersion).thenReturn(1) - whenever(nc.keyStorePassword).thenReturn("cordacadevpass") - whenever(nc.trustStorePassword).thenReturn("trustpass") - whenever(nc.rpcUsers).thenReturn(emptyList()) - whenever(nc.notary).thenReturn(notaryConfig) - whenever(nc.dataSourceProperties).thenReturn(makeTestDataSourceProperties(myLegalName.organisation)) - whenever(nc.database).thenReturn(makeTestDatabaseProperties()) - whenever(nc.emailAddress).thenReturn("") - whenever(nc.exportJMXto).thenReturn("") - whenever(nc.devMode).thenReturn(true) - whenever(nc.certificateSigningService).thenReturn(URL("http://localhost")) - whenever(nc.certificateChainCheckPolicies).thenReturn(emptyList()) - whenever(nc.verifierType).thenReturn(VerifierType.InMemory) - whenever(nc.messageRedeliveryDelaySeconds).thenReturn(5) - return nc + return rigorousMock().also { + doReturn(baseDirectory).whenever(it).baseDirectory + doReturn(myLegalName).whenever(it).myLegalName + doReturn(1).whenever(it).minimumPlatformVersion + doReturn("cordacadevpass").whenever(it).keyStorePassword + doReturn("trustpass").whenever(it).trustStorePassword + doReturn(emptyList()).whenever(it).rpcUsers + doReturn(notaryConfig).whenever(it).notary + doReturn(makeTestDataSourceProperties(myLegalName.organisation)).whenever(it).dataSourceProperties + doReturn(makeTestDatabaseProperties()).whenever(it).database + doReturn("").whenever(it).emailAddress + doReturn("").whenever(it).exportJMXto + doReturn(true).whenever(it).devMode + doReturn(URL("http://localhost")).whenever(it).certificateSigningService + doReturn(emptyList()).whenever(it).certificateChainCheckPolicies + doReturn(VerifierType.InMemory).whenever(it).verifierType + doReturn(5).whenever(it).messageRedeliveryDelaySeconds + doReturn(0L).whenever(it).additionalNodeInfoPollingFrequencyMsec + doReturn(null).whenever(it).networkMapService + doCallRealMethod().whenever(it).certificatesDirectory + doCallRealMethod().whenever(it).trustStoreFile + doCallRealMethod().whenever(it).sslKeystore + doCallRealMethod().whenever(it).nodeKeystore + doReturn(false).whenever(it).noNetworkMapServiceMode + } } diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt index f574d4d9bb..9efcc1cefb 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt @@ -2,13 +2,13 @@ package net.corda.testing.node import com.google.common.jimfs.Configuration.unix import com.google.common.jimfs.Jimfs +import com.nhaarman.mockito_kotlin.doReturn import com.nhaarman.mockito_kotlin.whenever import net.corda.core.crypto.entropyToKeyPair import net.corda.core.crypto.random63BitValue import net.corda.core.identity.CordaX500Name import net.corda.core.identity.Party import net.corda.core.identity.PartyAndCertificate -import net.corda.core.internal.cert import net.corda.core.internal.concurrent.doneFuture import net.corda.core.internal.createDirectories import net.corda.core.internal.createDirectory @@ -31,7 +31,6 @@ import net.corda.node.services.api.SchemaService import net.corda.node.services.config.BFTSMaRtConfiguration import net.corda.node.services.config.NodeConfiguration import net.corda.node.services.config.NotaryConfig -import net.corda.node.services.identity.PersistentIdentityService import net.corda.node.services.keys.E2ETestKeyManagementService import net.corda.node.services.messaging.MessagingService import net.corda.node.services.network.InMemoryNetworkMapService @@ -41,7 +40,6 @@ import net.corda.node.services.transactions.BFTSMaRt import net.corda.node.services.transactions.InMemoryTransactionVerifierService import net.corda.node.utilities.AffinityExecutor import net.corda.node.utilities.AffinityExecutor.ServiceAffinityExecutor -import net.corda.node.utilities.CertificateAndKeyPair import net.corda.nodeapi.internal.ServiceInfo import net.corda.testing.* import net.corda.testing.node.MockServices.Companion.MOCK_VERSION_INFO @@ -53,7 +51,6 @@ import java.math.BigInteger import java.nio.file.Path import java.security.KeyPair import java.security.PublicKey -import java.security.cert.X509Certificate import java.util.concurrent.TimeUnit import java.util.concurrent.atomic.AtomicInteger @@ -353,7 +350,7 @@ class MockNetwork(defaultParameters: MockNetworkParameters = MockNetworkParamete val config = testNodeConfiguration( baseDirectory = baseDirectory(id).createDirectories(), myLegalName = legalName ?: CordaX500Name(organisation = "Mock Company $id", locality = "London", country = "GB")).also { - whenever(it.dataSourceProperties).thenReturn(makeTestDataSourceProperties("node_${id}_net_$networkId")) + doReturn(makeTestDataSourceProperties("node_${id}_net_$networkId")).whenever(it).dataSourceProperties configOverrides(it) } return nodeFactory.create(config, this, networkMapAddress, id, notaryIdentity, entropyRoot).apply { @@ -391,7 +388,7 @@ class MockNetwork(defaultParameters: MockNetworkParameters = MockNetworkParamete @JvmOverloads fun createNotaryNode(legalName: CordaX500Name = DUMMY_NOTARY.name, validating: Boolean = true): StartedNode { return createNode(legalName = legalName, configOverrides = { - whenever(it.notary).thenReturn(NotaryConfig(validating)) + doReturn(NotaryConfig(validating)).whenever(it).notary }) } @@ -399,7 +396,7 @@ class MockNetwork(defaultParameters: MockNetworkParameters = MockNetworkParamete validating: Boolean = true, nodeFactory: Factory): StartedNode { return createNode(legalName = legalName, nodeFactory = nodeFactory, configOverrides = { - whenever(it.notary).thenReturn(NotaryConfig(validating)) + doReturn(NotaryConfig(validating)).whenever(it).notary }) } diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/CoreTestUtils.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/CoreTestUtils.kt index 71be78c0bd..a1cda49a22 100644 --- a/testing/test-utils/src/main/kotlin/net/corda/testing/CoreTestUtils.kt +++ b/testing/test-utils/src/main/kotlin/net/corda/testing/CoreTestUtils.kt @@ -3,6 +3,7 @@ package net.corda.testing +import com.nhaarman.mockito_kotlin.mock import net.corda.core.contracts.StateRef import net.corda.core.crypto.SecureHash import net.corda.core.crypto.generateKeyPair @@ -24,6 +25,8 @@ import net.corda.node.utilities.CertificateType import net.corda.node.utilities.X509Utilities import net.corda.nodeapi.config.SSLConfiguration import net.corda.nodeapi.internal.serialization.AMQP_ENABLED +import org.mockito.internal.stubbing.answers.ThrowsException +import org.mockito.stubbing.Answer import java.nio.file.Files import java.security.KeyPair import java.security.PublicKey @@ -177,3 +180,19 @@ fun NodeInfo.singleIdentityAndCert(): PartyAndCertificate = legalIdentitiesAndCe fun NodeInfo.singleIdentity(): Party = singleIdentityAndCert().party /** Returns the identity of the first notary found on the network */ fun ServiceHub.getDefaultNotary(): Party = networkMapCache.notaryIdentities.first() + +/** + * A method on a mock was called, but no behaviour was previously specified for that method. + * You can use [com.nhaarman.mockito_kotlin.doReturn] or similar to specify behaviour, see Mockito documentation for details. + */ +class UndefinedMockBehaviorException(message: String) : RuntimeException(message) + +/** + * Create a Mockito mock that has [UndefinedMockBehaviorException] as the default behaviour of all methods. + * @param T the type to mock. Note if you want to use [com.nhaarman.mockito_kotlin.doCallRealMethod] on a Kotlin interface, + * it won't work unless you mock a (trivial) abstract implementation of that interface instead. + */ +inline fun rigorousMock() = mock(Answer { + // Use ThrowsException to hack the stack trace, and lazily so we can customise the message: + ThrowsException(UndefinedMockBehaviorException("Please specify what should happen when '${it.method}' is called, or don't call it.")).answer(it) +}) diff --git a/tools/demobench/src/test/kotlin/net/corda/demobench/pty/ZeroFilterTest.kt b/tools/demobench/src/test/kotlin/net/corda/demobench/pty/ZeroFilterTest.kt index 3f9bb93d17..41ccda83bf 100644 --- a/tools/demobench/src/test/kotlin/net/corda/demobench/pty/ZeroFilterTest.kt +++ b/tools/demobench/src/test/kotlin/net/corda/demobench/pty/ZeroFilterTest.kt @@ -1,9 +1,12 @@ package net.corda.demobench.pty +import com.nhaarman.mockito_kotlin.doReturn +import com.nhaarman.mockito_kotlin.verify +import com.nhaarman.mockito_kotlin.whenever +import net.corda.testing.rigorousMock import org.junit.Assert.assertEquals import org.junit.Before import org.junit.Test -import org.mockito.Mockito.* import java.io.ByteArrayOutputStream import java.io.OutputStream import java.nio.charset.StandardCharsets.UTF_8 @@ -15,10 +18,8 @@ class ZeroFilterTest { @Before fun setup() { output = ByteArrayOutputStream() - - val process = mock(Process::class.java) - `when`(process.outputStream).thenReturn(output) - + val process = rigorousMock() + doReturn(output).whenever(process).outputStream filter = process.zeroFiltered().outputStream verify(process).outputStream } From 11db199b7be0cf9095fc7586a84d04a9092ee515 Mon Sep 17 00:00:00 2001 From: Viktor Kolomeyko Date: Fri, 20 Oct 2017 11:00:48 +0100 Subject: [PATCH 24/94] Fix-up: Bank Of Corda sample (#1912) In the previous version when running with `--role ISSUER` the application failed to start. The reason was that in spite of `quantity` and `currency` were optional, un-necessary `requestParams` been constructed regardless. --- .../net/corda/bank/BankOfCordaDriver.kt | 36 +++++++++++-------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/samples/bank-of-corda-demo/src/main/kotlin/net/corda/bank/BankOfCordaDriver.kt b/samples/bank-of-corda-demo/src/main/kotlin/net/corda/bank/BankOfCordaDriver.kt index 81dbf12166..b24a49b33a 100644 --- a/samples/bank-of-corda-demo/src/main/kotlin/net/corda/bank/BankOfCordaDriver.kt +++ b/samples/bank-of-corda-demo/src/main/kotlin/net/corda/bank/BankOfCordaDriver.kt @@ -23,8 +23,8 @@ fun main(args: Array) { BankOfCordaDriver().main(args) } -val BANK_USERNAME = "bankUser" -val BIGCORP_USERNAME = "bigCorpUser" +const val BANK_USERNAME = "bankUser" +const val BIGCORP_USERNAME = "bigCorpUser" val BIGCORP_LEGAL_NAME = CordaX500Name(organisation = "BigCorporation", locality = "New York", country = "US") @@ -53,9 +53,6 @@ private class BankOfCordaDriver { // The ISSUE_CASH will request some Cash from the ISSUER on behalf of Big Corporation node val role = options.valueOf(roleArg)!! - val requestParams = IssueRequestParams(options.valueOf(quantity), options.valueOf(currency), BIGCORP_LEGAL_NAME, - "1", BOC.name, DUMMY_NOTARY.name.copy(commonName = ValidatingNotaryService.id)) - try { when (role) { Role.ISSUER -> { @@ -78,16 +75,25 @@ private class BankOfCordaDriver { waitForAllNodesToFinish() }, isDebug = true) } - Role.ISSUE_CASH_RPC -> { - println("Requesting Cash via RPC ...") - val result = BankOfCordaClientApi(NetworkHostAndPort("localhost", 10006)).requestRPCIssue(requestParams) - println("Success!! You transaction receipt is ${result.tx.id}") - } - Role.ISSUE_CASH_WEB -> { - println("Requesting Cash via Web ...") - val result = BankOfCordaClientApi(NetworkHostAndPort("localhost", 10007)).requestWebIssue(requestParams) - if (result) - println("Successfully processed Cash Issue request") + else -> { + val requestParams = IssueRequestParams(options.valueOf(quantity), options.valueOf(currency), BIGCORP_LEGAL_NAME, + "1", BOC.name, DUMMY_NOTARY.name.copy(commonName = ValidatingNotaryService.id)) + when(role) { + Role.ISSUE_CASH_RPC -> { + println("Requesting Cash via RPC ...") + val result = BankOfCordaClientApi(NetworkHostAndPort("localhost", 10006)).requestRPCIssue(requestParams) + println("Success!! You transaction receipt is ${result.tx.id}") + } + Role.ISSUE_CASH_WEB -> { + println("Requesting Cash via Web ...") + val result = BankOfCordaClientApi(NetworkHostAndPort("localhost", 10007)).requestWebIssue(requestParams) + if (result) + println("Successfully processed Cash Issue request") + } + else -> { + throw IllegalArgumentException("Unrecognized role: " + role) + } + } } } } catch (e: Exception) { From 54f901c4fe1bf0752e45fca5c72f7b85c1c99609 Mon Sep 17 00:00:00 2001 From: Andras Slemmer Date: Mon, 11 Sep 2017 12:08:22 +0100 Subject: [PATCH 25/94] move SMM --- .../{StateMachineManager.kt => StateMachineManagerImpl.kt} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename node/src/main/kotlin/net/corda/node/services/statemachine/{StateMachineManager.kt => StateMachineManagerImpl.kt} (100%) diff --git a/node/src/main/kotlin/net/corda/node/services/statemachine/StateMachineManager.kt b/node/src/main/kotlin/net/corda/node/services/statemachine/StateMachineManagerImpl.kt similarity index 100% rename from node/src/main/kotlin/net/corda/node/services/statemachine/StateMachineManager.kt rename to node/src/main/kotlin/net/corda/node/services/statemachine/StateMachineManagerImpl.kt From c66a84bfc65c4f03ad6a497c0db1a6a824ec9b44 Mon Sep 17 00:00:00 2001 From: Andras Slemmer Date: Mon, 11 Sep 2017 12:09:32 +0100 Subject: [PATCH 26/94] Interface changes for multi-threading --- .ci/api-current.txt | 4 + .../corda/client/rpc/CordaRPCClientTest.kt | 3 +- .../kotlin/net/corda/core/flows/FlowLogic.kt | 31 +++++-- .../net/corda/core/flows/FlowSession.kt | 63 +++++++++++++- .../corda/core/internal/FlowStateMachine.kt | 11 +-- .../net/corda/docs/CustomVaultQueryTest.kt | 10 ++- .../net/corda/node/internal/AbstractNode.kt | 58 ++++++++----- .../corda/node/internal/CordaRPCOpsImpl.kt | 9 +- .../net/corda/node/internal/StartedNode.kt | 5 -- .../node/services/api/ServiceHubInternal.kt | 7 +- .../services/events/NodeSchedulerService.kt | 10 ++- .../node/services/messaging/Messaging.kt | 35 +++++++- .../services/messaging/NodeMessagingClient.kt | 14 ++- .../services/statemachine/FlowSessionImpl.kt | 40 ++++++--- .../statemachine/FlowStateMachineImpl.kt | 12 +-- .../statemachine/StateMachineManager.kt | 78 +++++++++++++++++ .../statemachine/StateMachineManagerImpl.kt | 85 +++++++------------ .../net/corda/node/shell/InteractiveShell.kt | 3 +- .../net/corda/node/InteractiveShellTest.kt | 4 +- .../events/NodeSchedulerServiceTest.kt | 5 +- .../statemachine/FlowFrameworkTests.kt | 18 ++-- .../corda/irs/api/NodeInterestRatesTest.kt | 4 +- .../net/corda/netmap/simulation/Simulation.kt | 9 +- .../testing/node/InMemoryMessagingNetwork.kt | 10 ++- .../kotlin/net/corda/testing/node/MockNode.kt | 2 +- .../net/corda/testing/node/MockServices.kt | 1 + 26 files changed, 377 insertions(+), 154 deletions(-) create mode 100644 node/src/main/kotlin/net/corda/node/services/statemachine/StateMachineManager.kt diff --git a/.ci/api-current.txt b/.ci/api-current.txt index 7c2e887573..14f2aaf9dd 100644 --- a/.ci/api-current.txt +++ b/.ci/api-current.txt @@ -1074,9 +1074,13 @@ public abstract class net.corda.core.flows.FlowLogic extends java.lang.Object public () @org.jetbrains.annotations.NotNull public abstract net.corda.core.identity.Party getCounterparty() @co.paralleluniverse.fibers.Suspendable @org.jetbrains.annotations.NotNull public abstract net.corda.core.flows.FlowInfo getCounterpartyFlowInfo() + @co.paralleluniverse.fibers.Suspendable @org.jetbrains.annotations.NotNull public abstract net.corda.core.flows.FlowInfo getCounterpartyFlowInfo(boolean) @co.paralleluniverse.fibers.Suspendable @org.jetbrains.annotations.NotNull public abstract net.corda.core.utilities.UntrustworthyData receive(Class) + @co.paralleluniverse.fibers.Suspendable @org.jetbrains.annotations.NotNull public abstract net.corda.core.utilities.UntrustworthyData receive(Class, boolean) @co.paralleluniverse.fibers.Suspendable public abstract void send(Object) + @co.paralleluniverse.fibers.Suspendable public abstract void send(Object, boolean) @co.paralleluniverse.fibers.Suspendable @org.jetbrains.annotations.NotNull public abstract net.corda.core.utilities.UntrustworthyData sendAndReceive(Class, Object) + @co.paralleluniverse.fibers.Suspendable @org.jetbrains.annotations.NotNull public abstract net.corda.core.utilities.UntrustworthyData sendAndReceive(Class, Object, boolean) ## public final class net.corda.core.flows.FlowStackSnapshot extends java.lang.Object public (java.time.Instant, String, List) diff --git a/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/CordaRPCClientTest.kt b/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/CordaRPCClientTest.kt index 277769354c..9479ffe36a 100644 --- a/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/CordaRPCClientTest.kt +++ b/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/CordaRPCClientTest.kt @@ -2,6 +2,7 @@ package net.corda.client.rpc import net.corda.core.crypto.random63BitValue import net.corda.core.flows.FlowInitiator +import net.corda.core.internal.concurrent.flatMap import net.corda.core.internal.packageName import net.corda.core.messaging.FlowProgressHandle import net.corda.core.messaging.StateMachineUpdate @@ -143,7 +144,7 @@ class CordaRPCClientTest : NodeBasedTest(listOf("net.corda.finance.contracts", C } } val nodeIdentity = node.info.chooseIdentity() - node.services.startFlow(CashIssueFlow(2000.DOLLARS, OpaqueBytes.of(0), nodeIdentity), FlowInitiator.Shell).resultFuture.getOrThrow() + node.services.startFlow(CashIssueFlow(2000.DOLLARS, OpaqueBytes.of(0), nodeIdentity), FlowInitiator.Shell).flatMap { it.resultFuture }.getOrThrow() proxy.startFlow(::CashIssueFlow, 123.DOLLARS, OpaqueBytes.of(0), diff --git a/core/src/main/kotlin/net/corda/core/flows/FlowLogic.kt b/core/src/main/kotlin/net/corda/core/flows/FlowLogic.kt index 0239c81f20..322a9f9925 100644 --- a/core/src/main/kotlin/net/corda/core/flows/FlowLogic.kt +++ b/core/src/main/kotlin/net/corda/core/flows/FlowLogic.kt @@ -41,6 +41,16 @@ import java.time.Instant * and request they start their counterpart flow, then make sure it's annotated with [InitiatingFlow]. This annotation * also has a version property to allow you to version your flow and enables a node to restrict support for the flow to * that particular version. + * + * Functions that suspend the flow (including all functions on [FlowSession]) accept a [maySkipCheckpoint] parameter + * defaulting to false, false meaning a checkpoint should always be created on suspend. This parameter may be set to + * true which allows the implementation to potentially optimise away the checkpoint, saving a roundtrip to the database. + * + * This option however comes with a big warning sign: Setting the parameter to true requires the flow's code to be + * replayable from the previous checkpoint (or start of flow) up until the next checkpoint (or end of flow) in order to + * prepare for hard failures. As suspending functions always commit the flow's database transaction regardless of this + * parameter the flow must be prepared for scenarios where a previous running of the flow *already committed its + * relevant database transactions*. Only set this option to true if you know what you're doing. */ abstract class FlowLogic { /** This is where you should log things to. */ @@ -123,7 +133,7 @@ abstract class FlowLogic { */ @Deprecated("Use FlowSession.getFlowInfo()", level = DeprecationLevel.WARNING) @Suspendable - fun getFlowInfo(otherParty: Party): FlowInfo = stateMachine.getFlowInfo(otherParty, flowUsedForSessions) + fun getFlowInfo(otherParty: Party): FlowInfo = stateMachine.getFlowInfo(otherParty, flowUsedForSessions, maySkipCheckpoint = false) /** * Serializes and queues the given [payload] object for sending to the [otherParty]. Suspends until a response @@ -157,7 +167,7 @@ abstract class FlowLogic { @Deprecated("Use FlowSession.sendAndReceive()", level = DeprecationLevel.WARNING) @Suspendable open fun sendAndReceive(receiveType: Class, otherParty: Party, payload: Any): UntrustworthyData { - return stateMachine.sendAndReceive(receiveType, otherParty, payload, flowUsedForSessions) + return stateMachine.sendAndReceive(receiveType, otherParty, payload, flowUsedForSessions, retrySend = false, maySkipCheckpoint = false) } /** @@ -171,17 +181,17 @@ abstract class FlowLogic { */ @Deprecated("Use FlowSession.sendAndReceiveWithRetry()", level = DeprecationLevel.WARNING) internal inline fun sendAndReceiveWithRetry(otherParty: Party, payload: Any): UntrustworthyData { - return stateMachine.sendAndReceive(R::class.java, otherParty, payload, flowUsedForSessions, retrySend = true) + return stateMachine.sendAndReceive(R::class.java, otherParty, payload, flowUsedForSessions, retrySend = true, maySkipCheckpoint = false) } @Suspendable internal fun FlowSession.sendAndReceiveWithRetry(receiveType: Class, payload: Any): UntrustworthyData { - return stateMachine.sendAndReceive(receiveType, counterparty, payload, flowUsedForSessions, retrySend = true) + return stateMachine.sendAndReceive(receiveType, counterparty, payload, flowUsedForSessions, retrySend = true, maySkipCheckpoint = false) } @Suspendable internal inline fun FlowSession.sendAndReceiveWithRetry(payload: Any): UntrustworthyData { - return stateMachine.sendAndReceive(R::class.java, counterparty, payload, flowUsedForSessions, retrySend = true) + return stateMachine.sendAndReceive(R::class.java, counterparty, payload, flowUsedForSessions, retrySend = true, maySkipCheckpoint = false) } /** @@ -206,7 +216,7 @@ abstract class FlowLogic { @Deprecated("Use FlowSession.receive()", level = DeprecationLevel.WARNING) @Suspendable open fun receive(receiveType: Class, otherParty: Party): UntrustworthyData { - return stateMachine.receive(receiveType, otherParty, flowUsedForSessions) + return stateMachine.receive(receiveType, otherParty, flowUsedForSessions, maySkipCheckpoint = false) } /** Suspends until a message has been received for each session in the specified [sessions]. @@ -250,7 +260,9 @@ abstract class FlowLogic { */ @Deprecated("Use FlowSession.send()", level = DeprecationLevel.WARNING) @Suspendable - open fun send(otherParty: Party, payload: Any) = stateMachine.send(otherParty, payload, flowUsedForSessions) + open fun send(otherParty: Party, payload: Any) { + stateMachine.send(otherParty, payload, flowUsedForSessions, maySkipCheckpoint = false) + } /** * Invokes the given subflow. This function returns once the subflow completes successfully with the result @@ -342,7 +354,10 @@ abstract class FlowLogic { * valid by the local node, but that doesn't imply the vault will consider it relevant. */ @Suspendable - fun waitForLedgerCommit(hash: SecureHash): SignedTransaction = stateMachine.waitForLedgerCommit(hash, this) + @JvmOverloads + fun waitForLedgerCommit(hash: SecureHash, maySkipCheckpoint: Boolean = false): SignedTransaction { + return stateMachine.waitForLedgerCommit(hash, this, maySkipCheckpoint = maySkipCheckpoint) + } /** * Returns a shallow copy of the Quasar stack frames at the time of call to [flowStackSnapshot]. Use this to inspect diff --git a/core/src/main/kotlin/net/corda/core/flows/FlowSession.kt b/core/src/main/kotlin/net/corda/core/flows/FlowSession.kt index ae49ea3ebb..b1782f5424 100644 --- a/core/src/main/kotlin/net/corda/core/flows/FlowSession.kt +++ b/core/src/main/kotlin/net/corda/core/flows/FlowSession.kt @@ -54,8 +54,20 @@ abstract class FlowSession { * Returns a [FlowInfo] object describing the flow [counterparty] is using. With [FlowInfo.flowVersion] it * provides the necessary information needed for the evolution of flows and enabling backwards compatibility. * - * This method can be called before any send or receive has been done with [counterparty]. In such a case this will force - * them to start their flow. + * This method can be called before any send or receive has been done with [counterparty]. In such a case this will + * force them to start their flow. + * + * @param maySkipCheckpoint setting it to true indicates to the platform that it may optimise away the checkpoint. + */ + @Suspendable + abstract fun getCounterpartyFlowInfo(maySkipCheckpoint: Boolean): FlowInfo + + /** + * Returns a [FlowInfo] object describing the flow [counterparty] is using. With [FlowInfo.flowVersion] it + * provides the necessary information needed for the evolution of flows and enabling backwards compatibility. + * + * This method can be called before any send or receive has been done with [counterparty]. In such a case this will + * force them to start their flow. */ @Suspendable abstract fun getCounterpartyFlowInfo(): FlowInfo @@ -80,8 +92,26 @@ abstract class FlowSession { /** * Serializes and queues the given [payload] object for sending to the [counterparty]. Suspends until a response - * is received, which must be of the given [receiveType]. Remember that when receiving data from other parties the data - * should not be trusted until it's been thoroughly verified for consistency and that all expectations are + * is received, which must be of the given [receiveType]. Remember that when receiving data from other parties the + * data should not be trusted until it's been thoroughly verified for consistency and that all expectations are + * satisfied, as a malicious peer may send you subtly corrupted data in order to exploit your code. + * + * Note that this function is not just a simple send+receive pair: it is more efficient and more correct to + * use this when you expect to do a message swap than do use [send] and then [receive] in turn. + * + * @param maySkipCheckpoint setting it to true indicates to the platform that it may optimise away the checkpoint. + * @return an [UntrustworthyData] wrapper around the received object. + */ + @Suspendable + abstract fun sendAndReceive( + receiveType: Class, + payload: Any, maySkipCheckpoint: Boolean + ): UntrustworthyData + + /** + * Serializes and queues the given [payload] object for sending to the [counterparty]. Suspends until a response + * is received, which must be of the given [receiveType]. Remember that when receiving data from other parties the + * data should not be trusted until it's been thoroughly verified for consistency and that all expectations are * satisfied, as a malicious peer may send you subtly corrupted data in order to exploit your code. * * Note that this function is not just a simple send+receive pair: it is more efficient and more correct to @@ -104,6 +134,19 @@ abstract class FlowSession { return receive(R::class.java) } + /** + * Suspends until [counterparty] sends us a message of type [receiveType]. + * + * Remember that when receiving data from other parties the data should not be trusted until it's been thoroughly + * verified for consistency and that all expectations are satisfied, as a malicious peer may send you subtly + * corrupted data in order to exploit your code. + * + * @param maySkipCheckpoint setting it to true indicates to the platform that it may optimise away the checkpoint. + * @return an [UntrustworthyData] wrapper around the received object. + */ + @Suspendable + abstract fun receive(receiveType: Class, maySkipCheckpoint: Boolean): UntrustworthyData + /** * Suspends until [counterparty] sends us a message of type [receiveType]. * @@ -116,6 +159,18 @@ abstract class FlowSession { @Suspendable abstract fun receive(receiveType: Class): UntrustworthyData + /** + * Queues the given [payload] for sending to the [counterparty] and continues without suspending. + * + * Note that the other party may receive the message at some arbitrary later point or not at all: if [counterparty] + * is offline then message delivery will be retried until it comes back or until the message is older than the + * network's event horizon time. + * + * @param maySkipCheckpoint setting it to true indicates to the platform that it may optimise away the checkpoint. + */ + @Suspendable + abstract fun send(payload: Any, maySkipCheckpoint: Boolean) + /** * Queues the given [payload] for sending to the [counterparty] and continues without suspending. * diff --git a/core/src/main/kotlin/net/corda/core/internal/FlowStateMachine.kt b/core/src/main/kotlin/net/corda/core/internal/FlowStateMachine.kt index a2b0e2fd15..5e4f3e490a 100644 --- a/core/src/main/kotlin/net/corda/core/internal/FlowStateMachine.kt +++ b/core/src/main/kotlin/net/corda/core/internal/FlowStateMachine.kt @@ -15,7 +15,7 @@ import java.time.Instant /** This is an internal interface that is implemented by code in the node module. You should look at [FlowLogic]. */ interface FlowStateMachine { @Suspendable - fun getFlowInfo(otherParty: Party, sessionFlow: FlowLogic<*>): FlowInfo + fun getFlowInfo(otherParty: Party, sessionFlow: FlowLogic<*>, maySkipCheckpoint: Boolean): FlowInfo @Suspendable fun initiateFlow(otherParty: Party, sessionFlow: FlowLogic<*>): FlowSession @@ -25,16 +25,17 @@ interface FlowStateMachine { otherParty: Party, payload: Any, sessionFlow: FlowLogic<*>, - retrySend: Boolean = false): UntrustworthyData + retrySend: Boolean, + maySkipCheckpoint: Boolean): UntrustworthyData @Suspendable - fun receive(receiveType: Class, otherParty: Party, sessionFlow: FlowLogic<*>): UntrustworthyData + fun receive(receiveType: Class, otherParty: Party, sessionFlow: FlowLogic<*>, maySkipCheckpoint: Boolean): UntrustworthyData @Suspendable - fun send(otherParty: Party, payload: Any, sessionFlow: FlowLogic<*>) + fun send(otherParty: Party, payload: Any, sessionFlow: FlowLogic<*>, maySkipCheckpoint: Boolean) @Suspendable - fun waitForLedgerCommit(hash: SecureHash, sessionFlow: FlowLogic<*>): SignedTransaction + fun waitForLedgerCommit(hash: SecureHash, sessionFlow: FlowLogic<*>, maySkipCheckpoint: Boolean): SignedTransaction @Suspendable fun sleepUntil(until: Instant) diff --git a/docs/source/example-code/src/test/kotlin/net/corda/docs/CustomVaultQueryTest.kt b/docs/source/example-code/src/test/kotlin/net/corda/docs/CustomVaultQueryTest.kt index 509cb65312..9690a5ab41 100644 --- a/docs/source/example-code/src/test/kotlin/net/corda/docs/CustomVaultQueryTest.kt +++ b/docs/source/example-code/src/test/kotlin/net/corda/docs/CustomVaultQueryTest.kt @@ -27,12 +27,18 @@ class CustomVaultQueryTest { @Before fun setup() { - mockNet = MockNetwork(threadPerNode = true, cordappPackages = listOf("net.corda.finance.contracts.asset", CashSchemaV1::class.packageName)) + mockNet = MockNetwork( + threadPerNode = true, + cordappPackages = listOf( + "net.corda.finance.contracts.asset", + CashSchemaV1::class.packageName, + "net.corda.docs" + ) + ) mockNet.createNotaryNode(legalName = DUMMY_NOTARY.name) nodeA = mockNet.createPartyNode() nodeB = mockNet.createPartyNode() nodeA.internals.registerInitiatedFlow(TopupIssuerFlow.TopupIssuer::class.java) - nodeA.installCordaService(CustomVaultQuery.Service::class.java) notary = nodeA.services.getDefaultNotary() } diff --git a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt index dee4123fd0..d53535380d 100644 --- a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt +++ b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt @@ -11,13 +11,10 @@ import net.corda.core.flows.* import net.corda.core.identity.CordaX500Name import net.corda.core.identity.Party import net.corda.core.identity.PartyAndCertificate -import net.corda.core.internal.VisibleForTesting -import net.corda.core.internal.cert +import net.corda.core.internal.* import net.corda.core.internal.concurrent.doneFuture import net.corda.core.internal.concurrent.flatMap import net.corda.core.internal.concurrent.openFuture -import net.corda.core.internal.toX509CertHolder -import net.corda.core.internal.uncheckedCast import net.corda.core.messaging.* import net.corda.core.node.AppServiceHub import net.corda.core.node.NodeInfo @@ -30,6 +27,7 @@ import net.corda.core.serialization.SingletonSerializeAsToken import net.corda.core.transactions.SignedTransaction import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.debug +import net.corda.core.utilities.getOrThrow import net.corda.node.VersionInfo import net.corda.node.internal.classloading.requireAnnotation import net.corda.node.internal.cordapp.CordappLoader @@ -146,6 +144,7 @@ abstract class AbstractNode(config: NodeConfiguration, protected var myNotaryIdentity: PartyAndCertificate? = null protected lateinit var checkpointStorage: CheckpointStorage protected lateinit var smm: StateMachineManager + private lateinit var tokenizableServices: List protected lateinit var attachments: NodeAttachmentService protected lateinit var inNodeNetworkMapService: NetworkMapService protected lateinit var network: MessagingService @@ -209,18 +208,11 @@ abstract class AbstractNode(config: NodeConfiguration, val startedImpl = initialiseDatabasePersistence(schemaService) { val transactionStorage = makeTransactionStorage() val stateLoader = StateLoaderImpl(transactionStorage) - val tokenizableServices = makeServices(schemaService, transactionStorage, stateLoader) + val services = makeServices(schemaService, transactionStorage, stateLoader) saveOwnNodeInfo() - smm = StateMachineManager(services, - checkpointStorage, - serverThread, - database, - busyNodeLatch, - cordappLoader.appClassLoader) + smm = makeStateMachineManager() val flowStarter = FlowStarterImpl(serverThread, smm) val schedulerService = NodeSchedulerService(platformClock, this@AbstractNode.database, flowStarter, stateLoader, unfinishedSchedules = busyNodeLatch, serverThread = serverThread) - smm.tokenizableServices.addAll(tokenizableServices) - smm.tokenizableServices.add(schedulerService) if (serverThread is ExecutorService) { runOnStop += { // We wait here, even though any in-flight messages should have been drained away because the @@ -233,7 +225,8 @@ abstract class AbstractNode(config: NodeConfiguration, val rpcOps = makeRPCOps(flowStarter) startMessagingService(rpcOps) installCoreFlows() - installCordaServices(flowStarter) + val cordaServices = installCordaServices(flowStarter) + tokenizableServices = services + cordaServices + schedulerService registerCordappFlows() _services.rpcFlows += cordappLoader.cordapps.flatMap { it.rpcFlows } FlowLogicRefFactoryImpl.classloader = cordappLoader.appClassLoader @@ -245,7 +238,7 @@ abstract class AbstractNode(config: NodeConfiguration, _nodeReadyFuture.captureLater(registerWithNetworkMapIfConfigured()) return startedImpl.apply { database.transaction { - smm.start() + smm.start(tokenizableServices) // Shut down the SMM so no Fibers are scheduled. runOnStop += { smm.stop(acceptableLiveFiberCountOnStop()) } schedulerService.start() @@ -254,20 +247,34 @@ abstract class AbstractNode(config: NodeConfiguration, } } + protected open fun makeStateMachineManager(): StateMachineManager { + return StateMachineManagerImpl( + services, + checkpointStorage, + serverThread, + database, + busyNodeLatch, + cordappLoader.appClassLoader + ) + } + private class ServiceInstantiationException(cause: Throwable?) : CordaException("Service Instantiation Error", cause) - private fun installCordaServices(flowStarter: FlowStarter) { + private fun installCordaServices(flowStarter: FlowStarter): List { val loadedServices = cordappLoader.cordapps.flatMap { it.services } - filterServicesToInstall(loadedServices).forEach { + return filterServicesToInstall(loadedServices).mapNotNull { try { installCordaService(flowStarter, it) } catch (e: NoSuchMethodException) { log.error("${it.name}, as a Corda service, must have a constructor with a single parameter of type " + ServiceHub::class.java.name) + null } catch (e: ServiceInstantiationException) { log.error("Corda service ${it.name} failed to instantiate", e.cause) + null } catch (e: Exception) { log.error("Unable to install Corda service ${it.name}", e) + null } } } @@ -309,11 +316,11 @@ abstract class AbstractNode(config: NodeConfiguration, return FlowHandleImpl(id = stateMachine.id, returnValue = stateMachine.resultFuture) } - private fun startFlowChecked(flow: FlowLogic): FlowStateMachineImpl { + private fun startFlowChecked(flow: FlowLogic): FlowStateMachine { val logicType = flow.javaClass require(logicType.isAnnotationPresent(StartableByService::class.java)) { "${logicType.name} was not designed for starting by a CordaService" } val currentUser = FlowInitiator.Service(serviceInstance.javaClass.name) - return flowStarter.startFlow(flow, currentUser) + return flowStarter.startFlow(flow, currentUser).getOrThrow() } override fun equals(other: Any?): Boolean { @@ -327,7 +334,7 @@ abstract class AbstractNode(config: NodeConfiguration, override fun hashCode() = Objects.hash(serviceHub, flowStarter, serviceInstance) } - internal fun installCordaService(flowStarter: FlowStarter, serviceClass: Class): T { + private fun installCordaService(flowStarter: FlowStarter, serviceClass: Class): T { serviceClass.requireAnnotation() val service = try { val serviceContext = AppServiceHubImpl(services, flowStarter) @@ -351,7 +358,6 @@ abstract class AbstractNode(config: NodeConfiguration, throw ServiceInstantiationException(e.cause) } cordappServices.putInstance(serviceClass, service) - smm.tokenizableServices += service if (service is NotaryService) handleCustomNotaryService(service) @@ -359,6 +365,12 @@ abstract class AbstractNode(config: NodeConfiguration, return service } + fun findTokenizableService(clazz: Class): T? { + return tokenizableServices.firstOrNull { clazz.isAssignableFrom(it.javaClass) }?.let { uncheckedCast(it) } + } + + inline fun findTokenizableService() = findTokenizableService(T::class.java) + private fun handleCustomNotaryService(service: NotaryService) { runOnStop += service::stop service.start() @@ -801,7 +813,7 @@ abstract class AbstractNode(config: NodeConfiguration, } internal class FlowStarterImpl(private val serverThread: AffinityExecutor, private val smm: StateMachineManager) : FlowStarter { - override fun startFlow(logic: FlowLogic, flowInitiator: FlowInitiator, ourIdentity: Party?): FlowStateMachineImpl { - return serverThread.fetchFrom { smm.add(logic, flowInitiator, ourIdentity) } + override fun startFlow(logic: FlowLogic, flowInitiator: FlowInitiator, ourIdentity: Party?): CordaFuture> { + return serverThread.fetchFrom { smm.startFlow(logic, flowInitiator, ourIdentity) } } } diff --git a/node/src/main/kotlin/net/corda/node/internal/CordaRPCOpsImpl.kt b/node/src/main/kotlin/net/corda/node/internal/CordaRPCOpsImpl.kt index af827b9e39..863835c25a 100644 --- a/node/src/main/kotlin/net/corda/node/internal/CordaRPCOpsImpl.kt +++ b/node/src/main/kotlin/net/corda/node/internal/CordaRPCOpsImpl.kt @@ -10,6 +10,7 @@ import net.corda.core.flows.StartableByRPC import net.corda.core.identity.AbstractParty import net.corda.core.identity.CordaX500Name import net.corda.core.identity.Party +import net.corda.core.internal.FlowStateMachine import net.corda.core.messaging.* import net.corda.core.node.NodeInfo import net.corda.core.node.services.NetworkMapCache @@ -18,12 +19,12 @@ import net.corda.core.node.services.vault.PageSpecification import net.corda.core.node.services.vault.QueryCriteria import net.corda.core.node.services.vault.Sort import net.corda.core.transactions.SignedTransaction +import net.corda.core.utilities.getOrThrow import net.corda.node.services.FlowPermissions.Companion.startFlowPermission import net.corda.node.services.api.FlowStarter import net.corda.node.services.api.ServiceHubInternal import net.corda.node.services.messaging.getRpcContext import net.corda.node.services.messaging.requirePermission -import net.corda.node.services.statemachine.FlowStateMachineImpl import net.corda.node.services.statemachine.StateMachineManager import net.corda.node.utilities.CordaPersistence import rx.Observable @@ -94,7 +95,7 @@ class CordaRPCOpsImpl( return database.transaction { val (allStateMachines, changes) = smm.track() DataFeed( - allStateMachines.map { stateMachineInfoFromFlowLogic(it.logic) }, + allStateMachines.map { stateMachineInfoFromFlowLogic(it) }, changes.map { stateMachineUpdateFromStateMachineChange(it) } ) } @@ -146,13 +147,13 @@ class CordaRPCOpsImpl( return FlowHandleImpl(id = stateMachine.id, returnValue = stateMachine.resultFuture) } - private fun startFlow(logicType: Class>, args: Array): FlowStateMachineImpl { + private fun startFlow(logicType: Class>, args: Array): FlowStateMachine { require(logicType.isAnnotationPresent(StartableByRPC::class.java)) { "${logicType.name} was not designed for RPC" } val rpcContext = getRpcContext() rpcContext.requirePermission(startFlowPermission(logicType)) val currentUser = FlowInitiator.RPC(rpcContext.currentUser.username) // TODO RPC flows should have mapping user -> identity that should be resolved automatically on starting flow. - return flowStarter.invokeFlowAsync(logicType, currentUser, *args) + return flowStarter.invokeFlowAsync(logicType, currentUser, *args).getOrThrow() } override fun attachmentExists(id: SecureHash): Boolean { diff --git a/node/src/main/kotlin/net/corda/node/internal/StartedNode.kt b/node/src/main/kotlin/net/corda/node/internal/StartedNode.kt index 2d0645cef4..71b80d5aea 100644 --- a/node/src/main/kotlin/net/corda/node/internal/StartedNode.kt +++ b/node/src/main/kotlin/net/corda/node/internal/StartedNode.kt @@ -31,11 +31,6 @@ interface StartedNode { val rpcOps: CordaRPCOps fun dispose() = internals.stop() fun > registerInitiatedFlow(initiatedFlowClass: Class) = internals.registerInitiatedFlow(initiatedFlowClass) - /** - * Use this method to install your Corda services in your tests. This is automatically done by the node when it - * starts up for all classes it finds which are annotated with [CordaService]. - */ - fun installCordaService(serviceClass: Class) = internals.installCordaService(services, serviceClass) } class StateLoaderImpl(private val validatedTransactions: TransactionStorage) : StateLoader { diff --git a/node/src/main/kotlin/net/corda/node/services/api/ServiceHubInternal.kt b/node/src/main/kotlin/net/corda/node/services/api/ServiceHubInternal.kt index de7b8eb2eb..de12960304 100644 --- a/node/src/main/kotlin/net/corda/node/services/api/ServiceHubInternal.kt +++ b/node/src/main/kotlin/net/corda/node/services/api/ServiceHubInternal.kt @@ -20,6 +20,7 @@ import net.corda.core.node.services.NetworkMapCacheBase import net.corda.core.node.services.TransactionStorage import net.corda.core.serialization.CordaSerializable import net.corda.core.transactions.SignedTransaction +import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.loggerFor import net.corda.node.internal.InitiatedFlowFactory import net.corda.node.internal.cordapp.CordappProviderInternal @@ -119,13 +120,13 @@ interface FlowStarter { * defaults to [FlowInitiator.RPC] with username "Only For Testing". */ @VisibleForTesting - fun startFlow(logic: FlowLogic): FlowStateMachine = startFlow(logic, FlowInitiator.RPC("Only For Testing")) + fun startFlow(logic: FlowLogic): FlowStateMachine = startFlow(logic, FlowInitiator.RPC("Only For Testing")).getOrThrow() /** * Starts an already constructed flow. Note that you must be on the server thread to call this method. * @param flowInitiator indicates who started the flow, see: [FlowInitiator]. */ - fun startFlow(logic: FlowLogic, flowInitiator: FlowInitiator, ourIdentity: Party? = null): FlowStateMachineImpl + fun startFlow(logic: FlowLogic, flowInitiator: FlowInitiator, ourIdentity: Party? = null): CordaFuture> /** * Will check [logicType] and [args] against a whitelist and if acceptable then construct and initiate the flow. @@ -138,7 +139,7 @@ interface FlowStarter { fun invokeFlowAsync( logicType: Class>, flowInitiator: FlowInitiator, - vararg args: Any?): FlowStateMachineImpl { + vararg args: Any?): CordaFuture> { val logicRef = FlowLogicRefFactoryImpl.createForRPC(logicType, *args) val logic: FlowLogic = uncheckedCast(FlowLogicRefFactoryImpl.toFlowLogic(logicRef)) return startFlow(logic, flowInitiator, ourIdentity = null) diff --git a/node/src/main/kotlin/net/corda/node/services/events/NodeSchedulerService.kt b/node/src/main/kotlin/net/corda/node/services/events/NodeSchedulerService.kt index a0c208a53e..85aa36a0cb 100644 --- a/node/src/main/kotlin/net/corda/node/services/events/NodeSchedulerService.kt +++ b/node/src/main/kotlin/net/corda/node/services/events/NodeSchedulerService.kt @@ -1,9 +1,8 @@ package net.corda.node.services.events import co.paralleluniverse.fibers.Suspendable -import co.paralleluniverse.strands.SettableFuture as QuasarSettableFuture import com.google.common.util.concurrent.ListenableFuture -import com.google.common.util.concurrent.SettableFuture as GuavaSettableFuture +import com.google.common.util.concurrent.SettableFuture import net.corda.core.contracts.SchedulableState import net.corda.core.contracts.ScheduledActivity import net.corda.core.contracts.ScheduledStateRef @@ -13,6 +12,7 @@ import net.corda.core.flows.FlowInitiator import net.corda.core.flows.FlowLogic import net.corda.core.internal.ThreadBox import net.corda.core.internal.VisibleForTesting +import net.corda.core.internal.concurrent.flatMap import net.corda.core.internal.until import net.corda.core.node.StateLoader import net.corda.core.schemas.PersistentStateRef @@ -36,6 +36,8 @@ import javax.annotation.concurrent.ThreadSafe import javax.persistence.Column import javax.persistence.EmbeddedId import javax.persistence.Entity +import co.paralleluniverse.strands.SettableFuture as QuasarSettableFuture +import com.google.common.util.concurrent.SettableFuture as GuavaSettableFuture /** * A first pass of a simple [SchedulerService] that works with [MutableClock]s for testing, demonstrations and simulations @@ -215,7 +217,7 @@ class NodeSchedulerService(private val clock: Clock, * cancelled then we run the scheduled action. Finally we remove that action from the scheduled actions and * recompute the next scheduled action. */ - internal fun rescheduleWakeUp() { + private fun rescheduleWakeUp() { // Note, we already have the mutex but we need the scope again here val (scheduledState, ourRescheduledFuture) = mutex.alreadyLocked { rescheduled?.cancel(false) @@ -245,7 +247,7 @@ class NodeSchedulerService(private val clock: Clock, val scheduledFlow = getScheduledFlow(scheduledState) if (scheduledFlow != null) { flowName = scheduledFlow.javaClass.name - val future = flowStarter.startFlow(scheduledFlow, FlowInitiator.Scheduled(scheduledState)).resultFuture + val future = flowStarter.startFlow(scheduledFlow, FlowInitiator.Scheduled(scheduledState)).flatMap { it.resultFuture } future.then { unfinishedSchedules.countDown() } diff --git a/node/src/main/kotlin/net/corda/node/services/messaging/Messaging.kt b/node/src/main/kotlin/net/corda/node/services/messaging/Messaging.kt index a708166239..1697b49047 100644 --- a/node/src/main/kotlin/net/corda/node/services/messaging/Messaging.kt +++ b/node/src/main/kotlin/net/corda/node/services/messaging/Messaging.kt @@ -83,9 +83,40 @@ interface MessagingService { * to send an ACK message back. * * @param retryId if provided the message will be scheduled for redelivery until [cancelRedelivery] is called for this id. - * Note that this feature should only be used when the target is an idempotent distributed service, e.g. a notary. + * Note that this feature should only be used when the target is an idempotent distributed service, e.g. a notary. + * @param sequenceKey an object that may be used to enable a parallel [MessagingService] implementation. Two + * subsequent send()s with the same [sequenceKey] (up to equality) are guaranteed to be delivered in the same + * sequence the send()s were called. By default this is chosen conservatively to be [target]. + * @param acknowledgementHandler if non-null this handler will be called once the sent message has been committed by + * the broker. Note that if specified [send] itself may return earlier than the commit. */ - fun send(message: Message, target: MessageRecipients, retryId: Long? = null) + fun send( + message: Message, + target: MessageRecipients, + retryId: Long? = null, + sequenceKey: Any = target, + acknowledgementHandler: (() -> Unit)? = null + ) + + /** A message with a target and sequenceKey specified. */ + data class AddressedMessage( + val message: Message, + val target: MessageRecipients, + val retryId: Long? = null, + val sequenceKey: Any = target + ) + + /** + * Sends a list of messages to the specified recipients. This function allows for an efficient batching + * implementation. + * + * @param addressedMessages The list of messages together with the recipients, retry ids and sequence keys. + * @param retryId if provided the message will be scheduled for redelivery until [cancelRedelivery] is called for this id. + * Note that this feature should only be used when the target is an idempotent distributed service, e.g. a notary. + * @param acknowledgementHandler if non-null this handler will be called once all sent messages have been committed + * by the broker. Note that if specified [send] itself may return earlier than the commit. + */ + fun send(addressedMessages: List, acknowledgementHandler: (() -> Unit)? = null) /** Cancels the scheduled message redelivery for the specified [retryId] */ fun cancelRedelivery(retryId: Long) diff --git a/node/src/main/kotlin/net/corda/node/services/messaging/NodeMessagingClient.kt b/node/src/main/kotlin/net/corda/node/services/messaging/NodeMessagingClient.kt index 5a9cd04730..401709f811 100644 --- a/node/src/main/kotlin/net/corda/node/services/messaging/NodeMessagingClient.kt +++ b/node/src/main/kotlin/net/corda/node/services/messaging/NodeMessagingClient.kt @@ -22,7 +22,7 @@ import net.corda.node.services.RPCUserService import net.corda.node.services.api.MonitoringService import net.corda.node.services.config.NodeConfiguration import net.corda.node.services.config.VerifierType -import net.corda.node.services.statemachine.StateMachineManager +import net.corda.node.services.statemachine.StateMachineManagerImpl import net.corda.node.services.transactions.InMemoryTransactionVerifierService import net.corda.node.services.transactions.OutOfProcessTransactionVerifierService import net.corda.node.utilities.* @@ -485,7 +485,7 @@ class NodeMessagingClient(override val config: NodeConfiguration, } } - override fun send(message: Message, target: MessageRecipients, retryId: Long?) { + override fun send(message: Message, target: MessageRecipients, retryId: Long?, sequenceKey: Any, acknowledgementHandler: (() -> Unit)?) { // We have to perform sending on a different thread pool, since using the same pool for messaging and // fibers leads to Netty buffer memory leaks, caused by both Netty and Quasar fiddling with thread-locals. messagingExecutor.fetchFrom { @@ -502,7 +502,7 @@ class NodeMessagingClient(override val config: NodeConfiguration, putStringProperty(HDR_DUPLICATE_DETECTION_ID, SimpleString(message.uniqueMessageId.toString())) // For demo purposes - if set then add a delay to messages in order to demonstrate that the flows are doing as intended - if (amqDelayMillis > 0 && message.topicSession.topic == StateMachineManager.sessionTopic.topic) { + if (amqDelayMillis > 0 && message.topicSession.topic == StateMachineManagerImpl.sessionTopic.topic) { putLongProperty(HDR_SCHEDULED_DELIVERY_TIME, System.currentTimeMillis() + amqDelayMillis) } } @@ -523,6 +523,14 @@ class NodeMessagingClient(override val config: NodeConfiguration, } } } + acknowledgementHandler?.invoke() + } + + override fun send(addressedMessages: List, acknowledgementHandler: (() -> Unit)?) { + for ((message, target, retryId, sequenceKey) in addressedMessages) { + send(message, target, retryId, sequenceKey, null) + } + acknowledgementHandler?.invoke() } private fun sendWithRetry(retryCount: Int, address: String, message: ClientMessage, retryId: Long) { diff --git a/node/src/main/kotlin/net/corda/node/services/statemachine/FlowSessionImpl.kt b/node/src/main/kotlin/net/corda/node/services/statemachine/FlowSessionImpl.kt index 044fdc0dbe..054d7c5d01 100644 --- a/node/src/main/kotlin/net/corda/node/services/statemachine/FlowSessionImpl.kt +++ b/node/src/main/kotlin/net/corda/node/services/statemachine/FlowSessionImpl.kt @@ -16,28 +16,46 @@ class FlowSessionImpl( internal lateinit var sessionFlow: FlowLogic<*> @Suspendable - override fun getCounterpartyFlowInfo(): FlowInfo { - return stateMachine.getFlowInfo(counterparty, sessionFlow) + override fun getCounterpartyFlowInfo(maySkipCheckpoint: Boolean): FlowInfo { + return stateMachine.getFlowInfo(counterparty, sessionFlow, maySkipCheckpoint) } @Suspendable - override fun sendAndReceive(receiveType: Class, payload: Any): UntrustworthyData { - return stateMachine.sendAndReceive(receiveType, counterparty, payload, sessionFlow) + override fun getCounterpartyFlowInfo() = getCounterpartyFlowInfo(maySkipCheckpoint = false) + + @Suspendable + override fun sendAndReceive( + receiveType: Class, + payload: Any, + maySkipCheckpoint: Boolean + ): UntrustworthyData { + return stateMachine.sendAndReceive( + receiveType, + counterparty, + payload, + sessionFlow, + retrySend = false, + maySkipCheckpoint = maySkipCheckpoint + ) } @Suspendable - internal fun sendAndReceiveWithRetry(receiveType: Class, payload: Any): UntrustworthyData { - return stateMachine.sendAndReceive(receiveType, counterparty, payload, sessionFlow, retrySend = true) + override fun sendAndReceive(receiveType: Class, payload: Any) = sendAndReceive(receiveType, payload, maySkipCheckpoint = false) + + @Suspendable + override fun receive(receiveType: Class, maySkipCheckpoint: Boolean): UntrustworthyData { + return stateMachine.receive(receiveType, counterparty, sessionFlow, maySkipCheckpoint) } @Suspendable - override fun receive(receiveType: Class): UntrustworthyData { - return stateMachine.receive(receiveType, counterparty, sessionFlow) + override fun receive(receiveType: Class) = receive(receiveType, maySkipCheckpoint = false) + + @Suspendable + override fun send(payload: Any, maySkipCheckpoint: Boolean) { + return stateMachine.send(counterparty, payload, sessionFlow, maySkipCheckpoint) } @Suspendable - override fun send(payload: Any) { - return stateMachine.send(counterparty, payload, sessionFlow) - } + override fun send(payload: Any) = send(payload, maySkipCheckpoint = false) } diff --git a/node/src/main/kotlin/net/corda/node/services/statemachine/FlowStateMachineImpl.kt b/node/src/main/kotlin/net/corda/node/services/statemachine/FlowStateMachineImpl.kt index a3df2461c8..57cc99378a 100644 --- a/node/src/main/kotlin/net/corda/node/services/statemachine/FlowStateMachineImpl.kt +++ b/node/src/main/kotlin/net/corda/node/services/statemachine/FlowStateMachineImpl.kt @@ -163,7 +163,7 @@ class FlowStateMachineImpl(override val id: StateMachineRunId, } @Suspendable - override fun getFlowInfo(otherParty: Party, sessionFlow: FlowLogic<*>): FlowInfo { + override fun getFlowInfo(otherParty: Party, sessionFlow: FlowLogic<*>, maySkipCheckpoint: Boolean): FlowInfo { val state = getConfirmedSession(otherParty, sessionFlow).state as FlowSessionState.Initiated return state.context } @@ -173,7 +173,8 @@ class FlowStateMachineImpl(override val id: StateMachineRunId, otherParty: Party, payload: Any, sessionFlow: FlowLogic<*>, - retrySend: Boolean): UntrustworthyData { + retrySend: Boolean, + maySkipCheckpoint: Boolean): UntrustworthyData { requireNonPrimitive(receiveType) logger.debug { "sendAndReceive(${receiveType.name}, $otherParty, ${payload.toString().abbreviate(300)}) ..." } val session = getConfirmedSessionIfPresent(otherParty, sessionFlow) @@ -192,7 +193,8 @@ class FlowStateMachineImpl(override val id: StateMachineRunId, @Suspendable override fun receive(receiveType: Class, otherParty: Party, - sessionFlow: FlowLogic<*>): UntrustworthyData { + sessionFlow: FlowLogic<*>, + maySkipCheckpoint: Boolean): UntrustworthyData { requireNonPrimitive(receiveType) logger.debug { "receive(${receiveType.name}, $otherParty) ..." } val session = getConfirmedSession(otherParty, sessionFlow) @@ -208,7 +210,7 @@ class FlowStateMachineImpl(override val id: StateMachineRunId, } @Suspendable - override fun send(otherParty: Party, payload: Any, sessionFlow: FlowLogic<*>) { + override fun send(otherParty: Party, payload: Any, sessionFlow: FlowLogic<*>, maySkipCheckpoint: Boolean) { logger.debug { "send($otherParty, ${payload.toString().abbreviate(300)})" } val session = getConfirmedSessionIfPresent(otherParty, sessionFlow) if (session == null) { @@ -220,7 +222,7 @@ class FlowStateMachineImpl(override val id: StateMachineRunId, } @Suspendable - override fun waitForLedgerCommit(hash: SecureHash, sessionFlow: FlowLogic<*>): SignedTransaction { + override fun waitForLedgerCommit(hash: SecureHash, sessionFlow: FlowLogic<*>, maySkipCheckpoint: Boolean): SignedTransaction { logger.debug { "waitForLedgerCommit($hash) ..." } suspend(WaitForLedgerCommit(hash, sessionFlow.stateMachine as FlowStateMachineImpl<*>)) val stx = serviceHub.validatedTransactions.getTransaction(hash) diff --git a/node/src/main/kotlin/net/corda/node/services/statemachine/StateMachineManager.kt b/node/src/main/kotlin/net/corda/node/services/statemachine/StateMachineManager.kt new file mode 100644 index 0000000000..e790d28b2c --- /dev/null +++ b/node/src/main/kotlin/net/corda/node/services/statemachine/StateMachineManager.kt @@ -0,0 +1,78 @@ +package net.corda.node.services.statemachine + +import net.corda.core.concurrent.CordaFuture +import net.corda.core.flows.FlowInitiator +import net.corda.core.flows.FlowLogic +import net.corda.core.identity.Party +import net.corda.core.internal.FlowStateMachine +import net.corda.core.messaging.DataFeed +import net.corda.core.utilities.Try +import rx.Observable + +/** + * A StateMachineManager is responsible for coordination and persistence of multiple [FlowStateMachine] objects. + * Each such object represents an instantiation of a (two-party) flow that has reached a particular point. + * + * An implementation of this interface will persist state machines to long term storage so they can survive process + * restarts and, if run with a single-threaded executor, will ensure no two state machines run concurrently with each + * other (bad for performance, good for programmer mental health!). + * + * A flow is a class with a single call method. The call method and any others it invokes are rewritten by a bytecode + * rewriting engine called Quasar, to ensure the code can be suspended and resumed at any point. + * + * TODO: Consider the issue of continuation identity more deeply: is it a safe assumption that a serialised + * continuation is always unique? + * TODO: Think about how to bring the system to a clean stop so it can be upgraded without any serialised stacks on disk + * TODO: Timeouts + * TODO: Surfacing of exceptions via an API and/or management UI + * TODO: Ability to control checkpointing explicitly, for cases where you know replaying a message can't hurt + * TODO: Don't store all active flows in memory, load from the database on demand. + */ +interface StateMachineManager { + /** + * Starts the state machine manager, loading and starting the state machines in storage. + */ + fun start(tokenizableServices: List) + /** + * Stops the state machine manager gracefully, waiting until all but [allowedUnsuspendedFiberCount] flows reach the + * next checkpoint. + */ + fun stop(allowedUnsuspendedFiberCount: Int) + + /** + * Starts a new flow. + * + * @param flowLogic The flow's code. + * @param flowInitiator The initiator of the flow. + */ + fun startFlow(flowLogic: FlowLogic, flowInitiator: FlowInitiator, ourIdentity: Party? = null): CordaFuture> + + /** + * Represents an addition/removal of a state machine. + */ + sealed class Change { + abstract val logic: FlowLogic<*> + data class Add(override val logic: FlowLogic<*>) : Change() + data class Removed(override val logic: FlowLogic<*>, val result: Try<*>) : Change() + } + + /** + * Returns the list of live state machines and a stream of subsequent additions/removals of them. + */ + fun track(): DataFeed>, Change> + + /** + * The stream of additions/removals of flows. + */ + val changes: Observable + + /** + * Returns the currently live flows of type [flowClass], and their corresponding result future. + */ + fun > findStateMachines(flowClass: Class): List>> + + /** + * Returns all currently live flows. + */ + val allStateMachines: List> +} \ No newline at end of file diff --git a/node/src/main/kotlin/net/corda/node/services/statemachine/StateMachineManagerImpl.kt b/node/src/main/kotlin/net/corda/node/services/statemachine/StateMachineManagerImpl.kt index 74697821e9..7fdd7f920c 100644 --- a/node/src/main/kotlin/net/corda/node/services/statemachine/StateMachineManagerImpl.kt +++ b/node/src/main/kotlin/net/corda/node/services/statemachine/StateMachineManagerImpl.kt @@ -16,6 +16,7 @@ import net.corda.core.crypto.random63BitValue import net.corda.core.flows.* import net.corda.core.identity.Party import net.corda.core.internal.* +import net.corda.core.internal.concurrent.doneFuture import net.corda.core.messaging.DataFeed import net.corda.core.serialization.SerializationDefaults.CHECKPOINT_CONTEXT import net.corda.core.serialization.SerializationDefaults.SERIALIZATION_FACTORY @@ -48,42 +49,24 @@ import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.Executors import java.util.concurrent.TimeUnit.SECONDS import javax.annotation.concurrent.ThreadSafe -import kotlin.collections.ArrayList /** - * A StateMachineManager is responsible for coordination and persistence of multiple [FlowStateMachineImpl] objects. - * Each such object represents an instantiation of a (two-party) flow that has reached a particular point. - * - * An implementation of this class will persist state machines to long term storage so they can survive process restarts - * and, if run with a single-threaded executor, will ensure no two state machines run concurrently with each other - * (bad for performance, good for programmer mental health!). - * - * A "state machine" is a class with a single call method. The call method and any others it invokes are rewritten by - * a bytecode rewriting engine called Quasar, to ensure the code can be suspended and resumed at any point. - * - * The SMM will always invoke the flow fibers on the given [AffinityExecutor], regardless of which thread actually - * starts them via [add]. - * - * TODO: Consider the issue of continuation identity more deeply: is it a safe assumption that a serialised - * continuation is always unique? - * TODO: Think about how to bring the system to a clean stop so it can be upgraded without any serialised stacks on disk - * TODO: Timeouts - * TODO: Surfacing of exceptions via an API and/or management UI - * TODO: Ability to control checkpointing explicitly, for cases where you know replaying a message can't hurt - * TODO: Don't store all active flows in memory, load from the database on demand. + * The StateMachineManagerImpl will always invoke the flow fibers on the given [AffinityExecutor], regardless of which + * thread actually starts them via [startFlow]. */ @ThreadSafe -class StateMachineManager(val serviceHub: ServiceHubInternal, - val checkpointStorage: CheckpointStorage, - val executor: AffinityExecutor, - val database: CordaPersistence, - private val unfinishedFibers: ReusableLatch = ReusableLatch(), - private val classloader: ClassLoader = javaClass.classLoader) { - +class StateMachineManagerImpl( + val serviceHub: ServiceHubInternal, + val checkpointStorage: CheckpointStorage, + val executor: AffinityExecutor, + val database: CordaPersistence, + private val unfinishedFibers: ReusableLatch = ReusableLatch(), + private val classloader: ClassLoader = StateMachineManagerImpl::class.java.classLoader +) : StateMachineManager { inner class FiberScheduler : FiberExecutorScheduler("Same thread scheduler", executor) companion object { - private val logger = loggerFor() + private val logger = loggerFor() internal val sessionTopic = TopicSession("platform.session") init { @@ -93,22 +76,15 @@ class StateMachineManager(val serviceHub: ServiceHubInternal, } } - sealed class Change { - abstract val logic: FlowLogic<*> - - data class Add(override val logic: FlowLogic<*>) : Change() - data class Removed(override val logic: FlowLogic<*>, val result: Try<*>) : Change() - } - // A list of all the state machines being managed by this class. We expose snapshots of it via the stateMachines // property. private class InnerState { var started = false val stateMachines = LinkedHashMap, Checkpoint>() - val changesPublisher = PublishSubject.create()!! + val changesPublisher = PublishSubject.create()!! val fibersWaitingForLedgerCommit = HashMultimap.create>()!! - fun notifyChangeObservers(change: Change) { + fun notifyChangeObservers(change: StateMachineManager.Change) { changesPublisher.bufferUntilDatabaseCommit().onNext(change) } } @@ -139,24 +115,22 @@ class StateMachineManager(val serviceHub: ServiceHubInternal, private val openSessions = ConcurrentHashMap() private val recentlyClosedSessions = ConcurrentHashMap() - internal val tokenizableServices = ArrayList() // Context for tokenized services in checkpoints + private lateinit var tokenizableServices: List private val serializationContext by lazy { SerializeAsTokenContextImpl(tokenizableServices, SERIALIZATION_FACTORY, CHECKPOINT_CONTEXT, serviceHub) } - fun findServices(predicate: (Any) -> Boolean) = tokenizableServices.filter(predicate) - /** Returns a list of all state machines executing the given flow logic at the top level (subflows do not count) */ - fun

, T> findStateMachines(flowClass: Class

): List>> { + override fun > findStateMachines(flowClass: Class): List>> { return mutex.locked { stateMachines.keys.mapNotNull { - flowClass.castIfPossible(it.logic)?.let { it to uncheckedCast, FlowStateMachineImpl>(it.stateMachine).resultFuture } + flowClass.castIfPossible(it.logic)?.let { it to uncheckedCast, FlowStateMachineImpl<*>>(it.stateMachine).resultFuture } } } } - val allStateMachines: List> + override val allStateMachines: List> get() = mutex.locked { stateMachines.keys.map { it.logic } } /** @@ -165,9 +139,10 @@ class StateMachineManager(val serviceHub: ServiceHubInternal, * * We use assignment here so that multiple subscribers share the same wrapped Observable. */ - val changes: Observable = mutex.content.changesPublisher.wrapWithDatabaseTransaction() + override val changes: Observable = mutex.content.changesPublisher.wrapWithDatabaseTransaction() - fun start() { + override fun start(tokenizableServices: List) { + this.tokenizableServices = tokenizableServices checkQuasarJavaAgentPresence() restoreFibersFromCheckpoints() listenToLedgerTransactions() @@ -207,12 +182,12 @@ class StateMachineManager(val serviceHub: ServiceHubInternal, } /** - * Start the shutdown process, bringing the [StateMachineManager] to a controlled stop. When this method returns, + * Start the shutdown process, bringing the [StateMachineManagerImpl] to a controlled stop. When this method returns, * all Fibers have been suspended and checkpointed, or have completed. * * @param allowedUnsuspendedFiberCount Optional parameter is used in some tests. */ - fun stop(allowedUnsuspendedFiberCount: Int = 0) { + override fun stop(allowedUnsuspendedFiberCount: Int) { require(allowedUnsuspendedFiberCount >= 0) mutex.locked { if (stopping) throw IllegalStateException("Already stopping!") @@ -229,9 +204,9 @@ class StateMachineManager(val serviceHub: ServiceHubInternal, * Atomic get snapshot + subscribe. This is needed so we don't miss updates between subscriptions to [changes] and * calls to [allStateMachines] */ - fun track(): DataFeed>, Change> { + override fun track(): DataFeed>, StateMachineManager.Change> { return mutex.locked { - DataFeed(stateMachines.keys.toList(), changesPublisher.bufferUntilSubscribed().wrapWithDatabaseTransaction()) + DataFeed(stateMachines.keys.map { it.logic }, changesPublisher.bufferUntilSubscribed().wrapWithDatabaseTransaction()) } } @@ -460,7 +435,7 @@ class StateMachineManager(val serviceHub: ServiceHubInternal, try { mutex.locked { stateMachines.remove(fiber)?.let { checkpointStorage.removeCheckpoint(it) } - notifyChangeObservers(Change.Removed(fiber.logic, result)) + notifyChangeObservers(StateMachineManager.Change.Removed(fiber.logic, result)) } endAllFiberSessions(fiber, result, propagated) } finally { @@ -473,7 +448,7 @@ class StateMachineManager(val serviceHub: ServiceHubInternal, mutex.locked { totalStartedFlows.inc() unfinishedFibers.countUp() - notifyChangeObservers(Change.Add(fiber.logic)) + notifyChangeObservers(StateMachineManager.Change.Add(fiber.logic)) } } @@ -526,11 +501,11 @@ class StateMachineManager(val serviceHub: ServiceHubInternal, * * Note that you must be on the [executor] thread. */ - fun add(logic: FlowLogic, flowInitiator: FlowInitiator, ourIdentity: Party? = null): FlowStateMachineImpl { + override fun startFlow(flowLogic: FlowLogic, flowInitiator: FlowInitiator, ourIdentity: Party?): CordaFuture> { // TODO: Check that logic has @Suspendable on its call method. executor.checkOnThread() val fiber = database.transaction { - val fiber = createFiber(logic, flowInitiator, ourIdentity) + val fiber = createFiber(flowLogic, flowInitiator, ourIdentity) updateCheckpoint(fiber) fiber } @@ -540,7 +515,7 @@ class StateMachineManager(val serviceHub: ServiceHubInternal, resumeFiber(fiber) } } - return fiber + return doneFuture(fiber) } private fun updateCheckpoint(fiber: FlowStateMachineImpl<*>) { diff --git a/node/src/main/kotlin/net/corda/node/shell/InteractiveShell.kt b/node/src/main/kotlin/net/corda/node/shell/InteractiveShell.kt index 75c4f85a5c..d818e30b8f 100644 --- a/node/src/main/kotlin/net/corda/node/shell/InteractiveShell.kt +++ b/node/src/main/kotlin/net/corda/node/shell/InteractiveShell.kt @@ -20,6 +20,7 @@ import net.corda.core.internal.concurrent.openFuture import net.corda.core.messaging.CordaRPCOps import net.corda.core.messaging.DataFeed import net.corda.core.messaging.StateMachineUpdate +import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.loggerFor import net.corda.node.internal.Node import net.corda.node.internal.StartedNode @@ -234,7 +235,7 @@ object InteractiveShell { val clazz: Class> = uncheckedCast(matches.single()) try { // TODO Flow invocation should use startFlowDynamic. - val fsm = runFlowFromString({ node.services.startFlow(it, FlowInitiator.Shell) }, inputData, clazz) + val fsm = runFlowFromString({ node.services.startFlow(it, FlowInitiator.Shell).getOrThrow() }, inputData, clazz) // Show the progress tracker on the console until the flow completes or is interrupted with a // Ctrl-C keypress. val latch = CountDownLatch(1) diff --git a/node/src/test/kotlin/net/corda/node/InteractiveShellTest.kt b/node/src/test/kotlin/net/corda/node/InteractiveShellTest.kt index a1eb79aefb..622fc9667c 100644 --- a/node/src/test/kotlin/net/corda/node/InteractiveShellTest.kt +++ b/node/src/test/kotlin/net/corda/node/InteractiveShellTest.kt @@ -52,7 +52,7 @@ class InteractiveShellTest { private fun check(input: String, expected: String) { var output: DummyFSM? = null InteractiveShell.runFlowFromString({ DummyFSM(it as FlowA).apply { output = this } }, input, FlowA::class.java, om) - assertEquals(expected, output!!.flowA.a, input) + assertEquals(expected, output!!.logic.a, input) } @Test @@ -83,5 +83,5 @@ class InteractiveShellTest { @Test fun party() = check("party: \"${MEGA_CORP.name}\"", MEGA_CORP.name.toString()) - class DummyFSM(val flowA: FlowA) : FlowStateMachine by rigorousMock() + class DummyFSM(override val logic: FlowA) : FlowStateMachine by rigorousMock() } diff --git a/node/src/test/kotlin/net/corda/node/services/events/NodeSchedulerServiceTest.kt b/node/src/test/kotlin/net/corda/node/services/events/NodeSchedulerServiceTest.kt index 53c7600ca3..d8a0c51096 100644 --- a/node/src/test/kotlin/net/corda/node/services/events/NodeSchedulerServiceTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/events/NodeSchedulerServiceTest.kt @@ -27,6 +27,7 @@ import net.corda.node.services.network.NetworkMapCacheImpl import net.corda.node.services.persistence.DBCheckpointStorage import net.corda.node.services.statemachine.FlowLogicRefFactoryImpl import net.corda.node.services.statemachine.StateMachineManager +import net.corda.node.services.statemachine.StateMachineManagerImpl import net.corda.node.services.vault.NodeVaultService import net.corda.node.utilities.AffinityExecutor import net.corda.node.utilities.CordaPersistence @@ -113,14 +114,14 @@ class NodeSchedulerServiceTest : SingletonSerializeAsToken() { doReturn(this@NodeSchedulerServiceTest).whenever(it).testReference } smmExecutor = AffinityExecutor.ServiceAffinityExecutor("test", 1) - mockSMM = StateMachineManager(services, DBCheckpointStorage(), smmExecutor, database) + mockSMM = StateMachineManagerImpl(services, DBCheckpointStorage(), smmExecutor, database) scheduler = NodeSchedulerService(testClock, database, FlowStarterImpl(smmExecutor, mockSMM), stateLoader, schedulerGatedExecutor, serverThread = smmExecutor) mockSMM.changes.subscribe { change -> if (change is StateMachineManager.Change.Removed && mockSMM.allStateMachines.isEmpty()) { smmHasRemovedAllFlows.countDown() } } - mockSMM.start() + mockSMM.start(emptyList()) scheduler.start() } } diff --git a/node/src/test/kotlin/net/corda/node/services/statemachine/FlowFrameworkTests.kt b/node/src/test/kotlin/net/corda/node/services/statemachine/FlowFrameworkTests.kt index e65592f70b..52b79b2dee 100644 --- a/node/src/test/kotlin/net/corda/node/services/statemachine/FlowFrameworkTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/statemachine/FlowFrameworkTests.kt @@ -64,6 +64,10 @@ class FlowFrameworkTests { private lateinit var alice: Party private lateinit var bob: Party + private fun StartedNode<*>.flushSmm() { + (this.smm as StateMachineManagerImpl).executor.flush() + } + @Before fun start() { mockNet = MockNetwork(servicePeerAllocationStrategy = RoundRobin(), cordappPackages = listOf("net.corda.finance.contracts", "net.corda.testing.contracts")) @@ -166,7 +170,7 @@ class FlowFrameworkTests { val restoredFlow = charlieNode.getSingleFlow().first assertEquals(false, restoredFlow.flowStarted) // Not started yet as no network activity has been allowed yet mockNet.runNetwork() // Allow network map messages to flow - charlieNode.smm.executor.flush() + charlieNode.flushSmm() assertEquals(true, restoredFlow.flowStarted) // Now we should have run the flow and hopefully cleared the init checkpoint charlieNode.internals.disableDBCloseOnStop() charlieNode.services.networkMapCache.clearNetworkMapCache() // zap persisted NetworkMapCache to force use of network. @@ -175,7 +179,7 @@ class FlowFrameworkTests { // Now it is completed the flow should leave no Checkpoint. charlieNode = mockNet.createNode(charlieNode.internals.id) mockNet.runNetwork() // Allow network map messages to flow - charlieNode.smm.executor.flush() + charlieNode.flushSmm() assertTrue(charlieNode.smm.findStateMachines(NoOpFlow::class.java).isEmpty()) } @@ -184,7 +188,7 @@ class FlowFrameworkTests { aliceNode.registerFlowFactory(ReceiveFlow::class) { InitiatedSendFlow("Hello", it) } bobNode.services.startFlow(ReceiveFlow(alice).nonTerminating()) // Prepare checkpointed receive flow // Make sure the add() has finished initial processing. - bobNode.smm.executor.flush() + bobNode.flushSmm() bobNode.internals.disableDBCloseOnStop() bobNode.dispose() // kill receiver val restoredFlow = bobNode.restartAndGetRestoredFlow() @@ -210,7 +214,7 @@ class FlowFrameworkTests { assertEquals(1, bobNode.checkpointStorage.checkpoints().size) } // Make sure the add() has finished initial processing. - bobNode.smm.executor.flush() + bobNode.flushSmm() bobNode.internals.disableDBCloseOnStop() // Restart node and thus reload the checkpoint and resend the message with same UUID bobNode.dispose() @@ -223,7 +227,7 @@ class FlowFrameworkTests { val (firstAgain, fut1) = node2b.getSingleFlow() // Run the network which will also fire up the second flow. First message should get deduped. So message data stays in sync. mockNet.runNetwork() - node2b.smm.executor.flush() + node2b.flushSmm() fut1.getOrThrow() val receivedCount = receivedSessionMessages.count { it.isPayloadTransfer } @@ -731,7 +735,7 @@ class FlowFrameworkTests { private fun StartedNode<*>.sendSessionMessage(message: SessionMessage, destination: Party) { services.networkService.apply { val address = getAddressOfParty(PartyInfo.SingleNode(destination, emptyList())) - send(createMessage(StateMachineManager.sessionTopic, message.serialize().bytes), address) + send(createMessage(StateMachineManagerImpl.sessionTopic, message.serialize().bytes), address) } } @@ -755,7 +759,7 @@ class FlowFrameworkTests { } private fun Observable.toSessionTransfers(): Observable { - return filter { it.message.topicSession == StateMachineManager.sessionTopic }.map { + return filter { it.message.topicSession == StateMachineManagerImpl.sessionTopic }.map { val from = it.sender.id val message = it.message.data.deserialize() SessionTransfer(from, sanitise(message), it.recipients) diff --git a/samples/irs-demo/src/test/kotlin/net/corda/irs/api/NodeInterestRatesTest.kt b/samples/irs-demo/src/test/kotlin/net/corda/irs/api/NodeInterestRatesTest.kt index 3953c876b0..40b84bd462 100644 --- a/samples/irs-demo/src/test/kotlin/net/corda/irs/api/NodeInterestRatesTest.kt +++ b/samples/irs-demo/src/test/kotlin/net/corda/irs/api/NodeInterestRatesTest.kt @@ -200,13 +200,13 @@ class NodeInterestRatesTest : TestDependencyInjectionBase() { @Test fun `network tearoff`() { - val mockNet = MockNetwork(initialiseSerialization = false, cordappPackages = listOf("net.corda.finance.contracts")) + val mockNet = MockNetwork(initialiseSerialization = false, cordappPackages = listOf("net.corda.finance.contracts", "net.corda.irs")) val n1 = mockNet.createNotaryNode() val oracleNode = mockNet.createNode().apply { internals.registerInitiatedFlow(NodeInterestRates.FixQueryHandler::class.java) internals.registerInitiatedFlow(NodeInterestRates.FixSignHandler::class.java) database.transaction { - installCordaService(NodeInterestRates.Oracle::class.java).knownFixes = TEST_DATA + internals.findTokenizableService(NodeInterestRates.Oracle::class.java)!!.knownFixes = TEST_DATA } } val tx = makePartialTX() diff --git a/samples/network-visualiser/src/main/kotlin/net/corda/netmap/simulation/Simulation.kt b/samples/network-visualiser/src/main/kotlin/net/corda/netmap/simulation/Simulation.kt index 19c5750d65..283eb64eda 100644 --- a/samples/network-visualiser/src/main/kotlin/net/corda/netmap/simulation/Simulation.kt +++ b/samples/network-visualiser/src/main/kotlin/net/corda/netmap/simulation/Simulation.kt @@ -12,11 +12,14 @@ import net.corda.node.internal.StartedNode import net.corda.node.services.config.NodeConfiguration import net.corda.node.services.statemachine.StateMachineManager import net.corda.nodeapi.internal.ServiceInfo -import net.corda.testing.* +import net.corda.testing.DUMMY_MAP +import net.corda.testing.DUMMY_NOTARY +import net.corda.testing.DUMMY_REGULATOR import net.corda.testing.node.InMemoryMessagingNetwork import net.corda.testing.node.MockNetwork import net.corda.testing.node.TestClock import net.corda.testing.node.setTo +import net.corda.testing.testNodeConfiguration import rx.Observable import rx.subjects.PublishSubject import java.math.BigInteger @@ -118,7 +121,7 @@ abstract class Simulation(val networkSendManuallyPumped: Boolean, registerInitiatedFlow(NodeInterestRates.FixSignHandler::class.java) javaClass.classLoader.getResourceAsStream("net/corda/irs/simulation/example.rates.txt").use { database.transaction { - installCordaService(NodeInterestRates.Oracle::class.java).uploadFixes(it.reader().readText()) + findTokenizableService(NodeInterestRates.Oracle::class.java)!!.uploadFixes(it.reader().readText()) } } } @@ -143,7 +146,7 @@ abstract class Simulation(val networkSendManuallyPumped: Boolean, val mockNet = MockNetwork( networkSendManuallyPumped = networkSendManuallyPumped, threadPerNode = runAsync, - cordappPackages = listOf("net.corda.irs.contract", "net.corda.finance.contract")) + cordappPackages = listOf("net.corda.irs.contract", "net.corda.finance.contract", "net.corda.irs")) // This one must come first. val networkMap = mockNet.startNetworkMapNode(nodeFactory = NetworkMapNodeFactory) val notary = mockNet.createNotaryNode(validating = false, nodeFactory = NotaryNodeFactory) diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/InMemoryMessagingNetwork.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/InMemoryMessagingNetwork.kt index a44cf1c4a5..b009106082 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/InMemoryMessagingNetwork.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/InMemoryMessagingNetwork.kt @@ -363,14 +363,22 @@ class InMemoryMessagingNetwork( state.locked { check(handlers.remove(registration as Handler)) } } - override fun send(message: Message, target: MessageRecipients, retryId: Long?) { + override fun send(message: Message, target: MessageRecipients, retryId: Long?, sequenceKey: Any, acknowledgementHandler: (() -> Unit)?) { check(running) msgSend(this, message, target) + acknowledgementHandler?.invoke() if (!sendManuallyPumped) { pumpSend(false) } } + override fun send(addressedMessages: List, acknowledgementHandler: (() -> Unit)?) { + for ((message, target, retryId, sequenceKey) in addressedMessages) { + send(message, target, retryId, sequenceKey, null) + } + acknowledgementHandler?.invoke() + } + override fun stop() { if (backgroundThread != null) { backgroundThread.interrupt() diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt index 9efcc1cefb..60d138248f 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt @@ -271,7 +271,7 @@ class MockNetwork(defaultParameters: MockNetworkParameters = MockNetworkParamete throw IllegalStateException("Unable to enumerate all nodes in BFT cluster.") } clusterNodes.forEach { - val notaryService = it.started!!.smm.findServices { it is BFTNonValidatingNotaryService }.single() as BFTNonValidatingNotaryService + val notaryService = it.findTokenizableService(BFTNonValidatingNotaryService::class.java)!! notaryService.waitUntilReplicaHasInitialized() } } diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockServices.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockServices.kt index fa881e1703..b0c962232a 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockServices.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockServices.kt @@ -30,6 +30,7 @@ import net.corda.node.services.persistence.HibernateConfiguration import net.corda.node.services.persistence.InMemoryStateMachineRecordedTransactionMappingStorage import net.corda.node.services.schema.HibernateObserver import net.corda.node.services.schema.NodeSchemaService +import net.corda.node.services.statemachine.FlowStateMachineImpl import net.corda.node.services.transactions.InMemoryTransactionVerifierService import net.corda.node.services.vault.NodeVaultService import net.corda.node.utilities.CordaPersistence From 1b7ebd4841787f9e046ea56595862eeaace88f6b Mon Sep 17 00:00:00 2001 From: bpaunescu Date: Fri, 20 Oct 2017 17:58:15 +0100 Subject: [PATCH 27/94] CORDA-351: added dependency check plugin to gradle build script (#1911) * CORDA-351: added dependency check plugin to gradle build script * CORDA-351: Added suppression stub file with example * CORDA-351: added suppresionFile property --- .ci/dependency-checker/suppressedLibraries.xml | 14 ++++++++++++++ build.gradle | 8 ++++++++ 2 files changed, 22 insertions(+) create mode 100644 .ci/dependency-checker/suppressedLibraries.xml diff --git a/.ci/dependency-checker/suppressedLibraries.xml b/.ci/dependency-checker/suppressedLibraries.xml new file mode 100644 index 0000000000..1150abff87 --- /dev/null +++ b/.ci/dependency-checker/suppressedLibraries.xml @@ -0,0 +1,14 @@ + + + + + + diff --git a/build.gradle b/build.gradle index 295f38750f..0541f3fadf 100644 --- a/build.gradle +++ b/build.gradle @@ -45,6 +45,7 @@ buildscript { ext.rxjava_version = '1.2.4' ext.dokka_version = '0.9.14' ext.eddsa_version = '0.2.0' + ext.dependency_checker_version = '3.0.1' // Update 121 is required for ObjectInputFilter and at time of writing 131 was latest: ext.java8_minUpdateVersion = '131' @@ -67,6 +68,7 @@ buildscript { classpath "org.jetbrains.dokka:dokka-gradle-plugin:${dokka_version}" classpath "org.ajoberstar:grgit:1.1.0" classpath "net.i2p.crypto:eddsa:$eddsa_version" // Needed for ServiceIdentityGenerator in the build environment. + classpath "org.owasp:dependency-check-gradle:${dependency_checker_version}" } } @@ -101,7 +103,13 @@ allprojects { apply plugin: 'kotlin' apply plugin: 'java' apply plugin: 'jacoco' + apply plugin: 'org.owasp.dependencycheck' + dependencyCheck { + suppressionFile = '.ci/dependency-checker/suppressedLibraries.xml' + cveValidForHours = 1 + format = 'ALL' + } sourceCompatibility = 1.8 targetCompatibility = 1.8 From 97f7c9b79d32f0570e84cb2fd859ed49ca1e9ec2 Mon Sep 17 00:00:00 2001 From: Katelyn Baker Date: Mon, 23 Oct 2017 10:32:03 +0100 Subject: [PATCH 28/94] CORDA-435 - Ensure Kryo only tests use Kryo serializatin context Also correct lambda typos (from lamba) --- .../net/corda/core/utilities/KotlinUtilsTest.kt | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/core/src/test/kotlin/net/corda/core/utilities/KotlinUtilsTest.kt b/core/src/test/kotlin/net/corda/core/utilities/KotlinUtilsTest.kt index 22ef438a6b..b4ef01dbbc 100644 --- a/core/src/test/kotlin/net/corda/core/utilities/KotlinUtilsTest.kt +++ b/core/src/test/kotlin/net/corda/core/utilities/KotlinUtilsTest.kt @@ -6,6 +6,7 @@ import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.deserialize import net.corda.core.serialization.serialize import net.corda.nodeapi.internal.serialization.KRYO_CHECKPOINT_CONTEXT +import net.corda.nodeapi.internal.serialization.KRYO_P2P_CONTEXT import net.corda.testing.TestDependencyInjectionBase import org.assertj.core.api.Assertions.assertThat import org.junit.Rule @@ -26,7 +27,7 @@ class KotlinUtilsTest : TestDependencyInjectionBase() { } @Test - fun `checkpointing a transient property with non-capturing lamba`() { + fun `checkpointing a transient property with non-capturing lambda`() { val original = NonCapturingTransientProperty() val originalVal = original.transientVal val copy = original.serialize(context = KRYO_CHECKPOINT_CONTEXT).deserialize(context = KRYO_CHECKPOINT_CONTEXT) @@ -36,15 +37,15 @@ class KotlinUtilsTest : TestDependencyInjectionBase() { } @Test - fun `serialise transient property with non-capturing lamba`() { + fun `serialise transient property with non-capturing lambda`() { expectedEx.expect(KryoException::class.java) expectedEx.expectMessage("is not annotated or on the whitelist, so cannot be used in serialization") val original = NonCapturingTransientProperty() - original.serialize() + original.serialize(context = KRYO_P2P_CONTEXT) } @Test - fun `deserialise transient property with non-capturing lamba`() { + fun `deserialise transient property with non-capturing lambda`() { expectedEx.expect(KryoException::class.java) expectedEx.expectMessage("is not annotated or on the whitelist, so cannot be used in serialization") val original = NonCapturingTransientProperty() @@ -52,7 +53,7 @@ class KotlinUtilsTest : TestDependencyInjectionBase() { } @Test - fun `checkpointing a transient property with capturing lamba`() { + fun `checkpointing a transient property with capturing lambda`() { val original = CapturingTransientProperty("Hello") val originalVal = original.transientVal val copy = original.serialize(context = KRYO_CHECKPOINT_CONTEXT).deserialize(context = KRYO_CHECKPOINT_CONTEXT) @@ -63,15 +64,15 @@ class KotlinUtilsTest : TestDependencyInjectionBase() { } @Test - fun `serialise transient property with capturing lamba`() { + fun `serialise transient property with capturing lambda`() { expectedEx.expect(KryoException::class.java) expectedEx.expectMessage("is not annotated or on the whitelist, so cannot be used in serialization") val original = CapturingTransientProperty("Hello") - original.serialize() + original.serialize(context = KRYO_P2P_CONTEXT) } @Test - fun `deserialise transient property with capturing lamba`() { + fun `deserialise transient property with capturing lambda`() { expectedEx.expect(KryoException::class.java) expectedEx.expectMessage("is not annotated or on the whitelist, so cannot be used in serialization") val original = CapturingTransientProperty("Hello") From 01728e5a47c0592013772b8b8fe528a986260767 Mon Sep 17 00:00:00 2001 From: Patrick Kuo Date: Mon, 23 Oct 2017 11:46:24 +0100 Subject: [PATCH 29/94] Network map service REST API wrapper (#1907) * Network map client - WIP * Java doc and doc for doc site * remove javax.ws dependency * NetworkParameter -> NetworkParameters * move network map client to node * Fix jetty test dependencies * NetworkParameter -> NetworkParameters * Address PR issues * Address PR issues and unit test fix * Address PR issues --- docs/source/network-map.rst | 38 +++++ node/build.gradle | 11 ++ .../node/services/network/NetworkMapClient.kt | 70 ++++++++ .../network/HTTPNetworkMapClientTest.kt | 154 ++++++++++++++++++ 4 files changed, 273 insertions(+) create mode 100644 docs/source/network-map.rst create mode 100644 node/src/main/kotlin/net/corda/node/services/network/NetworkMapClient.kt create mode 100644 node/src/test/kotlin/net/corda/node/services/network/HTTPNetworkMapClientTest.kt diff --git a/docs/source/network-map.rst b/docs/source/network-map.rst new file mode 100644 index 0000000000..d8ee82a2cf --- /dev/null +++ b/docs/source/network-map.rst @@ -0,0 +1,38 @@ +Network Map +=========== + +Protocol Design +--------------- +The node info publishing protocol: + +* Create a ``NodeInfo`` object, and sign it to create a ``SignedData`` object. TODO: We will need list of signatures in ``SignedData`` to support multiple node identities in the future. + +* Serialise the signed data and POST the data to the network map server. + +* The network map server validates the signature and acknowledges the registration with a HTTP 200 response, it will return HTTP 400 "Bad Request" if the data failed validation or if the public key wasn't registered with the network. + +* The network map server will sign and distribute the new network map periodically. + +Node side network map update protocol: + +* The Corda node will query the network map service periodically according to the ``Expires`` attribute in the HTTP header. + +* The network map service returns a signed ``NetworkMap`` object, containing list of node info hashes and the network parameters hashes. + +* The node updates its local copy of ``NodeInfos`` if it is different from the newly downloaded ``NetworkMap``. + +Network Map service REST API: + ++----------------+-----------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+ +| Request method | Path | Description | ++================+===================================+========================================================================================================================================================+ +| POST | /api/network-map/publish | Publish new ``NodeInfo`` to the network map service, the legal identity in ``NodeInfo`` must match with the identity registered with the doorman. | ++----------------+-----------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+ +| GET | /api/network-map | Retrieve ``NetworkMap`` from the server, the ``NetworkMap`` object contains list of node info hashes and NetworkParameters hash. | ++----------------+-----------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+ +| GET | /api/network-map/node-info/{hash} | Retrieve ``NodeInfo`` object with the same hash. | ++----------------+-----------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+ +| GET | /api/network-map/parameters/{hash}| Retrieve ``NetworkParameters`` object with the same hash. | ++----------------+-----------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+ + +TODO: Access control of the network map will be added in the future. \ No newline at end of file diff --git a/node/build.gradle b/node/build.gradle index 72f45ea297..7d3313d454 100644 --- a/node/build.gradle +++ b/node/build.gradle @@ -182,6 +182,17 @@ dependencies { smokeTestCompile project(':smoke-test-utils') smokeTestCompile "org.assertj:assertj-core:${assertj_version}" smokeTestCompile "junit:junit:$junit_version" + + // Jetty dependencies for NetworkMapClient test. + // Web stuff: for HTTP[S] servlets + testCompile "org.eclipse.jetty:jetty-servlet:${jetty_version}" + testCompile "org.eclipse.jetty:jetty-webapp:${jetty_version}" + testCompile "javax.servlet:javax.servlet-api:3.1.0" + + // Jersey for JAX-RS implementation for use in Jetty + testCompile "org.glassfish.jersey.core:jersey-server:${jersey_version}" + testCompile "org.glassfish.jersey.containers:jersey-container-servlet-core:${jersey_version}" + testCompile "org.glassfish.jersey.containers:jersey-container-jetty-http:${jersey_version}" } task integrationTest(type: Test) { diff --git a/node/src/main/kotlin/net/corda/node/services/network/NetworkMapClient.kt b/node/src/main/kotlin/net/corda/node/services/network/NetworkMapClient.kt new file mode 100644 index 0000000000..2aba89084d --- /dev/null +++ b/node/src/main/kotlin/net/corda/node/services/network/NetworkMapClient.kt @@ -0,0 +1,70 @@ +package net.corda.node.services.network + +import com.fasterxml.jackson.databind.ObjectMapper +import net.corda.core.crypto.SecureHash +import net.corda.core.crypto.SignedData +import net.corda.core.node.NodeInfo +import net.corda.core.serialization.deserialize +import net.corda.core.serialization.serialize +import java.net.HttpURLConnection +import java.net.URL + +interface NetworkMapClient { + /** + * Publish node info to network map service. + */ + fun publish(signedNodeInfo: SignedData) + + /** + * Retrieve [NetworkMap] from the network map service containing list of node info hashes and network parameter hash. + */ + // TODO: Use NetworkMap object when available. + fun getNetworkMap(): List + + /** + * Retrieve [NodeInfo] from network map service using the node info hash. + */ + fun getNodeInfo(nodeInfoHash: SecureHash): NodeInfo? + + // TODO: Implement getNetworkParameter when its available. + //fun getNetworkParameter(networkParameterHash: SecureHash): NetworkParameter +} + +class HTTPNetworkMapClient(private val networkMapUrl: String) : NetworkMapClient { + override fun publish(signedNodeInfo: SignedData) { + val publishURL = URL("$networkMapUrl/publish") + val conn = publishURL.openConnection() as HttpURLConnection + conn.doOutput = true + conn.requestMethod = "POST" + conn.setRequestProperty("Content-Type", "application/octet-stream") + conn.outputStream.write(signedNodeInfo.serialize().bytes) + when (conn.responseCode) { + HttpURLConnection.HTTP_OK -> return + HttpURLConnection.HTTP_UNAUTHORIZED -> throw IllegalArgumentException(conn.errorStream.bufferedReader().readLine()) + else -> throw IllegalArgumentException("Unexpected response code ${conn.responseCode}, response error message: '${conn.errorStream.bufferedReader().readLines()}'") + } + } + + override fun getNetworkMap(): List { + val conn = URL(networkMapUrl).openConnection() as HttpURLConnection + + return when (conn.responseCode) { + HttpURLConnection.HTTP_OK -> { + val response = conn.inputStream.bufferedReader().use { it.readLine() } + ObjectMapper().readValue(response, List::class.java).map { SecureHash.parse(it.toString()) } + } + else -> throw IllegalArgumentException("Unexpected response code ${conn.responseCode}, response error message: '${conn.errorStream.bufferedReader().readLines()}'") + } + } + + override fun getNodeInfo(nodeInfoHash: SecureHash): NodeInfo? { + val nodeInfoURL = URL("$networkMapUrl/$nodeInfoHash") + val conn = nodeInfoURL.openConnection() as HttpURLConnection + + return when (conn.responseCode) { + HttpURLConnection.HTTP_OK -> conn.inputStream.readBytes().deserialize() + HttpURLConnection.HTTP_NOT_FOUND -> null + else -> throw IllegalArgumentException("Unexpected response code ${conn.responseCode}, response error message: '${conn.errorStream.bufferedReader().readLines()}'") + } + } +} \ No newline at end of file diff --git a/node/src/test/kotlin/net/corda/node/services/network/HTTPNetworkMapClientTest.kt b/node/src/test/kotlin/net/corda/node/services/network/HTTPNetworkMapClientTest.kt new file mode 100644 index 0000000000..058762bb7f --- /dev/null +++ b/node/src/test/kotlin/net/corda/node/services/network/HTTPNetworkMapClientTest.kt @@ -0,0 +1,154 @@ +package net.corda.node.services.network + +import com.fasterxml.jackson.databind.ObjectMapper +import net.corda.core.crypto.* +import net.corda.core.identity.CordaX500Name +import net.corda.core.identity.PartyAndCertificate +import net.corda.core.node.NodeInfo +import net.corda.core.serialization.deserialize +import net.corda.core.serialization.serialize +import net.corda.core.utilities.NetworkHostAndPort +import net.corda.node.utilities.CertificateType +import net.corda.node.utilities.X509Utilities +import net.corda.testing.TestDependencyInjectionBase +import org.assertj.core.api.Assertions.assertThat +import org.bouncycastle.asn1.x500.X500Name +import org.bouncycastle.cert.X509CertificateHolder +import org.eclipse.jetty.server.Server +import org.eclipse.jetty.server.ServerConnector +import org.eclipse.jetty.server.handler.HandlerCollection +import org.eclipse.jetty.servlet.ServletContextHandler +import org.eclipse.jetty.servlet.ServletHolder +import org.glassfish.jersey.server.ResourceConfig +import org.glassfish.jersey.servlet.ServletContainer +import org.junit.After +import org.junit.Before +import org.junit.Test +import java.io.ByteArrayInputStream +import java.io.InputStream +import java.net.InetSocketAddress +import java.security.cert.CertPath +import java.security.cert.Certificate +import java.security.cert.CertificateFactory +import java.security.cert.X509Certificate +import javax.ws.rs.* +import javax.ws.rs.core.MediaType +import javax.ws.rs.core.Response +import javax.ws.rs.core.Response.ok +import kotlin.test.assertEquals + +class HTTPNetworkMapClientTest : TestDependencyInjectionBase() { + private lateinit var server: Server + + private lateinit var networkMapClient: NetworkMapClient + private val rootCAKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) + private val rootCACert = X509Utilities.createSelfSignedCACertificate(CordaX500Name(commonName = "Corda Node Root CA", organisation = "R3 LTD", locality = "London", country = "GB"), rootCAKey) + private val intermediateCAKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) + private val intermediateCACert = X509Utilities.createCertificate(CertificateType.INTERMEDIATE_CA, rootCACert, rootCAKey, X500Name("CN=Corda Node Intermediate CA,L=London"), intermediateCAKey.public) + + @Before + fun setUp() { + server = Server(InetSocketAddress("localhost", 0)).apply { + handler = HandlerCollection().apply { + addHandler(ServletContextHandler().apply { + contextPath = "/" + val resourceConfig = ResourceConfig().apply { + // Add your API provider classes (annotated for JAX-RS) here + register(MockNetworkMapServer()) + } + val jerseyServlet = ServletHolder(ServletContainer(resourceConfig)).apply { initOrder = 0 }// Initialise at server start + addServlet(jerseyServlet, "/api/*") + }) + } + } + server.start() + + while (!server.isStarted) { + Thread.sleep(100) + } + + val hostAndPort = server.connectors.mapNotNull { it as? ServerConnector }.first() + networkMapClient = HTTPNetworkMapClient("http://${hostAndPort.host}:${hostAndPort.localPort}/api/network-map") + } + + @After + fun tearDown() { + server.stop() + } + + @Test + fun `registered node is added to the network map`() { + // Create node info. + val signedNodeInfo = createNodeInfo("Test1") + val nodeInfo = signedNodeInfo.verified() + + networkMapClient.publish(signedNodeInfo) + + val nodeInfoHash = nodeInfo.serialize().sha256() + + assertThat(networkMapClient.getNetworkMap()).containsExactly(nodeInfoHash) + assertEquals(nodeInfo, networkMapClient.getNodeInfo(nodeInfoHash)) + + val signedNodeInfo2 = createNodeInfo("Test2") + val nodeInfo2 = signedNodeInfo2.verified() + networkMapClient.publish(signedNodeInfo2) + + val nodeInfoHash2 = nodeInfo2.serialize().sha256() + assertThat(networkMapClient.getNetworkMap()).containsExactly(nodeInfoHash, nodeInfoHash2) + assertEquals(nodeInfo2, networkMapClient.getNodeInfo(nodeInfoHash2)) + } + + private fun createNodeInfo(organisation: String): SignedData { + val keyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) + val clientCert = X509Utilities.createCertificate(CertificateType.CLIENT_CA, intermediateCACert, intermediateCAKey, CordaX500Name(organisation = organisation, locality = "London", country = "GB"), keyPair.public) + val certPath = buildCertPath(clientCert.toX509Certificate(), intermediateCACert.toX509Certificate(), rootCACert.toX509Certificate()) + val nodeInfo = NodeInfo(listOf(NetworkHostAndPort("my.$organisation.com", 1234)), listOf(PartyAndCertificate(certPath)), 1, serial = 1L) + + // Create digital signature. + val digitalSignature = DigitalSignature.WithKey(keyPair.public, Crypto.doSign(keyPair.private, nodeInfo.serialize().bytes)) + + return SignedData(nodeInfo.serialize(), digitalSignature) + } +} + +@Path("network-map") +// This is a stub implementation of the network map rest API. +internal class MockNetworkMapServer { + private val nodeInfos = mutableMapOf() + @POST + @Path("publish") + @Consumes(MediaType.APPLICATION_OCTET_STREAM) + fun publishNodeInfo(input: InputStream): Response { + val registrationData = input.readBytes().deserialize>() + val nodeInfo = registrationData.verified() + val nodeInfoHash = nodeInfo.serialize().sha256() + nodeInfos.put(nodeInfoHash, nodeInfo) + return ok().build() + } + + @GET + @Produces(MediaType.APPLICATION_JSON) + fun getNetworkMap(): Response { + return Response.ok(ObjectMapper().writeValueAsString(nodeInfos.keys.map { it.toString() })).build() + } + + @GET + @Path("{var}") + @Produces(MediaType.APPLICATION_OCTET_STREAM) + fun getNodeInfo(@PathParam("var") nodeInfoHash: String): Response { + val nodeInfo = nodeInfos[SecureHash.parse(nodeInfoHash)] + return if (nodeInfo != null) { + Response.ok(nodeInfo.serialize().bytes) + } else { + Response.status(Response.Status.NOT_FOUND) + }.build() + } +} + +private fun buildCertPath(vararg certificates: Certificate): CertPath { + return CertificateFactory.getInstance("X509").generateCertPath(certificates.asList()) +} + +private fun X509CertificateHolder.toX509Certificate(): X509Certificate { + return CertificateFactory.getInstance("X509").generateCertificate(ByteArrayInputStream(encoded)) as X509Certificate +} \ No newline at end of file From cbb51350970d5589cdbdcdda229c940e795b9c7a Mon Sep 17 00:00:00 2001 From: Viktor Kolomeyko Date: Mon, 23 Oct 2017 15:04:25 +0100 Subject: [PATCH 30/94] Fixing Bank-Of-Corda Demo in `master` (#1922) * Fix-up: Bank Of Corda sample Use correct CorDapp packages to scan (cherry picked from commit 2caa134) * Set adequate permissions for the nodes such that NodeExplorer can connect (cherry picked from commit ae88242) * Set adequate permissions for the nodes such that NodeExplorer can connect (cherry picked from commit ae88242) * Correct run configuration * Fix-up port numbers --- .../BankOfCordaDriverKt___Issue_Web.xml | 15 +++++++++++++ .../BankOfCordaDriverKt___Run_Stack.xml | 15 +++++++++++++ .../net/corda/bank/BankOfCordaDriver.kt | 22 ++++++++++++------- .../kotlin/net/corda/testing/driver/Driver.kt | 2 +- 4 files changed, 45 insertions(+), 9 deletions(-) create mode 100644 .idea/runConfigurations/BankOfCordaDriverKt___Issue_Web.xml create mode 100644 .idea/runConfigurations/BankOfCordaDriverKt___Run_Stack.xml diff --git a/.idea/runConfigurations/BankOfCordaDriverKt___Issue_Web.xml b/.idea/runConfigurations/BankOfCordaDriverKt___Issue_Web.xml new file mode 100644 index 0000000000..321d3d2d06 --- /dev/null +++ b/.idea/runConfigurations/BankOfCordaDriverKt___Issue_Web.xml @@ -0,0 +1,15 @@ + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations/BankOfCordaDriverKt___Run_Stack.xml b/.idea/runConfigurations/BankOfCordaDriverKt___Run_Stack.xml new file mode 100644 index 0000000000..ea61b6ef8d --- /dev/null +++ b/.idea/runConfigurations/BankOfCordaDriverKt___Run_Stack.xml @@ -0,0 +1,15 @@ + + + + + \ No newline at end of file diff --git a/samples/bank-of-corda-demo/src/main/kotlin/net/corda/bank/BankOfCordaDriver.kt b/samples/bank-of-corda-demo/src/main/kotlin/net/corda/bank/BankOfCordaDriver.kt index b24a49b33a..b526e580fe 100644 --- a/samples/bank-of-corda-demo/src/main/kotlin/net/corda/bank/BankOfCordaDriver.kt +++ b/samples/bank-of-corda-demo/src/main/kotlin/net/corda/bank/BankOfCordaDriver.kt @@ -5,6 +5,7 @@ import net.corda.bank.api.BankOfCordaClientApi import net.corda.bank.api.BankOfCordaWebApi.IssueRequestParams import net.corda.core.identity.CordaX500Name import net.corda.core.utilities.NetworkHostAndPort +import net.corda.finance.flows.CashConfigDataFlow import net.corda.finance.flows.CashExitFlow import net.corda.finance.flows.CashIssueAndPaymentFlow import net.corda.finance.flows.CashPaymentFlow @@ -57,23 +58,28 @@ private class BankOfCordaDriver { when (role) { Role.ISSUER -> { driver(dsl = { + startNotaryNode(providedName = DUMMY_NOTARY.name, validating = true) val bankUser = User( BANK_USERNAME, "test", permissions = setOf( + startFlowPermission(), + startFlowPermission(), + startFlowPermission(), startFlowPermission(), - startFlowPermission())) - val bigCorpUser = User(BIGCORP_USERNAME, "test", - permissions = setOf( - startFlowPermission())) - startNotaryNode(DUMMY_NOTARY.name, validating = true) + startFlowPermission() + )) val bankOfCorda = startNode( providedName = BOC.name, rpcUsers = listOf(bankUser)) + val bigCorpUser = User(BIGCORP_USERNAME, "test", + permissions = setOf( + startFlowPermission(), + startFlowPermission())) startNode(providedName = BIGCORP_LEGAL_NAME, rpcUsers = listOf(bigCorpUser)) startWebserver(bankOfCorda.get()) waitForAllNodesToFinish() - }, isDebug = true) + }, isDebug = true, extraCordappPackagesToScan = listOf("net.corda.finance.contracts.asset")) } else -> { val requestParams = IssueRequestParams(options.valueOf(quantity), options.valueOf(currency), BIGCORP_LEGAL_NAME, @@ -81,12 +87,12 @@ private class BankOfCordaDriver { when(role) { Role.ISSUE_CASH_RPC -> { println("Requesting Cash via RPC ...") - val result = BankOfCordaClientApi(NetworkHostAndPort("localhost", 10006)).requestRPCIssue(requestParams) + val result = BankOfCordaClientApi(NetworkHostAndPort("localhost", 10004)).requestRPCIssue(requestParams) println("Success!! You transaction receipt is ${result.tx.id}") } Role.ISSUE_CASH_WEB -> { println("Requesting Cash via Web ...") - val result = BankOfCordaClientApi(NetworkHostAndPort("localhost", 10007)).requestWebIssue(requestParams) + val result = BankOfCordaClientApi(NetworkHostAndPort("localhost", 10005)).requestWebIssue(requestParams) if (result) println("Successfully processed Cash Issue request") } diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt index d6733b1576..ff8bdc3e14 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt @@ -961,7 +961,7 @@ class DriverDSL( maximumHeapSize: String ): CordaFuture { val processFuture = executorService.fork { - log.info("Starting out-of-process Node ${nodeConf.myLegalName.organisation}") + log.info("Starting out-of-process Node ${nodeConf.myLegalName.organisation}, debug port is " + debugPort ?: "not enabled") // Write node.conf writeConfig(nodeConf.baseDirectory, "node.conf", config) From 303acf715f4a9a8841f274bd2387b5032285c24b Mon Sep 17 00:00:00 2001 From: Katelyn Baker Date: Mon, 23 Oct 2017 15:08:30 +0100 Subject: [PATCH 31/94] CORDA-435 - AMQP serialisation cannot work with private vals They won't be reported as properties by the introspector and thus we will fail to find a constructor for them. This makes sense as we will be unable to serialise an object whose members we cannot read --- .../corda/node/services/events/NodeSchedulerServiceTest.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/node/src/test/kotlin/net/corda/node/services/events/NodeSchedulerServiceTest.kt b/node/src/test/kotlin/net/corda/node/services/events/NodeSchedulerServiceTest.kt index d8a0c51096..446976faf2 100644 --- a/node/src/test/kotlin/net/corda/node/services/events/NodeSchedulerServiceTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/events/NodeSchedulerServiceTest.kt @@ -138,7 +138,9 @@ class NodeSchedulerServiceTest : SingletonSerializeAsToken() { resetTestSerialization() } - class TestState(private val flowLogicRef: FlowLogicRef, val instant: Instant, private val myIdentity: Party) : LinearState, SchedulableState { + // Ignore IntelliJ when it says these properties can be private, if they are we cannot serialise them + // in AMQP. + class TestState(val flowLogicRef: FlowLogicRef, val instant: Instant, val myIdentity: Party) : LinearState, SchedulableState { override val participants: List get() = listOf(myIdentity) From 2463cbd416efbc5425d4fbcbf2c33426b0855ef4 Mon Sep 17 00:00:00 2001 From: Katelyn Baker Date: Mon, 23 Oct 2017 16:23:12 +0100 Subject: [PATCH 32/94] CORDA-435 - AMQP enablement fixes AMQP has different serialization rules than Kryo surrounding the way we introspect objects to work out how to construct them --- .../services/vault/VaultSoftLockManagerTest.kt | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/node/src/test/kotlin/net/corda/node/services/vault/VaultSoftLockManagerTest.kt b/node/src/test/kotlin/net/corda/node/services/vault/VaultSoftLockManagerTest.kt index 48a8347969..341accf002 100644 --- a/node/src/test/kotlin/net/corda/node/services/vault/VaultSoftLockManagerTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/vault/VaultSoftLockManagerTest.kt @@ -40,7 +40,7 @@ import java.util.concurrent.atomic.AtomicBoolean import kotlin.reflect.jvm.jvmName import kotlin.test.assertEquals -private class NodePair(private val mockNet: MockNetwork) { +class NodePair(private val mockNet: MockNetwork) { private class ServerLogic(private val session: FlowSession, private val running: AtomicBoolean) : FlowLogic() { @Suspendable override fun call() { @@ -103,8 +103,8 @@ class VaultSoftLockManagerTest { mockNet.stopNodes() } - private object CommandDataImpl : CommandData - private class ClientLogic(nodePair: NodePair, private val state: ContractState) : NodePair.AbstractClientLogic>(nodePair) { + object CommandDataImpl : CommandData + class ClientLogic(nodePair: NodePair, val state: ContractState) : NodePair.AbstractClientLogic>(nodePair) { override fun callImpl() = run { subFlow(FinalityFlow(serviceHub.signInitialTransaction(TransactionBuilder(notary = ourIdentity).apply { addOutputState(state, ContractImpl::class.jvmName) @@ -116,12 +116,15 @@ class VaultSoftLockManagerTest { } } - private abstract class SingleParticipantState(nodePair: NodePair) : ContractState { - override val participants = listOf(nodePair.client.info.chooseIdentity()) + private abstract class ParticipantState(override val participants: List) : ContractState + + private class PlainOldState(participants: List) : ParticipantState(participants) { + constructor(nodePair: NodePair) : this(listOf(nodePair.client.info.chooseIdentity())) } - private class PlainOldState(nodePair: NodePair) : SingleParticipantState(nodePair) - private class FungibleAssetImpl(nodePair: NodePair) : SingleParticipantState(nodePair), FungibleAsset { + private class FungibleAssetImpl(participants: List) : ParticipantState(participants), FungibleAsset { + constructor(nodePair: NodePair) : this(listOf(nodePair.client.info.chooseIdentity())) + override val owner get() = participants[0] override fun withNewOwner(newOwner: AbstractParty) = throw UnsupportedOperationException() override val amount get() = Amount(1, Issued(PartyAndReference(owner, OpaqueBytes.of(1)), Unit)) From b04368e36a6133edb4e5d60220db5819b64b98c9 Mon Sep 17 00:00:00 2001 From: Alberto Arri <30873160+al-r3@users.noreply.github.com> Date: Mon, 23 Oct 2017 16:50:53 +0100 Subject: [PATCH 33/94] [CORDA-442] make MockNetwork not start a networkmap node (#1908) * [CORDA-442] make MockNetwork not start a networkmap node Now MockNetwork will put the appropriate NodeInfos inside each running node networkMapCache. Tests relating to networkmap node starting and interaction have been removed since they where relaying on MockNetwork --- .../confidential/SwapIdentitiesFlowTests.kt | 2 +- .../net/corda/core/flows/AttachmentTests.kt | 9 +- .../core/flows/CollectSignaturesFlowTests.kt | 1 - .../core/flows/ContractUpgradeFlowTest.kt | 1 - .../net/corda/core/flows/FinalityFlowTests.kt | 1 - .../AttachmentSerializationTest.kt | 6 +- .../net/corda/node/internal/AbstractNode.kt | 1 + .../net/corda/node/CordaRPCOpsImplTest.kt | 6 +- .../node/messaging/InMemoryMessagingTests.kt | 20 +- .../node/messaging/TwoPartyTradeFlowTests.kt | 13 +- .../corda/node/services/NotaryChangeTests.kt | 2 - .../services/events/ScheduledFlowTests.kt | 7 +- .../network/AbstractNetworkMapServiceTest.kt | 281 ------------------ .../network/InMemoryNetworkMapServiceTest.kt | 9 - .../services/network/NetworkMapCacheTest.kt | 18 +- .../PersistentNetworkMapServiceTest.kt | 56 ---- .../statemachine/FlowFrameworkTests.kt | 30 +- .../transactions/NotaryServiceTests.kt | 1 - .../ValidatingNotaryServiceTests.kt | 1 - .../vault/VaultSoftLockManagerTest.kt | 5 +- .../corda/netmap/simulation/IRSSimulation.kt | 2 +- .../net/corda/netmap/simulation/Simulation.kt | 35 +-- .../kotlin/net/corda/testing/NodeTestUtils.kt | 2 +- .../kotlin/net/corda/testing/node/MockNode.kt | 83 +++--- 24 files changed, 79 insertions(+), 513 deletions(-) delete mode 100644 node/src/test/kotlin/net/corda/node/services/network/AbstractNetworkMapServiceTest.kt delete mode 100644 node/src/test/kotlin/net/corda/node/services/network/InMemoryNetworkMapServiceTest.kt delete mode 100644 node/src/test/kotlin/net/corda/node/services/network/PersistentNetworkMapServiceTest.kt diff --git a/confidential-identities/src/test/kotlin/net/corda/confidential/SwapIdentitiesFlowTests.kt b/confidential-identities/src/test/kotlin/net/corda/confidential/SwapIdentitiesFlowTests.kt index 8e73042899..27fb5b6791 100644 --- a/confidential-identities/src/test/kotlin/net/corda/confidential/SwapIdentitiesFlowTests.kt +++ b/confidential-identities/src/test/kotlin/net/corda/confidential/SwapIdentitiesFlowTests.kt @@ -16,7 +16,7 @@ class SwapIdentitiesFlowTests { val mockNet = MockNetwork(threadPerNode = true) // Set up values we'll need - val notaryNode = mockNet.createNotaryNode() + mockNet.createNotaryNode() val aliceNode = mockNet.createPartyNode(ALICE.name) val bobNode = mockNet.createPartyNode(BOB.name) val alice = aliceNode.info.singleIdentity() diff --git a/core/src/test/kotlin/net/corda/core/flows/AttachmentTests.kt b/core/src/test/kotlin/net/corda/core/flows/AttachmentTests.kt index aae7f6fc0d..a9ba82d71b 100644 --- a/core/src/test/kotlin/net/corda/core/flows/AttachmentTests.kt +++ b/core/src/test/kotlin/net/corda/core/flows/AttachmentTests.kt @@ -7,7 +7,6 @@ import net.corda.core.crypto.sha256 import net.corda.core.identity.Party import net.corda.core.internal.FetchAttachmentsFlow import net.corda.core.internal.FetchDataFlow -import net.corda.core.messaging.SingleMessageRecipient import net.corda.core.utilities.getOrThrow import net.corda.node.internal.StartedNode import net.corda.node.services.config.NodeConfiguration @@ -60,7 +59,6 @@ class AttachmentTests { // Ensure that registration was successful before progressing any further mockNet.runNetwork() - aliceNode.internals.ensureRegistered() val alice = aliceNode.info.singleIdentity() aliceNode.internals.registerInitiatedFlow(FetchAttachmentsResponse::class.java) @@ -98,7 +96,6 @@ class AttachmentTests { // Ensure that registration was successful before progressing any further mockNet.runNetwork() - aliceNode.internals.ensureRegistered() aliceNode.internals.registerInitiatedFlow(FetchAttachmentsResponse::class.java) bobNode.internals.registerInitiatedFlow(FetchAttachmentsResponse::class.java) @@ -117,19 +114,17 @@ class AttachmentTests { fun `malicious response`() { // Make a node that doesn't do sanity checking at load time. val aliceNode = mockNet.createNotaryNode(legalName = ALICE.name, nodeFactory = object : MockNetwork.Factory { - override fun create(config: NodeConfiguration, network: MockNetwork, networkMapAddr: SingleMessageRecipient?, + override fun create(config: NodeConfiguration, network: MockNetwork, id: Int, notaryIdentity: Pair?, entropyRoot: BigInteger): MockNetwork.MockNode { - return object : MockNetwork.MockNode(config, network, networkMapAddr, id, notaryIdentity, entropyRoot) { + return object : MockNetwork.MockNode(config, network, id, notaryIdentity, entropyRoot) { override fun start() = super.start().apply { attachments.checkAttachmentsOnLoad = false } } } }, validating = false) val bobNode = mockNet.createNode(legalName = BOB.name) - // Ensure that registration was successful before progressing any further mockNet.runNetwork() - aliceNode.internals.ensureRegistered() val alice = aliceNode.services.myInfo.identityFromX500Name(ALICE_NAME) aliceNode.internals.registerInitiatedFlow(FetchAttachmentsResponse::class.java) diff --git a/core/src/test/kotlin/net/corda/core/flows/CollectSignaturesFlowTests.kt b/core/src/test/kotlin/net/corda/core/flows/CollectSignaturesFlowTests.kt index a0b2c0020c..9ecea5940b 100644 --- a/core/src/test/kotlin/net/corda/core/flows/CollectSignaturesFlowTests.kt +++ b/core/src/test/kotlin/net/corda/core/flows/CollectSignaturesFlowTests.kt @@ -44,7 +44,6 @@ class CollectSignaturesFlowTests { bobNode = mockNet.createPartyNode(BOB.name) charlieNode = mockNet.createPartyNode(CHARLIE.name) mockNet.runNetwork() - aliceNode.internals.ensureRegistered() alice = aliceNode.info.singleIdentity() bob = bobNode.info.singleIdentity() charlie = charlieNode.info.singleIdentity() diff --git a/core/src/test/kotlin/net/corda/core/flows/ContractUpgradeFlowTest.kt b/core/src/test/kotlin/net/corda/core/flows/ContractUpgradeFlowTest.kt index eba602a47e..19b93ba663 100644 --- a/core/src/test/kotlin/net/corda/core/flows/ContractUpgradeFlowTest.kt +++ b/core/src/test/kotlin/net/corda/core/flows/ContractUpgradeFlowTest.kt @@ -47,7 +47,6 @@ class ContractUpgradeFlowTest { // Process registration mockNet.runNetwork() - aliceNode.internals.ensureRegistered() notary = notaryNode.services.getDefaultNotary() } diff --git a/core/src/test/kotlin/net/corda/core/flows/FinalityFlowTests.kt b/core/src/test/kotlin/net/corda/core/flows/FinalityFlowTests.kt index eec08b4c6b..1bc496db6c 100644 --- a/core/src/test/kotlin/net/corda/core/flows/FinalityFlowTests.kt +++ b/core/src/test/kotlin/net/corda/core/flows/FinalityFlowTests.kt @@ -30,7 +30,6 @@ class FinalityFlowTests { val aliceNode = mockNet.createPartyNode(ALICE_NAME) val bobNode = mockNet.createPartyNode(BOB_NAME) mockNet.runNetwork() - aliceNode.internals.ensureRegistered() aliceServices = aliceNode.services bobServices = bobNode.services alice = aliceNode.info.singleIdentity() diff --git a/core/src/test/kotlin/net/corda/core/serialization/AttachmentSerializationTest.kt b/core/src/test/kotlin/net/corda/core/serialization/AttachmentSerializationTest.kt index f6d1eec6b2..62d9b0bded 100644 --- a/core/src/test/kotlin/net/corda/core/serialization/AttachmentSerializationTest.kt +++ b/core/src/test/kotlin/net/corda/core/serialization/AttachmentSerializationTest.kt @@ -9,7 +9,6 @@ import net.corda.core.flows.InitiatingFlow import net.corda.core.flows.TestDataVendingFlow import net.corda.core.internal.FetchAttachmentsFlow import net.corda.core.internal.FetchDataFlow -import net.corda.core.messaging.SingleMessageRecipient import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.unwrap import net.corda.node.internal.InitiatedFlowFactory @@ -74,7 +73,6 @@ class AttachmentSerializationTest { client = mockNet.createNode() client.internals.disableDBCloseOnStop() // Otherwise the in-memory database may disappear (taking the checkpoint with it) while we reboot the client. mockNet.runNetwork() - server.internals.ensureRegistered() } @After @@ -161,9 +159,9 @@ class AttachmentSerializationTest { private fun rebootClientAndGetAttachmentContent(checkAttachmentsOnLoad: Boolean = true): String { client.dispose() client = mockNet.createNode(client.internals.id, object : MockNetwork.Factory { - override fun create(config: NodeConfiguration, network: MockNetwork, networkMapAddr: SingleMessageRecipient?, + override fun create(config: NodeConfiguration, network: MockNetwork, id: Int, notaryIdentity: Pair?, entropyRoot: BigInteger): MockNetwork.MockNode { - return object : MockNetwork.MockNode(config, network, networkMapAddr, id, notaryIdentity, entropyRoot) { + return object : MockNetwork.MockNode(config, network, id, notaryIdentity, entropyRoot) { override fun start() = super.start().apply { attachments.checkAttachmentsOnLoad = checkAttachmentsOnLoad } } } diff --git a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt index d53535380d..f7adde2604 100644 --- a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt +++ b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt @@ -689,6 +689,7 @@ abstract class AbstractNode(config: NodeConfiguration, toRun() } runOnStop.clear() + _started = null } protected abstract fun makeMessagingService(legalIdentity: PartyAndCertificate): MessagingService diff --git a/node/src/test/kotlin/net/corda/node/CordaRPCOpsImplTest.kt b/node/src/test/kotlin/net/corda/node/CordaRPCOpsImplTest.kt index ec69a89fe5..ae39004b31 100644 --- a/node/src/test/kotlin/net/corda/node/CordaRPCOpsImplTest.kt +++ b/node/src/test/kotlin/net/corda/node/CordaRPCOpsImplTest.kt @@ -29,9 +29,12 @@ import net.corda.node.services.FlowPermissions.Companion.startFlowPermission import net.corda.node.services.messaging.CURRENT_RPC_CONTEXT import net.corda.node.services.messaging.RpcContext import net.corda.nodeapi.User -import net.corda.testing.* +import net.corda.testing.chooseIdentity +import net.corda.testing.expect +import net.corda.testing.expectEvents import net.corda.testing.node.MockNetwork import net.corda.testing.node.MockNetwork.MockNode +import net.corda.testing.sequence import org.apache.commons.io.IOUtils import org.assertj.core.api.Assertions.assertThatExceptionOfType import org.junit.After @@ -72,7 +75,6 @@ class CordaRPCOpsImplTest { )))) mockNet.runNetwork() - mockNet.networkMapNode.internals.ensureRegistered() notary = rpc.notaryIdentities().first() } diff --git a/node/src/test/kotlin/net/corda/node/messaging/InMemoryMessagingTests.kt b/node/src/test/kotlin/net/corda/node/messaging/InMemoryMessagingTests.kt index b3a3fb32e4..f5e842347e 100644 --- a/node/src/test/kotlin/net/corda/node/messaging/InMemoryMessagingTests.kt +++ b/node/src/test/kotlin/net/corda/node/messaging/InMemoryMessagingTests.kt @@ -47,23 +47,17 @@ class InMemoryMessagingTests { @Test fun basics() { - val node1 = mockNet.networkMapNode + val node1 = mockNet.createNode() val node2 = mockNet.createNode() val node3 = mockNet.createNode() val bits = "test-content".toByteArray() var finalDelivery: Message? = null - - with(node2) { - node2.network.addMessageHandler { msg, _ -> - node2.network.send(msg, node3.network.myAddress) - } + node2.network.addMessageHandler { msg, _ -> + node2.network.send(msg, node3.network.myAddress) } - - with(node3) { - node2.network.addMessageHandler { msg, _ -> - finalDelivery = msg - } + node3.network.addMessageHandler { msg, _ -> + finalDelivery = msg } // Node 1 sends a message and it should end up in finalDelivery, after we run the network @@ -76,7 +70,7 @@ class InMemoryMessagingTests { @Test fun broadcast() { - val node1 = mockNet.networkMapNode + val node1 = mockNet.createNode() val node2 = mockNet.createNode() val node3 = mockNet.createNode() @@ -95,7 +89,7 @@ class InMemoryMessagingTests { */ @Test fun `skip unhandled messages`() { - val node1 = mockNet.networkMapNode + val node1 = mockNet.createNode() val node2 = mockNet.createNode() var received = 0 diff --git a/node/src/test/kotlin/net/corda/node/messaging/TwoPartyTradeFlowTests.kt b/node/src/test/kotlin/net/corda/node/messaging/TwoPartyTradeFlowTests.kt index 148ca7bbb4..89e4c49cf5 100644 --- a/node/src/test/kotlin/net/corda/node/messaging/TwoPartyTradeFlowTests.kt +++ b/node/src/test/kotlin/net/corda/node/messaging/TwoPartyTradeFlowTests.kt @@ -13,7 +13,6 @@ import net.corda.core.internal.FlowStateMachine import net.corda.core.internal.concurrent.map import net.corda.core.internal.rootCause import net.corda.core.messaging.DataFeed -import net.corda.core.messaging.SingleMessageRecipient import net.corda.core.messaging.StateMachineTransactionMapping import net.corda.core.node.services.Vault import net.corda.core.serialization.CordaSerializable @@ -75,7 +74,7 @@ class TwoPartyTradeFlowTests(val anonymous: Boolean) { companion object { private val cordappPackages = listOf("net.corda.finance.contracts") @JvmStatic - @Parameterized.Parameters + @Parameterized.Parameters(name = "Anonymous = {0}") fun data(): Collection { return listOf(true, false) } @@ -269,9 +268,9 @@ class TwoPartyTradeFlowTests(val anonymous: Boolean) { // ... bring the node back up ... the act of constructing the SMM will re-register the message handlers // that Bob was waiting on before the reboot occurred. bobNode = mockNet.createNode(bobAddr.id, object : MockNetwork.Factory { - override fun create(config: NodeConfiguration, network: MockNetwork, networkMapAddr: SingleMessageRecipient?, + override fun create(config: NodeConfiguration, network: MockNetwork, id: Int, notaryIdentity: Pair?, entropyRoot: BigInteger): MockNetwork.MockNode { - return MockNetwork.MockNode(config, network, networkMapAddr, bobAddr.id, notaryIdentity, entropyRoot) + return MockNetwork.MockNode(config, network, bobAddr.id, notaryIdentity, entropyRoot) } }, BOB_NAME) @@ -311,10 +310,9 @@ class TwoPartyTradeFlowTests(val anonymous: Boolean) { return mockNet.createNode(nodeFactory = object : MockNetwork.Factory { override fun create(config: NodeConfiguration, network: MockNetwork, - networkMapAddr: SingleMessageRecipient?, id: Int, notaryIdentity: Pair?, entropyRoot: BigInteger): MockNetwork.MockNode { - return object : MockNetwork.MockNode(config, network, networkMapAddr, id, notaryIdentity, entropyRoot) { + return object : MockNetwork.MockNode(config, network, id, notaryIdentity, entropyRoot) { // That constructs a recording tx storage override fun makeTransactionStorage(): WritableTransactionStorage { return RecordingTransactionStorage(database, super.makeTransactionStorage()) @@ -332,7 +330,6 @@ class TwoPartyTradeFlowTests(val anonymous: Boolean) { val bobNode = makeNodeWithTracking(BOB_NAME) val bankNode = makeNodeWithTracking(BOC_NAME) mockNet.runNetwork() - notaryNode.internals.ensureRegistered() val notary = aliceNode.services.getDefaultNotary() val alice = aliceNode.info.singleIdentity() val bob = bobNode.info.singleIdentity() @@ -440,7 +437,6 @@ class TwoPartyTradeFlowTests(val anonymous: Boolean) { val bankNode = makeNodeWithTracking(BOC_NAME) mockNet.runNetwork() - notaryNode.internals.ensureRegistered() val notary = aliceNode.services.getDefaultNotary() val alice: Party = aliceNode.info.singleIdentity() val bank: Party = bankNode.info.singleIdentity() @@ -596,7 +592,6 @@ class TwoPartyTradeFlowTests(val anonymous: Boolean) { val bankNode = mockNet.createPartyNode(BOC_NAME) mockNet.runNetwork() - notaryNode.internals.ensureRegistered() val notary = aliceNode.services.getDefaultNotary() val alice = aliceNode.info.singleIdentity() val bob = bobNode.info.singleIdentity() diff --git a/node/src/test/kotlin/net/corda/node/services/NotaryChangeTests.kt b/node/src/test/kotlin/net/corda/node/services/NotaryChangeTests.kt index 58aea5fac8..1beceb0855 100644 --- a/node/src/test/kotlin/net/corda/node/services/NotaryChangeTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/NotaryChangeTests.kt @@ -13,7 +13,6 @@ import net.corda.core.transactions.WireTransaction import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.seconds import net.corda.node.internal.StartedNode -import net.corda.node.services.api.ServiceHubInternal import net.corda.testing.* import net.corda.testing.contracts.DummyContract import net.corda.testing.node.MockNetwork @@ -43,7 +42,6 @@ class NotaryChangeTests { clientNodeB = mockNet.createNode() newNotaryNode = mockNet.createNotaryNode(legalName = DUMMY_NOTARY.name.copy(organisation = "Dummy Notary 2")) mockNet.runNetwork() // Clear network map registration messages - oldNotaryNode.internals.ensureRegistered() oldNotaryParty = newNotaryNode.services.networkMapCache.getNotary(DUMMY_NOTARY_SERVICE_NAME)!! newNotaryParty = newNotaryNode.services.networkMapCache.getNotary(DUMMY_NOTARY_SERVICE_NAME.copy(organisation = "Dummy Notary 2"))!! } diff --git a/node/src/test/kotlin/net/corda/node/services/events/ScheduledFlowTests.kt b/node/src/test/kotlin/net/corda/node/services/events/ScheduledFlowTests.kt index be2fc12b96..9999b927e0 100644 --- a/node/src/test/kotlin/net/corda/node/services/events/ScheduledFlowTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/events/ScheduledFlowTests.kt @@ -16,8 +16,11 @@ import net.corda.core.transactions.TransactionBuilder import net.corda.core.utilities.getOrThrow import net.corda.node.internal.StartedNode import net.corda.node.services.statemachine.StateMachineManager -import net.corda.testing.* +import net.corda.testing.DUMMY_NOTARY +import net.corda.testing.chooseIdentity import net.corda.testing.contracts.DummyContract +import net.corda.testing.dummyCommand +import net.corda.testing.getDefaultNotary import net.corda.testing.node.MockNetwork import org.junit.After import org.junit.Assert.* @@ -96,8 +99,6 @@ class ScheduledFlowTests { val a = mockNet.createUnstartedNode() val b = mockNet.createUnstartedNode() - notaryNode.internals.ensureRegistered() - mockNet.startNodes() nodeA = a.started!! nodeB = b.started!! diff --git a/node/src/test/kotlin/net/corda/node/services/network/AbstractNetworkMapServiceTest.kt b/node/src/test/kotlin/net/corda/node/services/network/AbstractNetworkMapServiceTest.kt deleted file mode 100644 index b3b9a9f769..0000000000 --- a/node/src/test/kotlin/net/corda/node/services/network/AbstractNetworkMapServiceTest.kt +++ /dev/null @@ -1,281 +0,0 @@ -package net.corda.node.services.network - -import net.corda.core.concurrent.CordaFuture -import net.corda.core.identity.CordaX500Name -import net.corda.core.messaging.SingleMessageRecipient -import net.corda.core.node.NodeInfo -import net.corda.core.serialization.deserialize -import net.corda.core.utilities.getOrThrow -import net.corda.node.internal.StartedNode -import net.corda.node.services.api.NetworkMapCacheInternal -import net.corda.node.services.config.NodeConfiguration -import net.corda.node.services.messaging.MessagingService -import net.corda.node.services.messaging.send -import net.corda.node.services.messaging.sendRequest -import net.corda.node.services.network.AbstractNetworkMapServiceTest.Changed.Added -import net.corda.node.services.network.AbstractNetworkMapServiceTest.Changed.Removed -import net.corda.node.services.network.NetworkMapService.* -import net.corda.node.services.network.NetworkMapService.Companion.FETCH_TOPIC -import net.corda.node.services.network.NetworkMapService.Companion.PUSH_ACK_TOPIC -import net.corda.node.services.network.NetworkMapService.Companion.PUSH_TOPIC -import net.corda.node.services.network.NetworkMapService.Companion.QUERY_TOPIC -import net.corda.node.services.network.NetworkMapService.Companion.REGISTER_TOPIC -import net.corda.node.services.network.NetworkMapService.Companion.SUBSCRIPTION_TOPIC -import net.corda.node.utilities.AddOrRemove -import net.corda.node.utilities.AddOrRemove.ADD -import net.corda.node.utilities.AddOrRemove.REMOVE -import net.corda.nodeapi.internal.ServiceInfo -import net.corda.testing.* -import net.corda.testing.node.MockNetwork -import net.corda.testing.node.MockNetwork.MockNode -import org.assertj.core.api.Assertions.assertThat -import org.junit.After -import org.junit.Before -import org.junit.Test -import java.math.BigInteger -import java.security.KeyPair -import java.time.Instant -import java.util.* -import java.util.concurrent.LinkedBlockingQueue - -abstract class AbstractNetworkMapServiceTest { - lateinit var mockNet: MockNetwork - lateinit var mapServiceNode: StartedNode - lateinit var alice: StartedNode - - companion object { - val subscriberLegalName = CordaX500Name(organisation = "Subscriber", locality = "New York", country = "US") - } - - @Before - fun setup() { - mockNet = MockNetwork(defaultFactory = nodeFactory) - mapServiceNode = mockNet.networkMapNode - alice = mockNet.createNode(nodeFactory = nodeFactory, legalName = ALICE.name) - mockNet.runNetwork() - lastSerial = System.currentTimeMillis() - } - - @After - fun tearDown() { - mockNet.stopNodes() - } - - protected abstract val nodeFactory: MockNetwork.Factory<*> - - protected abstract val networkMapService: S - - // For persistent service, switch out the implementation for a newly instantiated one so we can check the state is preserved. - protected abstract fun swizzle() - - @Test - fun `all nodes register themselves`() { - // setup has run the network and so we immediately expect the network map service to be correctly populated - assertThat(alice.fetchMap()).containsOnly(Added(mapServiceNode), Added(alice)) - assertThat(alice.identityQuery()).isEqualTo(alice.info) - assertThat(mapServiceNode.identityQuery()).isEqualTo(mapServiceNode.info) - } - - @Test - fun `re-register the same node`() { - val response = alice.registration(ADD) - swizzle() - assertThat(response.getOrThrow().error).isNull() - assertThat(alice.fetchMap()).containsOnly(Added(mapServiceNode), Added(alice)) // Confirm it's a no-op - } - - @Test - fun `re-register with smaller serial value`() { - val response = alice.registration(ADD, serial = 1) - swizzle() - assertThat(response.getOrThrow().error).isNotNull() // Make sure send error message is sent back - assertThat(alice.fetchMap()).containsOnly(Added(mapServiceNode), Added(alice)) // Confirm it's a no-op - } - - @Test - fun `de-register node`() { - val response = alice.registration(REMOVE) - swizzle() - assertThat(response.getOrThrow().error).isNull() - assertThat(alice.fetchMap()).containsOnly(Added(mapServiceNode), Removed(alice)) - swizzle() - assertThat(alice.identityQuery()).isNull() - assertThat(mapServiceNode.identityQuery()).isEqualTo(mapServiceNode.info) - } - - @Test - fun `de-register same node again`() { - alice.registration(REMOVE) - val response = alice.registration(REMOVE) - swizzle() - assertThat(response.getOrThrow().error).isNotNull() // Make sure send error message is sent back - assertThat(alice.fetchMap()).containsOnly(Added(mapServiceNode), Removed(alice)) - } - - @Test - fun `de-register unknown node`() { - val bob = newNodeSeparateFromNetworkMap(BOB.name) - val response = bob.registration(REMOVE) - swizzle() - assertThat(response.getOrThrow().error).isNotNull() // Make sure send error message is sent back - assertThat(alice.fetchMap()).containsOnly(Added(mapServiceNode), Added(alice)) - } - - @Test - fun `subscribed while new node registers`() { - val updates = alice.subscribe() - swizzle() - val bob = addNewNodeToNetworkMap(BOB.name) - swizzle() - val update = updates.single() - assertThat(update.mapVersion).isEqualTo(networkMapService.mapVersion) - assertThat(update.wireReg.verified().toChanged()).isEqualTo(Added(bob.info)) - } - - @Test - fun `subscribed while node de-registers`() { - val bob = addNewNodeToNetworkMap(BOB.name) - val updates = alice.subscribe() - bob.registration(REMOVE) - swizzle() - assertThat(updates.map { it.wireReg.verified().toChanged() }).containsOnly(Removed(bob.info)) - } - - @Test - fun unsubscribe() { - val updates = alice.subscribe() - val bob = addNewNodeToNetworkMap(BOB.name) - alice.unsubscribe() - addNewNodeToNetworkMap(CHARLIE.name) - swizzle() - assertThat(updates.map { it.wireReg.verified().toChanged() }).containsOnly(Added(bob.info)) - } - - @Test - fun `surpass unacknowledged update limit`() { - val subscriber = newNodeSeparateFromNetworkMap(subscriberLegalName) - val updates = subscriber.subscribe() - val bob = addNewNodeToNetworkMap(BOB.name) - var serial = updates.first().wireReg.verified().serial - repeat(networkMapService.maxUnacknowledgedUpdates) { - bob.registration(ADD, serial = ++serial) - swizzle() - } - // We sent maxUnacknowledgedUpdates + 1 updates - the last one will be missed - assertThat(updates).hasSize(networkMapService.maxUnacknowledgedUpdates) - } - - @Test - fun `delay sending update ack until just before unacknowledged update limit`() { - val subscriber = newNodeSeparateFromNetworkMap(subscriberLegalName) - val updates = subscriber.subscribe() - val bob = addNewNodeToNetworkMap(BOB.name) - var serial = updates.first().wireReg.verified().serial - repeat(networkMapService.maxUnacknowledgedUpdates - 1) { - bob.registration(ADD, serial = ++serial) - swizzle() - } - // Subscriber will receive maxUnacknowledgedUpdates updates before sending ack - subscriber.ackUpdate(updates.last().mapVersion) - swizzle() - bob.registration(ADD, serial = ++serial) - assertThat(updates).hasSize(networkMapService.maxUnacknowledgedUpdates + 1) - assertThat(updates.last().wireReg.verified().serial).isEqualTo(serial) - } - - private fun StartedNode<*>.fetchMap(subscribe: Boolean = false, ifChangedSinceVersion: Int? = null): List { - val request = FetchMapRequest(subscribe, ifChangedSinceVersion, network.myAddress) - val response = services.networkService.sendRequest(FETCH_TOPIC, request, mapServiceNode.network.myAddress) - mockNet.runNetwork() - return response.getOrThrow().nodes?.map { it.toChanged() } ?: emptyList() - } - - private fun NodeRegistration.toChanged(): Changed = when (type) { - ADD -> Added(node) - REMOVE -> Removed(node) - } - - private fun StartedNode<*>.identityQuery(): NodeInfo? { - val request = QueryIdentityRequest(services.myInfo.chooseIdentityAndCert(), network.myAddress) - val response = services.networkService.sendRequest(QUERY_TOPIC, request, mapServiceNode.network.myAddress) - mockNet.runNetwork() - return response.getOrThrow().node - } - - private var lastSerial = Long.MIN_VALUE - - private fun StartedNode<*>.registration(addOrRemove: AddOrRemove, - serial: Long? = null): CordaFuture { - val distinctSerial = if (serial == null) { - ++lastSerial - } else { - lastSerial = serial - serial - } - val expires = Instant.now() + NetworkMapService.DEFAULT_EXPIRATION_PERIOD - val nodeRegistration = NodeRegistration(info, distinctSerial, addOrRemove, expires) - val request = RegistrationRequest(nodeRegistration.toWire(services.keyManagementService, info.chooseIdentity().owningKey), network.myAddress) - val response = services.networkService.sendRequest(REGISTER_TOPIC, request, mapServiceNode.network.myAddress) - mockNet.runNetwork() - return response - } - - private fun StartedNode<*>.subscribe(): Queue { - val request = SubscribeRequest(true, network.myAddress) - val updates = LinkedBlockingQueue() - services.networkService.addMessageHandler(PUSH_TOPIC) { message, _ -> - updates += message.data.deserialize() - } - val response = services.networkService.sendRequest(SUBSCRIPTION_TOPIC, request, mapServiceNode.network.myAddress) - mockNet.runNetwork() - assertThat(response.getOrThrow().confirmed).isTrue() - return updates - } - - private fun StartedNode<*>.unsubscribe() { - val request = SubscribeRequest(false, network.myAddress) - val response = services.networkService.sendRequest(SUBSCRIPTION_TOPIC, request, mapServiceNode.network.myAddress) - mockNet.runNetwork() - assertThat(response.getOrThrow().confirmed).isTrue() - } - - private fun StartedNode<*>.ackUpdate(mapVersion: Int) { - val request = UpdateAcknowledge(mapVersion, services.networkService.myAddress) - services.networkService.send(PUSH_ACK_TOPIC, MessagingService.DEFAULT_SESSION_ID, request, mapServiceNode.network.myAddress) - mockNet.runNetwork() - } - - private fun addNewNodeToNetworkMap(legalName: CordaX500Name): StartedNode { - val node = mockNet.createNode(legalName = legalName) - mockNet.runNetwork() - lastSerial = System.currentTimeMillis() - return node - } - - private fun newNodeSeparateFromNetworkMap(legalName: CordaX500Name): StartedNode { - return mockNet.createNode(legalName = legalName, nodeFactory = NoNMSNodeFactory) - } - - sealed class Changed { - data class Added(val node: NodeInfo) : Changed() { - constructor(node: StartedNode<*>) : this(node.info) - } - - data class Removed(val node: NodeInfo) : Changed() { - constructor(node: StartedNode<*>) : this(node.info) - } - } - - private object NoNMSNodeFactory : MockNetwork.Factory { - override fun create(config: NodeConfiguration, - network: MockNetwork, - networkMapAddr: SingleMessageRecipient?, - id: Int, - notaryIdentity: Pair?, - entropyRoot: BigInteger): MockNode { - return object : MockNode(config, network, null, id, notaryIdentity, entropyRoot) { - override fun makeNetworkMapService(network: MessagingService, networkMapCache: NetworkMapCacheInternal) = NullNetworkMapService - } - } - } -} diff --git a/node/src/test/kotlin/net/corda/node/services/network/InMemoryNetworkMapServiceTest.kt b/node/src/test/kotlin/net/corda/node/services/network/InMemoryNetworkMapServiceTest.kt deleted file mode 100644 index c6d8566560..0000000000 --- a/node/src/test/kotlin/net/corda/node/services/network/InMemoryNetworkMapServiceTest.kt +++ /dev/null @@ -1,9 +0,0 @@ -package net.corda.node.services.network - -import net.corda.testing.node.MockNetwork - -class InMemoryNetworkMapServiceTest : AbstractNetworkMapServiceTest() { - override val nodeFactory get() = MockNetwork.DefaultFactory - override val networkMapService: InMemoryNetworkMapService get() = mapServiceNode.inNodeNetworkMapService as InMemoryNetworkMapService - override fun swizzle() = Unit -} diff --git a/node/src/test/kotlin/net/corda/node/services/network/NetworkMapCacheTest.kt b/node/src/test/kotlin/net/corda/node/services/network/NetworkMapCacheTest.kt index 8d4f2d65a4..9ec782a048 100644 --- a/node/src/test/kotlin/net/corda/node/services/network/NetworkMapCacheTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/network/NetworkMapCacheTest.kt @@ -1,40 +1,24 @@ package net.corda.node.services.network import net.corda.core.node.services.NetworkMapCache -import net.corda.core.utilities.getOrThrow import net.corda.testing.ALICE import net.corda.testing.BOB import net.corda.testing.chooseIdentity import net.corda.testing.node.MockNetwork import org.assertj.core.api.Assertions.assertThat import org.junit.After -import org.junit.Before import org.junit.Test import java.math.BigInteger import kotlin.test.assertEquals class NetworkMapCacheTest { - lateinit var mockNet: MockNetwork - - @Before - fun setUp() { - mockNet = MockNetwork() - } + val mockNet: MockNetwork = MockNetwork() @After fun teardown() { mockNet.stopNodes() } - @Test - fun registerWithNetwork() { - mockNet.createNotaryNode() - val aliceNode = mockNet.createPartyNode(ALICE.name) - val future = aliceNode.services.networkMapCache.addMapService(aliceNode.network, mockNet.networkMapNode.network.myAddress, false, null) - mockNet.runNetwork() - future.getOrThrow() - } - @Test fun `key collision`() { val entropy = BigInteger.valueOf(24012017L) diff --git a/node/src/test/kotlin/net/corda/node/services/network/PersistentNetworkMapServiceTest.kt b/node/src/test/kotlin/net/corda/node/services/network/PersistentNetworkMapServiceTest.kt deleted file mode 100644 index 612c8e943a..0000000000 --- a/node/src/test/kotlin/net/corda/node/services/network/PersistentNetworkMapServiceTest.kt +++ /dev/null @@ -1,56 +0,0 @@ -package net.corda.node.services.network - -import net.corda.core.messaging.SingleMessageRecipient -import net.corda.node.services.api.NetworkMapCacheInternal -import net.corda.node.services.config.NodeConfiguration -import net.corda.node.services.messaging.MessagingService -import net.corda.nodeapi.internal.ServiceInfo -import net.corda.testing.node.MockNetwork -import net.corda.testing.node.MockNetwork.MockNode -import java.math.BigInteger -import java.security.KeyPair - -/** - * This class mirrors [InMemoryNetworkMapServiceTest] but switches in a [PersistentNetworkMapService] and - * repeatedly replaces it with new instances to check that the service correctly restores the most recent state. - */ -class PersistentNetworkMapServiceTest : AbstractNetworkMapServiceTest() { - - override val nodeFactory: MockNetwork.Factory<*> get() = NodeFactory - - override val networkMapService: PersistentNetworkMapService - get() = (mapServiceNode.inNodeNetworkMapService as SwizzleNetworkMapService).delegate - - override fun swizzle() { - mapServiceNode.database.transaction { - (mapServiceNode.inNodeNetworkMapService as SwizzleNetworkMapService).swizzle() - } - } - - private object NodeFactory : MockNetwork.Factory { - override fun create(config: NodeConfiguration, - network: MockNetwork, - networkMapAddr: SingleMessageRecipient?, - id: Int, - notaryIdentity: Pair?, - entropyRoot: BigInteger): MockNode { - return object : MockNode(config, network, networkMapAddr, id, notaryIdentity, entropyRoot) { - override fun makeNetworkMapService(network: MessagingService, networkMapCache: NetworkMapCacheInternal) = SwizzleNetworkMapService(network, networkMapCache) - } - } - } - - /** - * We use a special [NetworkMapService] that allows us to switch in a new instance at any time to check that the - * state within it is correctly restored. - */ - private class SwizzleNetworkMapService(private val delegateFactory: () -> PersistentNetworkMapService) : NetworkMapService { - constructor(network: MessagingService, networkMapCache: NetworkMapCacheInternal) : this({ PersistentNetworkMapService(network, networkMapCache, 1) }) - - var delegate = delegateFactory() - fun swizzle() { - delegate.unregisterNetworkHandlers() - delegate = delegateFactory() - } - } -} diff --git a/node/src/test/kotlin/net/corda/node/services/statemachine/FlowFrameworkTests.kt b/node/src/test/kotlin/net/corda/node/services/statemachine/FlowFrameworkTests.kt index 52b79b2dee..76b1ea9e3f 100644 --- a/node/src/test/kotlin/net/corda/node/services/statemachine/FlowFrameworkTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/statemachine/FlowFrameworkTests.kt @@ -39,6 +39,7 @@ import org.assertj.core.api.Assertions.assertThatThrownBy import org.assertj.core.api.AssertionsForClassTypes.assertThatExceptionOfType import org.junit.After import org.junit.Before +import org.junit.Ignore import org.junit.Test import rx.Notification import rx.Observable @@ -75,7 +76,6 @@ class FlowFrameworkTests { bobNode = mockNet.createNode(legalName = BOB_NAME) mockNet.runNetwork() - aliceNode.internals.ensureRegistered() // We intentionally create our own notary and ignore the one provided by the network // Note that these notaries don't operate correctly as they don't share their state. They are only used for testing @@ -156,33 +156,6 @@ class FlowFrameworkTests { assertEquals(true, flow.flowStarted) // Now we should have run the flow } - @Test - fun `flow added before network map will be init checkpointed`() { - var charlieNode = mockNet.createNode() //create vanilla node - val flow = NoOpFlow() - charlieNode.services.startFlow(flow) - assertEquals(false, flow.flowStarted) // Not started yet as no network activity has been allowed yet - charlieNode.internals.disableDBCloseOnStop() - charlieNode.services.networkMapCache.clearNetworkMapCache() // zap persisted NetworkMapCache to force use of network. - charlieNode.dispose() - - charlieNode = mockNet.createNode(charlieNode.internals.id) - val restoredFlow = charlieNode.getSingleFlow().first - assertEquals(false, restoredFlow.flowStarted) // Not started yet as no network activity has been allowed yet - mockNet.runNetwork() // Allow network map messages to flow - charlieNode.flushSmm() - assertEquals(true, restoredFlow.flowStarted) // Now we should have run the flow and hopefully cleared the init checkpoint - charlieNode.internals.disableDBCloseOnStop() - charlieNode.services.networkMapCache.clearNetworkMapCache() // zap persisted NetworkMapCache to force use of network. - charlieNode.dispose() - - // Now it is completed the flow should leave no Checkpoint. - charlieNode = mockNet.createNode(charlieNode.internals.id) - mockNet.runNetwork() // Allow network map messages to flow - charlieNode.flushSmm() - assertTrue(charlieNode.smm.findStateMachines(NoOpFlow::class.java).isEmpty()) - } - @Test fun `flow loaded from checkpoint will respond to messages from before start`() { aliceNode.registerFlowFactory(ReceiveFlow::class) { InitiatedSendFlow("Hello", it) } @@ -195,6 +168,7 @@ class FlowFrameworkTests { assertThat(restoredFlow.receivedPayloads[0]).isEqualTo("Hello") } + @Ignore("Some changes in startup order make this test's assumptions fail.") @Test fun `flow with send will resend on interrupted restart`() { val payload = random63BitValue() diff --git a/node/src/test/kotlin/net/corda/node/services/transactions/NotaryServiceTests.kt b/node/src/test/kotlin/net/corda/node/services/transactions/NotaryServiceTests.kt index 29ade9ec4f..87a5085cda 100644 --- a/node/src/test/kotlin/net/corda/node/services/transactions/NotaryServiceTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/transactions/NotaryServiceTests.kt @@ -39,7 +39,6 @@ class NotaryServiceTests { val notaryNode = mockNet.createNotaryNode(legalName = DUMMY_NOTARY.name, validating = false) aliceServices = mockNet.createNode(legalName = ALICE_NAME).services mockNet.runNetwork() // Clear network map registration messages - notaryNode.internals.ensureRegistered() notaryServices = notaryNode.services notary = notaryServices.getDefaultNotary() alice = aliceServices.myInfo.singleIdentity() diff --git a/node/src/test/kotlin/net/corda/node/services/transactions/ValidatingNotaryServiceTests.kt b/node/src/test/kotlin/net/corda/node/services/transactions/ValidatingNotaryServiceTests.kt index 6f3dd1ac66..139997f87b 100644 --- a/node/src/test/kotlin/net/corda/node/services/transactions/ValidatingNotaryServiceTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/transactions/ValidatingNotaryServiceTests.kt @@ -39,7 +39,6 @@ class ValidatingNotaryServiceTests { val notaryNode = mockNet.createNotaryNode(legalName = DUMMY_NOTARY.name) val aliceNode = mockNet.createNode(legalName = ALICE_NAME) mockNet.runNetwork() // Clear network map registration messages - notaryNode.internals.ensureRegistered() notaryServices = notaryNode.services aliceServices = aliceNode.services notary = notaryServices.getDefaultNotary() diff --git a/node/src/test/kotlin/net/corda/node/services/vault/VaultSoftLockManagerTest.kt b/node/src/test/kotlin/net/corda/node/services/vault/VaultSoftLockManagerTest.kt index 48a8347969..c9e2cbb3f1 100644 --- a/node/src/test/kotlin/net/corda/node/services/vault/VaultSoftLockManagerTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/vault/VaultSoftLockManagerTest.kt @@ -11,7 +11,6 @@ import net.corda.core.identity.AbstractParty import net.corda.core.internal.FlowStateMachine import net.corda.core.internal.packageName import net.corda.core.internal.uncheckedCast -import net.corda.core.messaging.SingleMessageRecipient import net.corda.core.node.StateLoader import net.corda.core.node.services.KeyManagementService import net.corda.core.node.services.queryBy @@ -84,8 +83,8 @@ class VaultSoftLockManagerTest { doNothing().whenever(it).softLockRelease(any(), anyOrNull()) } private val mockNet = MockNetwork(cordappPackages = listOf(ContractImpl::class.packageName), defaultFactory = object : MockNetwork.Factory { - override fun create(config: NodeConfiguration, network: MockNetwork, networkMapAddr: SingleMessageRecipient?, id: Int, notaryIdentity: Pair?, entropyRoot: BigInteger): MockNetwork.MockNode { - return object : MockNetwork.MockNode(config, network, networkMapAddr, id, notaryIdentity, entropyRoot) { + override fun create(config: NodeConfiguration, network: MockNetwork, id: Int, notaryIdentity: Pair?, entropyRoot: BigInteger): MockNetwork.MockNode { + return object : MockNetwork.MockNode(config, network, id, notaryIdentity, entropyRoot) { override fun makeVaultService(keyManagementService: KeyManagementService, stateLoader: StateLoader): VaultServiceInternal { val realVault = super.makeVaultService(keyManagementService, stateLoader) return object : VaultServiceInternal by realVault { diff --git a/samples/network-visualiser/src/main/kotlin/net/corda/netmap/simulation/IRSSimulation.kt b/samples/network-visualiser/src/main/kotlin/net/corda/netmap/simulation/IRSSimulation.kt index 0df1f7cdc2..95b203c7a2 100644 --- a/samples/network-visualiser/src/main/kotlin/net/corda/netmap/simulation/IRSSimulation.kt +++ b/samples/network-visualiser/src/main/kotlin/net/corda/netmap/simulation/IRSSimulation.kt @@ -45,7 +45,7 @@ class IRSSimulation(networkSendManuallyPumped: Boolean, runAsync: Boolean, laten private val executeOnNextIteration = Collections.synchronizedList(LinkedList<() -> Unit>()) override fun startMainSimulation(): CompletableFuture { - om = JacksonSupport.createInMemoryMapper(InMemoryIdentityService((banks + regulators + networkMap.internals + ratesOracle).flatMap { it.started!!.info.legalIdentitiesAndCerts }, trustRoot = DEV_TRUST_ROOT)) + om = JacksonSupport.createInMemoryMapper(InMemoryIdentityService((banks + regulators + ratesOracle).flatMap { it.started!!.info.legalIdentitiesAndCerts }, trustRoot = DEV_TRUST_ROOT)) registerFinanceJSONMappers(om) return startIRSDealBetween(0, 1).thenCompose { diff --git a/samples/network-visualiser/src/main/kotlin/net/corda/netmap/simulation/Simulation.kt b/samples/network-visualiser/src/main/kotlin/net/corda/netmap/simulation/Simulation.kt index 283eb64eda..c16edfcf7c 100644 --- a/samples/network-visualiser/src/main/kotlin/net/corda/netmap/simulation/Simulation.kt +++ b/samples/network-visualiser/src/main/kotlin/net/corda/netmap/simulation/Simulation.kt @@ -3,7 +3,6 @@ package net.corda.netmap.simulation import net.corda.core.flows.FlowLogic import net.corda.core.identity.CordaX500Name import net.corda.core.internal.uncheckedCast -import net.corda.core.messaging.SingleMessageRecipient import net.corda.core.utilities.ProgressTracker import net.corda.finance.utils.CityDatabase import net.corda.finance.utils.WorldMapLocation @@ -12,7 +11,6 @@ import net.corda.node.internal.StartedNode import net.corda.node.services.config.NodeConfiguration import net.corda.node.services.statemachine.StateMachineManager import net.corda.nodeapi.internal.ServiceInfo -import net.corda.testing.DUMMY_MAP import net.corda.testing.DUMMY_NOTARY import net.corda.testing.DUMMY_REGULATOR import net.corda.testing.node.InMemoryMessagingNetwork @@ -50,10 +48,10 @@ abstract class Simulation(val networkSendManuallyPumped: Boolean, // This puts together a mock network of SimulatedNodes. - open class SimulatedNode(config: NodeConfiguration, mockNet: MockNetwork, networkMapAddress: SingleMessageRecipient?, + open class SimulatedNode(config: NodeConfiguration, mockNet: MockNetwork, id: Int, notaryIdentity: Pair?, entropyRoot: BigInteger) - : MockNetwork.MockNode(config, mockNet, networkMapAddress, id, notaryIdentity, entropyRoot) { + : MockNetwork.MockNode(config, mockNet, id, notaryIdentity, entropyRoot) { override val started: StartedNode? get() = uncheckedCast(super.started) override fun findMyLocation(): WorldMapLocation? { return configuration.myLegalName.locality.let { CityDatabase[it] } @@ -63,7 +61,7 @@ abstract class Simulation(val networkSendManuallyPumped: Boolean, inner class BankFactory : MockNetwork.Factory { var counter = 0 - override fun create(config: NodeConfiguration, network: MockNetwork, networkMapAddr: SingleMessageRecipient?, + override fun create(config: NodeConfiguration, network: MockNetwork, id: Int, notaryIdentity: Pair?, entropyRoot: BigInteger): SimulatedNode { val letter = 'A' + counter val (city, country) = bankLocations[counter++ % bankLocations.size] @@ -71,7 +69,7 @@ abstract class Simulation(val networkSendManuallyPumped: Boolean, val cfg = testNodeConfiguration( baseDirectory = config.baseDirectory, myLegalName = CordaX500Name(organisation = "Bank $letter", locality = city, country = country)) - return SimulatedNode(cfg, network, networkMapAddr, id, notaryIdentity, entropyRoot) + return SimulatedNode(cfg, network, id, notaryIdentity, entropyRoot) } fun createAll(): List { @@ -84,25 +82,15 @@ abstract class Simulation(val networkSendManuallyPumped: Boolean, val bankFactory = BankFactory() - object NetworkMapNodeFactory : MockNetwork.Factory { - override fun create(config: NodeConfiguration, network: MockNetwork, networkMapAddr: SingleMessageRecipient?, - id: Int, notaryIdentity: Pair?, entropyRoot: BigInteger): SimulatedNode { - val cfg = testNodeConfiguration( - baseDirectory = config.baseDirectory, - myLegalName = DUMMY_MAP.name) - return object : SimulatedNode(cfg, network, networkMapAddr, id, notaryIdentity, entropyRoot) {} - } - } - object NotaryNodeFactory : MockNetwork.Factory { - override fun create(config: NodeConfiguration, network: MockNetwork, networkMapAddr: SingleMessageRecipient?, + override fun create(config: NodeConfiguration, network: MockNetwork, id: Int, notaryIdentity: Pair?, entropyRoot: BigInteger): SimulatedNode { requireNotNull(config.notary) val cfg = testNodeConfiguration( baseDirectory = config.baseDirectory, myLegalName = DUMMY_NOTARY.name, notaryConfig = config.notary) - return SimulatedNode(cfg, network, networkMapAddr, id, notaryIdentity, entropyRoot) + return SimulatedNode(cfg, network, id, notaryIdentity, entropyRoot) } } @@ -110,12 +98,12 @@ abstract class Simulation(val networkSendManuallyPumped: Boolean, // TODO: Make a more realistic legal name val RATES_SERVICE_NAME = CordaX500Name(organisation = "Rates Service Provider", locality = "Madrid", country = "ES") - override fun create(config: NodeConfiguration, network: MockNetwork, networkMapAddr: SingleMessageRecipient?, + override fun create(config: NodeConfiguration, network: MockNetwork, id: Int, notaryIdentity: Pair?, entropyRoot: BigInteger): SimulatedNode { val cfg = testNodeConfiguration( baseDirectory = config.baseDirectory, myLegalName = RATES_SERVICE_NAME) - return object : SimulatedNode(cfg, network, networkMapAddr, id, notaryIdentity, entropyRoot) { + return object : SimulatedNode(cfg, network, id, notaryIdentity, entropyRoot) { override fun start() = super.start().apply { registerInitiatedFlow(NodeInterestRates.FixQueryHandler::class.java) registerInitiatedFlow(NodeInterestRates.FixSignHandler::class.java) @@ -130,12 +118,12 @@ abstract class Simulation(val networkSendManuallyPumped: Boolean, } object RegulatorFactory : MockNetwork.Factory { - override fun create(config: NodeConfiguration, network: MockNetwork, networkMapAddr: SingleMessageRecipient?, + override fun create(config: NodeConfiguration, network: MockNetwork, id: Int, notaryIdentity: Pair?, entropyRoot: BigInteger): SimulatedNode { val cfg = testNodeConfiguration( baseDirectory = config.baseDirectory, myLegalName = DUMMY_REGULATOR.name) - return object : SimulatedNode(cfg, network, networkMapAddr, id, notaryIdentity, entropyRoot) { + return object : SimulatedNode(cfg, network, id, notaryIdentity, entropyRoot) { // TODO: Regulatory nodes don't actually exist properly, this is a last minute demo request. // So we just fire a message at a node that doesn't know how to handle it, and it'll ignore it. // But that's fine for visualisation purposes. @@ -148,13 +136,12 @@ abstract class Simulation(val networkSendManuallyPumped: Boolean, threadPerNode = runAsync, cordappPackages = listOf("net.corda.irs.contract", "net.corda.finance.contract", "net.corda.irs")) // This one must come first. - val networkMap = mockNet.startNetworkMapNode(nodeFactory = NetworkMapNodeFactory) val notary = mockNet.createNotaryNode(validating = false, nodeFactory = NotaryNodeFactory) val regulators = listOf(mockNet.createUnstartedNode(nodeFactory = RegulatorFactory)) val ratesOracle = mockNet.createUnstartedNode(nodeFactory = RatesOracleFactory) // All nodes must be in one of these two lists for the purposes of the visualiser tool. - val serviceProviders: List = listOf(notary.internals, ratesOracle, networkMap.internals) + val serviceProviders: List = listOf(notary.internals, ratesOracle) val banks: List = bankFactory.createAll() val clocks = (serviceProviders + regulators + banks).map { it.platformClock as TestClock } diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/NodeTestUtils.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/NodeTestUtils.kt index aed33f1885..e4c680da42 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/NodeTestUtils.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/NodeTestUtils.kt @@ -61,6 +61,7 @@ fun testNodeConfiguration( notaryConfig: NotaryConfig? = null): NodeConfiguration { abstract class MockableNodeConfiguration : NodeConfiguration // Otherwise Mockito is defeated by val getters. return rigorousMock().also { + doReturn(true).whenever(it).noNetworkMapServiceMode doReturn(baseDirectory).whenever(it).baseDirectory doReturn(myLegalName).whenever(it).myLegalName doReturn(1).whenever(it).minimumPlatformVersion @@ -83,6 +84,5 @@ fun testNodeConfiguration( doCallRealMethod().whenever(it).trustStoreFile doCallRealMethod().whenever(it).sslKeystore doCallRealMethod().whenever(it).nodeKeystore - doReturn(false).whenever(it).noNetworkMapServiceMode } } diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt index 60d138248f..2c87d33a9b 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt @@ -7,7 +7,6 @@ import com.nhaarman.mockito_kotlin.whenever import net.corda.core.crypto.entropyToKeyPair import net.corda.core.crypto.random63BitValue import net.corda.core.identity.CordaX500Name -import net.corda.core.identity.Party import net.corda.core.identity.PartyAndCertificate import net.corda.core.internal.concurrent.doneFuture import net.corda.core.internal.createDirectories @@ -41,9 +40,12 @@ import net.corda.node.services.transactions.InMemoryTransactionVerifierService import net.corda.node.utilities.AffinityExecutor import net.corda.node.utilities.AffinityExecutor.ServiceAffinityExecutor import net.corda.nodeapi.internal.ServiceInfo -import net.corda.testing.* +import net.corda.testing.DUMMY_NOTARY +import net.corda.testing.initialiseTestSerialization import net.corda.testing.node.MockServices.Companion.MOCK_VERSION_INFO import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties +import net.corda.testing.resetTestSerialization +import net.corda.testing.testNodeConfiguration import org.apache.activemq.artemis.utils.ReusableLatch import org.slf4j.Logger import java.io.Closeable @@ -98,10 +100,6 @@ class MockNetwork(defaultParameters: MockNetworkParameters = MockNetworkParamete /** Helper constructor for creating a [MockNetwork] with custom parameters from Java. */ constructor(parameters: MockNetworkParameters) : this(defaultParameters = parameters) - companion object { - // TODO In future PR we're removing the concept of network map node so the details of this mock are not important. - val MOCK_NET_MAP = Party(CordaX500Name(organisation = "Mock Network Map", locality = "Madrid", country = "ES"), DUMMY_KEY_1.public) - } var nextNodeId = 0 private set private val filesystem = Jimfs.newFileSystem(unix()) @@ -113,9 +111,6 @@ class MockNetwork(defaultParameters: MockNetworkParameters = MockNetworkParamete /** A read only view of the current set of executing nodes. */ val nodes: List get() = _nodes - private var _networkMapNode: StartedNode? = null - val networkMapNode: StartedNode get() = _networkMapNode ?: startNetworkMapNode() - init { if (initialiseSerialization) initialiseTestSerialization() filesystem.getPath("/nodes").createDirectory() @@ -124,19 +119,22 @@ class MockNetwork(defaultParameters: MockNetworkParameters = MockNetworkParamete /** Allows customisation of how nodes are created. */ interface Factory { /** + * @param config the configuration of the node to be created + * @param network a reference to the [MockNetwork] owning the node. + * @param id a unique identifier for the node. * @param notaryIdentity is an additional override to use in place of the node's default notary service, - * main usage is for when the node is part of a notary cluster. + * main usage is for when the node is part of a notary cluster. * @param entropyRoot the initial entropy value to use when generating keys. Defaults to an (insecure) random value, - * but can be overriden to cause nodes to have stable or colliding identity/service keys. + * but can be overriden to cause nodes to have stable or colliding identity/service keys. */ - fun create(config: NodeConfiguration, network: MockNetwork, networkMapAddr: SingleMessageRecipient?, - id: Int, notaryIdentity: Pair?, entropyRoot: BigInteger): N + fun create(config: NodeConfiguration, network: MockNetwork, id: Int, + notaryIdentity: Pair?, entropyRoot: BigInteger): N } object DefaultFactory : Factory { - override fun create(config: NodeConfiguration, network: MockNetwork, networkMapAddr: SingleMessageRecipient?, + override fun create(config: NodeConfiguration, network: MockNetwork, id: Int, notaryIdentity: Pair?, entropyRoot: BigInteger): MockNode { - return MockNode(config, network, networkMapAddr, id, notaryIdentity, entropyRoot) + return MockNode(config, network, id, notaryIdentity, entropyRoot) } } @@ -171,11 +169,11 @@ class MockNetwork(defaultParameters: MockNetworkParameters = MockNetworkParamete */ open class MockNode(config: NodeConfiguration, val mockNet: MockNetwork, - override val networkMapAddress: SingleMessageRecipient?, val id: Int, internal val notaryIdentity: Pair?, val entropyRoot: BigInteger = BigInteger.valueOf(random63BitValue())) : AbstractNode(config, TestClock(), MOCK_VERSION_INFO, CordappLoader.createDefaultWithTestPackages(config, mockNet.cordappPackages), mockNet.busyLatch) { + override val networkMapAddress = null var counter = entropyRoot override val log: Logger = loggerFor() override val serverThread: AffinityExecutor = @@ -258,6 +256,8 @@ class MockNetwork(defaultParameters: MockNetworkParameters = MockNetworkParamete dbCloser = null } + fun hasDBConnection() = dbCloser != null + // You can change this from zero if you have custom [FlowLogic] that park themselves. e.g. [StateMachineManagerTests] var acceptableLiveFiberCountOnStop: Int = 0 @@ -277,30 +277,6 @@ class MockNetwork(defaultParameters: MockNetworkParameters = MockNetworkParamete } } } - - /** - * Makes sure that the [MockNode] is correctly registered on the [MockNetwork] - * Please note that [MockNetwork.runNetwork] should be invoked to ensure that all the pending registration requests - * were duly processed - */ - fun ensureRegistered() { - _nodeReadyFuture.getOrThrow() - } - } - - fun startNetworkMapNode(nodeFactory: Factory? = null): StartedNode { - check(_networkMapNode == null) { "Trying to start more than one network map node" } - return uncheckedCast(createNodeImpl(networkMapAddress = null, - forcedID = null, - nodeFactory = nodeFactory ?: defaultFactory, - legalName = MOCK_NET_MAP.name, - notaryIdentity = null, - entropyRoot = BigInteger.valueOf(random63BitValue()), - configOverrides = {}, - start = true - ).started!!.apply { - _networkMapNode = this - }) } fun createUnstartedNode(forcedID: Int? = null, @@ -314,8 +290,7 @@ class MockNetwork(defaultParameters: MockNetworkParameters = MockNetworkParamete legalName: CordaX500Name? = null, notaryIdentity: Pair? = null, entropyRoot: BigInteger = BigInteger.valueOf(random63BitValue()), configOverrides: (NodeConfiguration) -> Any? = {}): N { - val networkMapAddress = networkMapNode.network.myAddress - return createNodeImpl(networkMapAddress, forcedID, nodeFactory, false, legalName, notaryIdentity, entropyRoot, configOverrides) + return createNodeImpl(forcedID, nodeFactory, false, legalName, notaryIdentity, entropyRoot, configOverrides) } /** @@ -338,11 +313,11 @@ class MockNetwork(defaultParameters: MockNetworkParameters = MockNetworkParamete legalName: CordaX500Name? = null, notaryIdentity: Pair? = null, entropyRoot: BigInteger = BigInteger.valueOf(random63BitValue()), configOverrides: (NodeConfiguration) -> Any? = {}): StartedNode { - val networkMapAddress = networkMapNode.network.myAddress - return uncheckedCast(createNodeImpl(networkMapAddress, forcedID, nodeFactory, true, legalName, notaryIdentity, entropyRoot, configOverrides).started)!! + return uncheckedCast(createNodeImpl(forcedID, nodeFactory, true, legalName, notaryIdentity, entropyRoot, configOverrides).started!! + .also { ensureAllNetworkMapCachesHaveAllNodeInfos() }) } - private fun createNodeImpl(networkMapAddress: SingleMessageRecipient?, forcedID: Int?, nodeFactory: Factory, + private fun createNodeImpl(forcedID: Int?, nodeFactory: Factory, start: Boolean, legalName: CordaX500Name?, notaryIdentity: Pair?, entropyRoot: BigInteger, configOverrides: (NodeConfiguration) -> Any?): N { @@ -353,10 +328,11 @@ class MockNetwork(defaultParameters: MockNetworkParameters = MockNetworkParamete doReturn(makeTestDataSourceProperties("node_${id}_net_$networkId")).whenever(it).dataSourceProperties configOverrides(it) } - return nodeFactory.create(config, this, networkMapAddress, id, notaryIdentity, entropyRoot).apply { + return nodeFactory.create(config, this, id, notaryIdentity, entropyRoot).apply { if (start) { start() - if (threadPerNode && networkMapAddress != null) nodeReadyFuture.getOrThrow() // XXX: What about manually-started nodes? + if (threadPerNode) nodeReadyFuture.getOrThrow() // XXX: What about manually-started nodes? + ensureAllNetworkMapCachesHaveAllNodeInfos() } _nodes.add(this) } @@ -372,6 +348,7 @@ class MockNetwork(defaultParameters: MockNetworkParameters = MockNetworkParamete */ @JvmOverloads fun runNetwork(rounds: Int = -1) { + ensureAllNetworkMapCachesHaveAllNodeInfos() check(!networkSendManuallyPumped) fun pumpAll() = messagingNetwork.endpoints.map { it.pumpReceive(false) } @@ -418,9 +395,21 @@ class MockNetwork(defaultParameters: MockNetworkParameters = MockNetworkParamete } } + private fun ensureAllNetworkMapCachesHaveAllNodeInfos() { + val infos = nodes.mapNotNull { it.started?.info } + nodes.filter { it.hasDBConnection() } + .mapNotNull { it.started?.services?.networkMapCache } + .forEach { + for (nodeInfo in infos) { + it.addNode(nodeInfo) + } + } + } + fun startNodes() { require(nodes.isNotEmpty()) nodes.forEach { it.started ?: it.start() } + ensureAllNetworkMapCachesHaveAllNodeInfos() } fun stopNodes() { From c5a6047b0e571404f2294f28aa7e74dc7c596433 Mon Sep 17 00:00:00 2001 From: Andrius Dagys Date: Mon, 23 Oct 2017 18:06:02 +0100 Subject: [PATCH 34/94] Minor fix for api checker script to support macOS --- .ci/check-api-changes.sh | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.ci/check-api-changes.sh b/.ci/check-api-changes.sh index 9621f90fce..72ae5f9d80 100755 --- a/.ci/check-api-changes.sh +++ b/.ci/check-api-changes.sh @@ -1,4 +1,5 @@ #!/bin/bash +set +o posix echo "Starting API Diff" @@ -11,7 +12,7 @@ if [ ! -f $apiCurrent ]; then fi # Remove the two header lines from the diff output. -diffContents=`diff -u $apiCurrent $APIHOME/../build/api/api-corda-*.txt | tail --lines=+3` +diffContents=`diff -u $apiCurrent $APIHOME/../build/api/api-corda-*.txt | tail -n +3` echo "Diff contents:" echo "$diffContents" echo @@ -36,7 +37,7 @@ function forUserImpl() { awk '/DoNotImplement/,/^##/{ next }{ print }' $1 } -userDiffContents=`diff -u <(forUserImpl $apiCurrent) <(forUserImpl $APIHOME/../build/api/api-corda-*.txt) | tail --lines=+3` +userDiffContents=`diff -u <(forUserImpl $apiCurrent) <(forUserImpl $APIHOME/../build/api/api-corda-*.txt) | tail -n +3` newAbstracts=$(echo "$userDiffContents" | grep "^+" | grep "\(public\|protected\) abstract") abstractCount=`grep -v "^$" < Date: Tue, 24 Oct 2017 11:41:59 +0100 Subject: [PATCH 35/94] Retrofit changes from Enterprise PR #61 (#1934) --- .../net/corda/core/schemas/NodeInfoSchema.kt | 11 +++---- .../identity/PersistentIdentityService.kt | 5 +-- .../keys/PersistentKeyManagementService.kt | 31 ++++++++++--------- .../network/PersistentNetworkMapService.kt | 15 +++++---- .../corda/node/utilities/CordaPersistence.kt | 8 +++++ 5 files changed, 42 insertions(+), 28 deletions(-) diff --git a/core/src/main/kotlin/net/corda/core/schemas/NodeInfoSchema.kt b/core/src/main/kotlin/net/corda/core/schemas/NodeInfoSchema.kt index a0e6c80f49..b8b8ef9460 100644 --- a/core/src/main/kotlin/net/corda/core/schemas/NodeInfoSchema.kt +++ b/core/src/main/kotlin/net/corda/core/schemas/NodeInfoSchema.kt @@ -85,13 +85,12 @@ object NodeInfoSchemaV1 : MappedSchema( @Table(name = "node_info_party_cert") data class DBPartyAndCertificate( @Id - @Column(name = "owning_key", length = 65535, nullable = false) - val owningKey: String, - - //@Id // TODO Do we assume that names are unique? Note: We can't have it as Id, because our toString on X500 is inconsistent. @Column(name = "party_name", nullable = false) val name: String, + @Column(name = "owning_key", length = 65535, nullable = false) + val owningKey: String, + @Column(name = "party_cert_binary") @Lob val partyCertBinary: ByteArray, @@ -102,10 +101,10 @@ object NodeInfoSchemaV1 : MappedSchema( private val persistentNodeInfos: Set = emptySet() ) { constructor(partyAndCert: PartyAndCertificate, isMain: Boolean = false) - : this(partyAndCert.party.owningKey.toBase58String(), partyAndCert.party.name.toString(), partyAndCert.serialize().bytes, isMain) + : this(partyAndCert.name.toString(), partyAndCert.party.owningKey.toBase58String(), partyAndCert.serialize().bytes, isMain) fun toLegalIdentityAndCert(): PartyAndCertificate { - return partyCertBinary.deserialize() + return partyCertBinary.deserialize() } } } diff --git a/node/src/main/kotlin/net/corda/node/services/identity/PersistentIdentityService.kt b/node/src/main/kotlin/net/corda/node/services/identity/PersistentIdentityService.kt index 7d3151c63b..04cb310e69 100644 --- a/node/src/main/kotlin/net/corda/node/services/identity/PersistentIdentityService.kt +++ b/node/src/main/kotlin/net/corda/node/services/identity/PersistentIdentityService.kt @@ -12,6 +12,7 @@ import net.corda.core.serialization.SingletonSerializeAsToken import net.corda.core.utilities.debug import net.corda.core.utilities.loggerFor import net.corda.node.utilities.AppendOnlyPersistentMap +import net.corda.node.utilities.MAX_HASH_HEX_SIZE import net.corda.node.utilities.NODE_DATABASE_PREFIX import org.bouncycastle.cert.X509CertificateHolder import java.io.ByteArrayInputStream @@ -72,7 +73,7 @@ class PersistentIdentityService(identities: Iterable = empt @javax.persistence.Table(name = "${NODE_DATABASE_PREFIX}identities") class PersistentIdentity( @Id - @Column(name = "pk_hash", length = 64) + @Column(name = "pk_hash", length = MAX_HASH_HEX_SIZE) var publicKeyHash: String = "", @Lob @@ -87,7 +88,7 @@ class PersistentIdentityService(identities: Iterable = empt @Column(name = "name", length = 128) var name: String = "", - @Column(name = "pk_hash", length = 64) + @Column(name = "pk_hash", length = MAX_HASH_HEX_SIZE) var publicKeyHash: String = "" ) diff --git a/node/src/main/kotlin/net/corda/node/services/keys/PersistentKeyManagementService.kt b/node/src/main/kotlin/net/corda/node/services/keys/PersistentKeyManagementService.kt index 4645c1d8e0..7316562ce1 100644 --- a/node/src/main/kotlin/net/corda/node/services/keys/PersistentKeyManagementService.kt +++ b/node/src/main/kotlin/net/corda/node/services/keys/PersistentKeyManagementService.kt @@ -8,9 +8,8 @@ import net.corda.core.serialization.SerializationDefaults import net.corda.core.serialization.SingletonSerializeAsToken import net.corda.core.serialization.deserialize import net.corda.core.serialization.serialize -import net.corda.core.utilities.parsePublicKeyBase58 -import net.corda.core.utilities.toBase58String import net.corda.node.utilities.AppendOnlyPersistentMap +import net.corda.node.utilities.MAX_HASH_HEX_SIZE import net.corda.node.utilities.NODE_DATABASE_PREFIX import org.bouncycastle.operator.ContentSigner import java.security.KeyPair @@ -36,27 +35,31 @@ class PersistentKeyManagementService(val identityService: IdentityService, class PersistentKey( @Id - @Column(length = 6000, name = "public_key") - var publicKey: String = "", + @Column(name = "public_key_hash", length = MAX_HASH_HEX_SIZE) + var publicKeyHash: String, + + @Lob + @Column(name = "public_key") + var publicKey: ByteArray = ByteArray(0), @Lob @Column(name = "private_key") var privateKey: ByteArray = ByteArray(0) - ) + ) { + constructor(publicKey: PublicKey, privateKey: PrivateKey) + : this(publicKey.toStringShort(), + publicKey.serialize(context = SerializationDefaults.STORAGE_CONTEXT).bytes, + privateKey.serialize(context = SerializationDefaults.STORAGE_CONTEXT).bytes) + } private companion object { fun createKeyMap(): AppendOnlyPersistentMap { return AppendOnlyPersistentMap( - toPersistentEntityKey = { it.toBase58String() }, - fromPersistentEntity = { - Pair(parsePublicKeyBase58(it.publicKey), - it.privateKey.deserialize(context = SerializationDefaults.STORAGE_CONTEXT)) - }, + toPersistentEntityKey = { it.toStringShort() }, + fromPersistentEntity = { Pair(it.publicKey.deserialize(context = SerializationDefaults.STORAGE_CONTEXT), + it.privateKey.deserialize(context = SerializationDefaults.STORAGE_CONTEXT)) }, toPersistentEntity = { key: PublicKey, value: PrivateKey -> - PersistentKey().apply { - publicKey = key.toBase58String() - privateKey = value.serialize(context = SerializationDefaults.STORAGE_CONTEXT).bytes - } + PersistentKey(key, value) }, persistentEntityClass = PersistentKey::class.java ) diff --git a/node/src/main/kotlin/net/corda/node/services/network/PersistentNetworkMapService.kt b/node/src/main/kotlin/net/corda/node/services/network/PersistentNetworkMapService.kt index 6d3600d398..3d1f7ebd97 100644 --- a/node/src/main/kotlin/net/corda/node/services/network/PersistentNetworkMapService.kt +++ b/node/src/main/kotlin/net/corda/node/services/network/PersistentNetworkMapService.kt @@ -1,6 +1,6 @@ package net.corda.node.services.network -import net.corda.core.utilities.toBase58String +import net.corda.core.crypto.toStringShort import net.corda.core.identity.PartyAndCertificate import net.corda.core.internal.ThreadBox import net.corda.core.messaging.SingleMessageRecipient @@ -9,7 +9,9 @@ import net.corda.core.serialization.deserialize import net.corda.core.serialization.serialize import net.corda.node.services.api.NetworkMapCacheInternal import net.corda.node.services.messaging.MessagingService -import net.corda.node.utilities.* +import net.corda.node.utilities.MAX_HASH_HEX_SIZE +import net.corda.node.utilities.NODE_DATABASE_PREFIX +import net.corda.node.utilities.PersistentMap import net.corda.nodeapi.ArtemisMessagingComponent import java.io.ByteArrayInputStream import java.security.cert.CertificateFactory @@ -31,8 +33,9 @@ class PersistentNetworkMapService(network: MessagingService, networkMapCache: Ne @Entity @Table(name = "${NODE_DATABASE_PREFIX}network_map_nodes") class NetworkNode( - @Id @Column(name = "node_party_key") - var publicKey: String = "", + @Id + @Column(name = "node_party_key_hash", length = MAX_HASH_HEX_SIZE) + var publicKeyHash: String, @Column var nodeParty: NodeParty = NodeParty(), @@ -58,14 +61,14 @@ class PersistentNetworkMapService(network: MessagingService, networkMapCache: Ne fun createNetworkNodesMap(): PersistentMap { return PersistentMap( - toPersistentEntityKey = { it.owningKey.toBase58String() }, + toPersistentEntityKey = { it.owningKey.toStringShort() }, fromPersistentEntity = { Pair(PartyAndCertificate(factory.generateCertPath(ByteArrayInputStream(it.nodeParty.certPath))), it.registrationInfo.deserialize(context = SerializationDefaults.STORAGE_CONTEXT)) }, toPersistentEntity = { key: PartyAndCertificate, value: NodeRegistrationInfo -> NetworkNode( - publicKey = key.owningKey.toBase58String(), + publicKeyHash = key.owningKey.toStringShort(), nodeParty = NodeParty( key.name.toString(), key.certificate.encoded, diff --git a/node/src/main/kotlin/net/corda/node/utilities/CordaPersistence.kt b/node/src/main/kotlin/net/corda/node/utilities/CordaPersistence.kt index 77055c6073..a8c85d105d 100644 --- a/node/src/main/kotlin/net/corda/node/utilities/CordaPersistence.kt +++ b/node/src/main/kotlin/net/corda/node/utilities/CordaPersistence.kt @@ -20,6 +20,14 @@ import java.util.concurrent.CopyOnWriteArrayList */ const val NODE_DATABASE_PREFIX = "node_" +/** + * The maximum supported field-size for hash HEX-encoded outputs (e.g. database fields). + * This value is enough to support hash functions with outputs up to 512 bits (e.g. SHA3-512), in which + * case 128 HEX characters are required. + * 130 was selected instead of 128, to allow for 2 extra characters that will be used as hash-scheme identifiers. + */ +internal const val MAX_HASH_HEX_SIZE = 130 + //HikariDataSource implements Closeable which allows CordaPersistence to be Closeable class CordaPersistence(var dataSource: HikariDataSource, private val schemaService: SchemaService, private val createIdentityService: () -> IdentityService, databaseProperties: Properties) : Closeable { From 927924498b017e6382208b2b80fe0e075ab35c7c Mon Sep 17 00:00:00 2001 From: Andrzej Cichocki Date: Tue, 24 Oct 2017 11:58:32 +0100 Subject: [PATCH 36/94] Introduce MockNodeParameters/Args (#1923) --- .../net/corda/core/flows/AttachmentTests.kt | 17 +-- .../AttachmentSerializationTest.kt | 13 +- .../contracts/asset/CashSelectionH2Test.kt | 7 +- .../node/services/BFTNotaryServiceTests.kt | 5 +- .../node/messaging/TwoPartyTradeFlowTests.kt | 28 +--- .../services/network/NetworkMapCacheTest.kt | 5 +- .../statemachine/FlowFrameworkTests.kt | 21 ++- .../transactions/NotaryServiceTests.kt | 3 +- .../ValidatingNotaryServiceTests.kt | 3 +- .../vault/VaultSoftLockManagerTest.kt | 12 +- .../net/corda/netmap/simulation/Simulation.kt | 101 ++++--------- .../kotlin/net/corda/testing/NodeTestUtils.kt | 6 +- .../kotlin/net/corda/testing/node/MockNode.kt | 134 ++++++++---------- 13 files changed, 134 insertions(+), 221 deletions(-) diff --git a/core/src/test/kotlin/net/corda/core/flows/AttachmentTests.kt b/core/src/test/kotlin/net/corda/core/flows/AttachmentTests.kt index a9ba82d71b..81e67b3b6e 100644 --- a/core/src/test/kotlin/net/corda/core/flows/AttachmentTests.kt +++ b/core/src/test/kotlin/net/corda/core/flows/AttachmentTests.kt @@ -9,21 +9,19 @@ import net.corda.core.internal.FetchAttachmentsFlow import net.corda.core.internal.FetchDataFlow import net.corda.core.utilities.getOrThrow import net.corda.node.internal.StartedNode -import net.corda.node.services.config.NodeConfiguration import net.corda.node.services.persistence.NodeAttachmentService -import net.corda.nodeapi.internal.ServiceInfo import net.corda.testing.ALICE import net.corda.testing.ALICE_NAME import net.corda.testing.BOB import net.corda.testing.node.MockNetwork +import net.corda.testing.node.MockNodeArgs +import net.corda.testing.node.MockNodeParameters import net.corda.testing.singleIdentity import org.junit.After import org.junit.Before import org.junit.Test import java.io.ByteArrayInputStream import java.io.ByteArrayOutputStream -import java.math.BigInteger -import java.security.KeyPair import java.util.jar.JarOutputStream import java.util.zip.ZipEntry import kotlin.test.assertEquals @@ -113,17 +111,14 @@ class AttachmentTests { @Test fun `malicious response`() { // Make a node that doesn't do sanity checking at load time. - val aliceNode = mockNet.createNotaryNode(legalName = ALICE.name, nodeFactory = object : MockNetwork.Factory { - override fun create(config: NodeConfiguration, network: MockNetwork, - id: Int, notaryIdentity: Pair?, - entropyRoot: BigInteger): MockNetwork.MockNode { - return object : MockNetwork.MockNode(config, network, id, notaryIdentity, entropyRoot) { + val aliceNode = mockNet.createNotaryNode(MockNodeParameters(legalName = ALICE.name), nodeFactory = object : MockNetwork.Factory { + override fun create(args: MockNodeArgs): MockNetwork.MockNode { + return object : MockNetwork.MockNode(args) { override fun start() = super.start().apply { attachments.checkAttachmentsOnLoad = false } } } }, validating = false) - val bobNode = mockNet.createNode(legalName = BOB.name) - + val bobNode = mockNet.createNode(MockNodeParameters(legalName = BOB.name)) mockNet.runNetwork() val alice = aliceNode.services.myInfo.identityFromX500Name(ALICE_NAME) diff --git a/core/src/test/kotlin/net/corda/core/serialization/AttachmentSerializationTest.kt b/core/src/test/kotlin/net/corda/core/serialization/AttachmentSerializationTest.kt index 62d9b0bded..8057164bcf 100644 --- a/core/src/test/kotlin/net/corda/core/serialization/AttachmentSerializationTest.kt +++ b/core/src/test/kotlin/net/corda/core/serialization/AttachmentSerializationTest.kt @@ -13,19 +13,17 @@ import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.unwrap import net.corda.node.internal.InitiatedFlowFactory import net.corda.node.internal.StartedNode -import net.corda.node.services.config.NodeConfiguration import net.corda.node.services.persistence.NodeAttachmentService import net.corda.node.utilities.currentDBSession -import net.corda.nodeapi.internal.ServiceInfo import net.corda.testing.chooseIdentity import net.corda.testing.node.MockNetwork +import net.corda.testing.node.MockNodeArgs +import net.corda.testing.node.MockNodeParameters import org.junit.After import org.junit.Before import org.junit.Test import java.io.ByteArrayOutputStream -import java.math.BigInteger import java.nio.charset.StandardCharsets.UTF_8 -import java.security.KeyPair import java.util.zip.ZipEntry import java.util.zip.ZipOutputStream import kotlin.test.assertEquals @@ -158,10 +156,9 @@ class AttachmentSerializationTest { private fun rebootClientAndGetAttachmentContent(checkAttachmentsOnLoad: Boolean = true): String { client.dispose() - client = mockNet.createNode(client.internals.id, object : MockNetwork.Factory { - override fun create(config: NodeConfiguration, network: MockNetwork, - id: Int, notaryIdentity: Pair?, entropyRoot: BigInteger): MockNetwork.MockNode { - return object : MockNetwork.MockNode(config, network, id, notaryIdentity, entropyRoot) { + client = mockNet.createNode(MockNodeParameters(client.internals.id), object : MockNetwork.Factory { + override fun create(args: MockNodeArgs): MockNetwork.MockNode { + return object : MockNetwork.MockNode(args) { override fun start() = super.start().apply { attachments.checkAttachmentsOnLoad = checkAttachmentsOnLoad } } } diff --git a/finance/src/test/kotlin/net/corda/finance/contracts/asset/CashSelectionH2Test.kt b/finance/src/test/kotlin/net/corda/finance/contracts/asset/CashSelectionH2Test.kt index e0cb3297b5..2a69ac0631 100644 --- a/finance/src/test/kotlin/net/corda/finance/contracts/asset/CashSelectionH2Test.kt +++ b/finance/src/test/kotlin/net/corda/finance/contracts/asset/CashSelectionH2Test.kt @@ -8,6 +8,7 @@ import net.corda.finance.flows.CashPaymentFlow import net.corda.finance.schemas.CashSchemaV1 import net.corda.testing.chooseIdentity import net.corda.testing.node.MockNetwork +import net.corda.testing.node.MockNodeParameters import org.assertj.core.api.Assertions.assertThatThrownBy import org.junit.Test @@ -19,12 +20,10 @@ class CashSelectionH2Test { val mockNet = MockNetwork(threadPerNode = true, cordappPackages = listOf("net.corda.finance.contracts.asset", CashSchemaV1::class.packageName)) try { val notaryNode = mockNet.createNotaryNode() - val bankA = mockNet.createNode(configOverrides = { existingConfig -> + val bankA = mockNet.createNode(MockNodeParameters(configOverrides = { existingConfig -> // Tweak connections to be minimal to make this easier (1 results in a hung node during start up, so use 2 connections). existingConfig.dataSourceProperties.setProperty("maximumPoolSize", "2") - existingConfig - }) - + })) mockNet.startNodes() // Start more cash spends than we have connections. If spend leaks a connection on retry, we will run out of connections. diff --git a/node/src/integration-test/kotlin/net/corda/node/services/BFTNotaryServiceTests.kt b/node/src/integration-test/kotlin/net/corda/node/services/BFTNotaryServiceTests.kt index 735a926462..3605ab14a4 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/BFTNotaryServiceTests.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/BFTNotaryServiceTests.kt @@ -30,6 +30,7 @@ import net.corda.testing.contracts.DummyContract import net.corda.testing.dummyCommand import net.corda.testing.getDefaultNotary import net.corda.testing.node.MockNetwork +import net.corda.testing.node.MockNodeParameters import org.junit.After import org.junit.Test import java.nio.file.Paths @@ -57,10 +58,10 @@ class BFTNotaryServiceTests { clusterName) val clusterAddresses = replicaIds.map { NetworkHostAndPort("localhost", 11000 + it * 10) } replicaIds.forEach { replicaId -> - mockNet.createNode(configOverrides = { + mockNet.createNode(MockNodeParameters(configOverrides = { val notary = NotaryConfig(validating = false, bftSMaRt = BFTSMaRtConfiguration(replicaId, clusterAddresses, exposeRaces = exposeRaces)) doReturn(notary).whenever(it).notary - }) + })) } mockNet.runNetwork() // Exchange initial network map registration messages. } diff --git a/node/src/test/kotlin/net/corda/node/messaging/TwoPartyTradeFlowTests.kt b/node/src/test/kotlin/net/corda/node/messaging/TwoPartyTradeFlowTests.kt index 89e4c49cf5..c5f893965e 100644 --- a/node/src/test/kotlin/net/corda/node/messaging/TwoPartyTradeFlowTests.kt +++ b/node/src/test/kotlin/net/corda/node/messaging/TwoPartyTradeFlowTests.kt @@ -34,17 +34,12 @@ import net.corda.finance.flows.TwoPartyTradeFlow.Buyer import net.corda.finance.flows.TwoPartyTradeFlow.Seller import net.corda.node.internal.StartedNode import net.corda.node.services.api.WritableTransactionStorage -import net.corda.node.services.config.NodeConfiguration import net.corda.node.services.persistence.DBTransactionStorage import net.corda.node.services.persistence.checkpoints import net.corda.node.utilities.CordaPersistence -import net.corda.nodeapi.internal.ServiceInfo import net.corda.testing.* import net.corda.testing.contracts.fillWithSomeTestCash -import net.corda.testing.node.InMemoryMessagingNetwork -import net.corda.testing.node.MockNetwork -import net.corda.testing.node.MockServices -import net.corda.testing.node.pumpReceive +import net.corda.testing.node.* import org.assertj.core.api.Assertions.assertThat import org.junit.After import org.junit.Before @@ -54,8 +49,6 @@ import org.junit.runners.Parameterized import rx.Observable import java.io.ByteArrayInputStream import java.io.ByteArrayOutputStream -import java.math.BigInteger -import java.security.KeyPair import java.util.* import java.util.jar.JarOutputStream import java.util.zip.ZipEntry @@ -267,13 +260,7 @@ class TwoPartyTradeFlowTests(val anonymous: Boolean) { // ... bring the node back up ... the act of constructing the SMM will re-register the message handlers // that Bob was waiting on before the reboot occurred. - bobNode = mockNet.createNode(bobAddr.id, object : MockNetwork.Factory { - override fun create(config: NodeConfiguration, network: MockNetwork, - id: Int, notaryIdentity: Pair?, entropyRoot: BigInteger): MockNetwork.MockNode { - return MockNetwork.MockNode(config, network, bobAddr.id, notaryIdentity, entropyRoot) - } - }, BOB_NAME) - + bobNode = mockNet.createNode(MockNodeParameters(bobAddr.id, BOB_NAME)) // Find the future representing the result of this state machine again. val bobFuture = bobNode.smm.findStateMachines(BuyerAcceptor::class.java).single().second @@ -307,19 +294,16 @@ class TwoPartyTradeFlowTests(val anonymous: Boolean) { // of gets and puts. private fun makeNodeWithTracking(name: CordaX500Name): StartedNode { // Create a node in the mock network ... - return mockNet.createNode(nodeFactory = object : MockNetwork.Factory { - override fun create(config: NodeConfiguration, - network: MockNetwork, - id: Int, notaryIdentity: Pair?, - entropyRoot: BigInteger): MockNetwork.MockNode { - return object : MockNetwork.MockNode(config, network, id, notaryIdentity, entropyRoot) { + return mockNet.createNode(MockNodeParameters(legalName = name), nodeFactory = object : MockNetwork.Factory { + override fun create(args: MockNodeArgs): MockNetwork.MockNode { + return object : MockNetwork.MockNode(args) { // That constructs a recording tx storage override fun makeTransactionStorage(): WritableTransactionStorage { return RecordingTransactionStorage(database, super.makeTransactionStorage()) } } } - }, legalName = name) + }) } @Test diff --git a/node/src/test/kotlin/net/corda/node/services/network/NetworkMapCacheTest.kt b/node/src/test/kotlin/net/corda/node/services/network/NetworkMapCacheTest.kt index 9ec782a048..b493466645 100644 --- a/node/src/test/kotlin/net/corda/node/services/network/NetworkMapCacheTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/network/NetworkMapCacheTest.kt @@ -5,6 +5,7 @@ import net.corda.testing.ALICE import net.corda.testing.BOB import net.corda.testing.chooseIdentity import net.corda.testing.node.MockNetwork +import net.corda.testing.node.MockNodeParameters import org.assertj.core.api.Assertions.assertThat import org.junit.After import org.junit.Test @@ -22,12 +23,12 @@ class NetworkMapCacheTest { @Test fun `key collision`() { val entropy = BigInteger.valueOf(24012017L) - val aliceNode = mockNet.createNode(nodeFactory = MockNetwork.DefaultFactory, legalName = ALICE.name, entropyRoot = entropy) + val aliceNode = mockNet.createNode(MockNodeParameters(legalName = ALICE.name, entropyRoot = entropy)) mockNet.runNetwork() // Node A currently knows only about itself, so this returns node A assertEquals(aliceNode.services.networkMapCache.getNodesByLegalIdentityKey(aliceNode.info.chooseIdentity().owningKey).singleOrNull(), aliceNode.info) - val bobNode = mockNet.createNode(nodeFactory = MockNetwork.DefaultFactory, legalName = BOB.name, entropyRoot = entropy) + val bobNode = mockNet.createNode(MockNodeParameters(legalName = BOB.name, entropyRoot = entropy)) assertEquals(aliceNode.info.chooseIdentity(), bobNode.info.chooseIdentity()) aliceNode.services.networkMapCache.addNode(bobNode.info) diff --git a/node/src/test/kotlin/net/corda/node/services/statemachine/FlowFrameworkTests.kt b/node/src/test/kotlin/net/corda/node/services/statemachine/FlowFrameworkTests.kt index 76b1ea9e3f..044c953422 100644 --- a/node/src/test/kotlin/net/corda/node/services/statemachine/FlowFrameworkTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/statemachine/FlowFrameworkTests.kt @@ -33,6 +33,7 @@ import net.corda.testing.node.InMemoryMessagingNetwork.MessageTransfer import net.corda.testing.node.InMemoryMessagingNetwork.ServicePeerAllocationStrategy.RoundRobin import net.corda.testing.node.MockNetwork import net.corda.testing.node.MockNetwork.MockNode +import net.corda.testing.node.MockNodeParameters import net.corda.testing.node.pumpReceive import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThatThrownBy @@ -72,9 +73,8 @@ class FlowFrameworkTests { @Before fun start() { mockNet = MockNetwork(servicePeerAllocationStrategy = RoundRobin(), cordappPackages = listOf("net.corda.finance.contracts", "net.corda.testing.contracts")) - aliceNode = mockNet.createNode(legalName = ALICE_NAME) - bobNode = mockNet.createNode(legalName = BOB_NAME) - + aliceNode = mockNet.createNode(MockNodeParameters(legalName = ALICE_NAME)) + bobNode = mockNet.createNode(MockNodeParameters(legalName = BOB_NAME)) mockNet.runNetwork() // We intentionally create our own notary and ignore the one provided by the network @@ -176,8 +176,7 @@ class FlowFrameworkTests { var sentCount = 0 mockNet.messagingNetwork.sentMessages.toSessionTransfers().filter { it.isPayloadTransfer }.forEach { sentCount++ } - - val charlieNode = mockNet.createNode(legalName = CHARLIE_NAME) + val charlieNode = mockNet.createNode(MockNodeParameters(legalName = CHARLIE_NAME)) val secondFlow = charlieNode.registerFlowFactory(PingPongFlow::class) { PingPongFlow(it, payload2) } mockNet.runNetwork() val charlie = charlieNode.info.singleIdentity() @@ -196,7 +195,7 @@ class FlowFrameworkTests { assertEquals(1, bobNode.checkpointStorage.checkpoints().size) // confirm checkpoint bobNode.services.networkMapCache.clearNetworkMapCache() } - val node2b = mockNet.createNode(bobNode.internals.id) + val node2b = mockNet.createNode(MockNodeParameters(bobNode.internals.id)) bobNode.internals.manuallyCloseDB() val (firstAgain, fut1) = node2b.getSingleFlow() // Run the network which will also fire up the second flow. First message should get deduped. So message data stays in sync. @@ -223,7 +222,7 @@ class FlowFrameworkTests { @Test fun `sending to multiple parties`() { - val charlieNode = mockNet.createNode(legalName = CHARLIE_NAME) + val charlieNode = mockNet.createNode(MockNodeParameters(legalName = CHARLIE_NAME)) mockNet.runNetwork() val charlie = charlieNode.info.singleIdentity() bobNode.registerFlowFactory(SendFlow::class) { InitiatedReceiveFlow(it).nonTerminating() } @@ -256,7 +255,7 @@ class FlowFrameworkTests { @Test fun `receiving from multiple parties`() { - val charlieNode = mockNet.createNode(legalName = CHARLIE_NAME) + val charlieNode = mockNet.createNode(MockNodeParameters(legalName = CHARLIE_NAME)) mockNet.runNetwork() val charlie = charlieNode.info.singleIdentity() val bobPayload = "Test 1" @@ -410,7 +409,7 @@ class FlowFrameworkTests { @Test fun `FlowException propagated in invocation chain`() { - val charlieNode = mockNet.createNode(legalName = CHARLIE_NAME) + val charlieNode = mockNet.createNode(MockNodeParameters(legalName = CHARLIE_NAME)) mockNet.runNetwork() val charlie = charlieNode.info.singleIdentity() @@ -425,7 +424,7 @@ class FlowFrameworkTests { @Test fun `FlowException thrown and there is a 3rd unrelated party flow`() { - val charlieNode = mockNet.createNode(legalName = CHARLIE_NAME) + val charlieNode = mockNet.createNode(MockNodeParameters(legalName = CHARLIE_NAME)) mockNet.runNetwork() val charlie = charlieNode.info.singleIdentity() @@ -674,7 +673,7 @@ class FlowFrameworkTests { private inline fun > StartedNode.restartAndGetRestoredFlow() = internals.run { disableDBCloseOnStop() // Handover DB to new node copy stop() - val newNode = mockNet.createNode(id) + val newNode = mockNet.createNode(MockNodeParameters(id)) newNode.internals.acceptableLiveFiberCountOnStop = 1 manuallyCloseDB() mockNet.runNetwork() // allow NetworkMapService messages to stabilise and thus start the state machine diff --git a/node/src/test/kotlin/net/corda/node/services/transactions/NotaryServiceTests.kt b/node/src/test/kotlin/net/corda/node/services/transactions/NotaryServiceTests.kt index 87a5085cda..2510497167 100644 --- a/node/src/test/kotlin/net/corda/node/services/transactions/NotaryServiceTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/transactions/NotaryServiceTests.kt @@ -17,6 +17,7 @@ import net.corda.node.services.api.StartedNodeServices import net.corda.testing.* import net.corda.testing.contracts.DummyContract import net.corda.testing.node.MockNetwork +import net.corda.testing.node.MockNodeParameters import org.assertj.core.api.Assertions.assertThat import org.junit.After import org.junit.Before @@ -37,7 +38,7 @@ class NotaryServiceTests { fun setup() { mockNet = MockNetwork(cordappPackages = listOf("net.corda.testing.contracts")) val notaryNode = mockNet.createNotaryNode(legalName = DUMMY_NOTARY.name, validating = false) - aliceServices = mockNet.createNode(legalName = ALICE_NAME).services + aliceServices = mockNet.createNode(MockNodeParameters(legalName = ALICE_NAME)).services mockNet.runNetwork() // Clear network map registration messages notaryServices = notaryNode.services notary = notaryServices.getDefaultNotary() diff --git a/node/src/test/kotlin/net/corda/node/services/transactions/ValidatingNotaryServiceTests.kt b/node/src/test/kotlin/net/corda/node/services/transactions/ValidatingNotaryServiceTests.kt index 139997f87b..1a1c74ea07 100644 --- a/node/src/test/kotlin/net/corda/node/services/transactions/ValidatingNotaryServiceTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/transactions/ValidatingNotaryServiceTests.kt @@ -18,6 +18,7 @@ import net.corda.node.services.issueInvalidState import net.corda.testing.* import net.corda.testing.contracts.DummyContract import net.corda.testing.node.MockNetwork +import net.corda.testing.node.MockNodeParameters import org.assertj.core.api.Assertions.assertThat import org.junit.After import org.junit.Before @@ -37,7 +38,7 @@ class ValidatingNotaryServiceTests { fun setup() { mockNet = MockNetwork(cordappPackages = listOf("net.corda.testing.contracts")) val notaryNode = mockNet.createNotaryNode(legalName = DUMMY_NOTARY.name) - val aliceNode = mockNet.createNode(legalName = ALICE_NAME) + val aliceNode = mockNet.createNode(MockNodeParameters(legalName = ALICE_NAME)) mockNet.runNetwork() // Clear network map registration messages notaryServices = notaryNode.services aliceServices = aliceNode.services diff --git a/node/src/test/kotlin/net/corda/node/services/vault/VaultSoftLockManagerTest.kt b/node/src/test/kotlin/net/corda/node/services/vault/VaultSoftLockManagerTest.kt index 29ebdc3c5e..ae1ac4da8f 100644 --- a/node/src/test/kotlin/net/corda/node/services/vault/VaultSoftLockManagerTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/vault/VaultSoftLockManagerTest.kt @@ -25,15 +25,13 @@ import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.unwrap import net.corda.node.internal.InitiatedFlowFactory import net.corda.node.services.api.VaultServiceInternal -import net.corda.node.services.config.NodeConfiguration -import net.corda.nodeapi.internal.ServiceInfo import net.corda.testing.chooseIdentity import net.corda.testing.node.MockNetwork import net.corda.testing.rigorousMock +import net.corda.testing.node.MockNodeArgs +import net.corda.testing.node.MockNodeParameters import org.junit.After import org.junit.Test -import java.math.BigInteger -import java.security.KeyPair import java.util.* import java.util.concurrent.atomic.AtomicBoolean import kotlin.reflect.jvm.jvmName @@ -72,7 +70,7 @@ class NodePair(private val mockNet: MockNetwork) { while (!serverRunning.get()) mockNet.runNetwork(1) if (rebootClient) { client.dispose() - client = mockNet.createNode(client.internals.id) + client = mockNet.createNode(MockNodeParameters(client.internals.id)) } return uncheckedCast(client.smm.allStateMachines.single().stateMachine) } @@ -83,8 +81,8 @@ class VaultSoftLockManagerTest { doNothing().whenever(it).softLockRelease(any(), anyOrNull()) } private val mockNet = MockNetwork(cordappPackages = listOf(ContractImpl::class.packageName), defaultFactory = object : MockNetwork.Factory { - override fun create(config: NodeConfiguration, network: MockNetwork, id: Int, notaryIdentity: Pair?, entropyRoot: BigInteger): MockNetwork.MockNode { - return object : MockNetwork.MockNode(config, network, id, notaryIdentity, entropyRoot) { + override fun create(args: MockNodeArgs): MockNetwork.MockNode { + return object : MockNetwork.MockNode(args) { override fun makeVaultService(keyManagementService: KeyManagementService, stateLoader: StateLoader): VaultServiceInternal { val realVault = super.makeVaultService(keyManagementService, stateLoader) return object : VaultServiceInternal by realVault { diff --git a/samples/network-visualiser/src/main/kotlin/net/corda/netmap/simulation/Simulation.kt b/samples/network-visualiser/src/main/kotlin/net/corda/netmap/simulation/Simulation.kt index c16edfcf7c..157f93eb58 100644 --- a/samples/network-visualiser/src/main/kotlin/net/corda/netmap/simulation/Simulation.kt +++ b/samples/network-visualiser/src/main/kotlin/net/corda/netmap/simulation/Simulation.kt @@ -1,5 +1,7 @@ package net.corda.netmap.simulation +import com.nhaarman.mockito_kotlin.doReturn +import com.nhaarman.mockito_kotlin.whenever import net.corda.core.flows.FlowLogic import net.corda.core.identity.CordaX500Name import net.corda.core.internal.uncheckedCast @@ -8,20 +10,14 @@ import net.corda.finance.utils.CityDatabase import net.corda.finance.utils.WorldMapLocation import net.corda.irs.api.NodeInterestRates import net.corda.node.internal.StartedNode -import net.corda.node.services.config.NodeConfiguration import net.corda.node.services.statemachine.StateMachineManager -import net.corda.nodeapi.internal.ServiceInfo import net.corda.testing.DUMMY_NOTARY import net.corda.testing.DUMMY_REGULATOR -import net.corda.testing.node.InMemoryMessagingNetwork -import net.corda.testing.node.MockNetwork -import net.corda.testing.node.TestClock -import net.corda.testing.node.setTo -import net.corda.testing.testNodeConfiguration +import net.corda.testing.node.* +import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties import rx.Observable import rx.subjects.PublishSubject import java.math.BigInteger -import java.security.KeyPair import java.time.LocalDate import java.time.LocalDateTime import java.time.ZoneOffset @@ -39,6 +35,13 @@ import java.util.concurrent.Future abstract class Simulation(val networkSendManuallyPumped: Boolean, runAsync: Boolean, latencyInjector: InMemoryMessagingNetwork.LatencyCalculator?) { + companion object { + private val defaultParams // The get() is necessary so that entropyRoot isn't shared. + get() = MockNodeParameters(configOverrides = { + doReturn(makeTestDataSourceProperties(it.myLegalName.organisation)).whenever(it).dataSourceProperties + }) + } + init { if (!runAsync && latencyInjector != null) throw IllegalArgumentException("The latency injector is only useful when using manual pumping.") @@ -47,63 +50,23 @@ abstract class Simulation(val networkSendManuallyPumped: Boolean, val bankLocations = listOf(Pair("London", "GB"), Pair("Frankfurt", "DE"), Pair("Rome", "IT")) // This puts together a mock network of SimulatedNodes. - - open class SimulatedNode(config: NodeConfiguration, mockNet: MockNetwork, - id: Int, notaryIdentity: Pair?, - entropyRoot: BigInteger) - : MockNetwork.MockNode(config, mockNet, id, notaryIdentity, entropyRoot) { + open class SimulatedNode(args: MockNodeArgs) : MockNetwork.MockNode(args) { override val started: StartedNode? get() = uncheckedCast(super.started) override fun findMyLocation(): WorldMapLocation? { return configuration.myLegalName.locality.let { CityDatabase[it] } } } - inner class BankFactory : MockNetwork.Factory { - var counter = 0 - - override fun create(config: NodeConfiguration, network: MockNetwork, - id: Int, notaryIdentity: Pair?, entropyRoot: BigInteger): SimulatedNode { - val letter = 'A' + counter - val (city, country) = bankLocations[counter++ % bankLocations.size] - - val cfg = testNodeConfiguration( - baseDirectory = config.baseDirectory, - myLegalName = CordaX500Name(organisation = "Bank $letter", locality = city, country = country)) - return SimulatedNode(cfg, network, id, notaryIdentity, entropyRoot) - } - - fun createAll(): List { - return bankLocations.mapIndexed { i, _ -> - // Use deterministic seeds so the simulation is stable. Needed so that party owning keys are stable. - mockNet.createUnstartedNode(nodeFactory = this, entropyRoot = BigInteger.valueOf(i.toLong())) - } - } - } - - val bankFactory = BankFactory() - - object NotaryNodeFactory : MockNetwork.Factory { - override fun create(config: NodeConfiguration, network: MockNetwork, - id: Int, notaryIdentity: Pair?, entropyRoot: BigInteger): SimulatedNode { - requireNotNull(config.notary) - val cfg = testNodeConfiguration( - baseDirectory = config.baseDirectory, - myLegalName = DUMMY_NOTARY.name, - notaryConfig = config.notary) - return SimulatedNode(cfg, network, id, notaryIdentity, entropyRoot) - } + private object SimulatedNodeFactory : MockNetwork.Factory { + override fun create(args: MockNodeArgs) = SimulatedNode(args) } object RatesOracleFactory : MockNetwork.Factory { // TODO: Make a more realistic legal name val RATES_SERVICE_NAME = CordaX500Name(organisation = "Rates Service Provider", locality = "Madrid", country = "ES") - override fun create(config: NodeConfiguration, network: MockNetwork, - id: Int, notaryIdentity: Pair?, entropyRoot: BigInteger): SimulatedNode { - val cfg = testNodeConfiguration( - baseDirectory = config.baseDirectory, - myLegalName = RATES_SERVICE_NAME) - return object : SimulatedNode(cfg, network, id, notaryIdentity, entropyRoot) { + override fun create(args: MockNodeArgs): SimulatedNode { + return object : SimulatedNode(args) { override fun start() = super.start().apply { registerInitiatedFlow(NodeInterestRates.FixQueryHandler::class.java) registerInitiatedFlow(NodeInterestRates.FixSignHandler::class.java) @@ -117,33 +80,23 @@ abstract class Simulation(val networkSendManuallyPumped: Boolean, } } - object RegulatorFactory : MockNetwork.Factory { - override fun create(config: NodeConfiguration, network: MockNetwork, - id: Int, notaryIdentity: Pair?, entropyRoot: BigInteger): SimulatedNode { - val cfg = testNodeConfiguration( - baseDirectory = config.baseDirectory, - myLegalName = DUMMY_REGULATOR.name) - return object : SimulatedNode(cfg, network, id, notaryIdentity, entropyRoot) { - // TODO: Regulatory nodes don't actually exist properly, this is a last minute demo request. - // So we just fire a message at a node that doesn't know how to handle it, and it'll ignore it. - // But that's fine for visualisation purposes. - } - } - } - val mockNet = MockNetwork( networkSendManuallyPumped = networkSendManuallyPumped, threadPerNode = runAsync, cordappPackages = listOf("net.corda.irs.contract", "net.corda.finance.contract", "net.corda.irs")) - // This one must come first. - val notary = mockNet.createNotaryNode(validating = false, nodeFactory = NotaryNodeFactory) - val regulators = listOf(mockNet.createUnstartedNode(nodeFactory = RegulatorFactory)) - val ratesOracle = mockNet.createUnstartedNode(nodeFactory = RatesOracleFactory) - + val notary = mockNet.createNotaryNode(defaultParams.copy(legalName = DUMMY_NOTARY.name), false, SimulatedNodeFactory) + // TODO: Regulatory nodes don't actually exist properly, this is a last minute demo request. + // So we just fire a message at a node that doesn't know how to handle it, and it'll ignore it. + // But that's fine for visualisation purposes. + val regulators = listOf(mockNet.createUnstartedNode(defaultParams.copy(legalName = DUMMY_REGULATOR.name), SimulatedNodeFactory)) + val ratesOracle = mockNet.createUnstartedNode(defaultParams.copy(legalName = RatesOracleFactory.RATES_SERVICE_NAME), RatesOracleFactory) // All nodes must be in one of these two lists for the purposes of the visualiser tool. val serviceProviders: List = listOf(notary.internals, ratesOracle) - val banks: List = bankFactory.createAll() - + val banks: List = bankLocations.mapIndexed { i, (city, country) -> + val legalName = CordaX500Name(organisation = "Bank ${'A' + i}", locality = city, country = country) + // Use deterministic seeds so the simulation is stable. Needed so that party owning keys are stable. + mockNet.createUnstartedNode(defaultParams.copy(legalName = legalName, entropyRoot = BigInteger.valueOf(i.toLong())), SimulatedNodeFactory) + } val clocks = (serviceProviders + regulators + banks).map { it.platformClock as TestClock } // These are used from the network visualiser tool. diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/NodeTestUtils.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/NodeTestUtils.kt index e4c680da42..768b7771c6 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/NodeTestUtils.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/NodeTestUtils.kt @@ -10,7 +10,6 @@ import net.corda.core.node.ServiceHub import net.corda.core.transactions.TransactionBuilder import net.corda.node.services.config.CertChainPolicyConfig import net.corda.node.services.config.NodeConfiguration -import net.corda.node.services.config.NotaryConfig import net.corda.node.services.config.VerifierType import net.corda.nodeapi.User import net.corda.testing.node.MockServices @@ -57,8 +56,7 @@ fun transaction( fun testNodeConfiguration( baseDirectory: Path, - myLegalName: CordaX500Name, - notaryConfig: NotaryConfig? = null): NodeConfiguration { + myLegalName: CordaX500Name): NodeConfiguration { abstract class MockableNodeConfiguration : NodeConfiguration // Otherwise Mockito is defeated by val getters. return rigorousMock().also { doReturn(true).whenever(it).noNetworkMapServiceMode @@ -68,7 +66,7 @@ fun testNodeConfiguration( doReturn("cordacadevpass").whenever(it).keyStorePassword doReturn("trustpass").whenever(it).trustStorePassword doReturn(emptyList()).whenever(it).rpcUsers - doReturn(notaryConfig).whenever(it).notary + doReturn(null).whenever(it).notary doReturn(makeTestDataSourceProperties(myLegalName.organisation)).whenever(it).dataSourceProperties doReturn(makeTestDatabaseProperties()).whenever(it).database doReturn("").whenever(it).emailAddress diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt index 2c87d33a9b..cf9be80a2b 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt @@ -77,6 +77,34 @@ data class MockNetworkParameters( fun setCordappPackages(cordappPackages: List) = copy(cordappPackages = cordappPackages) } +/** + * @param notaryIdentity a set of service entries to use in place of the node's default service entries, + * for example where a node's service is part of a cluster. + * @param entropyRoot the initial entropy value to use when generating keys. Defaults to an (insecure) random value, + * but can be overridden to cause nodes to have stable or colliding identity/service keys. + * @param configOverrides add/override behaviour of the [NodeConfiguration] mock object. + */ +@Suppress("unused") +data class MockNodeParameters( + val forcedID: Int? = null, + val legalName: CordaX500Name? = null, + val notaryIdentity: Pair? = null, + val entropyRoot: BigInteger = BigInteger.valueOf(random63BitValue()), + val configOverrides: (NodeConfiguration) -> Any? = {}) { + fun setForcedID(forcedID: Int?) = copy(forcedID = forcedID) + fun setLegalName(legalName: CordaX500Name?) = copy(legalName = legalName) + fun setNotaryIdentity(notaryIdentity: Pair?) = copy(notaryIdentity = notaryIdentity) + fun setEntropyRoot(entropyRoot: BigInteger) = copy(entropyRoot = entropyRoot) + fun setConfigOverrides(configOverrides: (NodeConfiguration) -> Any?) = copy(configOverrides = configOverrides) +} + +data class MockNodeArgs( + val config: NodeConfiguration, + val network: MockNetwork, + val id: Int, + val notaryIdentity: Pair?, + val entropyRoot: BigInteger) + /** * A mock node brings up a suite of in-memory services in a fast manner suitable for unit testing. * Components that do IO are either swapped out for mocks, or pointed to a [Jimfs] in memory filesystem or an in @@ -118,24 +146,11 @@ class MockNetwork(defaultParameters: MockNetworkParameters = MockNetworkParamete /** Allows customisation of how nodes are created. */ interface Factory { - /** - * @param config the configuration of the node to be created - * @param network a reference to the [MockNetwork] owning the node. - * @param id a unique identifier for the node. - * @param notaryIdentity is an additional override to use in place of the node's default notary service, - * main usage is for when the node is part of a notary cluster. - * @param entropyRoot the initial entropy value to use when generating keys. Defaults to an (insecure) random value, - * but can be overriden to cause nodes to have stable or colliding identity/service keys. - */ - fun create(config: NodeConfiguration, network: MockNetwork, id: Int, - notaryIdentity: Pair?, entropyRoot: BigInteger): N + fun create(args: MockNodeArgs): N } object DefaultFactory : Factory { - override fun create(config: NodeConfiguration, network: MockNetwork, - id: Int, notaryIdentity: Pair?, entropyRoot: BigInteger): MockNode { - return MockNode(config, network, id, notaryIdentity, entropyRoot) - } + override fun create(args: MockNodeArgs) = MockNode(args) } /** @@ -161,19 +176,17 @@ class MockNetwork(defaultParameters: MockNetworkParameters = MockNetworkParamete } } - /** - * @param notaryIdentity is an additional override to use in place of the node's default notary service, - * main usage is for when the node is part of a notary cluster. - * @param entropyRoot the initial entropy value to use when generating keys. Defaults to an (insecure) random value, - * but can be overriden to cause nodes to have stable or colliding identity/service keys. - */ - open class MockNode(config: NodeConfiguration, - val mockNet: MockNetwork, - val id: Int, - internal val notaryIdentity: Pair?, - val entropyRoot: BigInteger = BigInteger.valueOf(random63BitValue())) : - AbstractNode(config, TestClock(), MOCK_VERSION_INFO, CordappLoader.createDefaultWithTestPackages(config, mockNet.cordappPackages), mockNet.busyLatch) { + open class MockNode(args: MockNodeArgs) : AbstractNode( + args.config, + TestClock(), + MOCK_VERSION_INFO, + CordappLoader.createDefaultWithTestPackages(args.config, args.network.cordappPackages), + args.network.busyLatch) { + val mockNet = args.network override val networkMapAddress = null + val id = args.id + internal val notaryIdentity = args.notaryIdentity + val entropyRoot = args.entropyRoot var counter = entropyRoot override val log: Logger = loggerFor() override val serverThread: AffinityExecutor = @@ -279,56 +292,28 @@ class MockNetwork(defaultParameters: MockNetworkParameters = MockNetworkParamete } } - fun createUnstartedNode(forcedID: Int? = null, - legalName: CordaX500Name? = null, notaryIdentity: Pair? = null, - entropyRoot: BigInteger = BigInteger.valueOf(random63BitValue()), - configOverrides: (NodeConfiguration) -> Any? = {}): MockNode { - return createUnstartedNode(forcedID, defaultFactory, legalName, notaryIdentity, entropyRoot, configOverrides = configOverrides) - } - - fun createUnstartedNode(forcedID: Int? = null, nodeFactory: Factory, - legalName: CordaX500Name? = null, notaryIdentity: Pair? = null, - entropyRoot: BigInteger = BigInteger.valueOf(random63BitValue()), - configOverrides: (NodeConfiguration) -> Any? = {}): N { - return createNodeImpl(forcedID, nodeFactory, false, legalName, notaryIdentity, entropyRoot, configOverrides) - } - - /** - * Returns a node, optionally created by the passed factory method. - * @param notaryIdentity a set of service entries to use in place of the node's default service entries, - * for example where a node's service is part of a cluster. - * @param entropyRoot the initial entropy value to use when generating keys. Defaults to an (insecure) random value, - * but can be overridden to cause nodes to have stable or colliding identity/service keys. - * @param configOverrides add/override behaviour of the [NodeConfiguration] mock object. - */ - fun createNode(forcedID: Int? = null, - legalName: CordaX500Name? = null, notaryIdentity: Pair? = null, - entropyRoot: BigInteger = BigInteger.valueOf(random63BitValue()), - configOverrides: (NodeConfiguration) -> Any? = {}): StartedNode { - return createNode(forcedID, defaultFactory, legalName, notaryIdentity, entropyRoot, configOverrides = configOverrides) + fun createUnstartedNode(parameters: MockNodeParameters = MockNodeParameters()) = createUnstartedNode(parameters, defaultFactory) + fun createUnstartedNode(parameters: MockNodeParameters = MockNodeParameters(), nodeFactory: Factory): N { + return createNodeImpl(parameters, nodeFactory, false) } + fun createNode(parameters: MockNodeParameters = MockNodeParameters()): StartedNode = createNode(parameters, defaultFactory) /** Like the other [createNode] but takes a [Factory] and propagates its [MockNode] subtype. */ - fun createNode(forcedID: Int? = null, nodeFactory: Factory, - legalName: CordaX500Name? = null, notaryIdentity: Pair? = null, - entropyRoot: BigInteger = BigInteger.valueOf(random63BitValue()), - configOverrides: (NodeConfiguration) -> Any? = {}): StartedNode { - return uncheckedCast(createNodeImpl(forcedID, nodeFactory, true, legalName, notaryIdentity, entropyRoot, configOverrides).started!! - .also { ensureAllNetworkMapCachesHaveAllNodeInfos() }) + fun createNode(parameters: MockNodeParameters = MockNodeParameters(), nodeFactory: Factory): StartedNode { + val node: StartedNode = uncheckedCast(createNodeImpl(parameters, nodeFactory, true).started)!! + ensureAllNetworkMapCachesHaveAllNodeInfos() + return node } - private fun createNodeImpl(forcedID: Int?, nodeFactory: Factory, - start: Boolean, legalName: CordaX500Name?, notaryIdentity: Pair?, - entropyRoot: BigInteger, - configOverrides: (NodeConfiguration) -> Any?): N { - val id = forcedID ?: nextNodeId++ + private fun createNodeImpl(parameters: MockNodeParameters, nodeFactory: Factory, start: Boolean): N { + val id = parameters.forcedID ?: nextNodeId++ val config = testNodeConfiguration( baseDirectory = baseDirectory(id).createDirectories(), - myLegalName = legalName ?: CordaX500Name(organisation = "Mock Company $id", locality = "London", country = "GB")).also { + myLegalName = parameters.legalName ?: CordaX500Name(organisation = "Mock Company $id", locality = "London", country = "GB")).also { doReturn(makeTestDataSourceProperties("node_${id}_net_$networkId")).whenever(it).dataSourceProperties - configOverrides(it) + parameters.configOverrides(it) } - return nodeFactory.create(config, this, id, notaryIdentity, entropyRoot).apply { + return nodeFactory.create(MockNodeArgs(config, this, id, parameters.notaryIdentity, parameters.entropyRoot)).apply { if (start) { start() if (threadPerNode) nodeReadyFuture.getOrThrow() // XXX: What about manually-started nodes? @@ -364,23 +349,24 @@ class MockNetwork(defaultParameters: MockNetworkParameters = MockNetworkParamete @JvmOverloads fun createNotaryNode(legalName: CordaX500Name = DUMMY_NOTARY.name, validating: Boolean = true): StartedNode { - return createNode(legalName = legalName, configOverrides = { + return createNode(MockNodeParameters(legalName = legalName, configOverrides = { doReturn(NotaryConfig(validating)).whenever(it).notary - }) + })) } - fun createNotaryNode(legalName: CordaX500Name = DUMMY_NOTARY.name, + fun createNotaryNode(parameters: MockNodeParameters = MockNodeParameters(legalName = DUMMY_NOTARY.name), validating: Boolean = true, nodeFactory: Factory): StartedNode { - return createNode(legalName = legalName, nodeFactory = nodeFactory, configOverrides = { + return createNode(parameters.copy(configOverrides = { doReturn(NotaryConfig(validating)).whenever(it).notary - }) + parameters.configOverrides(it) + }), nodeFactory) } @JvmOverloads fun createPartyNode(legalName: CordaX500Name? = null, notaryIdentity: Pair? = null): StartedNode { - return createNode(legalName = legalName, notaryIdentity = notaryIdentity) + return createNode(MockNodeParameters(legalName = legalName, notaryIdentity = notaryIdentity)) } @Suppress("unused") // This is used from the network visualiser tool. From 4b8590ef41008aeba3a15de9efb64f272918141e Mon Sep 17 00:00:00 2001 From: Rick Parker Date: Tue, 24 Oct 2017 16:49:47 +0100 Subject: [PATCH 37/94] CORDA-736 Add some new features to corda.jar via node.conf for testing (#1926) --- docs/source/corda-configuration-file.rst | 10 ++ node/capsule/build.gradle | 6 +- node/src/main/java/CordaCaplet.java | 116 ++++++++++++++++-- .../net/corda/smoketesting/NodeProcess.kt | 4 +- 4 files changed, 122 insertions(+), 14 deletions(-) diff --git a/docs/source/corda-configuration-file.rst b/docs/source/corda-configuration-file.rst index 2b2627f56e..ce0b3412ad 100644 --- a/docs/source/corda-configuration-file.rst +++ b/docs/source/corda-configuration-file.rst @@ -154,3 +154,13 @@ path to the node's base directory. :certificateSigningService: Certificate Signing Server address. It is used by the certificate signing request utility to obtain SSL certificate. (See :doc:`permissioning` for more information.) + +:jvmArgs: An optional list of JVM args, as strings, which replace those inherited from the command line when launching via ``corda.jar`` + only. e.g. ``jvmArgs = [ "-Xmx220m", "-Xms220m", "-XX:+UseG1GC" ]`` + +:systemProperties: An optional map of additional system properties to be set when launching via ``corda.jar`` only. Keys and values + of the map should be strings. e.g. ``systemProperties = { visualvm.display.name = FooBar }`` + +:jarDirs: An optional list of file system directories containing JARs to include in the classpath when launching via ``corda.jar`` only. + Each should be a string. Only the JARs in the directories are added, not the directories themselves. This is useful + for including JDBC drivers and the like. e.g. ``jarDirs = [ 'lib' ]`` \ No newline at end of file diff --git a/node/capsule/build.gradle b/node/capsule/build.gradle index 1898e4453c..0256370147 100644 --- a/node/capsule/build.gradle +++ b/node/capsule/build.gradle @@ -31,7 +31,10 @@ task buildCordaJAR(type: FatCapsule, dependsOn: project(':node').compileJava) { "$rootDir/config/dev/log4j2.xml" ) from 'NOTICE' // Copy CDDL notice - + from { project(':node').configurations.runtime.allDependencies.matching { // Include config library JAR. + it.group.equals("com.typesafe") && it.name.equals("config") + }.collect { zipTree(project(':node').configurations.runtime.files(it).first()) } } + from { "$rootDir/node/build/resources/main/reference.conf" } capsuleManifest { applicationVersion = corda_release_version @@ -47,6 +50,7 @@ task buildCordaJAR(type: FatCapsule, dependsOn: project(':node').compileJava) { // JVM configuration: // - Constrain to small heap sizes to ease development on low end devices. // - Switch to the G1 GC which is going to be the default in Java 9 and gives low pause times/string dedup. + // NOTE: these can be overridden in node.conf. // // If you change these flags, please also update Driver.kt jvmArgs = ['-Xmx200m', '-XX:+UseG1GC'] diff --git a/node/src/main/java/CordaCaplet.java b/node/src/main/java/CordaCaplet.java index fa39580fa7..e42e5acd80 100644 --- a/node/src/main/java/CordaCaplet.java +++ b/node/src/main/java/CordaCaplet.java @@ -2,18 +2,73 @@ // must also be in the default package. When using Kotlin there are a whole host of exceptions // trying to construct this from Capsule, so it is written in Java. -import sun.misc.*; +import com.typesafe.config.*; +import sun.misc.Signal; +import sun.misc.SignalHandler; -import java.io.*; -import java.nio.file.*; +import java.io.File; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.*; public class CordaCaplet extends Capsule { + private Config nodeConfig = null; + private String baseDir = null; + protected CordaCaplet(Capsule pred) { super(pred); } + private Config parseConfigFile(List args) { + String baseDirOption = getOption(args, "--base-directory"); + this.baseDir = Paths.get((baseDirOption == null) ? "." : baseDirOption).toAbsolutePath().normalize().toString(); + String config = getOption(args, "--config-file"); + File configFile = (config == null) ? new File(baseDir, "node.conf") : new File(config); + try { + ConfigParseOptions parseOptions = ConfigParseOptions.defaults().setAllowMissing(false); + Config defaultConfig = ConfigFactory.parseResources("reference.conf", parseOptions); + Config baseDirectoryConfig = ConfigFactory.parseMap(Collections.singletonMap("baseDirectory", baseDir)); + Config nodeConfig = ConfigFactory.parseFile(configFile, parseOptions); + return baseDirectoryConfig.withFallback(nodeConfig).withFallback(defaultConfig).resolve(); + } catch (ConfigException e) { + log(LOG_QUIET, e); + return ConfigFactory.empty(); + } + } + + private String getOption(List args, String option) { + final String lowerCaseOption = option.toLowerCase(); + int index = 0; + for (String arg : args) { + if (arg.toLowerCase().equals(lowerCaseOption)) { + if (index < args.size() - 1) { + return args.get(index + 1); + } else { + return null; + } + } + index++; + } + return null; + } + + @Override + protected ProcessBuilder prelaunch(List jvmArgs, List args) { + nodeConfig = parseConfigFile(args); + return super.prelaunch(jvmArgs, args); + } + + // Add working directory variable to capsules string replacement variables. + @Override + protected String getVarValue(String var) { + if (var.equals("baseDirectory")) { + return baseDir; + } else { + return super.getVarValue(var); + } + } + /** * Overriding the Caplet classpath generation via the intended interface in Capsule. */ @@ -25,18 +80,55 @@ public class CordaCaplet extends Capsule { if (ATTR_APP_CLASS_PATH == attr) { T cp = super.attribute(attr); - (new File("cordapps")).mkdir(); - augmentClasspath((List) cp, "cordapps"); - augmentClasspath((List) cp, "plugins"); + (new File(baseDir, "cordapps")).mkdir(); + augmentClasspath((List) cp, new File(baseDir, "cordapps")); + augmentClasspath((List) cp, new File(baseDir, "plugins")); + // Add additional directories of JARs to the classpath (at the end). e.g. for JDBC drivers + try { + List jarDirs = nodeConfig.getStringList("jarDirs"); + log(LOG_VERBOSE, "Configured JAR directories = " + jarDirs); + for (String jarDir : jarDirs) { + augmentClasspath((List) cp, new File(jarDir)); + } + } catch (ConfigException.Missing e) { + // Ignore since it's ok to be Missing. Other errors would be unexpected. + } catch (ConfigException e) { + log(LOG_QUIET, e); + } return cp; - } - return super.attribute(attr); + } else if (ATTR_JVM_ARGS == attr) { + // Read JVM args from the config if specified, else leave alone. + List jvmArgs = new ArrayList<>((List) super.attribute(attr)); + try { + List configJvmArgs = nodeConfig.getStringList("jvmArgs"); + jvmArgs.clear(); + jvmArgs.addAll(configJvmArgs); + log(LOG_VERBOSE, "Configured JVM args = " + jvmArgs); + } catch (ConfigException.Missing e) { + // Ignore since it's ok to be Missing. Other errors would be unexpected. + } catch (ConfigException e) { + log(LOG_QUIET, e); + } + return (T) jvmArgs; + } else if (ATTR_SYSTEM_PROPERTIES == attr) { + // Add system properties, if specified, from the config. + Map systemProps = new LinkedHashMap<>((Map) super.attribute(attr)); + try { + Config overrideSystemProps = nodeConfig.getConfig("systemProperties"); + log(LOG_VERBOSE, "Configured system properties = " + overrideSystemProps); + for (Map.Entry entry : overrideSystemProps.entrySet()) { + systemProps.put(entry.getKey(), entry.getValue().unwrapped().toString()); + } + } catch (ConfigException.Missing e) { + // Ignore since it's ok to be Missing. Other errors would be unexpected. + } catch (ConfigException e) { + log(LOG_QUIET, e); + } + return (T) systemProps; + } else return super.attribute(attr); } - // TODO: Make directory configurable via the capsule manifest. - // TODO: Add working directory variable to capsules string replacement variables. - private void augmentClasspath(List classpath, String dirName) { - File dir = new File(dirName); + private void augmentClasspath(List classpath, File dir) { if (dir.exists()) { File[] files = dir.listFiles(); for (File file : files) { diff --git a/testing/smoke-test-utils/src/main/kotlin/net/corda/smoketesting/NodeProcess.kt b/testing/smoke-test-utils/src/main/kotlin/net/corda/smoketesting/NodeProcess.kt index ae43e144db..28d1afb9bf 100644 --- a/testing/smoke-test-utils/src/main/kotlin/net/corda/smoketesting/NodeProcess.kt +++ b/testing/smoke-test-utils/src/main/kotlin/net/corda/smoketesting/NodeProcess.kt @@ -96,8 +96,10 @@ class NodeProcess( private fun startNode(nodeDir: Path): Process { val builder = ProcessBuilder() - .command(javaPath.toString(), "-jar", cordaJar.toString()) + .command(javaPath.toString(), "-Dcapsule.log=verbose", "-jar", cordaJar.toString()) .directory(nodeDir.toFile()) + .redirectError(ProcessBuilder.Redirect.INHERIT) + .redirectOutput(ProcessBuilder.Redirect.INHERIT) builder.environment().putAll(mapOf( "CAPSULE_CACHE_DIR" to (buildDirectory / "capsule").toString() From 2fe3fbbfef7e871a841a8abae03021ba2c99f38e Mon Sep 17 00:00:00 2001 From: cburlinchon <31621751+cburlinchon@users.noreply.github.com> Date: Tue, 24 Oct 2017 17:24:38 +0100 Subject: [PATCH 38/94] CORDA-699 Add injection or modification of memory network messages (#1920) --- .../mocknetwork/TutorialMockNetwork.kt | 111 ++++++++++++++++++ docs/source/flow-testing.rst | 15 ++- .../testing/node/InMemoryMessagingNetwork.kt | 2 +- .../kotlin/net/corda/testing/node/MockNode.kt | 20 +++- 4 files changed, 145 insertions(+), 3 deletions(-) create mode 100644 docs/source/example-code/src/main/kotlin/net/corda/docs/tutorial/mocknetwork/TutorialMockNetwork.kt diff --git a/docs/source/example-code/src/main/kotlin/net/corda/docs/tutorial/mocknetwork/TutorialMockNetwork.kt b/docs/source/example-code/src/main/kotlin/net/corda/docs/tutorial/mocknetwork/TutorialMockNetwork.kt new file mode 100644 index 0000000000..9e86b57aab --- /dev/null +++ b/docs/source/example-code/src/main/kotlin/net/corda/docs/tutorial/mocknetwork/TutorialMockNetwork.kt @@ -0,0 +1,111 @@ +package net.corda.docs.tutorial.mocknetwork + +import co.paralleluniverse.fibers.Suspendable +import net.corda.core.contracts.requireThat +import net.corda.core.flows.FlowLogic +import net.corda.core.flows.FlowSession +import net.corda.core.flows.InitiatedBy +import net.corda.core.flows.InitiatingFlow +import net.corda.core.identity.Party +import net.corda.core.messaging.MessageRecipients +import net.corda.core.serialization.deserialize +import net.corda.core.serialization.serialize +import net.corda.core.utilities.getOrThrow +import net.corda.core.utilities.unwrap +import net.corda.node.internal.StartedNode +import net.corda.node.services.messaging.Message +import net.corda.node.services.statemachine.SessionData +import net.corda.testing.node.InMemoryMessagingNetwork +import net.corda.testing.node.MessagingServiceSpy +import net.corda.testing.node.MockNetwork +import net.corda.testing.node.setMessagingServiceSpy +import org.junit.After +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.rules.ExpectedException + +class TutorialMockNetwork { + + @InitiatingFlow + class FlowA(private val otherParty: Party) : FlowLogic() { + + @Suspendable + override fun call() { + val session = initiateFlow(otherParty) + + session.receive().unwrap { + requireThat { "Expected to receive 1" using (it == 1) } + } + + session.receive().unwrap { + requireThat { "Expected to receive 2" using (it == 2) } + } + } + } + + @InitiatedBy(FlowA::class) + class FlowB(private val session: FlowSession) : FlowLogic() { + + @Suspendable + override fun call() { + session.send(1) + session.send(2) + } + } + + lateinit private var mockNet: MockNetwork + lateinit private var notary: StartedNode + lateinit private var nodeA: StartedNode + lateinit private var nodeB: StartedNode + + @Rule + @JvmField + val expectedEx: ExpectedException = ExpectedException.none() + + @Before + fun setUp() { + mockNet = MockNetwork() + notary = mockNet.createNotaryNode() + nodeA = mockNet.createPartyNode() + nodeB = mockNet.createPartyNode() + + nodeB.registerInitiatedFlow(FlowB::class.java) + + mockNet.runNetwork() + } + + @After + fun tearDown() { + mockNet.stopNodes() + } + + @Test + fun `fail if initiated doesn't send back 1 on first result`() { + + // DOCSTART 1 + // modify message if it's 1 + nodeB.setMessagingServiceSpy(object : MessagingServiceSpy(nodeB.network) { + + override fun send(message: Message, target: MessageRecipients, retryId: Long?, sequenceKey: Any, acknowledgementHandler: (() -> Unit)?) { + val messageData = message.data.deserialize() + + if (messageData is SessionData && messageData.payload.deserialize() == 1) { + val alteredMessageData = SessionData(messageData.recipientSessionId, 99.serialize()).serialize().bytes + messagingService.send(InMemoryMessagingNetwork.InMemoryMessage(message.topicSession, alteredMessageData, message.uniqueMessageId), target, retryId) + } else { + messagingService.send(message, target, retryId) + } + } + }) + // DOCEND 1 + + val initiatingReceiveFlow = nodeA.services.startFlow(FlowA(nodeB.info.legalIdentities.first())) + + mockNet.runNetwork() + + expectedEx.expect(IllegalArgumentException::class.java) + expectedEx.expectMessage("Expected to receive 1") + initiatingReceiveFlow.resultFuture.getOrThrow() + } +} \ No newline at end of file diff --git a/docs/source/flow-testing.rst b/docs/source/flow-testing.rst index 967dbe9877..8e131b94ff 100644 --- a/docs/source/flow-testing.rst +++ b/docs/source/flow-testing.rst @@ -63,4 +63,17 @@ transaction as shown here. With regards to initiated flows (see :doc:`flow-state-machines` for information on initiated and initiating flows), the full node automatically registers them by scanning the CorDapp jars. In a unit test environment this is not possible so -``MockNode`` has the ``registerInitiatedFlow`` method to manually register an initiated flow. \ No newline at end of file +``MockNode`` has the ``registerInitiatedFlow`` method to manually register an initiated flow. + +MockNetwork message manipulation +-------------------------------- +The MockNetwork has the ability to manipulate message streams. You can use this to test your flows behaviour on corrupted, +or malicious data received. + +Message modification example in ``TutorialMockNetwork.kt``: + +.. literalinclude:: ../../docs/source/example-code/src/main/kotlin/net/corda/docs/tutorial/mocknetwork/TutorialMockNetwork.kt + :language: kotlin + :start-after: DOCSTART 1 + :end-before: DOCEND 1 + :dedent: 8 diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/InMemoryMessagingNetwork.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/InMemoryMessagingNetwork.kt index b009106082..406e72dda0 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/InMemoryMessagingNetwork.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/InMemoryMessagingNetwork.kt @@ -279,7 +279,7 @@ class InMemoryMessagingNetwork( _sentMessages.onNext(transfer) } - private data class InMemoryMessage(override val topicSession: TopicSession, + data class InMemoryMessage(override val topicSession: TopicSession, override val data: ByteArray, override val uniqueMessageId: UUID, override val debugTimestamp: Instant = Instant.now()) : Message { diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt index cf9be80a2b..71a9148900 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt @@ -17,6 +17,7 @@ import net.corda.core.messaging.RPCOps import net.corda.core.messaging.SingleMessageRecipient import net.corda.core.node.services.IdentityService import net.corda.core.node.services.KeyManagementService +import net.corda.core.node.services.PartyInfo import net.corda.core.serialization.SerializationWhitelist import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.getOrThrow @@ -31,7 +32,7 @@ import net.corda.node.services.config.BFTSMaRtConfiguration import net.corda.node.services.config.NodeConfiguration import net.corda.node.services.config.NotaryConfig import net.corda.node.services.keys.E2ETestKeyManagementService -import net.corda.node.services.messaging.MessagingService +import net.corda.node.services.messaging.* import net.corda.node.services.network.InMemoryNetworkMapService import net.corda.node.services.network.NetworkMapService import net.corda.node.services.transactions.BFTNonValidatingNotaryService @@ -53,6 +54,7 @@ import java.math.BigInteger import java.nio.file.Path import java.security.KeyPair import java.security.PublicKey +import java.util.* import java.util.concurrent.TimeUnit import java.util.concurrent.atomic.AtomicInteger @@ -214,6 +216,10 @@ class MockNetwork(defaultParameters: MockNetworkParameters = MockNetworkParamete .getOrThrow() } + fun setMessagingServiceSpy(messagingServiceSpy: MessagingServiceSpy) { + network = messagingServiceSpy + } + override fun makeKeyManagementService(identityService: IdentityService): KeyManagementService { return E2ETestKeyManagementService(identityService, partyKeys + (notaryIdentity?.let { setOf(it.second) } ?: emptySet())) } @@ -421,4 +427,16 @@ fun network(nodesCount: Int, action: MockNetwork.(nodes: List it.createPartyNode() } action(it, nodes, notary) } +} + +/** + * Extend this class in order to intercept and modify messages passing through the [MessagingService] when using the [InMemoryNetwork]. + */ +open class MessagingServiceSpy(val messagingService: MessagingService) : MessagingService by messagingService + +/** + * Attach a [MessagingServiceSpy] to the [MockNode] allowing interception and modification of messages. + */ +fun StartedNode.setMessagingServiceSpy(messagingServiceSpy: MessagingServiceSpy) { + internals.setMessagingServiceSpy(messagingServiceSpy) } \ No newline at end of file From 5349d4f850052404d592cec5705e9c3797282e93 Mon Sep 17 00:00:00 2001 From: josecoll Date: Tue, 24 Oct 2017 22:11:41 +0100 Subject: [PATCH 39/94] Standardisation of Public Keys in Schema entities. (#68) (#1936) * Standardisation of Public Keys in Schema entities. (#68) * Standardisation in usage of Public Keys in Schema entities. Use PK Hash where optimal, otherwise use ByteArray/LOB representation of PK. * Redundant after rebase. * Use .encoded and Crypto.decodeKey(bytes) instead of Corda serialization. * Optimize DBPartyAndCertificate entity to store and query on ownerKeyHash. * Updated API stability check for schema attribute change. --- .ci/api-current.txt | 2 +- .../net/corda/core/schemas/NodeInfoSchema.kt | 18 ++++++++++++------ .../net/corda/core/utilities/EncodingUtils.kt | 8 ++++++++ .../corda/finance/contracts/CommercialPaper.kt | 8 ++++---- .../net/corda/finance/contracts/asset/Cash.kt | 4 ++-- .../cash/selection/CashSelectionH2Impl.kt | 5 +++-- .../net/corda/finance/schemas/CashSchemaV1.kt | 11 +++++------ .../finance/schemas/CommercialPaperSchemaV1.kt | 13 +++++++------ .../contracts/asset/DummyFungibleContract.kt | 12 ++++++------ .../finance/schemas/SampleCashSchemaV1.kt | 9 +++++---- .../schemas/SampleCommercialPaperSchemaV1.kt | 13 +++++++------ .../schemas/SampleCommercialPaperSchemaV2.kt | 5 +++-- .../identity/PersistentIdentityService.kt | 2 +- .../keys/PersistentKeyManagementService.kt | 13 ++++--------- .../network/PersistentNetworkMapCache.kt | 7 +++++-- .../network/PersistentNetworkMapService.kt | 2 +- .../BFTNonValidatingNotaryService.kt | 6 ++++-- .../PersistentUniquenessProvider.kt | 12 +++++++----- .../corda/node/utilities/CordaPersistence.kt | 8 -------- .../persistence/HibernateConfigurationTest.kt | 2 +- .../node/services/vault/VaultQueryTests.kt | 15 ++++++++------- 21 files changed, 94 insertions(+), 81 deletions(-) diff --git a/.ci/api-current.txt b/.ci/api-current.txt index 14f2aaf9dd..e66520b6f2 100644 --- a/.ci/api-current.txt +++ b/.ci/api-current.txt @@ -2374,7 +2374,7 @@ public static final class net.corda.core.schemas.NodeInfoSchemaV1$DBHostAndPort$ @org.jetbrains.annotations.NotNull public final net.corda.core.schemas.NodeInfoSchemaV1$DBPartyAndCertificate copy(String, String, byte[], boolean, Set) public boolean equals(Object) @org.jetbrains.annotations.NotNull public final String getName() - @org.jetbrains.annotations.NotNull public final String getOwningKey() + @org.jetbrains.annotations.NotNull public final String getOwningKeyHash() @org.jetbrains.annotations.NotNull public final byte[] getPartyCertBinary() public int hashCode() public final boolean isMain() diff --git a/core/src/main/kotlin/net/corda/core/schemas/NodeInfoSchema.kt b/core/src/main/kotlin/net/corda/core/schemas/NodeInfoSchema.kt index b8b8ef9460..80c0d842ae 100644 --- a/core/src/main/kotlin/net/corda/core/schemas/NodeInfoSchema.kt +++ b/core/src/main/kotlin/net/corda/core/schemas/NodeInfoSchema.kt @@ -1,11 +1,13 @@ package net.corda.core.schemas +import net.corda.core.crypto.toStringShort import net.corda.core.identity.PartyAndCertificate import net.corda.core.node.NodeInfo +import net.corda.core.serialization.SerializationDefaults import net.corda.core.serialization.deserialize import net.corda.core.serialization.serialize +import net.corda.core.utilities.MAX_HASH_HEX_SIZE import net.corda.core.utilities.NetworkHostAndPort -import net.corda.core.utilities.toBase58String import java.io.Serializable import javax.persistence.* @@ -88,20 +90,24 @@ object NodeInfoSchemaV1 : MappedSchema( @Column(name = "party_name", nullable = false) val name: String, - @Column(name = "owning_key", length = 65535, nullable = false) - val owningKey: String, - - @Column(name = "party_cert_binary") @Lob + @Column(name = "owning_key_hash", length = MAX_HASH_HEX_SIZE) + val owningKeyHash: String, + + @Lob + @Column(name = "party_cert_binary") val partyCertBinary: ByteArray, + val isMain: Boolean, @ManyToMany(mappedBy = "legalIdentitiesAndCerts", cascade = arrayOf(CascadeType.ALL)) // ManyToMany because of distributed services. private val persistentNodeInfos: Set = emptySet() ) { constructor(partyAndCert: PartyAndCertificate, isMain: Boolean = false) - : this(partyAndCert.name.toString(), partyAndCert.party.owningKey.toBase58String(), partyAndCert.serialize().bytes, isMain) + : this(partyAndCert.name.toString(), + partyAndCert.party.owningKey.toStringShort(), + partyAndCert.serialize(context = SerializationDefaults.STORAGE_CONTEXT).bytes, isMain) fun toLegalIdentityAndCert(): PartyAndCertificate { return partyCertBinary.deserialize() diff --git a/core/src/main/kotlin/net/corda/core/utilities/EncodingUtils.kt b/core/src/main/kotlin/net/corda/core/utilities/EncodingUtils.kt index 2fd31c9590..f4fdca5196 100644 --- a/core/src/main/kotlin/net/corda/core/utilities/EncodingUtils.kt +++ b/core/src/main/kotlin/net/corda/core/utilities/EncodingUtils.kt @@ -12,6 +12,14 @@ import javax.xml.bind.DatatypeConverter // This file includes useful encoding methods and extension functions for the most common encoding/decoding operations. +/** + * The maximum supported field-size for hash HEX-encoded outputs (e.g. database fields). + * This value is enough to support hash functions with outputs up to 512 bits (e.g. SHA3-512), in which + * case 128 HEX characters are required. + * 130 was selected instead of 128, to allow for 2 extra characters that will be used as hash-scheme identifiers. + */ +const val MAX_HASH_HEX_SIZE = 130 + // [ByteArray] encoders /** Convert a byte array to a Base58 encoded [String]. */ diff --git a/finance/src/main/kotlin/net/corda/finance/contracts/CommercialPaper.kt b/finance/src/main/kotlin/net/corda/finance/contracts/CommercialPaper.kt index 1c302480f3..7707bf71cf 100644 --- a/finance/src/main/kotlin/net/corda/finance/contracts/CommercialPaper.kt +++ b/finance/src/main/kotlin/net/corda/finance/contracts/CommercialPaper.kt @@ -3,7 +3,7 @@ package net.corda.finance.contracts import co.paralleluniverse.fibers.Suspendable import net.corda.core.contracts.* import net.corda.core.crypto.NullKeys.NULL_PARTY -import net.corda.core.utilities.toBase58String +import net.corda.core.crypto.toStringShort import net.corda.core.identity.AbstractParty import net.corda.core.identity.Party import net.corda.core.identity.PartyAndCertificate @@ -76,13 +76,13 @@ class CommercialPaper : Contract { override fun generateMappedObject(schema: MappedSchema): PersistentState { return when (schema) { is CommercialPaperSchemaV1 -> CommercialPaperSchemaV1.PersistentCommercialPaperState( - issuanceParty = this.issuance.party.owningKey.toBase58String(), + issuancePartyHash = this.issuance.party.owningKey.toStringShort(), issuanceRef = this.issuance.reference.bytes, - owner = this.owner.owningKey.toBase58String(), + ownerHash = this.owner.owningKey.toStringShort(), maturity = this.maturityDate, faceValue = this.faceValue.quantity, currency = this.faceValue.token.product.currencyCode, - faceValueIssuerParty = this.faceValue.token.issuer.party.owningKey.toBase58String(), + faceValueIssuerPartyHash = this.faceValue.token.issuer.party.owningKey.toStringShort(), faceValueIssuerRef = this.faceValue.token.issuer.reference.bytes ) /** Additional schema mappings would be added here (eg. CommercialPaperV2, ...) */ diff --git a/finance/src/main/kotlin/net/corda/finance/contracts/asset/Cash.kt b/finance/src/main/kotlin/net/corda/finance/contracts/asset/Cash.kt index d8ead23d9d..59aef7bded 100644 --- a/finance/src/main/kotlin/net/corda/finance/contracts/asset/Cash.kt +++ b/finance/src/main/kotlin/net/corda/finance/contracts/asset/Cash.kt @@ -8,6 +8,7 @@ import net.corda.core.contracts.* import net.corda.core.contracts.Amount.Companion.sumOrThrow import net.corda.core.crypto.NullKeys.NULL_PARTY import net.corda.core.crypto.entropyToKeyPair +import net.corda.core.crypto.toStringShort import net.corda.core.identity.AbstractParty import net.corda.core.identity.CordaX500Name import net.corda.core.identity.Party @@ -19,7 +20,6 @@ import net.corda.core.schemas.PersistentState import net.corda.core.schemas.QueryableState import net.corda.core.transactions.LedgerTransaction import net.corda.core.transactions.TransactionBuilder -import net.corda.core.utilities.toBase58String import net.corda.finance.contracts.asset.cash.selection.AbstractCashSelection import net.corda.finance.schemas.CashSchemaV1 import net.corda.finance.utils.sumCash @@ -83,7 +83,7 @@ class Cash : OnLedgerAsset() { owner = this.owner, pennies = this.amount.quantity, currency = this.amount.token.product.currencyCode, - issuerParty = this.amount.token.issuer.party.owningKey.toBase58String(), + issuerPartyHash = this.amount.token.issuer.party.owningKey.toStringShort(), issuerRef = this.amount.token.issuer.reference.bytes ) /** Additional schema mappings would be added here (eg. CashSchemaV2, CashSchemaV3, ...) */ diff --git a/finance/src/main/kotlin/net/corda/finance/contracts/asset/cash/selection/CashSelectionH2Impl.kt b/finance/src/main/kotlin/net/corda/finance/contracts/asset/cash/selection/CashSelectionH2Impl.kt index 5894eff73f..0a072897cd 100644 --- a/finance/src/main/kotlin/net/corda/finance/contracts/asset/cash/selection/CashSelectionH2Impl.kt +++ b/finance/src/main/kotlin/net/corda/finance/contracts/asset/cash/selection/CashSelectionH2Impl.kt @@ -1,6 +1,7 @@ package net.corda.finance.contracts.asset.cash.selection import net.corda.core.contracts.Amount +import net.corda.core.crypto.toStringShort import net.corda.core.identity.AbstractParty import net.corda.core.identity.Party import net.corda.core.utilities.* @@ -44,7 +45,7 @@ class CashSelectionH2Impl : AbstractCashSelection() { (if (notary != null) " AND vs.notary_name = ?" else "") + (if (onlyFromIssuerParties.isNotEmpty()) - " AND ccs.issuer_key IN (?)" else "") + + " AND ccs.issuer_key_hash IN (?)" else "") + (if (withIssuerRefs.isNotEmpty()) " AND ccs.issuer_ref IN (?)" else "") @@ -57,7 +58,7 @@ class CashSelectionH2Impl : AbstractCashSelection() { if (notary != null) psSelectJoin.setString(++pIndex, notary.name.toString()) if (onlyFromIssuerParties.isNotEmpty()) - psSelectJoin.setObject(++pIndex, onlyFromIssuerParties.map { it.owningKey.toBase58String() as Any}.toTypedArray() ) + psSelectJoin.setObject(++pIndex, onlyFromIssuerParties.map { it.owningKey.toStringShort() as Any}.toTypedArray() ) if (withIssuerRefs.isNotEmpty()) psSelectJoin.setObject(++pIndex, withIssuerRefs.map { it.bytes.toHexString() as Any }.toTypedArray()) log.debug { psSelectJoin.toString() } diff --git a/finance/src/main/kotlin/net/corda/finance/schemas/CashSchemaV1.kt b/finance/src/main/kotlin/net/corda/finance/schemas/CashSchemaV1.kt index 5fdfc58492..57261a13fe 100644 --- a/finance/src/main/kotlin/net/corda/finance/schemas/CashSchemaV1.kt +++ b/finance/src/main/kotlin/net/corda/finance/schemas/CashSchemaV1.kt @@ -4,10 +4,8 @@ import net.corda.core.identity.AbstractParty import net.corda.core.schemas.MappedSchema import net.corda.core.schemas.PersistentState import net.corda.core.serialization.CordaSerializable -import javax.persistence.Column -import javax.persistence.Entity -import javax.persistence.Index -import javax.persistence.Table +import net.corda.core.utilities.MAX_HASH_HEX_SIZE +import javax.persistence.* /** * An object used to fully qualify the [CashSchema] family name (i.e. independent of version). @@ -35,8 +33,9 @@ object CashSchemaV1 : MappedSchema(schemaFamily = CashSchema.javaClass, version @Column(name = "ccy_code", length = 3) var currency: String, - @Column(name = "issuer_key") - var issuerParty: String, + @Lob + @Column(name = "issuer_key_hash", length = MAX_HASH_HEX_SIZE) + var issuerPartyHash: String, @Column(name = "issuer_ref") var issuerRef: ByteArray diff --git a/finance/src/main/kotlin/net/corda/finance/schemas/CommercialPaperSchemaV1.kt b/finance/src/main/kotlin/net/corda/finance/schemas/CommercialPaperSchemaV1.kt index f8c1b46b0e..6af1da2ce1 100644 --- a/finance/src/main/kotlin/net/corda/finance/schemas/CommercialPaperSchemaV1.kt +++ b/finance/src/main/kotlin/net/corda/finance/schemas/CommercialPaperSchemaV1.kt @@ -3,6 +3,7 @@ package net.corda.finance.schemas import net.corda.core.schemas.MappedSchema import net.corda.core.schemas.PersistentState import net.corda.core.serialization.CordaSerializable +import net.corda.core.utilities.MAX_HASH_HEX_SIZE import java.time.Instant import javax.persistence.Column import javax.persistence.Entity @@ -26,14 +27,14 @@ object CommercialPaperSchemaV1 : MappedSchema(schemaFamily = CommercialPaperSche Index(name = "maturity_index", columnList = "maturity_instant"), Index(name = "face_value_index", columnList = "face_value"))) class PersistentCommercialPaperState( - @Column(name = "issuance_key") - var issuanceParty: String, + @Column(name = "issuance_key_hash", length = MAX_HASH_HEX_SIZE) + var issuancePartyHash: String, @Column(name = "issuance_ref") var issuanceRef: ByteArray, - @Column(name = "owner_key") - var owner: String, + @Column(name = "owner_key_hash", length = MAX_HASH_HEX_SIZE) + var ownerHash: String, @Column(name = "maturity_instant") var maturity: Instant, @@ -44,8 +45,8 @@ object CommercialPaperSchemaV1 : MappedSchema(schemaFamily = CommercialPaperSche @Column(name = "ccy_code", length = 3) var currency: String, - @Column(name = "face_value_issuer_key") - var faceValueIssuerParty: String, + @Column(name = "face_value_issuer_key_hash", length = MAX_HASH_HEX_SIZE) + var faceValueIssuerPartyHash: String, @Column(name = "face_value_issuer_ref") var faceValueIssuerRef: ByteArray diff --git a/finance/src/test/kotlin/net/corda/finance/contracts/asset/DummyFungibleContract.kt b/finance/src/test/kotlin/net/corda/finance/contracts/asset/DummyFungibleContract.kt index da55397e23..62702f215d 100644 --- a/finance/src/test/kotlin/net/corda/finance/contracts/asset/DummyFungibleContract.kt +++ b/finance/src/test/kotlin/net/corda/finance/contracts/asset/DummyFungibleContract.kt @@ -1,19 +1,19 @@ package net.corda.finance.contracts.asset import net.corda.core.contracts.* -import net.corda.core.utilities.toBase58String +import net.corda.core.crypto.toStringShort import net.corda.core.identity.AbstractParty import net.corda.core.internal.Emoji import net.corda.core.schemas.MappedSchema import net.corda.core.schemas.PersistentState import net.corda.core.schemas.QueryableState import net.corda.core.transactions.LedgerTransaction -import net.corda.finance.utils.sumCash -import net.corda.finance.utils.sumCashOrNull -import net.corda.finance.utils.sumCashOrZero import net.corda.finance.schemas.SampleCashSchemaV1 import net.corda.finance.schemas.SampleCashSchemaV2 import net.corda.finance.schemas.SampleCashSchemaV3 +import net.corda.finance.utils.sumCash +import net.corda.finance.utils.sumCashOrNull +import net.corda.finance.utils.sumCashOrZero import java.security.PublicKey import java.util.* @@ -43,10 +43,10 @@ class DummyFungibleContract : OnLedgerAsset SampleCashSchemaV1.PersistentCashState( - owner = this.owner.owningKey.toBase58String(), + ownerHash = this.owner.owningKey.toStringShort(), pennies = this.amount.quantity, currency = this.amount.token.product.currencyCode, - issuerParty = this.amount.token.issuer.party.owningKey.toBase58String(), + issuerPartyHash = this.amount.token.issuer.party.owningKey.toStringShort(), issuerRef = this.amount.token.issuer.reference.bytes ) is SampleCashSchemaV2 -> SampleCashSchemaV2.PersistentCashState( diff --git a/finance/src/test/kotlin/net/corda/finance/schemas/SampleCashSchemaV1.kt b/finance/src/test/kotlin/net/corda/finance/schemas/SampleCashSchemaV1.kt index b94d6ac700..5acd34fe12 100644 --- a/finance/src/test/kotlin/net/corda/finance/schemas/SampleCashSchemaV1.kt +++ b/finance/src/test/kotlin/net/corda/finance/schemas/SampleCashSchemaV1.kt @@ -2,6 +2,7 @@ package net.corda.finance.schemas import net.corda.core.schemas.MappedSchema import net.corda.core.schemas.PersistentState +import net.corda.core.utilities.MAX_HASH_HEX_SIZE import javax.persistence.Column import javax.persistence.Entity import javax.persistence.Index @@ -22,8 +23,8 @@ object SampleCashSchemaV1 : MappedSchema(schemaFamily = CashSchema.javaClass, ve indexes = arrayOf(Index(name = "ccy_code_idx", columnList = "ccy_code"), Index(name = "pennies_idx", columnList = "pennies"))) class PersistentCashState( - @Column(name = "owner_key") - var owner: String, + @Column(name = "owner_key_hash", length = MAX_HASH_HEX_SIZE) + var ownerHash: String, @Column(name = "pennies") var pennies: Long, @@ -31,8 +32,8 @@ object SampleCashSchemaV1 : MappedSchema(schemaFamily = CashSchema.javaClass, ve @Column(name = "ccy_code", length = 3) var currency: String, - @Column(name = "issuer_key") - var issuerParty: String, + @Column(name = "issuer_key_hash", length = MAX_HASH_HEX_SIZE) + var issuerPartyHash: String, @Column(name = "issuer_ref") var issuerRef: ByteArray diff --git a/finance/src/test/kotlin/net/corda/finance/schemas/SampleCommercialPaperSchemaV1.kt b/finance/src/test/kotlin/net/corda/finance/schemas/SampleCommercialPaperSchemaV1.kt index 4da37b7143..e73983a5e2 100644 --- a/finance/src/test/kotlin/net/corda/finance/schemas/SampleCommercialPaperSchemaV1.kt +++ b/finance/src/test/kotlin/net/corda/finance/schemas/SampleCommercialPaperSchemaV1.kt @@ -2,6 +2,7 @@ package net.corda.finance.schemas import net.corda.core.schemas.MappedSchema import net.corda.core.schemas.PersistentState +import net.corda.core.utilities.MAX_HASH_HEX_SIZE import java.time.Instant import javax.persistence.Column import javax.persistence.Entity @@ -24,14 +25,14 @@ object SampleCommercialPaperSchemaV1 : MappedSchema(schemaFamily = CommercialPap Index(name = "maturity_index", columnList = "maturity_instant"), Index(name = "face_value_index", columnList = "face_value"))) class PersistentCommercialPaperState( - @Column(name = "issuance_key") - var issuanceParty: String, + @Column(name = "issuance_key_hash", length = MAX_HASH_HEX_SIZE) + var issuancePartyHash: String, @Column(name = "issuance_ref") var issuanceRef: ByteArray, - @Column(name = "owner_key") - var owner: String, + @Column(name = "owner_key_hash", length = MAX_HASH_HEX_SIZE) + var ownerHash: String, @Column(name = "maturity_instant") var maturity: Instant, @@ -42,8 +43,8 @@ object SampleCommercialPaperSchemaV1 : MappedSchema(schemaFamily = CommercialPap @Column(name = "ccy_code", length = 3) var currency: String, - @Column(name = "face_value_issuer_key") - var faceValueIssuerParty: String, + @Column(name = "face_value_issuer_key_hash", length = MAX_HASH_HEX_SIZE) + var faceValueIssuerPartyHash: String, @Column(name = "face_value_issuer_ref") var faceValueIssuerRef: ByteArray diff --git a/finance/src/test/kotlin/net/corda/finance/schemas/SampleCommercialPaperSchemaV2.kt b/finance/src/test/kotlin/net/corda/finance/schemas/SampleCommercialPaperSchemaV2.kt index a3b855fe25..d7bb1d5633 100644 --- a/finance/src/test/kotlin/net/corda/finance/schemas/SampleCommercialPaperSchemaV2.kt +++ b/finance/src/test/kotlin/net/corda/finance/schemas/SampleCommercialPaperSchemaV2.kt @@ -3,6 +3,7 @@ package net.corda.finance.schemas import net.corda.core.identity.AbstractParty import net.corda.core.schemas.CommonSchemaV1 import net.corda.core.schemas.MappedSchema +import net.corda.core.utilities.MAX_HASH_HEX_SIZE import java.time.Instant import javax.persistence.Column import javax.persistence.Entity @@ -26,8 +27,8 @@ object SampleCommercialPaperSchemaV2 : MappedSchema(schemaFamily = CommercialPap @Column(name = "ccy_code", length = 3) var currency: String, - @Column(name = "face_value_issuer_key") - var faceValueIssuerParty: String, + @Column(name = "face_value_issuer_key_hash", length = MAX_HASH_HEX_SIZE) + var faceValueIssuerPartyHash: String, @Column(name = "face_value_issuer_ref") var faceValueIssuerRef: ByteArray, diff --git a/node/src/main/kotlin/net/corda/node/services/identity/PersistentIdentityService.kt b/node/src/main/kotlin/net/corda/node/services/identity/PersistentIdentityService.kt index 04cb310e69..0a847019b1 100644 --- a/node/src/main/kotlin/net/corda/node/services/identity/PersistentIdentityService.kt +++ b/node/src/main/kotlin/net/corda/node/services/identity/PersistentIdentityService.kt @@ -11,8 +11,8 @@ import net.corda.core.node.services.UnknownAnonymousPartyException import net.corda.core.serialization.SingletonSerializeAsToken import net.corda.core.utilities.debug import net.corda.core.utilities.loggerFor +import net.corda.core.utilities.MAX_HASH_HEX_SIZE import net.corda.node.utilities.AppendOnlyPersistentMap -import net.corda.node.utilities.MAX_HASH_HEX_SIZE import net.corda.node.utilities.NODE_DATABASE_PREFIX import org.bouncycastle.cert.X509CertificateHolder import java.io.ByteArrayInputStream diff --git a/node/src/main/kotlin/net/corda/node/services/keys/PersistentKeyManagementService.kt b/node/src/main/kotlin/net/corda/node/services/keys/PersistentKeyManagementService.kt index 7316562ce1..b449653085 100644 --- a/node/src/main/kotlin/net/corda/node/services/keys/PersistentKeyManagementService.kt +++ b/node/src/main/kotlin/net/corda/node/services/keys/PersistentKeyManagementService.kt @@ -4,12 +4,9 @@ import net.corda.core.crypto.* import net.corda.core.identity.PartyAndCertificate import net.corda.core.node.services.IdentityService import net.corda.core.node.services.KeyManagementService -import net.corda.core.serialization.SerializationDefaults import net.corda.core.serialization.SingletonSerializeAsToken -import net.corda.core.serialization.deserialize -import net.corda.core.serialization.serialize +import net.corda.core.utilities.MAX_HASH_HEX_SIZE import net.corda.node.utilities.AppendOnlyPersistentMap -import net.corda.node.utilities.MAX_HASH_HEX_SIZE import net.corda.node.utilities.NODE_DATABASE_PREFIX import org.bouncycastle.operator.ContentSigner import java.security.KeyPair @@ -47,17 +44,15 @@ class PersistentKeyManagementService(val identityService: IdentityService, var privateKey: ByteArray = ByteArray(0) ) { constructor(publicKey: PublicKey, privateKey: PrivateKey) - : this(publicKey.toStringShort(), - publicKey.serialize(context = SerializationDefaults.STORAGE_CONTEXT).bytes, - privateKey.serialize(context = SerializationDefaults.STORAGE_CONTEXT).bytes) + : this(publicKey.toStringShort(), publicKey.encoded, privateKey.encoded) } private companion object { fun createKeyMap(): AppendOnlyPersistentMap { return AppendOnlyPersistentMap( toPersistentEntityKey = { it.toStringShort() }, - fromPersistentEntity = { Pair(it.publicKey.deserialize(context = SerializationDefaults.STORAGE_CONTEXT), - it.privateKey.deserialize(context = SerializationDefaults.STORAGE_CONTEXT)) }, + fromPersistentEntity = { Pair(Crypto.decodePublicKey(it.publicKey), Crypto.decodePrivateKey( + it.privateKey)) }, toPersistentEntity = { key: PublicKey, value: PrivateKey -> PersistentKey(key, value) }, diff --git a/node/src/main/kotlin/net/corda/node/services/network/PersistentNetworkMapCache.kt b/node/src/main/kotlin/net/corda/node/services/network/PersistentNetworkMapCache.kt index c6caa567da..d242f3869f 100644 --- a/node/src/main/kotlin/net/corda/node/services/network/PersistentNetworkMapCache.kt +++ b/node/src/main/kotlin/net/corda/node/services/network/PersistentNetworkMapCache.kt @@ -1,6 +1,8 @@ package net.corda.node.services.network import net.corda.core.concurrent.CordaFuture +import net.corda.core.crypto.SecureHash +import net.corda.core.crypto.toStringShort import net.corda.core.identity.AbstractParty import net.corda.core.identity.CordaX500Name import net.corda.core.identity.Party @@ -17,6 +19,7 @@ import net.corda.core.node.services.NetworkMapCache.MapChange import net.corda.core.node.services.NotaryService import net.corda.core.node.services.PartyInfo import net.corda.core.schemas.NodeInfoSchemaV1 +import net.corda.core.serialization.SerializationDefaults import net.corda.core.serialization.SingletonSerializeAsToken import net.corda.core.serialization.deserialize import net.corda.core.serialization.serialize @@ -323,9 +326,9 @@ open class PersistentNetworkMapCache(private val database: CordaPersistence, con private fun findByIdentityKey(session: Session, identityKey: PublicKey): List { val query = session.createQuery( - "SELECT n FROM ${NodeInfoSchemaV1.PersistentNodeInfo::class.java.name} n JOIN n.legalIdentitiesAndCerts l WHERE l.owningKey = :owningKey", + "SELECT n FROM ${NodeInfoSchemaV1.PersistentNodeInfo::class.java.name} n JOIN n.legalIdentitiesAndCerts l WHERE l.owningKeyHash = :owningKeyHash", NodeInfoSchemaV1.PersistentNodeInfo::class.java) - query.setParameter("owningKey", identityKey.toBase58String()) + query.setParameter("owningKeyHash", identityKey.toStringShort()) return query.resultList } diff --git a/node/src/main/kotlin/net/corda/node/services/network/PersistentNetworkMapService.kt b/node/src/main/kotlin/net/corda/node/services/network/PersistentNetworkMapService.kt index 3d1f7ebd97..e12e1703f4 100644 --- a/node/src/main/kotlin/net/corda/node/services/network/PersistentNetworkMapService.kt +++ b/node/src/main/kotlin/net/corda/node/services/network/PersistentNetworkMapService.kt @@ -7,9 +7,9 @@ import net.corda.core.messaging.SingleMessageRecipient import net.corda.core.serialization.SerializationDefaults import net.corda.core.serialization.deserialize import net.corda.core.serialization.serialize +import net.corda.core.utilities.MAX_HASH_HEX_SIZE import net.corda.node.services.api.NetworkMapCacheInternal import net.corda.node.services.messaging.MessagingService -import net.corda.node.utilities.MAX_HASH_HEX_SIZE import net.corda.node.utilities.NODE_DATABASE_PREFIX import net.corda.node.utilities.PersistentMap import net.corda.nodeapi.ArtemisMessagingComponent diff --git a/node/src/main/kotlin/net/corda/node/services/transactions/BFTNonValidatingNotaryService.kt b/node/src/main/kotlin/net/corda/node/services/transactions/BFTNonValidatingNotaryService.kt index f0c1e17823..41fe9abde8 100644 --- a/node/src/main/kotlin/net/corda/node/services/transactions/BFTNonValidatingNotaryService.kt +++ b/node/src/main/kotlin/net/corda/node/services/transactions/BFTNonValidatingNotaryService.kt @@ -3,6 +3,7 @@ package net.corda.node.services.transactions import co.paralleluniverse.fibers.Suspendable import com.google.common.util.concurrent.SettableFuture import net.corda.core.contracts.StateRef +import net.corda.core.crypto.Crypto import net.corda.core.crypto.DigitalSignature import net.corda.core.crypto.SecureHash import net.corda.core.flows.FlowLogic @@ -15,6 +16,7 @@ import net.corda.core.node.services.NotaryService import net.corda.core.node.services.TimeWindowChecker import net.corda.core.node.services.UniquenessProvider import net.corda.core.schemas.PersistentStateRef +import net.corda.core.serialization.SerializationDefaults import net.corda.core.serialization.deserialize import net.corda.core.serialization.serialize import net.corda.core.transactions.FilteredTransaction @@ -110,7 +112,7 @@ class BFTNonValidatingNotaryService(override val services: ServiceHubInternal, inputIndex = it.consumingIndex, requestingParty = Party( name = CordaX500Name.parse(it.party.name), - owningKey = parsePublicKeyBase58(it.party.owningKey)))) + owningKey = Crypto.decodePublicKey(it.party.owningKey)))) }, toPersistentEntity = { (txHash, index): StateRef, (id, inputIndex, requestingParty): UniquenessProvider.ConsumingTx -> PersistedCommittedState( @@ -118,7 +120,7 @@ class BFTNonValidatingNotaryService(override val services: ServiceHubInternal, consumingTxHash = id.toString(), consumingIndex = inputIndex, party = PersistentUniquenessProvider.PersistentParty(requestingParty.name.toString(), - requestingParty.owningKey.toBase58String()) + requestingParty.owningKey.encoded) ) }, persistentEntityClass = PersistedCommittedState::class.java diff --git a/node/src/main/kotlin/net/corda/node/services/transactions/PersistentUniquenessProvider.kt b/node/src/main/kotlin/net/corda/node/services/transactions/PersistentUniquenessProvider.kt index f669ae3426..d3cfaaa877 100644 --- a/node/src/main/kotlin/net/corda/node/services/transactions/PersistentUniquenessProvider.kt +++ b/node/src/main/kotlin/net/corda/node/services/transactions/PersistentUniquenessProvider.kt @@ -1,6 +1,7 @@ package net.corda.node.services.transactions import net.corda.core.contracts.StateRef +import net.corda.core.crypto.Crypto import net.corda.core.crypto.SecureHash import net.corda.core.identity.CordaX500Name import net.corda.core.identity.Party @@ -8,10 +9,11 @@ import net.corda.core.internal.ThreadBox import net.corda.core.node.services.UniquenessException import net.corda.core.node.services.UniquenessProvider import net.corda.core.schemas.PersistentStateRef +import net.corda.core.serialization.SerializationDefaults import net.corda.core.serialization.SingletonSerializeAsToken +import net.corda.core.serialization.deserialize +import net.corda.core.serialization.serialize import net.corda.core.utilities.loggerFor -import net.corda.core.utilities.parsePublicKeyBase58 -import net.corda.core.utilities.toBase58String import net.corda.node.utilities.AppendOnlyPersistentMap import net.corda.node.utilities.NODE_DATABASE_PREFIX import java.io.Serializable @@ -44,7 +46,7 @@ class PersistentUniquenessProvider : UniquenessProvider, SingletonSerializeAsTok var name: String = "", @Column(name = "requesting_party_key", length = 255) - var owningKey: String = "" + var owningKey: ByteArray = ByteArray(0) ) : Serializable @Entity @@ -75,14 +77,14 @@ class PersistentUniquenessProvider : UniquenessProvider, SingletonSerializeAsTok inputIndex = it.consumingIndex, requestingParty = Party( name = CordaX500Name.parse(it.party.name), - owningKey = parsePublicKeyBase58(it.party.owningKey)))) + owningKey = Crypto.decodePublicKey(it.party.owningKey)))) }, toPersistentEntity = { (txHash, index): StateRef, (id, inputIndex, requestingParty): UniquenessProvider.ConsumingTx -> PersistentNotaryCommit( id = PersistentStateRef(txHash.toString(), index), consumingTxHash = id.toString(), consumingIndex = inputIndex, - party = PersistentParty(requestingParty.name.toString(), requestingParty.owningKey.toBase58String()) + party = PersistentParty(requestingParty.name.toString(), requestingParty.owningKey.encoded) ) }, persistentEntityClass = PersistentNotaryCommit::class.java diff --git a/node/src/main/kotlin/net/corda/node/utilities/CordaPersistence.kt b/node/src/main/kotlin/net/corda/node/utilities/CordaPersistence.kt index a8c85d105d..77055c6073 100644 --- a/node/src/main/kotlin/net/corda/node/utilities/CordaPersistence.kt +++ b/node/src/main/kotlin/net/corda/node/utilities/CordaPersistence.kt @@ -20,14 +20,6 @@ import java.util.concurrent.CopyOnWriteArrayList */ const val NODE_DATABASE_PREFIX = "node_" -/** - * The maximum supported field-size for hash HEX-encoded outputs (e.g. database fields). - * This value is enough to support hash functions with outputs up to 512 bits (e.g. SHA3-512), in which - * case 128 HEX characters are required. - * 130 was selected instead of 128, to allow for 2 extra characters that will be used as hash-scheme identifiers. - */ -internal const val MAX_HASH_HEX_SIZE = 130 - //HikariDataSource implements Closeable which allows CordaPersistence to be Closeable class CordaPersistence(var dataSource: HikariDataSource, private val schemaService: SchemaService, private val createIdentityService: () -> IdentityService, databaseProperties: Properties) : Closeable { diff --git a/node/src/test/kotlin/net/corda/node/services/persistence/HibernateConfigurationTest.kt b/node/src/test/kotlin/net/corda/node/services/persistence/HibernateConfigurationTest.kt index 0e1cdc1047..9f6fe30d11 100644 --- a/node/src/test/kotlin/net/corda/node/services/persistence/HibernateConfigurationTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/persistence/HibernateConfigurationTest.kt @@ -448,7 +448,7 @@ class HibernateConfigurationTest : TestDependencyInjectionBase() { criteriaBuilder.sum(cashStates.get("pennies"))) // group by - criteriaQuery.groupBy(cashStates.get("issuerParty"), cashStates.get("currency")) + criteriaQuery.groupBy(cashStates.get("issuerPartyHash"), cashStates.get("currency")) // order by criteriaQuery.orderBy(criteriaBuilder.desc(criteriaBuilder.sum(cashStates.get("pennies")))) diff --git a/node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt b/node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt index 8d34babb97..a71f84f6ea 100644 --- a/node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt @@ -3,6 +3,7 @@ package net.corda.node.services.vault import net.corda.core.contracts.* import net.corda.core.crypto.SecureHash import net.corda.core.crypto.entropyToKeyPair +import net.corda.core.crypto.toStringShort import net.corda.core.identity.CordaX500Name import net.corda.core.identity.Party import net.corda.core.identity.PartyAndCertificate @@ -693,7 +694,7 @@ class VaultQueryTests : TestDependencyInjectionBase() { services.fillWithSomeTestCash(100.SWISS_FRANCS, notaryServices, DUMMY_NOTARY, 1, 1, Random(0L)) } database.transaction { - val logicalExpression = builder { CashSchemaV1.PersistentCashState::issuerParty.isNull() } + val logicalExpression = builder { CashSchemaV1.PersistentCashState::issuerPartyHash.isNull() } val criteria = VaultCustomQueryCriteria(logicalExpression) val results = vaultService.queryBy(criteria) assertThat(results.states).hasSize(0) @@ -708,7 +709,7 @@ class VaultQueryTests : TestDependencyInjectionBase() { services.fillWithSomeTestCash(100.SWISS_FRANCS, notaryServices, DUMMY_NOTARY, 1, 1, Random(0L)) } database.transaction { - val logicalExpression = builder { CashSchemaV1.PersistentCashState::issuerParty.notNull() } + val logicalExpression = builder { CashSchemaV1.PersistentCashState::issuerPartyHash.notNull() } val criteria = VaultCustomQueryCriteria(logicalExpression) val results = vaultService.queryBy(criteria) assertThat(results.states).hasSize(3) @@ -830,7 +831,7 @@ class VaultQueryTests : TestDependencyInjectionBase() { database.transaction { // DOCSTART VaultQueryExample23 val sum = builder { - CashSchemaV1.PersistentCashState::pennies.sum(groupByColumns = listOf(CashSchemaV1.PersistentCashState::issuerParty, + CashSchemaV1.PersistentCashState::pennies.sum(groupByColumns = listOf(CashSchemaV1.PersistentCashState::issuerPartyHash, CashSchemaV1.PersistentCashState::currency), orderBy = Sort.Direction.DESC) } @@ -841,16 +842,16 @@ class VaultQueryTests : TestDependencyInjectionBase() { assertThat(results.otherResults).hasSize(12) assertThat(results.otherResults[0]).isEqualTo(40000L) - assertThat(results.otherResults[1]).isEqualTo(BOC_PUBKEY.toBase58String()) + assertThat(results.otherResults[1]).isEqualTo(BOC_PUBKEY.toStringShort()) assertThat(results.otherResults[2]).isEqualTo("GBP") assertThat(results.otherResults[3]).isEqualTo(30000L) - assertThat(results.otherResults[4]).isEqualTo(DUMMY_CASH_ISSUER.party.owningKey.toBase58String()) + assertThat(results.otherResults[4]).isEqualTo(DUMMY_CASH_ISSUER.party.owningKey.toStringShort()) assertThat(results.otherResults[5]).isEqualTo("GBP") assertThat(results.otherResults[6]).isEqualTo(20000L) - assertThat(results.otherResults[7]).isEqualTo(BOC_PUBKEY.toBase58String()) + assertThat(results.otherResults[7]).isEqualTo(BOC_PUBKEY.toStringShort()) assertThat(results.otherResults[8]).isEqualTo("USD") assertThat(results.otherResults[9]).isEqualTo(10000L) - assertThat(results.otherResults[10]).isEqualTo(DUMMY_CASH_ISSUER.party.owningKey.toBase58String()) + assertThat(results.otherResults[10]).isEqualTo(DUMMY_CASH_ISSUER.party.owningKey.toStringShort()) assertThat(results.otherResults[11]).isEqualTo("USD") } } From 5bb4601812daa37bc4fadbad9db3b8dbdc3de21a Mon Sep 17 00:00:00 2001 From: Joel Dudley Date: Wed, 25 Oct 2017 09:29:32 +0100 Subject: [PATCH 40/94] Reflects V1.0 changes on Master re: internal samples and identity sync flow API. --- docs/source/api-identity.rst | 176 +++---- docs/source/getting-set-up.rst | 10 +- docs/source/quickstart-index.rst | 2 +- docs/source/running-the-demos.rst | 437 ------------------ .../corda/finance/flows/TwoPartyTradeFlow.kt | 2 + samples/attachment-demo/README.md | 27 +- samples/bank-of-corda-demo/README.md | 79 ++-- samples/irs-demo/README.md | 32 +- samples/notary-demo/README.md | 62 ++- samples/trader-demo/README.md | 29 +- 10 files changed, 270 insertions(+), 586 deletions(-) delete mode 100644 docs/source/running-the-demos.rst diff --git a/docs/source/api-identity.rst b/docs/source/api-identity.rst index fb3a18342e..3ab041d6b2 100644 --- a/docs/source/api-identity.rst +++ b/docs/source/api-identity.rst @@ -7,61 +7,59 @@ API: Identity Party ----- -Identities on the network are represented by ``AbstractParty``. There are two types of ``AbstractParty``: +Parties on the network are represented using the ``AbstractParty`` class. There are two types of ``AbstractParty``: * ``Party``, identified by a ``PublicKey`` and a ``CordaX500Name`` +* ``AnonymousParty``, identified by a ``PublicKey`` only -* ``AnonymousParty``, identified by a ``PublicKey`` +Using ``AnonymousParty`` to identify parties in states and commands prevents nodes from learning the identities +of the parties involved in a transaction when they verify the transaction's dependency chain. When preserving the +anonymity of each party is not required (e.g. for internal processing), ``Party`` can be used instead. -For example, in a transaction sent to your node as part of a chain of custody it is important you can convince yourself -of the transaction's validity, but equally important that you don't learn anything about who was involved in that -transaction. In these cases ``AnonymousParty`` should be used by flows constructing when transaction states and commands. -In contrast, for internal processing where extended details of a party are required, the ``Party`` class should be used -instead. The identity service provides functionality for flows to resolve anonymous parties to full parties, dependent -on the anonymous party's identity having been registered with the node earlier (typically this is handled by -``SwapIdentitiesFlow`` or ``IdentitySyncFlow``, discussed below). +The identity service allows flows to resolve ``AnonymousParty`` to ``Party``, but only if the anonymous party's +identity has already been registered with the node (typically handled by ``SwapIdentitiesFlow`` or +``IdentitySyncFlow``, discussed below). -Party names are held within the ``CordaX500Name`` data class, which enforces the structure of names within Corda, as -well as ensuring a consistent rendering of the names in plain text. +Party names use the ``CordaX500Name`` data class, which enforces the structure of names within Corda, as well as +ensuring a consistent rendering of the names in plain text. -The support for both Party and AnonymousParty classes in Corda enables sophisticated selective disclosure of identity -information. For example, it is possible to construct a Transaction using an AnonymousParty, so nobody can learn of your -involvement by inspection of the transaction, yet prove to specific counterparts that this AnonymousParty actually is -owned by your well known identity. This disclosure is achieved through the use of the PartyAndCertificate data class -which can be propagated to those who need to know, and contains the Party's X.509 certificate path to provide proof of -ownership by a well known identity. +Support for both ``Party`` and ``AnonymousParty`` classes in Corda enables sophisticated selective disclosure of +identity information. For example, it is possible to construct a transaction using an ``AnonymousParty`` (so nobody can +learn of your involvement by inspection of the transaction), yet prove to specific counterparts that this +``AnonymousParty`` actually corresponds to your well-known identity. This is achieved using the +``PartyAndCertificate`` data class, which contains the X.509 certificate path proving that a given ``AnonymousParty`` +corresponds to a given ``Party``. Each ``PartyAndCertificate`` can be propagated to counterparties on a need-to-know +basis. -The PartyAndCertificate class is also used in the network map service to represent well known identities, in which -scenario the certificate path proves its issuance by the Doorman service. +The ``PartyAndCertificate`` class is also used by the network map service to represent well-known identities, with the +certificate path proving the certificate was issued by the doorman service. - -Confidential Identities +Confidential identities ----------------------- - -Confidential identities are key pairs where the corresponding X.509 certificate (and path) are not made public, so that parties who -are not involved in the transaction cannot identify its participants. They are owned by a well known identity, which -must sign the X.509 certificate. Before constructing a new transaction the involved parties must generate and send new -confidential identities to each other, a process which is managed using ``SwapIdentitiesFlow`` (discussed below). The -public keys of these confidential identities are then used when generating output states and commands for the transaction. +Confidential identities are key pairs where the corresponding X.509 certificate (and path) are not made public, so that +parties who are not involved in the transaction cannot identify the owner. They are owned by a well-known identity, +which must sign the X.509 certificate. Before constructing a new transaction the involved parties must generate and +exchange new confidential identities, a process which is managed using ``SwapIdentitiesFlow`` (discussed below). The +public keys of these confidential identities are then used when generating output states and commands for the +transaction. Where using outputs from a previous transaction in a new transaction, counterparties may need to know who the involved -parties are. One example is in ``TwoPartyTradeFlow`` which delegates to ``CollectSignaturesFlow`` to gather certificates -from both parties. ``CollectSignaturesFlow`` requires that a confidential identity of the initiating node has signed -the transaction, and verifying this requires the receiving node has a copy of the confidential identity for the input -state. ``IdentitySyncFlow`` can be used to synchronize the confidential identities we have the certificate paths for, in -a single transaction, to another node. +parties are. One example is the ``TwoPartyTradeFlow``, where an existing asset is exchanged for cash. If confidential +identities are being used, the buyer will want to ensure that the asset being transferred is owned by the seller, and +the seller will likewise want to ensure that the cash being transferred is owned by the buyer. Verifying this requires +both nodes to have a copy of the confidential identities for the asset and cash input states. ``IdentitySyncFlow`` +manages this process. It takes as inputs a transaction and a counterparty, and for every confidential identity involved +in that transaction for which the calling node holds the certificate path, it sends this certificate path to the +counterparty. -.. note:: ``CollectSignaturesFlow`` requires that the initiating node has signed the transaction, and as such all nodes - providing signatures must recognise the signing key used by the initiating node as being either its well known identity - or a confidential identity they have the certificate for. +SwapIdentitiesFlow +~~~~~~~~~~~~~~~~~~ +``SwapIdentitiesFlow`` is typically run as a subflow of another flow. It takes as its sole constructor argument the +counterparty we want to exchange confidential identities with. It returns a mapping from the identities of the caller +and the counterparty to their new confidential identities. In the future, this flow will be extended to handle swapping +identities with multiple parties at once. -Swap identities flow -~~~~~~~~~~~~~~~~~~~~ - -``SwapIdentitiesFlow`` takes the party to swap identities with in its constructor (the counterparty), and is typically run as a subflow of -another flow. It returns a mapping from well known identities of the calling flow and our counterparty to the new -confidential identities; in future this will be extended to handle swapping identities with multiple parties. -You can see an example of it being used in ``TwoPartyDealFlow.kt``: +You can see an example of using ``SwapIdentitiesFlow`` in ``TwoPartyDealFlow.kt``: .. container:: codeset @@ -71,30 +69,35 @@ You can see an example of it being used in ``TwoPartyDealFlow.kt``: :end-before: DOCEND 2 :dedent: 8 -The swap identities flow goes through the following key steps: +``SwapIdentitiesFlow`` goes through the following key steps: -1. Generate a nonce value to form a challenge to the other nodes. -2. Send nonce value to all counterparties, and receive their nonce values. -3. Generate a new confidential identity from our well known identity. +1. Generate a nonce value to form a challenge to the other nodes +2. Send nonce value to all counterparties, and receive their nonce values +3. Generate a new confidential identity from our well-known identity 4. Create a data blob containing the new confidential identity (public key, name and X.509 certificate path), - and the hash of the nonce values. -5. Sign the resulting data blob with the confidential identity's private key. -6. Send the confidential identity and data blob signature to all counterparties, while receiving theirs. -7. Verify the signatures to ensure that identities were generated by the involved set of parties. -8. Verify the confidential identities are owned by the expected well known identities. -9. Store the confidential identities and return them to the calling flow. + and the hash of the nonce values +5. Sign the resulting data blob with the confidential identity's private key +6. Send the confidential identity and data blob signature to all counterparties, while receiving theirs +7. Verify the signatures to ensure that identities were generated by the involved set of parties +8. Verify the confidential identities are owned by the expected well known identities +9. Store the confidential identities and return them to the calling flow -This ensures not only that the confidential identity X.509 certificates are signed by the correct well known identities, -but also that the confidential identity private key is held by the counterparty, and that a party cannot claim ownership -another party's confidential identities belong to its well known identity. +This ensures not only that the confidential identity X.509 certificates are signed by the correct well-known +identities, but also that the confidential identity private key is held by the counterparty, and that a party cannot +claim ownership of another party's confidential identities. -Identity synchronization flow -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +IdentitySyncFlow +~~~~~~~~~~~~~~~~ +When constructing a transaction whose input states reference confidential identities, it is common for counterparties +to require knowledge of which well-known identity each confidential identity maps to. ``IdentitySyncFlow`` handles this +process. You can see an example of its use in ``TwoPartyTradeFlow.kt``. -When constructing a transaction whose input states reference confidential identities, it is common for other signing -entities (counterparties) to require to know which well known identities those confidential identities map to. The -``IdentitySyncFlow`` handles distribution of a node's confidential identities, and you can see an example of its -use in ``TwoPartyTradeFlow.kt``: +``IdentitySyncFlow`` is divided into two parts: + +* ``IdentitySyncFlow.Send`` +* ``IdentitySyncFlow.Receive`` + +``IdentitySyncFlow.Send`` is invoked by the party initiating the identity synchronization: .. container:: codeset @@ -106,33 +109,40 @@ use in ``TwoPartyTradeFlow.kt``: The identity synchronization flow goes through the following key steps: -1. Extract participant identities from all input and output states. Filter this set down to confidential identities - of the flow's well known identity. Required signers on commands are currently ignored as they are presumed to be - included in the participants on states, or to be well known identities of services (such as an oracle service). +1. Extract participant identities from all input and output states and remove any well known identities. Required + signers on commands are currently ignored as they are presumed to be included in the participants on states, or to + be well-known identities of services (such as an oracle service) 2. For each counterparty node, send a list of the public keys of the confidential identities, and receive back a list - of those the counterparty needs the certificate path for. -3. Verify the requested list of identities contains only confidential identities in the offered list, and abort otherwise. -4. Send the requested confidential identities as ``PartyAndCertificate`` instances to the counterparty. + of those the counterparty needs the certificate path for +3. Verify the requested list of identities contains only confidential identities in the offered list, and abort + otherwise +4. Send the requested confidential identities as ``PartyAndCertificate`` instances to the counterparty -.. note:: ``IdentitySyncFlow`` works on a push basis. Receiving nodes can only request confidential identities being - offered by the initiating node. There is no standard flow for nodes to collect - confidential identities before assembling a transaction, and this is left for individual flows to manage if required. +.. note:: ``IdentitySyncFlow`` works on a push basis. The initiating node can only send confidential identities it has + the X.509 certificates for, and the remote nodes can only request confidential identities being offered (are + referenced in the transaction passed to the initiating flow). There is no standard flow for nodes to collect + confidential identities before assembling a transaction, and this is left for individual flows to manage if + required. -``IdentitySyncFlow`` will serve only confidential identities in the provided transaction, limited to those that are -signed by the well known identity the flow is initiated by. This is done to avoid a risk of a node including -states it doesn't have the well known identity of participants in, to try convincing one of its counterparties to -reveal the identity. In case of a more complex transaction where multiple well known identities need confidential -identities distributed this flow should be run by each node in turn. For example: +Meanwhile, ``IdentitySyncFlow.Receive`` is invoked by all the other (non-initiating) parties involved in the identity +synchronization process: + +.. container:: codeset + + .. literalinclude:: ../../finance/src/main/kotlin/net/corda/finance/flows/TwoPartyTradeFlow.kt + :language: kotlin + :start-after: DOCSTART 07 + :end-before: DOCEND 07 + :dedent: 12 + +``IdentitySyncFlow`` will serve all confidential identities in the provided transaction, irrespective of well-known +identity. This is important for more complex transaction cases with 3+ parties, for example: * Alice is building the transaction, and provides some input state *x* owned by a confidential identity of Alice * Bob provides some input state *y* owned by a confidential identity of Bob * Charlie provides some input state *z* owned by a confidential identity of Charlie -Alice, Bob and Charlie must all run ``IdentitySyncFlow`` to send their involved confidential identities to the other -parties. For an illustration of the security implications of not requiring this, consider: - -1. Alice is building the transaction, and provides some input state *x* owned by a confidential identity of Alice -2. Bob provides some input state *y* owned by a confidential identity it doesn't know the well known identity of, but - Alice does. -3. Alice runs ``IdentitySyncFlow`` and sends not just their confidential identity, but also the confidential identity - in state *y*, violating the privacy model. \ No newline at end of file +Alice may know all of the confidential identities ahead of time, but Bob not know about Charlie's and vice-versa. +The assembled transaction therefore has three input states *x*, *y* and *z*, for which only Alice possesses +certificates for all confidential identities. ``IdentitySyncFlow`` must send not just Alice's confidential identity but +also any other identities in the transaction to the Bob and Charlie. \ No newline at end of file diff --git a/docs/source/getting-set-up.rst b/docs/source/getting-set-up.rst index f6ce762df1..fa71c4106d 100644 --- a/docs/source/getting-set-up.rst +++ b/docs/source/getting-set-up.rst @@ -155,9 +155,9 @@ A CorDapp template that you can use as the basis for your own CorDapps is availa https://github.com/corda/cordapp-template-kotlin.git -And a simple example CorDapp for you to explore basic concepts is available here: +And a list of simple sample CorDapps for you to explore basic concepts is available here: - https://github.com/corda/cordapp-example.git + https://www.corda.net/samples/ You can clone these repos to your local machine by running the command ``git clone [repo URL]``. @@ -168,9 +168,9 @@ The best way to check that everything is working fine is by taking a deeper look Next, you should read through :doc:`Corda Key Concepts ` to understand how Corda works. -By then, you'll be ready to start writing your own CorDapps. Learn how to do this in the -:doc:`Hello, World tutorial `. You may want to refer to the :doc:`API documentation ` along the -way. +You'll then be ready to start writing your own CorDapps. Learn how to do this in the +:doc:`Hello, World tutorial `. You'll want to refer to the :doc:`API docs `, the +:doc:`flow cookbook ` and the `samples `_ along the way. If you encounter any issues, please see the :doc:`troubleshooting` page, or get in touch with us on the `forums `_ or via `slack `_. diff --git a/docs/source/quickstart-index.rst b/docs/source/quickstart-index.rst index 59f6042658..02e9045ac9 100644 --- a/docs/source/quickstart-index.rst +++ b/docs/source/quickstart-index.rst @@ -6,6 +6,6 @@ Quickstart getting-set-up tutorial-cordapp - running-the-demos + Sample CorDapps building-against-master CLI-vs-IDE \ No newline at end of file diff --git a/docs/source/running-the-demos.rst b/docs/source/running-the-demos.rst deleted file mode 100644 index 11ebb794c2..0000000000 --- a/docs/source/running-the-demos.rst +++ /dev/null @@ -1,437 +0,0 @@ -Running the demos -================= - -.. contents:: - -The `Corda repository `_ contains a number of demo programs demonstrating -Corda's functionality: - -1. The :ref:`trader-demo`, which shows a delivery-vs-payment atomic swap of commercial paper for cash -2. The :ref:`irs-demo`, which shows two nodes establishing an interest rate swap and performing fixings with a - rates oracle -3. The :ref:`attachment-demo`, which demonstrates uploading attachments to nodes -4. The :ref:`notary-demo`, which shows three different types of notaries and a single node getting multiple transactions - notarised -5. The :ref:`bank-of-corda-demo`, which shows a node acting as an issuer of assets (the Bank of Corda) while remote client - applications request issuance of some cash on behalf of a node called Big Corporation - -If any of the demos don't work, please raise an issue on `GitHub `_. - -.. note:: If you are running the demos from the command line in Linux (but not macOS), you may have to install xterm. - -.. note:: If you would like to see flow activity on the nodes type in the node terminal ``flow watch``. - -.. _trader-demo: - -Trader demo ------------ - -This demo brings up four nodes: Bank A, Bank B, Bank Of Corda, and a notary/network map node that they all use. Bank A will -be the buyer, and requests some cash from the Bank of Corda in order to acquire commercial paper from Bank B, the seller. - -To run from the command line in Unix: - -1. Run ``./gradlew samples:trader-demo:deployNodes`` to create a set of configs and installs under ``samples/trader-demo/build/nodes`` -2. Run ``./samples/trader-demo/build/nodes/runnodes`` to open up four new terminals with the four nodes -3. Run ``./gradlew samples:trader-demo:runBank`` to instruct the bank node to issue cash and commercial paper to the buyer and seller nodes respectively. -4. Run ``./gradlew samples:trader-demo:runSeller`` to trigger the transaction. If you entered ``flow watch`` - -you can see flows running on both sides of transaction. Additionally you should see final trade information displayed -to your terminal. - -To run from the command line in Windows: - -1. Run ``gradlew samples:trader-demo:deployNodes`` to create a set of configs and installs under ``samples\trader-demo\build\nodes`` -2. Run ``samples\trader-demo\build\nodes\runnodes`` to open up four new terminals with the four nodes -3. Run ``gradlew samples:trader-demo:runBank`` to instruct the buyer node to request issuance of some cash from the Bank of Corda node -4. Run ``gradlew samples:trader-demo:runSeller`` to trigger the transaction. If you entered ``flow watch`` - -you can see flows running on both sides of transaction. Additionally you should see final trade information displayed -to your terminal. - -.. _irs-demo: - -IRS demo --------- - -This demo brings up three nodes: Bank A, Bank B and a node that simultaneously runs a notary, a network map and an interest rates -oracle. The two banks agree on an interest rate swap, and then do regular fixings of the deal as the time -on a simulated clock passes. - -To run from the command line in Unix: - -1. Run ``./gradlew samples:irs-demo:deployNodes`` to install configs and a command line tool under ``samples/irs-demo/build`` -2. Run ``./gradlew samples:irs-demo:installDist`` -3. Move to the ``samples/irs-demo/build`` directory -4. Run ``./nodes/runnodes`` to open up three new terminals with the three nodes (you may have to install xterm). - -To run from the command line in Windows: - -1. Run ``gradlew.bat samples:irs-demo:deployNodes`` to install configs and a command line tool under ``samples\irs-demo\build`` -2. Run ``gradlew.bat samples:irs-demo:installDist`` -3. Run ``cd samples\irs-demo\build`` to change current working directory -4. Run ``nodes\runnodes`` to open up several 6 terminals, 2 for each node. First terminal is a web-server associated with every node and second one is Corda interactive shell for the node. - -This demo also has a web app. To use this, run nodes and then navigate to -http://localhost:10007/web/irsdemo and http://localhost:10010/web/irsdemo to see each node's view of the ledger. - -To use the web app, click the "Create Deal" button, fill in the form, then click the "Submit" button. You can then -use the time controls at the top left of the home page to run the fixings. Click any individual trade in the blotter to view it. - -.. note:: The IRS web UI currently has a bug when changing the clock time where it may show no numbers or apply fixings inconsistently. - The issues will be addressed in a future milestone release. Meanwhile, you can take a look at a simpler oracle example https://github.com/corda/oracle-example - -.. _attachment-demo: - -Attachment demo ---------------- - -This demo brings up three nodes, and sends a transaction containing an attachment from one to the other. - -To run from the command line in Unix: - -1. Run ``./gradlew samples:attachment-demo:deployNodes`` to create a set of configs and installs under ``samples/attachment-demo/build/nodes`` -2. Run ``./samples/attachment-demo/build/nodes/runnodes`` to open up three new terminal tabs/windows with the three nodes and webserver for BankB -3. Run ``./gradlew samples:attachment-demo:runRecipient``, which will block waiting for a trade to start -4. Run ``./gradlew samples:attachment-demo:runSender`` in another terminal window to send the attachment. Now look at the other windows to - see the output of the demo - -To run from the command line in Windows: - -1. Run ``gradlew samples:attachment-demo:deployNodes`` to create a set of configs and installs under ``samples\attachment-demo\build\nodes`` -2. Run ``samples\attachment-demo\build\nodes\runnodes`` to open up three new terminal tabs/windows with the three nodes and webserver for BankB -3. Run ``gradlew samples:attachment-demo:runRecipient``, which will block waiting for a trade to start -4. Run ``gradlew samples:attachment-demo:runSender`` in another terminal window to send the attachment. Now look at the other windows to - see the output of the demo - -.. _notary-demo: - -Notary demo ------------ - -This demo shows a party getting transactions notarised by either a single-node or a distributed notary service. -All versions of the demo start two counterparty nodes. -One of the counterparties will generate transactions that transfer a self-issued asset to the other party and submit them for notarisation. - -* The `Raft `_ version of the demo will start three distributed notary nodes. -* The `BFT SMaRt `_ version of the demo will start four distributed notary nodes. -* The Single version of the demo will start a single-node validating notary service. -* The Custom version of the demo will load and start a custom single-node notary service that is defined the demo CorDapp. - -The output will display a list of notarised transaction IDs and corresponding signer public keys. In the Raft distributed notary, -every node in the cluster can service client requests, and one signature is sufficient to satisfy the notary composite key requirement. -In the BFT SMaRt distributed notary, three signatures are required. -You will notice that successive transactions get signed by different members of the cluster (usually allocated in a random order). - -To run the Raft version of the demo from the command line in Unix: - -1. Run ``./gradlew samples:notary-demo:deployNodes``, which will create node directories for all versions of the demo, - with configs under ``samples/notary-demo/build/nodes/nodesRaft`` (``nodesBFT``, ``nodesSingle``, and ``nodesCustom`` for - BFT, Single and Custom notaries respectively). -2. Run ``./samples/notary-demo/build/nodes/nodesRaft/runnodes``, which will start the nodes in separate terminal windows/tabs. - Wait until a "Node started up and registered in ..." message appears on each of the terminals -3. Run ``./gradlew samples:notary-demo:notarise`` to make a call to the "Party" node to initiate notarisation requests - In a few seconds you will see a message "Notarised 10 transactions" with a list of transaction ids and the signer public keys - -To run from the command line in Windows: - -1. Run ``gradlew samples:notary-demo:deployNodes``, which will create all three types of notaries' node directories - with configs under ``samples/notary-demo/build/nodes/nodesRaft`` (``nodesBFT``, ``nodesSingle``, and ``nodesCustom`` for - BFT, Single and Custom notaries respectively). -2. Run ``samples\notary-demo\build\nodes\nodesRaft\runnodes``, which will start the nodes in separate terminal windows/tabs. - Wait until a "Node started up and registered in ..." message appears on each of the terminals -3. Run ``gradlew samples:notary-demo:notarise`` to make a call to the "Party" node to initiate notarisation requests - In a few seconds you will see a message "Notarised 10 transactions" with a list of transaction ids and the signer public keys - -To run the BFT SMaRt notary demo, use ``nodesBFT`` instead of ``nodesRaft`` in the path (you will see messages from notary nodes -trying to communicate each other sometime with connection errors, that's normal). For a single notary node, use ``nodesSingle``. -For the custom notary service use ``nodesCustom`. - -Distributed notary nodes store consumed states in a replicated commit log, which is backed by a H2 database on each node. -You can ascertain that the commit log is synchronised across the cluster by accessing and comparing each of the nodes' backing stores -by using the H2 web console: - -- Firstly, download `H2 web console `_ (download the "platform-independent zip"), - and start it using a script in the extracted folder: ``sh h2/bin/h2.sh`` (or ``h2\bin\h2`` for Windows) - -- If you are uncertain as to which version of h2 to install or if you have connectivity issues, refer to ``build.gradle`` - located in the corda directory and locate ``h2_version``. Use a client of the same major version - even if still in beta. - -- The H2 web console should start up in a web browser tab. To connect we first need to obtain a JDBC connection string. - Each node outputs its connection string in the terminal window as it starts up. In a terminal window where a **notary** node is running, - look for the following string: - - ``Database connection url is : jdbc:h2:tcp://10.18.0.150:56736/node`` - - You can use the string on the right to connect to the h2 database: just paste it into the `JDBC URL` field and click *Connect*. - You will be presented with a web application that enumerates all the available tables and provides an interface for you to query them using SQL - -- The committed states are stored in the ``NOTARY_COMMITTED_STATES`` table (for Raft) or ``NODE_BFT_SMART_NOTARY_COMMITTED_STATES`` (for BFT). - Note that in the Raft case the raw data is not human-readable, but we're only interested in the row count for this demo - -.. _bank-of-corda-demo: - -Bank Of Corda demo ------------------- - -This demo brings up three nodes: a notary, a node acting as the Bank of Corda that accepts requests for issuance of some asset -and a node acting as Big Corporation which requests issuance of an asset (cash in this example). - -Upon receipt of a request the Bank of Corda node self-issues the asset and then transfers ownership to the requester -after successful notarisation and recording of the issue transaction on the ledger. - -.. note:: The Bank of Corda is somewhat like a "Bitcoin faucet" that dispenses free bitcoins to developers for - testing and experimentation purposes. - -To run from the command line in Unix: - -1. Run ``./gradlew samples:bank-of-corda-demo:deployNodes`` to create a set of configs and installs under ``samples/bank-of-corda-demo/build/nodes`` -2. Run ``./samples/bank-of-corda-demo/build/nodes/runnodes`` to open up three new terminal tabs/windows with the three nodes -3. Run ``./gradlew samples:bank-of-corda-demo:runRPCCashIssue`` to trigger a cash issuance request -4. Run ``./gradlew samples:bank-of-corda-demo:runWebCashIssue`` to trigger another cash issuance request. - Now look at your terminal tab/window to see the output of the demo - -To run from the command line in Windows: - -1. Run ``gradlew samples:bank-of-corda-demo:deployNodes`` to create a set of configs and installs under ``samples\bank-of-corda-demo\build\nodes`` -2. Run ``samples\bank-of-corda-demo\build\nodes\runnodes`` to open up three new terminal tabs/windows with the three nodes -3. Run ``gradlew samples:bank-of-corda-demo:runRPCCashIssue`` to trigger a cash issuance request -4. Run ``gradlew samples:bank-of-corda-demo:runWebCashIssue`` to trigger another cash issuance request. - Now look at the your terminal tab/window to see the output of the demo - -.. note:: To verify that the Bank of Corda node is alive and running, navigate to the following URL: - http://localhost:10007/api/bank/date - -In the window you run the command you should see (in case of Web, RPC is simmilar): - -- Requesting Cash via Web ... -- Successfully processed Cash Issue request - -If you want to see flow activity enter in node's shell ``flow watch``. It will display all state machines -running currently on the node. - -Launch the Explorer application to visualize the issuance and transfer of cash for each node: - - ``./gradlew tools:explorer:run`` (on Unix) or ``gradlew tools:explorer:run`` (on Windows) - -Using the following login details: - -- For the Bank of Corda node: localhost / port 10006 / username bankUser / password test -- For the Big Corporation node: localhost / port 10009 / username bigCorpUser / password test - -See https://docs.corda.net/node-explorer.html for further details on usage. - -.. _simm-demo: - -SIMM and Portfolio Demo - aka the Initial Margin Agreement Demo ---------------------------------------------------------------- - -Background and SIMM Introduction -******************************** - -This app is a demonstration of how Corda can be used for the real world requirement of initial margin calculation and -agreement; featuring the integration of complex and industry proven third party libraries into Corda nodes. - -SIMM is an acronym for "Standard Initial Margin Model". It is effectively the calculation of a "margin" that is paid -by one party to another when they agree a trade on certain types of transaction. - -The SIMM was introduced to standardise the calculation of how much margin counterparties charge each other on their -bilateral transactions. Before SIMM, each counterparty computed margins according to its own model and it was made it very - difficult to agree the exact margin with the counterparty that faces the same trade on the other side. - -To enact this, in September 2016, the ISDA committee - with full backing from various governing bodies - -`issued a ruling on what is known as the ISDA SIMM ™ model `_, -a way of fairly and consistently calculating this margin. Any parties wishing to trade a financial product that is -covered under this ruling would, independently, use this model and calculate their margin payment requirement, -agree it with their trading counterparty and then pay (or receive, depending on the results of this calculation) -this amount. In the case of disagreement that is not resolved in a timely fashion, this payment would increase -and so therefore it is in the parties' interest to reach agreement in as short as time frame as possible. - -To be more accurate, the SIMM calculation is not performed on just one trade - it is calculated on an aggregate of -intermediary values (which in this model are sensitivities to risk factors) from a portfolio of trades; therefore -the input to a SIMM is actually this data, not the individual trades themselves. - -Also note that implementations of the SIMM are actually protected and subject to license restrictions by ISDA -(this is due to the model itself being protected). We were fortunate enough to technically partner with -`OpenGamma `_ who allowed us to demonstrate the SIMM process using their proprietary model. -In the source code released, we have replaced their analytics engine with very simple stub functions that allow -the process to run without actually calculating correct values, and can easily be swapped out in place for their real libraries. - -What happens in the demo (notionally) -************************************* - -Preliminaries - - Ensure that there are a number of live trades with another party based on financial products that are covered under the - ISDA SIMM agreement (if none, then use the demo to enter some simple trades as described below). - -Initial Margin Agreement Process - - Agree that one will be performing the margining calculation against a portfolio of trades with another party, and agree the trades in that portfolio. In practice, one node will start the flow but it does not matter which node does. - - Individually (at the node level), identify the data (static, reference etc) one will need in order to be able to calculate the metrics on those trades - - Confirm with the other counterparty the dataset from the above set - - Calculate any intermediary steps and values needed for the margin calculation (ie sensitivities to risk factors) - - Agree on the results of these steps - - Calculate the initial margin - - Agree on the calculation of the above with the other party - - In practice, pay (or receive) this margin (omitted for the sake of complexity for this example) - -Demo execution (step by step) -***************************** - -**Setting up the Corda infrastructure** - -To run from the command line in Unix: - -1. Deploy the nodes using ``./gradlew samples:simm-valuation-demo:deployNodes`` -2. Run the nodes using ``./samples/simm-valuation-demo/build/nodes/runnodes`` - -To run from the command line in Windows: - -1. Deploy the nodes using ``gradlew samples:simm-valuation-demo:deployNodes`` -2. Run the nodes using ``samples\simm-valuation-demo\build\nodes\runnodes`` - -**Getting Bank A's details** - -From the command line run - -.. sourcecode:: bash - - curl http://localhost:10005/api/simmvaluationdemo/whoami - -The response should be something like - -.. sourcecode:: none - - { - "self" : { - "id" : "8Kqd4oWdx4KQGHGQW3FwXHQpjiv7cHaSsaAWMwRrK25bBJj792Z4rag7EtA", - "text" : "C=GB,L=London,O=Bank A" - }, - "counterparties" : [ - { - "id" : "8Kqd4oWdx4KQGHGL1DzULumUmZyyokeSGJDY1n5M6neUfAj2sjbf65wYwQM", - "text" : "C=JP,L=Tokyo,O=Bank C" - }, - { - "id" : "8Kqd4oWdx4KQGHGTBm34eCM2nrpcWKeM1ZG3DUYat3JTFUQTwB3Lv2WbPM8", - "text" : "C=US,L=New York,O=Bank B" - } - ] - } - -Now, if we ask the same question of Bank C we will see that it's id matches the id for Bank C as a counter -party to Bank A and Bank A will appear as a counter party - -.. sourcecode:: bash - - curl -i -H "Content-Type: application/json" -X GET http://localhost:10011/api/simmvaluationdemo/whoami - -**Creating a trade with Bank C** - -In what follows, we assume we are Bank A (which is listening on port 10005) - -Notice the id field in the output of the ``whoami`` command. We are going to use the id assocatied -with Bank C, one of our counter parties, to create a trade. The general command for this is: - -.. sourcecode:: bash - - curl -i -H "Content-Type: application/json" -X PUT -d <<>> http://localhost:10005/api/simmvaluationdemo/<<>>/trades - -where the representation of the trade is - -.. sourcecode:: none - - { - "id" : "trade1", - "description" : "desc", - "tradeDate" : [ 2016, 6, 6 ], - "convention" : "EUR_FIXED_1Y_EURIBOR_3M", - "startDate" : [ 2016, 6, 6 ], - "endDate" : [ 2020, 1, 2 ], - "buySell" : "BUY", - "notional" : "1000", - "fixedRate" : "0.1" - } - -Continuing our example, the specific command we would run is - -.. sourcecode:: bash - - curl -i -H "Content-Type: application/json" \ - -X PUT \ - -d '{"id":"trade1","description" : "desc","tradeDate" : [ 2016, 6, 6 ], "convention" : "EUR_FIXED_1Y_EURIBOR_3M", "startDate" : [ 2016, 6, 6 ], "endDate" : [ 2020, 1, 2 ], "buySell" : "BUY", "notional" : "1000", "fixedRate" : "0.1"}' \ - http://localhost:10005/api/simmvaluationdemo/8Kqd4oWdx4KQGHGL1DzULumUmZyyokeSGJDY1n5M6neUfAj2sjbf65wYwQM/trades - -With an expected response of - -.. sourcecode:: none - - HTTP/1.1 202 Accepted - Date: Thu, 28 Sep 2017 17:19:39 GMT - Content-Type: text/plain - Access-Control-Allow-Origin: * - Content-Length: 2 - Server: Jetty(9.3.9.v20160517) - -**Verifying trade completion** - -With the trade completed and stored by both parties, the complete list of trades with our couterparty can be seen with the following command - -.. sourcecode:: bash - - curl -X GET http://localhost:10005/api/simmvaluationdemo/<<>>/trades - -The command for our example, using Bank A, would thus be - -.. sourcecode:: bash - - curl -X GET http://localhost:10005/api/simmvaluationdemo/8Kqd4oWdx4KQGHGL1DzULumUmZyyokeSGJDY1n5M6neUfAj2sjbf65wYwQM/trades - -whilst a specific trade can be seen with - -.. sourcecode:: bash - - curl -X GET http://localhost:10005/api/simmvaluationdemo/<<>>/trades/<<>> - -If we look at the trade we created above, we assigned it the id "trade1", the complete command in this case would be - -.. sourcecode:: bash - - curl -X GET http://localhost:10005/api/simmvaluationdemo/8Kqd4oWdx4KQGHGL1DzULumUmZyyokeSGJDY1n5M6neUfAj2sjbf65wYwQM/trades/trade1 - -**Generating a valuation** - -.. sourcecode:: bash - - curl -i -H "Content-Type: application/json" \ - -X POST \ - -d <<>> - http://localhost:10005/api/simmvaluationdemo/<<>>/portfolio/valuations/calculate - -Again, the specific command to continue our example would be - -.. sourcecode:: bash - - curl -i -H "Content-Type: application/json" \ - -X POST \ - -d '{"valuationDate":[2016,6,6]}' \ - http://localhost:10005/api/simmvaluationdemo/8Kqd4oWdx4KQGHGL1DzLumUmZyyokeSGJDY1n5M6neUfAj2sjbf65wYwQM/portfolio/valuations/calculate - -**Viewing a valuation** - -In the same way we can ask for specific instances of trades with a counter party, we can request details of valuations - -.. sourcecode:: bash - - curl -i -H "Content-Type: application/json" -X GET http://localhost:10005/api/simmvaluationdemo/<<>>/portfolio/valuations - -The specific command for out Bank A example is - -.. sourcecode:: bash - - curl -i -H "Content-Type: application/json" \ - -X GET http://localhost:10005/api/simmvaluationdemo/8Kqd4oWdx4KQGHGL1DzULumUmZyyokeSGJDY1n5M6neUfAj2sjbf65YwQM/portfolio/valuations - - - - - diff --git a/finance/src/main/kotlin/net/corda/finance/flows/TwoPartyTradeFlow.kt b/finance/src/main/kotlin/net/corda/finance/flows/TwoPartyTradeFlow.kt index 2d5093a583..9d18c2d3a5 100644 --- a/finance/src/main/kotlin/net/corda/finance/flows/TwoPartyTradeFlow.kt +++ b/finance/src/main/kotlin/net/corda/finance/flows/TwoPartyTradeFlow.kt @@ -87,9 +87,11 @@ object TwoPartyTradeFlow { // Verify and sign the transaction. progressTracker.currentStep = VERIFYING_AND_SIGNING + // DOCSTART 07 // Sync identities to ensure we know all of the identities involved in the transaction we're about to // be asked to sign subFlow(IdentitySyncFlow.Receive(otherSideSession)) + // DOCEND 07 // DOCSTART 5 val signTransactionFlow = object : SignTransactionFlow(otherSideSession, VERIFYING_AND_SIGNING.childProgressTracker()) { diff --git a/samples/attachment-demo/README.md b/samples/attachment-demo/README.md index effd75fc67..4aa02651f2 100644 --- a/samples/attachment-demo/README.md +++ b/samples/attachment-demo/README.md @@ -1,14 +1,23 @@ -# Attachment Demo +# Attachment Demo -This code demonstrates sending a transaction with an attachment from one to node to another, and the receiving node accessing the attachment. +This demo brings up three nodes, and sends a transaction containing an attachment from one to the other. -Please see the either the [online documentation](https://docs.corda.net/running-the-demos.html#attachment-demo) for more info on the attachment demo, or the [included offline version](../../docs/build/html/running-the-demos.html#attachment-demo). +To run from the command line in Unix: -From the root directory of the repository, run the following commands (on mac / unix, replace `gradle` with `./gradlew`) +1. Run ``./gradlew samples:attachment-demo:deployNodes`` to create a set of configs and installs under + ``samples/attachment-demo/build/nodes`` +2. Run ``./samples/attachment-demo/build/nodes/runnodes`` to open up three new terminal tabs/windows with the three + nodes and webserver for BankB +3. Run ``./gradlew samples:attachment-demo:runRecipient``, which will block waiting for a trade to start +4. Run ``./gradlew samples:attachment-demo:runSender`` in another terminal window to send the attachment. Now look at + the other windows to see the output of the demo - gradle samples:attachment-demo:deployNodes +To run from the command line in Windows: - ./samples/attachment-demo/build/nodes/runnodes - - gradle samples:attachment-demo:runRecipient # (in one window) - gradle samples:attachment-demo:runSender # (in another window) \ No newline at end of file +1. Run ``gradlew samples:attachment-demo:deployNodes`` to create a set of configs and installs under + ``samples\attachment-demo\build\nodes`` +2. Run ``samples\attachment-demo\build\nodes\runnodes`` to open up three new terminal tabs/windows with the three nodes + and webserver for BankB +3. Run ``gradlew samples:attachment-demo:runRecipient``, which will block waiting for a trade to start +4. Run ``gradlew samples:attachment-demo:runSender`` in another terminal window to send the attachment. Now look at the + other windows to see the output of the demo \ No newline at end of file diff --git a/samples/bank-of-corda-demo/README.md b/samples/bank-of-corda-demo/README.md index 54d30376fe..6effb22a99 100644 --- a/samples/bank-of-corda-demo/README.md +++ b/samples/bank-of-corda-demo/README.md @@ -1,58 +1,53 @@ -# Bank of Corda demo -Please see docs/build/html/running-the-demos.html +Bank Of Corda demo +------------------ -This program simulates the role of an asset issuing authority (eg. central bank for cash) by accepting requests -from third parties to issue some quantity of an asset and transfer that ownership to the requester. -The issuing authority accepts requests via the [IssuerFlow] flow, self-issues the asset and transfers -ownership to the issue requester. Notarisation and signing form part of the flow. +This demo brings up three nodes: a notary, a node acting as the Bank of Corda that accepts requests for issuance of +some asset and a node acting as Big Corporation which requests issuance of an asset (cash in this example). -The requesting party can be a CorDapp (running locally or remotely to the Bank of Corda node), a remote RPC client or -a Web Client. +Upon receipt of a request the Bank of Corda node self-issues the asset and then transfers ownership to the requester +after successful notarisation and recording of the issue transaction on the ledger. -## Prerequisites +.. note:: The Bank of Corda is somewhat like a "Bitcoin faucet" that dispenses free bitcoins to developers for + testing and experimentation purposes. -You will need to have [JDK 8](http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html) -installed and available on your path. +To run from the command line in Unix: -## Getting Started +1. Run ``./gradlew samples:bank-of-corda-demo:deployNodes`` to create a set of configs and installs under + ``samples/bank-of-corda-demo/build/nodes`` +2. Run ``./samples/bank-of-corda-demo/build/nodes/runnodes`` to open up three new terminal tabs/windows with the three + nodes +3. Run ``./gradlew samples:bank-of-corda-demo:runRPCCashIssue`` to trigger a cash issuance request +4. Run ``./gradlew samples:bank-of-corda-demo:runWebCashIssue`` to trigger another cash issuance request. + Now look at your terminal tab/window to see the output of the demo -1. Launch the Bank of Corda node (and associated Notary) by running: -[BankOfCordaDriver] --role ISSUER -(to validate your Node is running you can try navigating to this sample link: http://localhost:10005/api/bank/date) +To run from the command line in Windows: -Each of the following commands will launch a separate Node called Big Corporation which will become the owner -of some Cash following an issue request: +1. Run ``gradlew samples:bank-of-corda-demo:deployNodes`` to create a set of configs and installs under + ``samples\bank-of-corda-demo\build\nodes`` +2. Run ``samples\bank-of-corda-demo\build\nodes\runnodes`` to open up three new terminal tabs/windows with the three + nodes +3. Run ``gradlew samples:bank-of-corda-demo:runRPCCashIssue`` to trigger a cash issuance request +4. Run ``gradlew samples:bank-of-corda-demo:runWebCashIssue`` to trigger another cash issuance request. + Now look at the your terminal tab/window to see the output of the demo -2. Run the Bank of Corda Client driver (to simulate a web issue requester) by running: -[BankOfCordaDriver] --role ISSUE_CASH_WEB -This demonstrates a remote application acting on behalf of the Big Corporation and communicating directly with the -Bank of Corda node via HTTP to request issuance of some cash. +To verify that the Bank of Corda node is alive and running, navigate to the following URL: +http://localhost:10007/api/bank/date -3. Run the Bank of Corda Client driver (to simulate an RPC issue requester) by running: -[BankOfCordaDriver] --role ISSUE_CASH_RPC -Similar to 3 above, but using RPC as the remote communications mechanism. +In the window you run the command you should see (in case of Web, RPC is similar): -## Developer notes +- Requesting Cash via Web ... +- Successfully processed Cash Issue request -Testing of the Bank of Corda application is demonstrated at two levels: -1. Unit testing the flow uses the [LedgerDSL] and [MockServices]. Please see [IssuerFlowTest] - The IssuerFlow is one of several reusable flows defined in the finance package. -2. Integration testing via RPC and HTTP uses the [Driver] DSL to launch standalone node instances +If you want to see flow activity enter in node's shell ``flow watch``. It will display all state machines running +currently on the node. -Security -The RPC API requires a client to pass in user credentials: - client.start("bankUser","test") -which are validated on the Bank of Corda node against those configured at node startup: - User("bankUser", "test", permissions = setOf(startFlowPermission())) - startNode(BOC.name, rpcUsers = listOf(user)) +Launch the Explorer application to visualize the issuance and transfer of cash for each node: -Notary -We are using a [SimpleNotaryService] in this example, but could easily switch to a [ValidatingNotaryService] + ``./gradlew tools:explorer:run`` (on Unix) or ``gradlew tools:explorer:run`` (on Windows) -## Integration with other Demos and Tools +Using the following login details: -The Bank of Corda issuer node concept has been integrated into the Explorer tool (simulation nodes) and Trader Demo. +- For the Bank of Corda node: localhost / port 10006 / username bankUser / password test +- For the Big Corporation node: localhost / port 10009 / username bigCorpUser / password test -## Further Reading - -Tutorials and developer docs for Cordapps and Corda are [here](https://docs.corda.net/). \ No newline at end of file +See https://docs.corda.net/node-explorer.html for further details on usage. \ No newline at end of file diff --git a/samples/irs-demo/README.md b/samples/irs-demo/README.md index b56f4b5321..38a24fc15e 100644 --- a/samples/irs-demo/README.md +++ b/samples/irs-demo/README.md @@ -1,7 +1,33 @@ # IRS Demo -This code demonstrates an Interest Rate Swap agreement between two banks, confirming the transaction utilising the services -of a notary and then retrieving and applying interest rates from an oracle service. +This demo brings up three nodes: Bank A, Bank B and a node that simultaneously runs a notary, a network map and an +interest rates oracle. The two banks agree on an interest rate swap, and then do regular fixings of the deal as the +time on a simulated clock passes. +To run from the command line in Unix: -Please see the either the [online documentation](https://docs.corda.net/running-the-demos.html#irs-demo) for more info on the attachment demo, or the [included offline version](../../docs/build/html/running-the-demos.html#irs-demo). \ No newline at end of file +1. Run ``./gradlew samples:irs-demo:deployNodes`` to install configs and a command line tool under + ``samples/irs-demo/build`` +2. Run ``./gradlew samples:irs-demo:installDist`` +3. Move to the ``samples/irs-demo/build`` directory +4. Run ``./nodes/runnodes`` to open up three new terminals with the three nodes (you may have to install xterm) + +To run from the command line in Windows: + +1. Run ``gradlew.bat samples:irs-demo:deployNodes`` to install configs and a command line tool under + ``samples\irs-demo\build`` +2. Run ``gradlew.bat samples:irs-demo:installDist`` +3. Run ``cd samples\irs-demo\build`` to change current working directory +4. Run ``nodes\runnodes`` to open up several 6 terminals, 2 for each node. First terminal is a web-server associated + with every node and second one is Corda interactive shell for the node + +This demo also has a web app. To use this, run nodes and then navigate to http://localhost:10007/web/irsdemo and +http://localhost:10010/web/irsdemo to see each node's view of the ledger. + +To use the web app, click the "Create Deal" button, fill in the form, then click the "Submit" button. You can then use +the time controls at the top left of the home page to run the fixings. Click any individual trade in the blotter to +view it. + +*Note:* The IRS web UI currently has a bug when changing the clock time where it may show no numbers or apply fixings +inconsistently. The issues will be addressed in a future milestone release. Meanwhile, you can take a look at a simpler +oracle example here: https://github.com/corda/oracle-example. \ No newline at end of file diff --git a/samples/notary-demo/README.md b/samples/notary-demo/README.md index 3abafa3d81..e0bba709ef 100644 --- a/samples/notary-demo/README.md +++ b/samples/notary-demo/README.md @@ -1,5 +1,61 @@ -# Distributed Notary Demo +Notary demo +----------- -This program is a simple demonstration of a node getting multiple transactions notarised by a distributed (Raft or BFT SMaRt) notary. +This demo shows a party getting transactions notarised by either a single-node or a distributed notary service. +All versions of the demo start two counterparty nodes. -Please see docs/build/html/running-the-demos.html to learn how to use this demo. +One of the counterparties will generate transactions that transfer a self-issued asset to the other party and submit +them for notarisation. + +The Raft (https://raft.github.io/) version of the demo will start three distributed notary nodes. +The BFT SMaRt (https://bft-smart.github.io/library/) version of the demo will start four distributed notary nodes. + +The output will display a list of notarised transaction IDs and corresponding signer public keys. In the Raft distributed notary, +every node in the cluster can service client requests, and one signature is sufficient to satisfy the notary composite key requirement. +In the BFT SMaRt distributed notary, three signatures are required. +You will notice that successive transactions get signed by different members of the cluster (usually allocated in a random order). + +To run the Raft version of the demo from the command line in Unix: + +1. Run ``./gradlew samples:notary-demo:deployNodes``, which will create all three types of notaries' node directories + with configs under ``samples/notary-demo/build/nodes/nodesRaft`` (``nodesBFT`` and ``nodesSingle`` for BFT and + Single notaries). +2. Run ``./samples/notary-demo/build/nodes/nodesRaft/runnodes``, which will start the nodes in separate terminal windows/tabs. + Wait until a "Node started up and registered in ..." message appears on each of the terminals +3. Run ``./gradlew samples:notary-demo:notarise`` to make a call to the "Party" node to initiate notarisation requests + In a few seconds you will see a message "Notarised 10 transactions" with a list of transaction ids and the signer public keys + +To run from the command line in Windows: + +1. Run ``gradlew samples:notary-demo:deployNodes``, which will create all three types of notaries' node directories + with configs under ``samples/notary-demo/build/nodes/nodesRaft`` (``nodesBFT`` and ``nodesSingle`` for BFT and + Single notaries). +2. Run ``samples\notary-demo\build\nodes\nodesRaft\runnodes``, which will start the nodes in separate terminal windows/tabs. + Wait until a "Node started up and registered in ..." message appears on each of the terminals +3. Run ``gradlew samples:notary-demo:notarise`` to make a call to the "Party" node to initiate notarisation requests + In a few seconds you will see a message "Notarised 10 transactions" with a list of transaction ids and the signer public keys + +To run the BFT SMaRt notary demo, use ``nodesBFT`` instead of ``nodesRaft`` in the path (you will see messages from notary nodes +trying to communicate each other sometime with connection errors, that's normal). For a single notary node, use ``nodesSingle``. + +Distributed notary nodes store consumed states in a replicated commit log, which is backed by a H2 database on each node. +You can ascertain that the commit log is synchronised across the cluster by accessing and comparing each of the nodes' backing stores +by using the H2 web console: + +- Firstly, download H2 web console (http://www.h2database.com/html/download.html) (download the "platform-independent zip"), + and start it using a script in the extracted folder: ``sh h2/bin/h2.sh`` (or ``h2\bin\h2`` for Windows) + +- If you are uncertain as to which version of h2 to install or if you have connectivity issues, refer to ``build.gradle`` + located in the corda directory and locate ``h2_version``. Use a client of the same major version - even if still in beta. + +- The H2 web console should start up in a web browser tab. To connect we first need to obtain a JDBC connection string. + Each node outputs its connection string in the terminal window as it starts up. In a terminal window where a **notary** node is running, + look for the following string: + + ``Database connection url is : jdbc:h2:tcp://10.18.0.150:56736/node`` + + You can use the string on the right to connect to the h2 database: just paste it into the `JDBC URL` field and click *Connect*. + You will be presented with a web application that enumerates all the available tables and provides an interface for you to query them using SQL + +- The committed states are stored in the ``NOTARY_COMMITTED_STATES`` table (for Raft) or ``NODE_BFT_SMART_NOTARY_COMMITTED_STATES`` (for BFT). + Note that in the Raft case the raw data is not human-readable, but we're only interested in the row count for this demo \ No newline at end of file diff --git a/samples/trader-demo/README.md b/samples/trader-demo/README.md index ff4a5c936e..4d8e4693bf 100644 --- a/samples/trader-demo/README.md +++ b/samples/trader-demo/README.md @@ -1,5 +1,28 @@ -# Trader Demo +Trader demo +----------- -This code demonstrates four nodes, a notary, an issuer of cash (Bank of Corda), and two parties trading with each other, exchanging cash for a commercial paper. +This demo brings up four nodes: Bank A, Bank B, Bank Of Corda, and a notary/network map node that they all use. Bank A +will be the buyer, and requests some cash from the Bank of Corda in order to acquire commercial paper from Bank B, the +seller. -Please see the either the [online documentation](https://docs.corda.net/running-the-demos.html#trader-demo) for more info on the attachment demo, or the [included offline version](../../docs/build/html/running-the-demos.html#trader-demo). +To run from the command line in Unix: + +1. Run ``./gradlew samples:trader-demo:deployNodes`` to create a set of configs and installs under + ``samples/trader-demo/build/nodes`` +2. Run ``./samples/trader-demo/build/nodes/runnodes`` to open up four new terminals with the four nodes +3. Run ``./gradlew samples:trader-demo:runBank`` to instruct the bank node to issue cash and commercial paper to the + buyer and seller nodes respectively +4. Run ``./gradlew samples:trader-demo:runSeller`` to trigger the transaction. If you entered ``flow watch``, you can + see flows running on both sides of transaction. Additionally you should see final trade information displayed to + your terminal + +To run from the command line in Windows: + +1. Run ``gradlew samples:trader-demo:deployNodes`` to create a set of configs and installs under + ``samples\trader-demo\build\nodes`` +2. Run ``samples\trader-demo\build\nodes\runnodes`` to open up four new terminals with the four nodes +3. Run ``gradlew samples:trader-demo:runBank`` to instruct the buyer node to request issuance of some cash from the + Bank of Corda node +4. Run ``gradlew samples:trader-demo:runSeller`` to trigger the transaction. If you entered ``flow watch``, you can see + flows running on both sides of transaction. Additionally you should see final trade information displayed to your + terminal \ No newline at end of file From 1100cb1cb73153bf59cd60cb4a23d9dfec1af059 Mon Sep 17 00:00:00 2001 From: Katelyn Baker Date: Wed, 25 Oct 2017 09:39:44 +0100 Subject: [PATCH 41/94] Exclude IntelliJ custom dictionaries from git --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index ad05b9958f..eedec3c1be 100644 --- a/.gitignore +++ b/.gitignore @@ -35,6 +35,7 @@ lib/quasar.jar .idea/dataSources .idea/markdown-navigator .idea/runConfigurations +.idea/dictionaries /gradle-plugins/.idea/ # Include the -parameters compiler option by default in IntelliJ required for serialization. From ba7514644608db983722330f557e44f9bf91f42e Mon Sep 17 00:00:00 2001 From: Andrzej Cichocki Date: Wed, 25 Oct 2017 14:33:36 +0100 Subject: [PATCH 42/94] CORDA-716 Unpollute MockNode with Simulation-specific code (#1945) --- .../net/corda/netmap/NetworkMapVisualiser.kt | 3 +- .../net/corda/netmap/VisualiserViewModel.kt | 1 + .../net/corda/netmap/simulation/Simulation.kt | 51 ++++++++----------- .../kotlin/net/corda/testing/node/MockNode.kt | 17 +------ 4 files changed, 24 insertions(+), 48 deletions(-) diff --git a/samples/network-visualiser/src/main/kotlin/net/corda/netmap/NetworkMapVisualiser.kt b/samples/network-visualiser/src/main/kotlin/net/corda/netmap/NetworkMapVisualiser.kt index ee1ca11da9..7034e4b4e8 100644 --- a/samples/network-visualiser/src/main/kotlin/net/corda/netmap/NetworkMapVisualiser.kt +++ b/samples/network-visualiser/src/main/kotlin/net/corda/netmap/NetworkMapVisualiser.kt @@ -15,7 +15,6 @@ import net.corda.core.serialization.deserialize import net.corda.core.utilities.ProgressTracker import net.corda.netmap.VisualiserViewModel.Style import net.corda.netmap.simulation.IRSSimulation -import net.corda.netmap.simulation.Simulation import net.corda.node.services.network.NetworkMapService import net.corda.node.services.statemachine.SessionConfirm import net.corda.node.services.statemachine.SessionEnd @@ -118,7 +117,7 @@ class NetworkMapVisualiser : Application() { } } // Pulse all parties in a trade when the trade completes - simulation.doneSteps.observeOn(uiThread).subscribe { nodes: Collection -> + simulation.doneSteps.observeOn(uiThread).subscribe { nodes: Collection -> nodes.forEach { viewModel.nodesToWidgets[it]!!.longPulseAnim.play() } } diff --git a/samples/network-visualiser/src/main/kotlin/net/corda/netmap/VisualiserViewModel.kt b/samples/network-visualiser/src/main/kotlin/net/corda/netmap/VisualiserViewModel.kt index f8f9c1f625..eda67a0277 100644 --- a/samples/network-visualiser/src/main/kotlin/net/corda/netmap/VisualiserViewModel.kt +++ b/samples/network-visualiser/src/main/kotlin/net/corda/netmap/VisualiserViewModel.kt @@ -11,6 +11,7 @@ import net.corda.core.identity.CordaX500Name import net.corda.core.utilities.ProgressTracker import net.corda.finance.utils.ScreenCoordinate import net.corda.netmap.simulation.IRSSimulation +import net.corda.netmap.simulation.place import net.corda.testing.chooseIdentity import net.corda.testing.node.MockNetwork import java.util.* diff --git a/samples/network-visualiser/src/main/kotlin/net/corda/netmap/simulation/Simulation.kt b/samples/network-visualiser/src/main/kotlin/net/corda/netmap/simulation/Simulation.kt index 157f93eb58..0533308ec2 100644 --- a/samples/network-visualiser/src/main/kotlin/net/corda/netmap/simulation/Simulation.kt +++ b/samples/network-visualiser/src/main/kotlin/net/corda/netmap/simulation/Simulation.kt @@ -4,16 +4,15 @@ import com.nhaarman.mockito_kotlin.doReturn import com.nhaarman.mockito_kotlin.whenever import net.corda.core.flows.FlowLogic import net.corda.core.identity.CordaX500Name -import net.corda.core.internal.uncheckedCast import net.corda.core.utilities.ProgressTracker import net.corda.finance.utils.CityDatabase -import net.corda.finance.utils.WorldMapLocation import net.corda.irs.api.NodeInterestRates import net.corda.node.internal.StartedNode import net.corda.node.services.statemachine.StateMachineManager import net.corda.testing.DUMMY_NOTARY import net.corda.testing.DUMMY_REGULATOR import net.corda.testing.node.* +import net.corda.testing.node.MockNetwork.MockNode import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties import rx.Observable import rx.subjects.PublishSubject @@ -26,6 +25,8 @@ import java.util.concurrent.CompletableFuture import java.util.concurrent.CompletableFuture.allOf import java.util.concurrent.Future +internal val MockNode.place get() = configuration.myLegalName.locality.let { CityDatabase[it] }!! + /** * Base class for network simulations that are based on the unit test / mock environment. * @@ -49,24 +50,12 @@ abstract class Simulation(val networkSendManuallyPumped: Boolean, val bankLocations = listOf(Pair("London", "GB"), Pair("Frankfurt", "DE"), Pair("Rome", "IT")) - // This puts together a mock network of SimulatedNodes. - open class SimulatedNode(args: MockNodeArgs) : MockNetwork.MockNode(args) { - override val started: StartedNode? get() = uncheckedCast(super.started) - override fun findMyLocation(): WorldMapLocation? { - return configuration.myLegalName.locality.let { CityDatabase[it] } - } - } - - private object SimulatedNodeFactory : MockNetwork.Factory { - override fun create(args: MockNodeArgs) = SimulatedNode(args) - } - - object RatesOracleFactory : MockNetwork.Factory { + object RatesOracleFactory : MockNetwork.Factory { // TODO: Make a more realistic legal name val RATES_SERVICE_NAME = CordaX500Name(organisation = "Rates Service Provider", locality = "Madrid", country = "ES") - override fun create(args: MockNodeArgs): SimulatedNode { - return object : SimulatedNode(args) { + override fun create(args: MockNodeArgs): MockNode { + return object : MockNode(args) { override fun start() = super.start().apply { registerInitiatedFlow(NodeInterestRates.FixQueryHandler::class.java) registerInitiatedFlow(NodeInterestRates.FixSignHandler::class.java) @@ -84,28 +73,28 @@ abstract class Simulation(val networkSendManuallyPumped: Boolean, networkSendManuallyPumped = networkSendManuallyPumped, threadPerNode = runAsync, cordappPackages = listOf("net.corda.irs.contract", "net.corda.finance.contract", "net.corda.irs")) - val notary = mockNet.createNotaryNode(defaultParams.copy(legalName = DUMMY_NOTARY.name), false, SimulatedNodeFactory) + val notary = mockNet.createNotaryNode(DUMMY_NOTARY.name, false) // TODO: Regulatory nodes don't actually exist properly, this is a last minute demo request. // So we just fire a message at a node that doesn't know how to handle it, and it'll ignore it. // But that's fine for visualisation purposes. - val regulators = listOf(mockNet.createUnstartedNode(defaultParams.copy(legalName = DUMMY_REGULATOR.name), SimulatedNodeFactory)) + val regulators = listOf(mockNet.createUnstartedNode(defaultParams.copy(legalName = DUMMY_REGULATOR.name))) val ratesOracle = mockNet.createUnstartedNode(defaultParams.copy(legalName = RatesOracleFactory.RATES_SERVICE_NAME), RatesOracleFactory) // All nodes must be in one of these two lists for the purposes of the visualiser tool. - val serviceProviders: List = listOf(notary.internals, ratesOracle) - val banks: List = bankLocations.mapIndexed { i, (city, country) -> + val serviceProviders: List = listOf(notary.internals, ratesOracle) + val banks: List = bankLocations.mapIndexed { i, (city, country) -> val legalName = CordaX500Name(organisation = "Bank ${'A' + i}", locality = city, country = country) // Use deterministic seeds so the simulation is stable. Needed so that party owning keys are stable. - mockNet.createUnstartedNode(defaultParams.copy(legalName = legalName, entropyRoot = BigInteger.valueOf(i.toLong())), SimulatedNodeFactory) + mockNet.createUnstartedNode(defaultParams.copy(legalName = legalName, entropyRoot = BigInteger.valueOf(i.toLong()))) } val clocks = (serviceProviders + regulators + banks).map { it.platformClock as TestClock } // These are used from the network visualiser tool. - private val _allFlowSteps = PublishSubject.create>() - private val _doneSteps = PublishSubject.create>() + private val _allFlowSteps = PublishSubject.create>() + private val _doneSteps = PublishSubject.create>() @Suppress("unused") - val allFlowSteps: Observable> = _allFlowSteps + val allFlowSteps: Observable> = _allFlowSteps @Suppress("unused") - val doneSteps: Observable> = _doneSteps + val doneSteps: Observable> = _doneSteps private var pumpCursor = 0 @@ -133,7 +122,7 @@ abstract class Simulation(val networkSendManuallyPumped: Boolean, * A place for simulations to stash human meaningful text about what the node is "thinking", which might appear * in the UI somewhere. */ - val extraNodeLabels: MutableMap = Collections.synchronizedMap(HashMap()) + val extraNodeLabels: MutableMap = Collections.synchronizedMap(HashMap()) /** * Iterates the simulation by one step. @@ -164,7 +153,7 @@ abstract class Simulation(val networkSendManuallyPumped: Boolean, return null } - protected fun showProgressFor(nodes: List>) { + protected fun showProgressFor(nodes: List>) { nodes.forEach { node -> node.smm.changes.filter { it is StateMachineManager.Change.Add }.subscribe { linkFlowProgress(node.internals, it.logic) @@ -172,7 +161,7 @@ abstract class Simulation(val networkSendManuallyPumped: Boolean, } } - private fun linkFlowProgress(node: SimulatedNode, flow: FlowLogic<*>) { + private fun linkFlowProgress(node: MockNode, flow: FlowLogic<*>) { val pt = flow.progressTracker ?: return pt.changes.subscribe { change: ProgressTracker.Change -> // Runs on node thread. @@ -181,14 +170,14 @@ abstract class Simulation(val networkSendManuallyPumped: Boolean, } - protected fun showConsensusFor(nodes: List) { + protected fun showConsensusFor(nodes: List) { val node = nodes.first() node.started!!.smm.changes.filter { it is StateMachineManager.Change.Add }.first().subscribe { linkConsensus(nodes, it.logic) } } - private fun linkConsensus(nodes: Collection, flow: FlowLogic<*>) { + private fun linkConsensus(nodes: Collection, flow: FlowLogic<*>) { flow.progressTracker?.changes?.subscribe { _: ProgressTracker.Change -> // Runs on node thread. if (flow.progressTracker!!.currentStep == ProgressTracker.DONE) { diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt index 71a9148900..e0bec83c67 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt @@ -17,12 +17,10 @@ import net.corda.core.messaging.RPCOps import net.corda.core.messaging.SingleMessageRecipient import net.corda.core.node.services.IdentityService import net.corda.core.node.services.KeyManagementService -import net.corda.core.node.services.PartyInfo import net.corda.core.serialization.SerializationWhitelist import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.loggerFor -import net.corda.finance.utils.WorldMapLocation import net.corda.node.internal.AbstractNode import net.corda.node.internal.StartedNode import net.corda.node.internal.cordapp.CordappLoader @@ -54,7 +52,6 @@ import java.math.BigInteger import java.nio.file.Path import java.security.KeyPair import java.security.PublicKey -import java.util.* import java.util.concurrent.TimeUnit import java.util.concurrent.atomic.AtomicInteger @@ -241,9 +238,6 @@ class MockNetwork(defaultParameters: MockNetworkParameters = MockNetworkParamete // It's OK to not have a network map service in the mock network. override fun noNetworkMapConfigured() = doneFuture(Unit) - // There is no need to slow down the unit tests by initialising CityDatabase - open fun findMyLocation(): WorldMapLocation? = null // It's left only for NetworkVisualiserSimulation - override fun makeTransactionVerifierService() = InMemoryTransactionVerifierService(1) override fun myAddresses() = emptyList() @@ -253,13 +247,6 @@ class MockNetwork(defaultParameters: MockNetworkParameters = MockNetworkParamete val testSerializationWhitelists by lazy { super.serializationWhitelists.toMutableList() } override val serializationWhitelists: List get() = testSerializationWhitelists - - // This does not indirect through the NodeInfo object so it can be called before the node is started. - // It is used from the network visualiser tool. - @Suppress("unused") - val place: WorldMapLocation - get() = findMyLocation()!! - private var dbCloser: (() -> Any?)? = null override fun initialiseDatabasePersistence(schemaService: SchemaService, insideTransaction: () -> T) = super.initialiseDatabasePersistence(schemaService) { dbCloser = database::close @@ -430,12 +417,12 @@ fun network(nodesCount: Int, action: MockNetwork.(nodes: List.setMessagingServiceSpy(messagingServiceSpy: MessagingServiceSpy) { internals.setMessagingServiceSpy(messagingServiceSpy) From 44a7d872d85e78efa0f06008bdedce27a569f1c5 Mon Sep 17 00:00:00 2001 From: Maksymilian Pawlak <120831+m4ksio@users.noreply.github.com> Date: Wed, 25 Oct 2017 16:40:21 +0100 Subject: [PATCH 43/94] =?UTF-8?q?IRS=20Demo=20-=20split=20IRS=20Demo=20int?= =?UTF-8?q?o=20two=20separate=20applications=20to=20showcase=20=E2=80=A6?= =?UTF-8?q?=20(#1638)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * IRS Demo - split IRS Demo into two separate applications to showcase the separation and usage of RPC client --- .gitignore | 1 + .idea/compiler.xml | 8 +- .../source/running-the-demos.rst | 0 samples/irs-demo/README.md | 27 ++-- samples/irs-demo/build.gradle | 107 +++++---------- samples/irs-demo/cordapp/build.gradle | 115 ++++++++++++++++ .../net/corda/irs/api/NodeInterestRates.kt | 0 .../main/kotlin/net/corda/irs/contract/IRS.kt | 0 .../net/corda/irs/contract/IRSExport.kt | 0 .../kotlin/net/corda/irs/contract/IRSUtils.kt | 0 .../net/corda/irs/flows/AutoOfferFlow.kt | 0 .../kotlin/net/corda/irs/flows/FixingFlow.kt | 0 .../net/corda/irs/flows/RatesFixFlow.kt | 0 .../net/corda/irs/utilities/OracleUtils.kt | 0 .../corda/irs/simulation/example.rates.txt | 0 .../src/test/kotlin/net/corda/irs/Main.kt | 0 .../corda/irs/api/NodeInterestRatesTest.kt | 0 .../kotlin/net/corda/irs/contract/IRSTests.kt | 0 .../corda/irs/flows/UpdateBusinessDayFlow.kt | 0 .../kotlin/net/corda/irs/IRSDemoTest.kt | 60 +++++---- .../net/corda/test/spring/SpringDriver.kt | 124 ++++++++++++++++++ .../kotlin/net/corda/irs/plugin/IRSPlugin.kt | 16 --- .../net/corda/irs/utilities/HttpUtils.kt | 42 ------ ...webserver.services.WebServerPluginRegistry | 2 - .../net/corda/irs/plugin/IRSDemoPlugin.kt | 9 -- ...webserver.services.WebServerPluginRegistry | 2 - samples/irs-demo/web/build.gradle | 55 ++++++++ .../corda/irs/web/IrsDemoWebApplication.kt | 52 ++++++++ .../corda/irs/web/api/InterestSwapRestAPI.kt} | 54 ++++---- .../resources/application-BankA.properties | 2 + .../resources/application-BankB.properties | 2 + .../application-NotaryService.properties | 2 + .../src/main/resources/application.properties | 2 + .../src/main/resources/static}/.bowerrc | 0 .../src/main/resources/static}/bower.json | 0 .../src/main/resources/static}/css/main.css | 0 .../src/main/resources/static}/index.html | 0 .../src/main/resources/static}/js/Deal.js | 0 .../src/main/resources/static}/js/app.js | 0 .../angular-fcsa-number/.bower.json | 0 .../angular-fcsa-number/CHANGLOG.md | 0 .../angular-fcsa-number/LICENSE | 0 .../angular-fcsa-number/README.md | 0 .../angular-fcsa-number/bower.json | 0 .../angular-fcsa-number/e2e/fcsaNumber.e2e.js | 0 .../angular-fcsa-number/e2e/protractor.config | 0 .../angular-fcsa-number/e2e/web/app.js | 0 .../e2e/web/public/angular.js | 0 .../e2e/web/public/fcsaNumber.js | 0 .../e2e/web/public/index.html | 0 .../angular-fcsa-number/src/fcsaNumber.js | 0 .../angular-fcsa-number/src/fcsaNumber.min.js | 0 .../angular-route/.bower.json | 0 .../bower_components/angular-route/LICENSE.md | 0 .../bower_components/angular-route/README.md | 0 .../angular-route/angular-route.js | 0 .../angular-route/angular-route.min.js | 0 .../angular-route/angular-route.min.js.map | 0 .../bower_components/angular-route/bower.json | 0 .../bower_components/angular-route/index.js | 0 .../angular-route/package.json | 0 .../js/bower_components/angular/.bower.json | 0 .../js/bower_components/angular/LICENSE.md | 0 .../js/bower_components/angular/README.md | 0 .../bower_components/angular/angular-csp.css | 0 .../js/bower_components/angular/angular.js | 0 .../bower_components/angular/angular.min.js | 0 .../angular/angular.min.js.gzip | Bin .../angular/angular.min.js.map | 0 .../js/bower_components/angular/bower.json | 0 .../js/bower_components/angular/index.js | 0 .../js/bower_components/angular/package.json | 0 .../jquery.maskedinput/.bower.json | 0 .../jquery.maskedinput/CONTRIBUTING.md | 0 .../jquery.maskedinput/LICENSE | 0 .../jquery.maskedinput/README.md | 0 .../jquery.maskedinput/bower.json | 0 .../jquery.maskedinput/demo/datepicker.html | 0 .../jquery.maskedinput/demo/index.html | 0 .../dist/jquery.maskedinput.js | 0 .../dist/jquery.maskedinput.min.js | 0 .../jquery.maskedinput/gruntfile.js | 0 .../jquery.maskedinput.nuspec | 0 .../jquery.maskedinput/package.json | 0 .../src/jquery.maskedinput.js | 0 .../js/bower_components/jquery/.bower.json | 0 .../js/bower_components/jquery/AUTHORS.txt | 0 .../js/bower_components/jquery/LICENSE.txt | 0 .../js/bower_components/jquery/README.md | 0 .../js/bower_components/jquery/bower.json | 0 .../js/bower_components/jquery/dist/core.js | 0 .../js/bower_components/jquery/dist/jquery.js | 0 .../jquery/dist/jquery.min.js | 0 .../jquery/dist/jquery.min.map | 0 .../jquery/dist/jquery.slim.js | 0 .../jquery/dist/jquery.slim.min.js | 0 .../jquery/dist/jquery.slim.min.map | 0 .../jquery/external/sizzle/LICENSE.txt | 0 .../jquery/external/sizzle/dist/sizzle.js | 0 .../jquery/external/sizzle/dist/sizzle.min.js | 0 .../external/sizzle/dist/sizzle.min.map | 0 .../jquery/src/.eslintrc.json | 0 .../js/bower_components/jquery/src/ajax.js | 0 .../bower_components/jquery/src/ajax/jsonp.js | 0 .../bower_components/jquery/src/ajax/load.js | 0 .../jquery/src/ajax/parseXML.js | 0 .../jquery/src/ajax/script.js | 0 .../jquery/src/ajax/var/location.js | 0 .../jquery/src/ajax/var/nonce.js | 0 .../jquery/src/ajax/var/rquery.js | 0 .../bower_components/jquery/src/ajax/xhr.js | 0 .../bower_components/jquery/src/attributes.js | 0 .../jquery/src/attributes/attr.js | 0 .../jquery/src/attributes/classes.js | 0 .../jquery/src/attributes/prop.js | 0 .../jquery/src/attributes/support.js | 0 .../jquery/src/attributes/val.js | 0 .../bower_components/jquery/src/callbacks.js | 0 .../js/bower_components/jquery/src/core.js | 0 .../jquery/src/core/DOMEval.js | 0 .../jquery/src/core/access.js | 0 .../bower_components/jquery/src/core/init.js | 0 .../jquery/src/core/parseHTML.js | 0 .../jquery/src/core/ready-no-deferred.js | 0 .../bower_components/jquery/src/core/ready.js | 0 .../jquery/src/core/readyException.js | 0 .../jquery/src/core/stripAndCollapse.js | 0 .../jquery/src/core/support.js | 0 .../jquery/src/core/var/rsingleTag.js | 0 .../js/bower_components/jquery/src/css.js | 0 .../jquery/src/css/addGetHookIf.js | 0 .../jquery/src/css/adjustCSS.js | 0 .../bower_components/jquery/src/css/curCSS.js | 0 .../jquery/src/css/hiddenVisibleSelectors.js | 0 .../jquery/src/css/showHide.js | 0 .../jquery/src/css/support.js | 0 .../jquery/src/css/var/cssExpand.js | 0 .../jquery/src/css/var/getStyles.js | 0 .../jquery/src/css/var/isHiddenWithinTree.js | 0 .../jquery/src/css/var/rmargin.js | 0 .../jquery/src/css/var/rnumnonpx.js | 0 .../jquery/src/css/var/swap.js | 0 .../js/bower_components/jquery/src/data.js | 0 .../bower_components/jquery/src/data/Data.js | 0 .../jquery/src/data/var/acceptData.js | 0 .../jquery/src/data/var/dataPriv.js | 0 .../jquery/src/data/var/dataUser.js | 0 .../bower_components/jquery/src/deferred.js | 0 .../jquery/src/deferred/exceptionHook.js | 0 .../bower_components/jquery/src/deprecated.js | 0 .../bower_components/jquery/src/dimensions.js | 0 .../js/bower_components/jquery/src/effects.js | 0 .../jquery/src/effects/Tween.js | 0 .../jquery/src/effects/animatedSelector.js | 0 .../js/bower_components/jquery/src/event.js | 0 .../bower_components/jquery/src/event/ajax.js | 0 .../jquery/src/event/alias.js | 0 .../jquery/src/event/focusin.js | 0 .../jquery/src/event/support.js | 0 .../jquery/src/event/trigger.js | 0 .../jquery/src/exports/amd.js | 0 .../jquery/src/exports/global.js | 0 .../js/bower_components/jquery/src/jquery.js | 0 .../jquery/src/manipulation.js | 0 .../jquery/src/manipulation/_evalUrl.js | 0 .../jquery/src/manipulation/buildFragment.js | 0 .../jquery/src/manipulation/getAll.js | 0 .../jquery/src/manipulation/setGlobalEval.js | 0 .../jquery/src/manipulation/support.js | 0 .../src/manipulation/var/rcheckableType.js | 0 .../src/manipulation/var/rscriptType.js | 0 .../jquery/src/manipulation/var/rtagName.js | 0 .../jquery/src/manipulation/wrapMap.js | 0 .../js/bower_components/jquery/src/offset.js | 0 .../js/bower_components/jquery/src/queue.js | 0 .../jquery/src/queue/delay.js | 0 .../jquery/src/selector-native.js | 0 .../jquery/src/selector-sizzle.js | 0 .../bower_components/jquery/src/selector.js | 0 .../bower_components/jquery/src/serialize.js | 0 .../bower_components/jquery/src/traversing.js | 0 .../jquery/src/traversing/findFilter.js | 0 .../jquery/src/traversing/var/dir.js | 0 .../src/traversing/var/rneedsContext.js | 0 .../jquery/src/traversing/var/siblings.js | 0 .../jquery/src/var/ObjectFunctionString.js | 0 .../js/bower_components/jquery/src/var/arr.js | 0 .../jquery/src/var/class2type.js | 0 .../bower_components/jquery/src/var/concat.js | 0 .../jquery/src/var/document.js | 0 .../jquery/src/var/documentElement.js | 0 .../jquery/src/var/fnToString.js | 0 .../jquery/src/var/getProto.js | 0 .../bower_components/jquery/src/var/hasOwn.js | 0 .../jquery/src/var/indexOf.js | 0 .../bower_components/jquery/src/var/pnum.js | 0 .../bower_components/jquery/src/var/push.js | 0 .../jquery/src/var/rcssNum.js | 0 .../jquery/src/var/rnothtmlwhite.js | 0 .../bower_components/jquery/src/var/slice.js | 0 .../jquery/src/var/support.js | 0 .../jquery/src/var/toString.js | 0 .../js/bower_components/jquery/src/wrap.js | 0 .../js/bower_components/lodash/.bower.json | 0 .../js/bower_components/lodash/.editorconfig | 0 .../js/bower_components/lodash/.gitattributes | 0 .../lodash/.github/CONTRIBUTING.md | 0 .../js/bower_components/lodash/.gitignore | 0 .../js/bower_components/lodash/.jscsrc | 0 .../lodash/.markdown-doctest-setup.js | 0 .../js/bower_components/lodash/.travis.yml | 0 .../js/bower_components/lodash/CHANGELOG | 0 .../js/bower_components/lodash/LICENSE | 0 .../js/bower_components/lodash/README.md | 0 .../lodash/dist/lodash.core.js | 0 .../lodash/dist/lodash.core.min.js | 0 .../bower_components/lodash/dist/lodash.fp.js | 0 .../lodash/dist/lodash.fp.min.js | 0 .../js/bower_components/lodash/dist/lodash.js | 0 .../lodash/dist/lodash.min.js | 0 .../lodash/dist/mapping.fp.js | 2 +- .../js/bower_components/lodash/doc/README.md | 0 .../lodash/fp/_baseConvert.js | 0 .../lodash/fp/_convertBrowser.js | 0 .../js/bower_components/lodash/fp/_mapping.js | 0 .../bower_components/lodash/fp/placeholder.js | 0 .../lodash/lib/common/file.js | 0 .../lodash/lib/common/mapping.js | 0 .../lodash/lib/common/minify.js | 0 .../lodash/lib/common/uglify.options.js | 0 .../lodash/lib/common/util.js | 0 .../lodash/lib/fp/build-dist.js | 0 .../lodash/lib/fp/build-doc.js | 0 .../lodash/lib/fp/build-modules.js | 0 .../lodash/lib/fp/template/doc/wiki.jst | 0 .../lib/fp/template/modules/_falseOptions.jst | 0 .../lodash/lib/fp/template/modules/_util.jst | 0 .../lodash/lib/fp/template/modules/alias.jst | 0 .../lib/fp/template/modules/category.jst | 0 .../lib/fp/template/modules/convert.jst | 0 .../lodash/lib/fp/template/modules/fp.jst | 0 .../lodash/lib/fp/template/modules/module.jst | 0 .../lodash/lib/fp/template/modules/thru.jst | 0 .../lodash/lib/main/build-dist.js | 0 .../lodash/lib/main/build-doc.js | 0 .../lodash/lib/main/build-modules.js | 0 .../lodash/lib/main/build-site.js | 0 .../js/bower_components/lodash/lodash.js | 0 .../js/bower_components/lodash/package.json | 0 .../lodash/perf/asset/perf-ui.js | 0 .../bower_components/lodash/perf/index.html | 0 .../js/bower_components/lodash/perf/perf.js | 0 .../lodash/test/asset/test-ui.js | 0 .../lodash/test/asset/worker.js | 0 .../lodash/test/backbone.html | 0 .../js/bower_components/lodash/test/fp.html | 0 .../bower_components/lodash/test/index.html | 0 .../js/bower_components/lodash/test/remove.js | 0 .../bower_components/lodash/test/saucelabs.js | 0 .../bower_components/lodash/test/test-fp.js | 0 .../js/bower_components/lodash/test/test.js | 0 .../lodash/test/underscore.html | 0 .../lodash/vendor/backbone/LICENSE | 0 .../lodash/vendor/backbone/backbone.js | 0 .../lodash/vendor/backbone/test/collection.js | 0 .../lodash/vendor/backbone/test/events.js | 0 .../lodash/vendor/backbone/test/model.js | 0 .../lodash/vendor/backbone/test/noconflict.js | 0 .../lodash/vendor/backbone/test/router.js | 0 .../vendor/backbone/test/setup/dom-setup.js | 0 .../vendor/backbone/test/setup/environment.js | 0 .../lodash/vendor/backbone/test/sync.js | 0 .../lodash/vendor/backbone/test/view.js | 0 .../lodash/vendor/firebug-lite/license.txt | 0 .../vendor/firebug-lite/skin/xp/blank.gif | Bin .../vendor/firebug-lite/skin/xp/buttonBg.png | Bin .../firebug-lite/skin/xp/buttonBgHover.png | Bin .../vendor/firebug-lite/skin/xp/debugger.css | 0 .../vendor/firebug-lite/skin/xp/detach.png | Bin .../firebug-lite/skin/xp/detachHover.png | Bin .../vendor/firebug-lite/skin/xp/disable.gif | Bin .../vendor/firebug-lite/skin/xp/disable.png | Bin .../firebug-lite/skin/xp/disableHover.gif | Bin .../firebug-lite/skin/xp/disableHover.png | Bin .../vendor/firebug-lite/skin/xp/down.png | Bin .../firebug-lite/skin/xp/downActive.png | Bin .../vendor/firebug-lite/skin/xp/downHover.png | Bin .../firebug-lite/skin/xp/errorIcon-sm.png | Bin .../vendor/firebug-lite/skin/xp/errorIcon.gif | Bin .../vendor/firebug-lite/skin/xp/errorIcon.png | Bin .../firebug-lite/skin/xp/firebug-1.3a2.css | 0 .../firebug-lite/skin/xp/firebug.IE6.css | 0 .../vendor/firebug-lite/skin/xp/firebug.css | 0 .../vendor/firebug-lite/skin/xp/firebug.html | 0 .../vendor/firebug-lite/skin/xp/firebug.png | Bin .../vendor/firebug-lite/skin/xp/group.gif | Bin .../vendor/firebug-lite/skin/xp/html.css | 2 +- .../vendor/firebug-lite/skin/xp/infoIcon.gif | Bin .../vendor/firebug-lite/skin/xp/infoIcon.png | Bin .../firebug-lite/skin/xp/loading_16.gif | Bin .../vendor/firebug-lite/skin/xp/min.png | Bin .../vendor/firebug-lite/skin/xp/minHover.png | Bin .../vendor/firebug-lite/skin/xp/off.png | Bin .../vendor/firebug-lite/skin/xp/offHover.png | Bin .../skin/xp/pixel_transparent.gif | Bin .../firebug-lite/skin/xp/roundCorner.svg | 0 .../vendor/firebug-lite/skin/xp/search.gif | Bin .../vendor/firebug-lite/skin/xp/search.png | Bin .../vendor/firebug-lite/skin/xp/shadow.gif | Bin .../vendor/firebug-lite/skin/xp/shadow2.gif | Bin .../firebug-lite/skin/xp/shadowAlpha.png | Bin .../vendor/firebug-lite/skin/xp/sprite.png | Bin .../firebug-lite/skin/xp/tabHoverLeft.png | Bin .../firebug-lite/skin/xp/tabHoverMid.png | Bin .../firebug-lite/skin/xp/tabHoverRight.png | Bin .../vendor/firebug-lite/skin/xp/tabLeft.png | Bin .../firebug-lite/skin/xp/tabMenuCheckbox.png | Bin .../firebug-lite/skin/xp/tabMenuPin.png | Bin .../firebug-lite/skin/xp/tabMenuRadio.png | Bin .../firebug-lite/skin/xp/tabMenuTarget.png | Bin .../skin/xp/tabMenuTargetHover.png | Bin .../vendor/firebug-lite/skin/xp/tabMid.png | Bin .../vendor/firebug-lite/skin/xp/tabRight.png | Bin .../skin/xp/textEditorBorders.gif | Bin .../skin/xp/textEditorBorders.png | Bin .../skin/xp/textEditorCorners.gif | Bin .../skin/xp/textEditorCorners.png | Bin .../firebug-lite/skin/xp/titlebarMid.png | Bin .../firebug-lite/skin/xp/toolbarMid.png | Bin .../firebug-lite/skin/xp/tree_close.gif | Bin .../vendor/firebug-lite/skin/xp/tree_open.gif | Bin .../firebug-lite/skin/xp/twistyClosed.png | Bin .../firebug-lite/skin/xp/twistyOpen.png | Bin .../lodash/vendor/firebug-lite/skin/xp/up.png | Bin .../vendor/firebug-lite/skin/xp/upActive.png | Bin .../vendor/firebug-lite/skin/xp/upHover.png | Bin .../firebug-lite/skin/xp/warningIcon.gif | Bin .../firebug-lite/skin/xp/warningIcon.png | Bin .../firebug-lite/src/firebug-lite-debug.js | 2 +- .../lodash/vendor/json-js/json2.js | 0 .../lodash/vendor/underscore/LICENSE | 0 .../lodash/vendor/underscore/test/arrays.js | 0 .../lodash/vendor/underscore/test/chaining.js | 0 .../vendor/underscore/test/collections.js | 0 .../vendor/underscore/test/cross-document.js | 0 .../vendor/underscore/test/functions.js | 0 .../lodash/vendor/underscore/test/objects.js | 0 .../lodash/vendor/underscore/test/utility.js | 0 .../vendor/underscore/underscore-min.js | 0 .../lodash/vendor/underscore/underscore.js | 0 .../js/bower_components/requirejs/.bower.json | 0 .../js/bower_components/requirejs/LICENSE | 0 .../js/bower_components/requirejs/README.md | 0 .../js/bower_components/requirejs/bower.json | 0 .../js/bower_components/requirejs/require.js | 0 .../js/bower_components/semantic/.bower.json | 0 .../bower_components/semantic/.csscomb.json | 0 .../js/bower_components/semantic/.csslintrc | 0 .../js/bower_components/semantic/.gitignore | 0 .../js/bower_components/semantic/.jshintrc | 0 .../bower_components/semantic/CONTRIBUTING.md | 0 .../js/bower_components/semantic/LICENSE.md | 0 .../js/bower_components/semantic/README.md | 0 .../semantic/RELEASE-NOTES.md | 0 .../js/bower_components/semantic/bower.json | 0 .../bower_components/semantic/composer.json | 0 .../semantic/dist/components/accordion.css | 0 .../semantic/dist/components/accordion.js | 0 .../dist/components/accordion.min.css | 0 .../semantic/dist/components/accordion.min.js | 0 .../semantic/dist/components/ad.css | 0 .../semantic/dist/components/ad.min.css | 0 .../semantic/dist/components/api.js | 0 .../semantic/dist/components/api.min.js | 0 .../semantic/dist/components/breadcrumb.css | 0 .../dist/components/breadcrumb.min.css | 0 .../semantic/dist/components/button.css | 0 .../semantic/dist/components/button.min.css | 0 .../semantic/dist/components/card.css | 0 .../semantic/dist/components/card.min.css | 0 .../semantic/dist/components/checkbox.css | 0 .../semantic/dist/components/checkbox.js | 0 .../semantic/dist/components/checkbox.min.css | 0 .../semantic/dist/components/checkbox.min.js | 0 .../semantic/dist/components/colorize.js | 0 .../semantic/dist/components/colorize.min.js | 0 .../semantic/dist/components/comment.css | 0 .../semantic/dist/components/comment.min.css | 0 .../semantic/dist/components/container.css | 0 .../dist/components/container.min.css | 0 .../semantic/dist/components/dimmer.css | 0 .../semantic/dist/components/dimmer.js | 0 .../semantic/dist/components/dimmer.min.css | 0 .../semantic/dist/components/dimmer.min.js | 0 .../semantic/dist/components/divider.css | 0 .../semantic/dist/components/divider.min.css | 0 .../semantic/dist/components/dropdown.css | 0 .../semantic/dist/components/dropdown.js | 0 .../semantic/dist/components/dropdown.min.css | 0 .../semantic/dist/components/dropdown.min.js | 0 .../semantic/dist/components/embed.css | 0 .../semantic/dist/components/embed.js | 0 .../semantic/dist/components/embed.min.css | 0 .../semantic/dist/components/embed.min.js | 0 .../semantic/dist/components/feed.css | 0 .../semantic/dist/components/feed.min.css | 0 .../semantic/dist/components/flag.css | 0 .../semantic/dist/components/flag.min.css | 0 .../semantic/dist/components/form.css | 0 .../semantic/dist/components/form.js | 0 .../semantic/dist/components/form.min.css | 0 .../semantic/dist/components/form.min.js | 0 .../semantic/dist/components/grid.css | 0 .../semantic/dist/components/grid.min.css | 0 .../semantic/dist/components/header.css | 0 .../semantic/dist/components/header.min.css | 0 .../semantic/dist/components/icon.css | 0 .../semantic/dist/components/icon.min.css | 0 .../semantic/dist/components/image.css | 0 .../semantic/dist/components/image.min.css | 0 .../semantic/dist/components/input.css | 0 .../semantic/dist/components/input.min.css | 0 .../semantic/dist/components/item.css | 0 .../semantic/dist/components/item.min.css | 0 .../semantic/dist/components/label.css | 0 .../semantic/dist/components/label.min.css | 0 .../semantic/dist/components/list.css | 0 .../semantic/dist/components/list.min.css | 0 .../semantic/dist/components/loader.css | 0 .../semantic/dist/components/loader.min.css | 0 .../semantic/dist/components/menu.css | 0 .../semantic/dist/components/menu.min.css | 0 .../semantic/dist/components/message.css | 0 .../semantic/dist/components/message.min.css | 0 .../semantic/dist/components/modal.css | 0 .../semantic/dist/components/modal.js | 0 .../semantic/dist/components/modal.min.css | 0 .../semantic/dist/components/modal.min.js | 0 .../semantic/dist/components/nag.css | 0 .../semantic/dist/components/nag.js | 0 .../semantic/dist/components/nag.min.css | 0 .../semantic/dist/components/nag.min.js | 0 .../semantic/dist/components/popup.css | 0 .../semantic/dist/components/popup.js | 0 .../semantic/dist/components/popup.min.css | 0 .../semantic/dist/components/popup.min.js | 0 .../semantic/dist/components/progress.css | 0 .../semantic/dist/components/progress.js | 0 .../semantic/dist/components/progress.min.css | 0 .../semantic/dist/components/progress.min.js | 0 .../semantic/dist/components/rail.css | 0 .../semantic/dist/components/rail.min.css | 0 .../semantic/dist/components/rating.css | 0 .../semantic/dist/components/rating.js | 0 .../semantic/dist/components/rating.min.css | 0 .../semantic/dist/components/rating.min.js | 0 .../semantic/dist/components/reset.css | 0 .../semantic/dist/components/reset.min.css | 0 .../semantic/dist/components/reveal.css | 0 .../semantic/dist/components/reveal.min.css | 0 .../semantic/dist/components/search.css | 0 .../semantic/dist/components/search.js | 0 .../semantic/dist/components/search.min.css | 0 .../semantic/dist/components/search.min.js | 0 .../semantic/dist/components/segment.css | 0 .../semantic/dist/components/segment.min.css | 0 .../semantic/dist/components/shape.css | 0 .../semantic/dist/components/shape.js | 0 .../semantic/dist/components/shape.min.css | 0 .../semantic/dist/components/shape.min.js | 0 .../semantic/dist/components/sidebar.css | 0 .../semantic/dist/components/sidebar.js | 0 .../semantic/dist/components/sidebar.min.css | 0 .../semantic/dist/components/sidebar.min.js | 0 .../semantic/dist/components/site.css | 0 .../semantic/dist/components/site.js | 0 .../semantic/dist/components/site.min.css | 0 .../semantic/dist/components/site.min.js | 0 .../semantic/dist/components/state.js | 0 .../semantic/dist/components/state.min.js | 0 .../semantic/dist/components/statistic.css | 0 .../dist/components/statistic.min.css | 0 .../semantic/dist/components/step.css | 0 .../semantic/dist/components/step.min.css | 0 .../semantic/dist/components/sticky.css | 0 .../semantic/dist/components/sticky.js | 0 .../semantic/dist/components/sticky.min.css | 0 .../semantic/dist/components/sticky.min.js | 0 .../semantic/dist/components/tab.css | 0 .../semantic/dist/components/tab.js | 0 .../semantic/dist/components/tab.min.css | 0 .../semantic/dist/components/tab.min.js | 0 .../semantic/dist/components/table.css | 0 .../semantic/dist/components/table.min.css | 0 .../semantic/dist/components/transition.css | 0 .../semantic/dist/components/transition.js | 0 .../dist/components/transition.min.css | 0 .../dist/components/transition.min.js | 0 .../semantic/dist/components/video.css | 0 .../semantic/dist/components/video.js | 0 .../semantic/dist/components/video.min.css | 0 .../semantic/dist/components/video.min.js | 0 .../semantic/dist/components/visibility.js | 0 .../dist/components/visibility.min.js | 0 .../semantic/dist/components/visit.js | 0 .../semantic/dist/components/visit.min.js | 0 .../semantic/dist/semantic.css | 0 .../semantic/dist/semantic.js | 0 .../semantic/dist/semantic.min.css | 0 .../semantic/dist/semantic.min.js | 0 .../dist/themes/basic/assets/fonts/icons.eot | Bin .../dist/themes/basic/assets/fonts/icons.svg | 0 .../dist/themes/basic/assets/fonts/icons.ttf | Bin .../dist/themes/basic/assets/fonts/icons.woff | Bin .../themes/default/assets/fonts/icons.eot | Bin .../themes/default/assets/fonts/icons.otf | Bin .../themes/default/assets/fonts/icons.svg | 0 .../themes/default/assets/fonts/icons.ttf | Bin .../themes/default/assets/fonts/icons.woff | Bin .../themes/default/assets/fonts/icons.woff2 | Bin .../themes/default/assets/images/flags.png | Bin .../github/assets/fonts/octicons-local.ttf | Bin .../themes/github/assets/fonts/octicons.svg | 0 .../themes/github/assets/fonts/octicons.ttf | Bin .../themes/github/assets/fonts/octicons.woff | Bin .../themes/material/assets/fonts/icons.eot | Bin .../themes/material/assets/fonts/icons.svg | 0 .../themes/material/assets/fonts/icons.ttf | Bin .../themes/material/assets/fonts/icons.woff | Bin .../examples/assets/images/avatar/nan.jpg | Bin .../examples/assets/images/avatar/tom.jpg | Bin .../semantic/examples/assets/images/bg.jpg | Bin .../semantic/examples/assets/images/logo.png | Bin .../images/wireframe/centered-paragraph.png | Bin .../assets/images/wireframe/image-square.png | Bin .../assets/images/wireframe/image-text.png | Bin .../assets/images/wireframe/image.png | Bin .../images/wireframe/media-paragraph-alt.png | Bin .../images/wireframe/media-paragraph.png | Bin .../assets/images/wireframe/paragraph.png | Bin .../images/wireframe/short-paragraph.png | Bin .../assets/images/wireframe/square-image.png | Bin .../assets/images/wireframe/text-image.png | Bin .../assets/images/wireframe/white-image.png | Bin .../examples/assets/library/iframe-content.js | 0 .../examples/assets/library/iframe.js | 0 .../examples/assets/library/jquery.min.js | 0 .../semantic/examples/assets/show-examples.js | 0 .../semantic/examples/attached.html | 0 .../semantic/examples/bootstrap.html | 0 .../semantic/examples/components/button.html | 0 .../semantic/examples/components/card.html | 0 .../semantic/examples/components/input.html | 0 .../semantic/examples/components/menu.html | 0 .../semantic/examples/components/site.html | 0 .../semantic/examples/components/table.html | 0 .../semantic/examples/fixed.html | 0 .../semantic/examples/grid.html | 0 .../semantic/examples/homepage.html | 0 .../semantic/examples/login.html | 0 .../semantic/examples/responsive.html | 0 .../semantic/examples/sticky.html | 0 .../semantic/examples/theming.html | 0 .../js/bower_components/semantic/gulpfile.js | 0 .../bower_components/semantic/karma.conf.js | 0 .../js/bower_components/semantic/logo.png | Bin .../js/bower_components/semantic/package.json | 0 .../semantic/semantic.json.example | 0 .../bower_components/semantic/src/README.md | 0 .../_site/collections/breadcrumb.overrides | 0 .../_site/collections/breadcrumb.variables | 0 .../src/_site/collections/form.overrides | 0 .../src/_site/collections/form.variables | 0 .../src/_site/collections/grid.overrides | 0 .../src/_site/collections/grid.variables | 0 .../src/_site/collections/menu.overrides | 0 .../src/_site/collections/menu.variables | 0 .../src/_site/collections/message.overrides | 0 .../src/_site/collections/message.variables | 0 .../src/_site/collections/table.overrides | 0 .../src/_site/collections/table.variables | 0 .../src/_site/elements/button.overrides | 0 .../src/_site/elements/button.variables | 0 .../src/_site/elements/container.overrides | 0 .../src/_site/elements/container.variables | 0 .../src/_site/elements/divider.overrides | 0 .../src/_site/elements/divider.variables | 0 .../src/_site/elements/flag.overrides | 0 .../src/_site/elements/flag.variables | 0 .../src/_site/elements/header.overrides | 0 .../src/_site/elements/header.variables | 0 .../src/_site/elements/icon.overrides | 0 .../src/_site/elements/icon.variables | 0 .../src/_site/elements/image.overrides | 0 .../src/_site/elements/image.variables | 0 .../src/_site/elements/input.overrides | 0 .../src/_site/elements/input.variables | 0 .../src/_site/elements/label.overrides | 0 .../src/_site/elements/label.variables | 0 .../src/_site/elements/list.overrides | 0 .../src/_site/elements/list.variables | 0 .../src/_site/elements/loader.overrides | 0 .../src/_site/elements/loader.variables | 0 .../src/_site/elements/rail.overrides | 0 .../src/_site/elements/rail.variables | 0 .../src/_site/elements/reveal.overrides | 0 .../src/_site/elements/reveal.variables | 0 .../src/_site/elements/segment.overrides | 0 .../src/_site/elements/segment.variables | 0 .../src/_site/elements/step.overrides | 0 .../src/_site/elements/step.variables | 0 .../src/_site/globals/reset.overrides | 0 .../src/_site/globals/reset.variables | 0 .../semantic/src/_site/globals/site.overrides | 0 .../semantic/src/_site/globals/site.variables | 0 .../src/_site/modules/accordion.overrides | 0 .../src/_site/modules/accordion.variables | 0 .../src/_site/modules/chatroom.overrides | 0 .../src/_site/modules/chatroom.variables | 0 .../src/_site/modules/checkbox.overrides | 0 .../src/_site/modules/checkbox.variables | 0 .../src/_site/modules/dimmer.overrides | 0 .../src/_site/modules/dimmer.variables | 0 .../src/_site/modules/dropdown.overrides | 0 .../src/_site/modules/dropdown.variables | 0 .../src/_site/modules/embed.overrides | 0 .../src/_site/modules/embed.variables} | 0 .../src/_site/modules/modal.overrides | 0 .../src/_site/modules/modal.variables | 0 .../semantic/src/_site/modules/nag.overrides | 0 .../semantic/src/_site/modules/nag.variables | 0 .../src/_site/modules/popup.overrides | 0 .../src/_site/modules/popup.variables | 0 .../src/_site/modules/progress.overrides | 0 .../src/_site/modules/progress.variables | 0 .../src/_site/modules/rating.overrides | 0 .../src/_site/modules/rating.variables | 0 .../src/_site/modules/search.overrides | 0 .../src/_site/modules/search.variables | 0 .../src/_site/modules/shape.overrides | 0 .../src/_site/modules/shape.variables | 0 .../src/_site/modules/sidebar.overrides | 0 .../src/_site/modules/sidebar.variables | 0 .../src/_site/modules/sticky.overrides | 0 .../src/_site/modules/sticky.variables | 0 .../semantic/src/_site/modules/tab.overrides | 0 .../semantic/src/_site/modules/tab.variables | 0 .../src/_site/modules/transition.overrides | 0 .../src/_site/modules/transition.variables | 0 .../semantic/src/_site/views/ad.overrides | 0 .../semantic/src/_site/views/ad.variables | 0 .../semantic/src/_site/views/card.overrides | 0 .../semantic/src/_site/views/card.variables | 0 .../src/_site/views/comment.overrides | 0 .../src/_site/views/comment.variables | 0 .../semantic/src/_site/views/feed.overrides | 0 .../semantic/src/_site/views/feed.variables | 0 .../semantic/src/_site/views/item.overrides | 0 .../semantic/src/_site/views/item.variables | 0 .../src/_site/views/statistic.overrides | 0 .../src/_site/views/statistic.variables | 0 .../semantic/src/definitions/behaviors/api.js | 0 .../src/definitions/behaviors/colorize.js | 0 .../src/definitions/behaviors/form.js | 0 .../src/definitions/behaviors/state.js | 0 .../src/definitions/behaviors/visibility.js | 0 .../src/definitions/behaviors/visit.js | 0 .../definitions/collections/breadcrumb.less | 0 .../src/definitions/collections/form.less | 0 .../src/definitions/collections/grid.less | 0 .../src/definitions/collections/menu.less | 0 .../src/definitions/collections/message.less | 0 .../src/definitions/collections/table.less | 0 .../src/definitions/elements/button.less | 0 .../src/definitions/elements/container.less | 0 .../src/definitions/elements/divider.less | 0 .../src/definitions/elements/flag.less | 0 .../src/definitions/elements/header.less | 0 .../src/definitions/elements/icon.less | 0 .../src/definitions/elements/image.less | 0 .../src/definitions/elements/input.less | 0 .../src/definitions/elements/label.less | 0 .../src/definitions/elements/list.less | 0 .../src/definitions/elements/loader.less | 0 .../src/definitions/elements/rail.less | 0 .../src/definitions/elements/reveal.less | 0 .../src/definitions/elements/segment.less | 0 .../src/definitions/elements/step.less | 0 .../src/definitions/globals/reset.less | 0 .../semantic/src/definitions/globals/site.js | 0 .../src/definitions/globals/site.less | 0 .../src/definitions/modules/accordion.js | 0 .../src/definitions/modules/accordion.less | 0 .../src/definitions/modules/checkbox.js | 0 .../src/definitions/modules/checkbox.less | 0 .../src/definitions/modules/dimmer.js | 0 .../src/definitions/modules/dimmer.less | 0 .../src/definitions/modules/dropdown.js | 0 .../src/definitions/modules/dropdown.less | 0 .../semantic/src/definitions/modules/embed.js | 0 .../src/definitions/modules/embed.less | 0 .../semantic/src/definitions/modules/modal.js | 0 .../src/definitions/modules/modal.less | 0 .../semantic/src/definitions/modules/nag.js | 0 .../semantic/src/definitions/modules/nag.less | 0 .../semantic/src/definitions/modules/popup.js | 0 .../src/definitions/modules/popup.less | 0 .../src/definitions/modules/progress.js | 0 .../src/definitions/modules/progress.less | 0 .../src/definitions/modules/rating.js | 0 .../src/definitions/modules/rating.less | 0 .../src/definitions/modules/search.js | 0 .../src/definitions/modules/search.less | 0 .../semantic/src/definitions/modules/shape.js | 0 .../src/definitions/modules/shape.less | 0 .../src/definitions/modules/sidebar.js | 0 .../src/definitions/modules/sidebar.less | 0 .../src/definitions/modules/sticky.js | 0 .../src/definitions/modules/sticky.less | 0 .../semantic/src/definitions/modules/tab.js | 0 .../semantic/src/definitions/modules/tab.less | 0 .../src/definitions/modules/transition.js | 0 .../src/definitions/modules/transition.less | 0 .../semantic/src/definitions/views/ad.less | 0 .../semantic/src/definitions/views/card.less | 0 .../src/definitions/views/comment.less | 0 .../semantic/src/definitions/views/feed.less | 0 .../semantic/src/definitions/views/item.less | 0 .../src/definitions/views/statistic.less | 0 .../semantic/src/semantic.less | 0 .../semantic/src/theme.config.example | 0 .../bower_components/semantic/src/theme.less | 0 .../themes/amazon/elements/button.overrides | 0 .../themes/amazon/elements/button.variables | 0 .../src/themes/amazon/globals/site.variables | 0 .../src/themes/basic/assets/fonts/icons.eot | Bin .../src/themes/basic/assets/fonts/icons.svg | 0 .../src/themes/basic/assets/fonts/icons.ttf | Bin .../src/themes/basic/assets/fonts/icons.woff | Bin .../themes/basic/collections/table.overrides | 0 .../themes/basic/collections/table.variables | 0 .../themes/basic/elements/button.overrides | 0 .../themes/basic/elements/button.variables | 0 .../src/themes/basic/elements/icon.overrides | 0 .../src/themes/basic/elements/icon.variables | 0 .../src/themes/basic/elements/step.overrides | 0 .../src/themes/basic/elements/step.variables | 0 .../src/themes/basic/globals/reset.overrides | 0 .../src/themes/basic/globals/reset.variables | 0 .../themes/basic/modules/progress.overrides | 0 .../themes/basic/modules/progress.variables | 0 .../src/themes/basic/views/card.overrides | 0 .../src/themes/basic/views/card.variables | 0 .../themes/bookish/elements/header.overrides | 0 .../themes/bookish/elements/header.variables | 0 .../bootstrap3}/elements/button.overrides | 0 .../bootstrap3/elements/button.variables | 0 .../themes/chubby/collections/form.overrides | 0 .../themes/chubby/collections/form.variables | 0 .../themes/chubby/collections/menu.overrides | 0 .../themes/chubby/collections/menu.variables | 0 .../themes/chubby/elements/button.overrides | 0 .../themes/chubby/elements/button.variables | 0 .../themes/chubby/elements/header.overrides | 0 .../themes/chubby/elements/header.variables | 0 .../themes/chubby/modules/accordion.overrides | 0 .../themes/chubby/modules/accordion.variables | 0 .../src/themes/chubby/views/comment.overrides | 0 .../src/themes/chubby/views/comment.variables | 0 .../classic/collections/table.overrides | 0 .../classic/collections/table.variables | 0 .../themes/classic/elements/button.overrides | 0 .../themes/classic/elements/button.variables | 0 .../themes/classic/elements/header.overrides | 0 .../themes/classic/elements/header.variables | 0 .../themes/classic/modules/progress.overrides | 0 .../themes/classic/modules/progress.variables | 0 .../src/themes/classic/views/card.overrides | 0 .../src/themes/classic/views/card.variables | 0 .../themes/colored/modules/checkbox.overrides | 0 .../themes/colored/modules/checkbox.variables | 0 .../src/themes/default/assets/fonts/icons.eot | Bin .../src/themes/default/assets/fonts/icons.svg | 0 .../src/themes/default/assets/fonts/icons.ttf | Bin .../themes/default/assets/fonts/icons.woff | Bin .../themes/default/assets/fonts/icons.woff2 | Bin .../themes/default/assets/images/flags.png | Bin .../default/collections/breadcrumb.overrides | 0 .../default/collections/breadcrumb.variables | 0 .../themes/default/collections/form.overrides | 0 .../themes/default/collections/form.variables | 0 .../themes/default/collections/grid.overrides | 0 .../themes/default/collections/grid.variables | 0 .../themes/default/collections/menu.overrides | 0 .../themes/default/collections/menu.variables | 0 .../default/collections/message.overrides | 0 .../default/collections/message.variables | 0 .../default/collections/table.overrides | 0 .../default/collections/table.variables | 0 .../themes/default/elements/button.overrides | 0 .../themes/default/elements/button.variables | 0 .../default/elements/container.overrides | 0 .../default/elements/container.variables | 0 .../themes/default/elements/divider.overrides | 0 .../themes/default/elements/divider.variables | 0 .../themes/default/elements/flag.overrides | 0 .../themes/default/elements/flag.variables | 0 .../themes/default/elements/header.overrides | 0 .../themes/default/elements/header.variables | 0 .../themes/default/elements/icon.overrides | 0 .../themes/default/elements/icon.variables | 0 .../themes/default/elements/image.overrides | 0 .../themes/default/elements/image.variables | 0 .../themes/default/elements/input.overrides | 0 .../themes/default/elements/input.variables | 0 .../themes/default/elements/label.overrides | 0 .../themes/default/elements/label.variables | 0 .../themes/default/elements/list.overrides | 0 .../themes/default/elements/list.variables | 0 .../themes/default/elements/loader.overrides | 0 .../themes/default/elements/loader.variables | 0 .../themes/default/elements/rail.overrides | 0 .../themes/default/elements/rail.variables | 0 .../themes/default/elements/reveal.overrides | 0 .../themes/default/elements/reveal.variables | 0 .../themes/default/elements/segment.overrides | 0 .../themes/default/elements/segment.variables | 0 .../themes/default/elements/step.overrides | 0 .../themes/default/elements/step.variables | 0 .../themes/default/globals/reset.overrides | 0 .../themes/default/globals/reset.variables | 0 .../src/themes/default/globals/site.overrides | 0 .../src/themes/default/globals/site.variables | 0 .../default/modules/accordion.overrides | 0 .../default/modules/accordion.variables | 0 .../themes/default/modules/chatroom.overrides | 0 .../themes/default/modules/chatroom.variables | 0 .../themes/default/modules/checkbox.overrides | 0 .../themes/default/modules/checkbox.variables | 0 .../themes/default/modules/dimmer.overrides | 0 .../themes/default/modules/dimmer.variables | 0 .../themes/default/modules/dropdown.overrides | 0 .../themes/default/modules/dropdown.variables | 0 .../themes/default/modules/embed.overrides | 0 .../themes/default/modules/embed.variables | 0 .../themes/default/modules/modal.overrides | 0 .../themes/default/modules/modal.variables | 0 .../src/themes/default/modules/nag.overrides | 0 .../src/themes/default/modules/nag.variables | 0 .../themes/default/modules/popup.overrides | 0 .../themes/default/modules/popup.variables | 0 .../themes/default/modules/progress.overrides | 0 .../themes/default/modules/progress.variables | 0 .../themes/default/modules/rating.overrides | 0 .../themes/default/modules/rating.variables | 0 .../themes/default/modules/search.overrides | 0 .../themes/default/modules/search.variables | 0 .../themes/default/modules/shape.overrides | 0 .../themes/default/modules/shape.variables | 0 .../themes/default/modules/sidebar.overrides | 0 .../themes/default/modules/sidebar.variables | 0 .../themes/default/modules/sticky.overrides | 0 .../themes/default/modules/sticky.variables | 0 .../src/themes/default/modules/tab.overrides | 0 .../src/themes/default/modules/tab.variables | 0 .../default/modules/transition.overrides | 0 .../default/modules/transition.variables | 0 .../src/themes/default/views/ad.overrides | 0 .../src/themes/default/views/ad.variables | 0 .../src/themes/default/views/card.overrides | 0 .../src/themes/default/views/card.variables | 0 .../themes/default/views/comment.overrides | 0 .../themes/default/views/comment.variables | 0 .../src/themes/default/views/feed.overrides | 0 .../src/themes/default/views/feed.variables | 0 .../src/themes/default/views/item.overrides | 0 .../src/themes/default/views/item.variables | 0 .../themes/default/views/statistic.overrides | 0 .../themes/default/views/statistic.variables | 0 .../src/themes/duo/elements/loader.overrides | 0 .../src/themes/duo/elements/loader.variables | 0 .../fixed-width/collections/grid.overrides | 0 .../fixed-width/collections/grid.variables | 0 .../fixed-width/modules/modal.overrides | 0 .../fixed-width/modules/modal.variables | 0 .../themes/flat/collections/form.overrides | 0 .../themes/flat/collections/form.variables | 0 .../src/themes/flat/globals/site.overrides | 0 .../src/themes/flat/globals/site.variables | 0 .../github/assets/fonts/octicons-local.ttf | Bin .../themes/github/assets/fonts/octicons.svg | 0 .../themes/github/assets/fonts/octicons.ttf | Bin .../themes/github/assets/fonts/octicons.woff | Bin .../github/collections/breadcrumb.variables | 0 .../themes/github/collections/form.overrides | 0 .../themes/github/collections/form.variables | 0 .../themes/github/collections/grid.variables | 0 .../themes/github/collections/menu.overrides | 0 .../themes/github/collections/menu.variables | 0 .../github/collections/message.overrides | 0 .../github/collections/message.variables | 0 .../themes/github/collections/table.variables | 0 .../themes/github/elements/button.overrides | 0 .../themes/github/elements/button.variables | 0 .../themes/github/elements/header.variables | 0 .../src/themes/github/elements/icon.overrides | 0 .../src/themes/github/elements/icon.variables | 0 .../themes/github/elements/image.variables | 0 .../themes/github/elements/input.overrides | 0 .../themes/github/elements/input.variables | 0 .../themes/github/elements/label.overrides | 0 .../themes/github/elements/label.variables | 0 .../themes/github/elements/segment.overrides | 0 .../themes/github/elements/segment.variables | 0 .../src/themes/github/elements/step.overrides | 0 .../src/themes/github/elements/step.variables | 0 .../src/themes/github/globals/site.variables | 0 .../themes/github/modules/dropdown.overrides | 0 .../themes/github/modules/dropdown.variables | 0 .../src/themes/github/modules/popup.variables | 0 .../gmail/collections/message.overrides | 0 .../gmail/collections/message.variables | 0 .../src/themes/instagram/views/card.overrides | 0 .../src/themes/instagram/views/card.variables | 0 .../themes/material/assets/fonts/icons.eot | Bin .../themes/material/assets/fonts/icons.svg | 0 .../themes/material/assets/fonts/icons.ttf | Bin .../themes/material/assets/fonts/icons.woff | Bin .../material/collections/menu.overrides | 0 .../material/collections/menu.variables | 0 .../themes/material/elements/button.overrides | 0 .../themes/material/elements/button.variables | 0 .../themes/material/elements/header.overrides | 0 .../themes/material/elements/header.variables | 0 .../themes/material/elements/icon.overrides | 0 .../themes/material/elements/icon.variables | 0 .../themes/material/globals/site.overrides | 0 .../themes/material/globals/site.variables | 0 .../material/modules/dropdown.overrides | 0 .../material/modules/dropdown.variables | 0 .../themes/material/modules/modal.overrides | 0 .../themes/material/modules/modal.variables | 0 .../themes/pulsar/elements/loader.overrides | 0 .../themes/pulsar/elements/loader.variables | 0 .../themes/raised/elements/button.overrides | 0 .../themes/raised/elements/button.variables | 0 .../themes/resetcss/globals/reset.overrides | 0 .../themes/resetcss/globals/reset.variables | 0 .../themes/round/elements/button.overrides | 0 .../themes/round/elements/button.variables | 0 .../src/themes/rtl/globals/site.overrides | 0 .../src/themes/rtl/globals/site.variables | 0 .../themes/striped/modules/progress.overrides | 0 .../themes/striped/modules/progress.variables | 0 .../src/themes/timeline/views/feed.overrides | 0 .../src/themes/timeline/views/feed.variables | 0 .../themes/twitter/elements/button.overrides | 0 .../themes/twitter/elements/button.variables | 0 .../bower_components/semantic/tasks/README.md | 0 .../semantic/tasks/admin/components/create.js | 0 .../semantic/tasks/admin/components/init.js | 0 .../semantic/tasks/admin/components/update.js | 0 .../tasks/admin/distributions/create.js | 0 .../tasks/admin/distributions/init.js | 0 .../tasks/admin/distributions/update.js | 0 .../semantic/tasks/admin/publish.js | 0 .../semantic/tasks/admin/register.js | 0 .../semantic/tasks/admin/release.js | 0 .../bower_components/semantic/tasks/build.js | 0 .../semantic/tasks/check-install.js | 0 .../bower_components/semantic/tasks/clean.js | 0 .../semantic/tasks/collections/README.md | 0 .../semantic/tasks/collections/admin.js | 0 .../semantic/tasks/collections/build.js | 0 .../semantic/tasks/collections/internal.js | 0 .../semantic/tasks/collections/rtl.js | 0 .../semantic/tasks/config/admin/github.js | 0 .../tasks/config/admin/oauth.example.js | 0 .../semantic/tasks/config/admin/release.js | 0 .../tasks/config/admin/templates/README.md | 0 .../tasks/config/admin/templates/bower.json | 0 .../admin/templates/component-package.js | 0 .../config/admin/templates/composer.json | 0 .../config/admin/templates/css-package.js | 0 .../config/admin/templates/less-package.js | 0 .../tasks/config/admin/templates/package.json | 0 .../semantic/tasks/config/defaults.js | 0 .../semantic/tasks/config/docs.js | 0 .../semantic/tasks/config/npm/gulpfile.js | 0 .../semantic/tasks/config/project/config.js | 0 .../semantic/tasks/config/project/install.js | 0 .../semantic/tasks/config/project/release.js | 0 .../semantic/tasks/config/tasks.js | 0 .../semantic/tasks/config/user.js | 0 .../semantic/tasks/docs/build.js | 0 .../semantic/tasks/docs/metadata.js | 0 .../semantic/tasks/docs/serve.js | 0 .../semantic/tasks/install.js | 0 .../semantic/tasks/rtl/build.js | 0 .../semantic/tasks/rtl/watch.js | 0 .../semantic/tasks/version.js | 0 .../bower_components/semantic/tasks/watch.js | 0 .../static}/js/controllers/CreateDeal.js | 0 .../resources/static}/js/controllers/Deal.js | 0 .../resources/static}/js/controllers/Home.js | 0 .../resources/static}/js/require-config.js | 0 .../src/main/resources/static}/js/routes.js | 0 .../static}/js/services/HttpErrorHandler.js | 0 .../resources/static}/js/services/NodeApi.js | 0 .../static}/js/utils/dayCountBasisLookup.js | 0 .../resources/static}/js/utils/semantic.js | 0 .../resources/static}/js/viewmodel/Common.js | 0 .../resources/static}/js/viewmodel/Deal.js | 0 .../static}/js/viewmodel/FixedLeg.js | 0 .../static}/js/viewmodel/FixedRate.js | 0 .../static}/js/viewmodel/FloatingLeg.js | 0 .../resources/static}/view/create-deal.html | 0 .../src/main/resources/static}/view/deal.html | 0 .../src/main/resources/static}/view/home.html | 0 .../irs/web/IrsDemoWebApplicationTests.kt | 19 +++ .../irs/web}/api/InterestRatesSwapDemoAPI.kt | 40 +++--- .../kotlin/net/corda/irs/web/demo}/IRSDemo.kt | 2 +- .../corda/irs/web/demo}/IrsDemoClientApi.kt | 7 +- .../web}/simulation/example-irs-trade.json | 0 .../net/corda/irs/web}/simulation/trade.json | 0 .../src/test/resources/scripts/runwebapps.bat | 3 + .../test/resources/scripts/runwebapps.scpt | 12 ++ .../src/test/resources/scripts/runwebapps.sh | 14 ++ samples/network-visualiser/build.gradle | 17 ++- .../corda/netmap/simulation/IRSSimulation.kt | 2 +- settings.gradle | 2 + .../kotlin/net/corda/testing/driver/Driver.kt | 50 +++++++ .../corda/testing/driver/ProcessUtilities.kt | 7 +- .../kotlin/net/corda/testing/http/HttpApi.kt | 7 + .../net/corda/testing/http/HttpUtils.kt | 7 +- 1035 files changed, 623 insertions(+), 254 deletions(-) rename samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/modules/embed.variables => docs/source/running-the-demos.rst (100%) create mode 100644 samples/irs-demo/cordapp/build.gradle rename samples/irs-demo/{ => cordapp}/src/main/kotlin/net/corda/irs/api/NodeInterestRates.kt (100%) rename samples/irs-demo/{ => cordapp}/src/main/kotlin/net/corda/irs/contract/IRS.kt (100%) rename samples/irs-demo/{ => cordapp}/src/main/kotlin/net/corda/irs/contract/IRSExport.kt (100%) rename samples/irs-demo/{ => cordapp}/src/main/kotlin/net/corda/irs/contract/IRSUtils.kt (100%) rename samples/irs-demo/{ => cordapp}/src/main/kotlin/net/corda/irs/flows/AutoOfferFlow.kt (100%) rename samples/irs-demo/{ => cordapp}/src/main/kotlin/net/corda/irs/flows/FixingFlow.kt (100%) rename samples/irs-demo/{ => cordapp}/src/main/kotlin/net/corda/irs/flows/RatesFixFlow.kt (100%) rename samples/irs-demo/{ => cordapp}/src/main/kotlin/net/corda/irs/utilities/OracleUtils.kt (100%) rename samples/irs-demo/{ => cordapp}/src/main/resources/net/corda/irs/simulation/example.rates.txt (100%) rename samples/irs-demo/{ => cordapp}/src/test/kotlin/net/corda/irs/Main.kt (100%) rename samples/irs-demo/{ => cordapp}/src/test/kotlin/net/corda/irs/api/NodeInterestRatesTest.kt (100%) rename samples/irs-demo/{ => cordapp}/src/test/kotlin/net/corda/irs/contract/IRSTests.kt (100%) rename samples/irs-demo/{ => cordapp}/src/test/kotlin/net/corda/irs/flows/UpdateBusinessDayFlow.kt (100%) create mode 100644 samples/irs-demo/src/integration-test/kotlin/net/corda/test/spring/SpringDriver.kt delete mode 100644 samples/irs-demo/src/main/kotlin/net/corda/irs/plugin/IRSPlugin.kt delete mode 100644 samples/irs-demo/src/main/kotlin/net/corda/irs/utilities/HttpUtils.kt delete mode 100644 samples/irs-demo/src/main/resources/META-INF/services/net.corda.webserver.services.WebServerPluginRegistry delete mode 100644 samples/irs-demo/src/test/kotlin/net/corda/irs/plugin/IRSDemoPlugin.kt delete mode 100644 samples/irs-demo/src/test/resources/META-INF/services/net.corda.webserver.services.WebServerPluginRegistry create mode 100644 samples/irs-demo/web/build.gradle create mode 100644 samples/irs-demo/web/src/main/kotlin/net/corda/irs/web/IrsDemoWebApplication.kt rename samples/irs-demo/{src/main/kotlin/net/corda/irs/api/InterestRateSwapAPI.kt => web/src/main/kotlin/net/corda/irs/web/api/InterestSwapRestAPI.kt} (68%) create mode 100644 samples/irs-demo/web/src/main/resources/application-BankA.properties create mode 100644 samples/irs-demo/web/src/main/resources/application-BankB.properties create mode 100644 samples/irs-demo/web/src/main/resources/application-NotaryService.properties create mode 100644 samples/irs-demo/web/src/main/resources/application.properties rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/.bowerrc (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/bower.json (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/css/main.css (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/index.html (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/Deal.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/app.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/angular-fcsa-number/.bower.json (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/angular-fcsa-number/CHANGLOG.md (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/angular-fcsa-number/LICENSE (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/angular-fcsa-number/README.md (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/angular-fcsa-number/bower.json (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/angular-fcsa-number/e2e/fcsaNumber.e2e.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/angular-fcsa-number/e2e/protractor.config (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/angular-fcsa-number/e2e/web/app.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/angular-fcsa-number/e2e/web/public/angular.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/angular-fcsa-number/e2e/web/public/fcsaNumber.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/angular-fcsa-number/e2e/web/public/index.html (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/angular-fcsa-number/src/fcsaNumber.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/angular-fcsa-number/src/fcsaNumber.min.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/angular-route/.bower.json (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/angular-route/LICENSE.md (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/angular-route/README.md (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/angular-route/angular-route.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/angular-route/angular-route.min.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/angular-route/angular-route.min.js.map (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/angular-route/bower.json (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/angular-route/index.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/angular-route/package.json (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/angular/.bower.json (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/angular/LICENSE.md (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/angular/README.md (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/angular/angular-csp.css (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/angular/angular.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/angular/angular.min.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/angular/angular.min.js.gzip (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/angular/angular.min.js.map (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/angular/bower.json (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/angular/index.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/angular/package.json (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery.maskedinput/.bower.json (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery.maskedinput/CONTRIBUTING.md (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery.maskedinput/LICENSE (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery.maskedinput/README.md (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery.maskedinput/bower.json (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery.maskedinput/demo/datepicker.html (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery.maskedinput/demo/index.html (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery.maskedinput/dist/jquery.maskedinput.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery.maskedinput/dist/jquery.maskedinput.min.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery.maskedinput/gruntfile.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery.maskedinput/jquery.maskedinput.nuspec (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery.maskedinput/package.json (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery.maskedinput/src/jquery.maskedinput.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery/.bower.json (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery/AUTHORS.txt (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery/LICENSE.txt (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery/README.md (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery/bower.json (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery/dist/core.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery/dist/jquery.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery/dist/jquery.min.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery/dist/jquery.min.map (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery/dist/jquery.slim.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery/dist/jquery.slim.min.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery/dist/jquery.slim.min.map (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery/external/sizzle/LICENSE.txt (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery/external/sizzle/dist/sizzle.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery/external/sizzle/dist/sizzle.min.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery/external/sizzle/dist/sizzle.min.map (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery/src/.eslintrc.json (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery/src/ajax.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery/src/ajax/jsonp.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery/src/ajax/load.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery/src/ajax/parseXML.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery/src/ajax/script.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery/src/ajax/var/location.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery/src/ajax/var/nonce.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery/src/ajax/var/rquery.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery/src/ajax/xhr.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery/src/attributes.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery/src/attributes/attr.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery/src/attributes/classes.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery/src/attributes/prop.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery/src/attributes/support.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery/src/attributes/val.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery/src/callbacks.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery/src/core.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery/src/core/DOMEval.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery/src/core/access.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery/src/core/init.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery/src/core/parseHTML.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery/src/core/ready-no-deferred.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery/src/core/ready.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery/src/core/readyException.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery/src/core/stripAndCollapse.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery/src/core/support.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery/src/core/var/rsingleTag.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery/src/css.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery/src/css/addGetHookIf.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery/src/css/adjustCSS.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery/src/css/curCSS.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery/src/css/hiddenVisibleSelectors.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery/src/css/showHide.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery/src/css/support.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery/src/css/var/cssExpand.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery/src/css/var/getStyles.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery/src/css/var/isHiddenWithinTree.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery/src/css/var/rmargin.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery/src/css/var/rnumnonpx.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery/src/css/var/swap.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery/src/data.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery/src/data/Data.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery/src/data/var/acceptData.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery/src/data/var/dataPriv.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery/src/data/var/dataUser.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery/src/deferred.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery/src/deferred/exceptionHook.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery/src/deprecated.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery/src/dimensions.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery/src/effects.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery/src/effects/Tween.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery/src/effects/animatedSelector.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery/src/event.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery/src/event/ajax.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery/src/event/alias.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery/src/event/focusin.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery/src/event/support.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery/src/event/trigger.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery/src/exports/amd.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery/src/exports/global.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery/src/jquery.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery/src/manipulation.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery/src/manipulation/_evalUrl.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery/src/manipulation/buildFragment.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery/src/manipulation/getAll.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery/src/manipulation/setGlobalEval.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery/src/manipulation/support.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery/src/manipulation/var/rcheckableType.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery/src/manipulation/var/rscriptType.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery/src/manipulation/var/rtagName.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery/src/manipulation/wrapMap.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery/src/offset.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery/src/queue.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery/src/queue/delay.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery/src/selector-native.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery/src/selector-sizzle.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery/src/selector.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery/src/serialize.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery/src/traversing.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery/src/traversing/findFilter.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery/src/traversing/var/dir.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery/src/traversing/var/rneedsContext.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery/src/traversing/var/siblings.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery/src/var/ObjectFunctionString.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery/src/var/arr.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery/src/var/class2type.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery/src/var/concat.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery/src/var/document.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery/src/var/documentElement.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery/src/var/fnToString.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery/src/var/getProto.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery/src/var/hasOwn.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery/src/var/indexOf.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery/src/var/pnum.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery/src/var/push.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery/src/var/rcssNum.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery/src/var/rnothtmlwhite.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery/src/var/slice.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery/src/var/support.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery/src/var/toString.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/jquery/src/wrap.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/.bower.json (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/.editorconfig (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/.gitattributes (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/.github/CONTRIBUTING.md (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/.gitignore (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/.jscsrc (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/.markdown-doctest-setup.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/.travis.yml (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/CHANGELOG (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/LICENSE (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/README.md (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/dist/lodash.core.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/dist/lodash.core.min.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/dist/lodash.fp.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/dist/lodash.fp.min.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/dist/lodash.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/dist/lodash.min.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/dist/mapping.fp.js (99%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/doc/README.md (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/fp/_baseConvert.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/fp/_convertBrowser.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/fp/_mapping.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/fp/placeholder.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/lib/common/file.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/lib/common/mapping.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/lib/common/minify.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/lib/common/uglify.options.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/lib/common/util.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/lib/fp/build-dist.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/lib/fp/build-doc.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/lib/fp/build-modules.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/lib/fp/template/doc/wiki.jst (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/lib/fp/template/modules/_falseOptions.jst (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/lib/fp/template/modules/_util.jst (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/lib/fp/template/modules/alias.jst (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/lib/fp/template/modules/category.jst (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/lib/fp/template/modules/convert.jst (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/lib/fp/template/modules/fp.jst (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/lib/fp/template/modules/module.jst (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/lib/fp/template/modules/thru.jst (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/lib/main/build-dist.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/lib/main/build-doc.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/lib/main/build-modules.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/lib/main/build-site.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/lodash.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/package.json (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/perf/asset/perf-ui.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/perf/index.html (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/perf/perf.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/test/asset/test-ui.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/test/asset/worker.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/test/backbone.html (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/test/fp.html (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/test/index.html (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/test/remove.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/test/saucelabs.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/test/test-fp.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/test/test.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/test/underscore.html (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/vendor/backbone/LICENSE (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/vendor/backbone/backbone.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/vendor/backbone/test/collection.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/vendor/backbone/test/events.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/vendor/backbone/test/model.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/vendor/backbone/test/noconflict.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/vendor/backbone/test/router.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/vendor/backbone/test/setup/dom-setup.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/vendor/backbone/test/setup/environment.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/vendor/backbone/test/sync.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/vendor/backbone/test/view.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/vendor/firebug-lite/license.txt (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/vendor/firebug-lite/skin/xp/blank.gif (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/vendor/firebug-lite/skin/xp/buttonBg.png (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/vendor/firebug-lite/skin/xp/buttonBgHover.png (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/vendor/firebug-lite/skin/xp/debugger.css (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/vendor/firebug-lite/skin/xp/detach.png (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/vendor/firebug-lite/skin/xp/detachHover.png (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/vendor/firebug-lite/skin/xp/disable.gif (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/vendor/firebug-lite/skin/xp/disable.png (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/vendor/firebug-lite/skin/xp/disableHover.gif (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/vendor/firebug-lite/skin/xp/disableHover.png (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/vendor/firebug-lite/skin/xp/down.png (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/vendor/firebug-lite/skin/xp/downActive.png (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/vendor/firebug-lite/skin/xp/downHover.png (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/vendor/firebug-lite/skin/xp/errorIcon-sm.png (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/vendor/firebug-lite/skin/xp/errorIcon.gif (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/vendor/firebug-lite/skin/xp/errorIcon.png (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/vendor/firebug-lite/skin/xp/firebug-1.3a2.css (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/vendor/firebug-lite/skin/xp/firebug.IE6.css (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/vendor/firebug-lite/skin/xp/firebug.css (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/vendor/firebug-lite/skin/xp/firebug.html (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/vendor/firebug-lite/skin/xp/firebug.png (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/vendor/firebug-lite/skin/xp/group.gif (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/vendor/firebug-lite/skin/xp/html.css (99%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/vendor/firebug-lite/skin/xp/infoIcon.gif (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/vendor/firebug-lite/skin/xp/infoIcon.png (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/vendor/firebug-lite/skin/xp/loading_16.gif (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/vendor/firebug-lite/skin/xp/min.png (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/vendor/firebug-lite/skin/xp/minHover.png (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/vendor/firebug-lite/skin/xp/off.png (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/vendor/firebug-lite/skin/xp/offHover.png (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/vendor/firebug-lite/skin/xp/pixel_transparent.gif (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/vendor/firebug-lite/skin/xp/roundCorner.svg (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/vendor/firebug-lite/skin/xp/search.gif (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/vendor/firebug-lite/skin/xp/search.png (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/vendor/firebug-lite/skin/xp/shadow.gif (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/vendor/firebug-lite/skin/xp/shadow2.gif (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/vendor/firebug-lite/skin/xp/shadowAlpha.png (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/vendor/firebug-lite/skin/xp/sprite.png (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/vendor/firebug-lite/skin/xp/tabHoverLeft.png (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/vendor/firebug-lite/skin/xp/tabHoverMid.png (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/vendor/firebug-lite/skin/xp/tabHoverRight.png (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/vendor/firebug-lite/skin/xp/tabLeft.png (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/vendor/firebug-lite/skin/xp/tabMenuCheckbox.png (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/vendor/firebug-lite/skin/xp/tabMenuPin.png (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/vendor/firebug-lite/skin/xp/tabMenuRadio.png (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/vendor/firebug-lite/skin/xp/tabMenuTarget.png (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/vendor/firebug-lite/skin/xp/tabMenuTargetHover.png (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/vendor/firebug-lite/skin/xp/tabMid.png (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/vendor/firebug-lite/skin/xp/tabRight.png (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/vendor/firebug-lite/skin/xp/textEditorBorders.gif (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/vendor/firebug-lite/skin/xp/textEditorBorders.png (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/vendor/firebug-lite/skin/xp/textEditorCorners.gif (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/vendor/firebug-lite/skin/xp/textEditorCorners.png (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/vendor/firebug-lite/skin/xp/titlebarMid.png (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/vendor/firebug-lite/skin/xp/toolbarMid.png (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/vendor/firebug-lite/skin/xp/tree_close.gif (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/vendor/firebug-lite/skin/xp/tree_open.gif (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/vendor/firebug-lite/skin/xp/twistyClosed.png (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/vendor/firebug-lite/skin/xp/twistyOpen.png (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/vendor/firebug-lite/skin/xp/up.png (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/vendor/firebug-lite/skin/xp/upActive.png (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/vendor/firebug-lite/skin/xp/upHover.png (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/vendor/firebug-lite/skin/xp/warningIcon.gif (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/vendor/firebug-lite/skin/xp/warningIcon.png (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/vendor/firebug-lite/src/firebug-lite-debug.js (99%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/vendor/json-js/json2.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/vendor/underscore/LICENSE (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/vendor/underscore/test/arrays.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/vendor/underscore/test/chaining.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/vendor/underscore/test/collections.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/vendor/underscore/test/cross-document.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/vendor/underscore/test/functions.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/vendor/underscore/test/objects.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/vendor/underscore/test/utility.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/vendor/underscore/underscore-min.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/lodash/vendor/underscore/underscore.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/requirejs/.bower.json (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/requirejs/LICENSE (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/requirejs/README.md (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/requirejs/bower.json (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/requirejs/require.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/.bower.json (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/.csscomb.json (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/.csslintrc (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/.gitignore (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/.jshintrc (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/CONTRIBUTING.md (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/LICENSE.md (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/README.md (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/RELEASE-NOTES.md (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/bower.json (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/composer.json (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/accordion.css (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/accordion.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/accordion.min.css (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/accordion.min.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/ad.css (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/ad.min.css (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/api.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/api.min.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/breadcrumb.css (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/breadcrumb.min.css (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/button.css (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/button.min.css (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/card.css (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/card.min.css (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/checkbox.css (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/checkbox.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/checkbox.min.css (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/checkbox.min.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/colorize.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/colorize.min.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/comment.css (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/comment.min.css (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/container.css (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/container.min.css (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/dimmer.css (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/dimmer.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/dimmer.min.css (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/dimmer.min.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/divider.css (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/divider.min.css (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/dropdown.css (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/dropdown.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/dropdown.min.css (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/dropdown.min.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/embed.css (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/embed.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/embed.min.css (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/embed.min.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/feed.css (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/feed.min.css (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/flag.css (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/flag.min.css (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/form.css (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/form.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/form.min.css (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/form.min.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/grid.css (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/grid.min.css (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/header.css (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/header.min.css (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/icon.css (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/icon.min.css (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/image.css (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/image.min.css (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/input.css (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/input.min.css (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/item.css (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/item.min.css (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/label.css (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/label.min.css (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/list.css (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/list.min.css (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/loader.css (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/loader.min.css (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/menu.css (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/menu.min.css (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/message.css (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/message.min.css (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/modal.css (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/modal.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/modal.min.css (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/modal.min.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/nag.css (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/nag.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/nag.min.css (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/nag.min.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/popup.css (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/popup.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/popup.min.css (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/popup.min.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/progress.css (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/progress.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/progress.min.css (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/progress.min.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/rail.css (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/rail.min.css (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/rating.css (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/rating.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/rating.min.css (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/rating.min.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/reset.css (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/reset.min.css (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/reveal.css (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/reveal.min.css (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/search.css (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/search.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/search.min.css (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/search.min.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/segment.css (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/segment.min.css (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/shape.css (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/shape.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/shape.min.css (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/shape.min.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/sidebar.css (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/sidebar.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/sidebar.min.css (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/sidebar.min.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/site.css (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/site.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/site.min.css (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/site.min.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/state.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/state.min.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/statistic.css (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/statistic.min.css (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/step.css (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/step.min.css (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/sticky.css (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/sticky.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/sticky.min.css (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/sticky.min.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/tab.css (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/tab.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/tab.min.css (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/tab.min.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/table.css (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/table.min.css (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/transition.css (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/transition.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/transition.min.css (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/transition.min.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/video.css (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/video.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/video.min.css (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/video.min.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/visibility.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/visibility.min.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/visit.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/components/visit.min.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/semantic.css (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/semantic.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/semantic.min.css (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/semantic.min.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/themes/basic/assets/fonts/icons.eot (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/themes/basic/assets/fonts/icons.svg (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/themes/basic/assets/fonts/icons.ttf (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/themes/basic/assets/fonts/icons.woff (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/themes/default/assets/fonts/icons.eot (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/themes/default/assets/fonts/icons.otf (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/themes/default/assets/fonts/icons.svg (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/themes/default/assets/fonts/icons.ttf (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/themes/default/assets/fonts/icons.woff (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/themes/default/assets/fonts/icons.woff2 (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/themes/default/assets/images/flags.png (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/themes/github/assets/fonts/octicons-local.ttf (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/themes/github/assets/fonts/octicons.svg (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/themes/github/assets/fonts/octicons.ttf (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/themes/github/assets/fonts/octicons.woff (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/themes/material/assets/fonts/icons.eot (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/themes/material/assets/fonts/icons.svg (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/themes/material/assets/fonts/icons.ttf (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/dist/themes/material/assets/fonts/icons.woff (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/examples/assets/images/avatar/nan.jpg (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/examples/assets/images/avatar/tom.jpg (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/examples/assets/images/bg.jpg (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/examples/assets/images/logo.png (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/examples/assets/images/wireframe/centered-paragraph.png (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/examples/assets/images/wireframe/image-square.png (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/examples/assets/images/wireframe/image-text.png (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/examples/assets/images/wireframe/image.png (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/examples/assets/images/wireframe/media-paragraph-alt.png (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/examples/assets/images/wireframe/media-paragraph.png (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/examples/assets/images/wireframe/paragraph.png (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/examples/assets/images/wireframe/short-paragraph.png (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/examples/assets/images/wireframe/square-image.png (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/examples/assets/images/wireframe/text-image.png (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/examples/assets/images/wireframe/white-image.png (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/examples/assets/library/iframe-content.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/examples/assets/library/iframe.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/examples/assets/library/jquery.min.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/examples/assets/show-examples.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/examples/attached.html (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/examples/bootstrap.html (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/examples/components/button.html (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/examples/components/card.html (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/examples/components/input.html (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/examples/components/menu.html (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/examples/components/site.html (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/examples/components/table.html (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/examples/fixed.html (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/examples/grid.html (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/examples/homepage.html (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/examples/login.html (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/examples/responsive.html (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/examples/sticky.html (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/examples/theming.html (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/gulpfile.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/karma.conf.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/logo.png (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/package.json (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/semantic.json.example (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/README.md (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/_site/collections/breadcrumb.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/_site/collections/breadcrumb.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/_site/collections/form.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/_site/collections/form.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/_site/collections/grid.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/_site/collections/grid.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/_site/collections/menu.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/_site/collections/menu.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/_site/collections/message.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/_site/collections/message.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/_site/collections/table.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/_site/collections/table.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/_site/elements/button.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/_site/elements/button.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/_site/elements/container.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/_site/elements/container.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/_site/elements/divider.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/_site/elements/divider.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/_site/elements/flag.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/_site/elements/flag.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/_site/elements/header.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/_site/elements/header.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/_site/elements/icon.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/_site/elements/icon.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/_site/elements/image.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/_site/elements/image.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/_site/elements/input.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/_site/elements/input.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/_site/elements/label.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/_site/elements/label.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/_site/elements/list.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/_site/elements/list.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/_site/elements/loader.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/_site/elements/loader.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/_site/elements/rail.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/_site/elements/rail.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/_site/elements/reveal.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/_site/elements/reveal.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/_site/elements/segment.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/_site/elements/segment.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/_site/elements/step.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/_site/elements/step.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/_site/globals/reset.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/_site/globals/reset.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/_site/globals/site.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/_site/globals/site.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/_site/modules/accordion.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/_site/modules/accordion.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/_site/modules/chatroom.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/_site/modules/chatroom.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/_site/modules/checkbox.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/_site/modules/checkbox.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/_site/modules/dimmer.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/_site/modules/dimmer.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/_site/modules/dropdown.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/_site/modules/dropdown.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/_site/modules/embed.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb/js/bower_components/semantic/src/themes/bootstrap3/elements/button.overrides => web/src/main/resources/static/js/bower_components/semantic/src/_site/modules/embed.variables} (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/_site/modules/modal.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/_site/modules/modal.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/_site/modules/nag.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/_site/modules/nag.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/_site/modules/popup.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/_site/modules/popup.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/_site/modules/progress.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/_site/modules/progress.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/_site/modules/rating.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/_site/modules/rating.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/_site/modules/search.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/_site/modules/search.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/_site/modules/shape.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/_site/modules/shape.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/_site/modules/sidebar.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/_site/modules/sidebar.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/_site/modules/sticky.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/_site/modules/sticky.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/_site/modules/tab.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/_site/modules/tab.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/_site/modules/transition.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/_site/modules/transition.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/_site/views/ad.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/_site/views/ad.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/_site/views/card.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/_site/views/card.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/_site/views/comment.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/_site/views/comment.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/_site/views/feed.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/_site/views/feed.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/_site/views/item.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/_site/views/item.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/_site/views/statistic.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/_site/views/statistic.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/definitions/behaviors/api.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/definitions/behaviors/colorize.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/definitions/behaviors/form.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/definitions/behaviors/state.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/definitions/behaviors/visibility.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/definitions/behaviors/visit.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/definitions/collections/breadcrumb.less (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/definitions/collections/form.less (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/definitions/collections/grid.less (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/definitions/collections/menu.less (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/definitions/collections/message.less (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/definitions/collections/table.less (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/definitions/elements/button.less (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/definitions/elements/container.less (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/definitions/elements/divider.less (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/definitions/elements/flag.less (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/definitions/elements/header.less (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/definitions/elements/icon.less (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/definitions/elements/image.less (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/definitions/elements/input.less (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/definitions/elements/label.less (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/definitions/elements/list.less (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/definitions/elements/loader.less (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/definitions/elements/rail.less (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/definitions/elements/reveal.less (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/definitions/elements/segment.less (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/definitions/elements/step.less (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/definitions/globals/reset.less (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/definitions/globals/site.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/definitions/globals/site.less (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/definitions/modules/accordion.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/definitions/modules/accordion.less (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/definitions/modules/checkbox.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/definitions/modules/checkbox.less (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/definitions/modules/dimmer.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/definitions/modules/dimmer.less (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/definitions/modules/dropdown.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/definitions/modules/dropdown.less (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/definitions/modules/embed.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/definitions/modules/embed.less (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/definitions/modules/modal.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/definitions/modules/modal.less (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/definitions/modules/nag.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/definitions/modules/nag.less (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/definitions/modules/popup.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/definitions/modules/popup.less (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/definitions/modules/progress.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/definitions/modules/progress.less (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/definitions/modules/rating.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/definitions/modules/rating.less (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/definitions/modules/search.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/definitions/modules/search.less (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/definitions/modules/shape.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/definitions/modules/shape.less (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/definitions/modules/sidebar.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/definitions/modules/sidebar.less (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/definitions/modules/sticky.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/definitions/modules/sticky.less (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/definitions/modules/tab.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/definitions/modules/tab.less (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/definitions/modules/transition.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/definitions/modules/transition.less (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/definitions/views/ad.less (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/definitions/views/card.less (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/definitions/views/comment.less (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/definitions/views/feed.less (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/definitions/views/item.less (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/definitions/views/statistic.less (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/semantic.less (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/theme.config.example (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/theme.less (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/amazon/elements/button.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/amazon/elements/button.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/amazon/globals/site.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/basic/assets/fonts/icons.eot (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/basic/assets/fonts/icons.svg (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/basic/assets/fonts/icons.ttf (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/basic/assets/fonts/icons.woff (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/basic/collections/table.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/basic/collections/table.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/basic/elements/button.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/basic/elements/button.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/basic/elements/icon.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/basic/elements/icon.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/basic/elements/step.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/basic/elements/step.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/basic/globals/reset.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/basic/globals/reset.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/basic/modules/progress.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/basic/modules/progress.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/basic/views/card.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/basic/views/card.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/bookish/elements/header.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/bookish/elements/header.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb/js/bower_components/semantic/src/themes/round => web/src/main/resources/static/js/bower_components/semantic/src/themes/bootstrap3}/elements/button.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/bootstrap3/elements/button.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/chubby/collections/form.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/chubby/collections/form.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/chubby/collections/menu.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/chubby/collections/menu.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/chubby/elements/button.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/chubby/elements/button.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/chubby/elements/header.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/chubby/elements/header.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/chubby/modules/accordion.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/chubby/modules/accordion.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/chubby/views/comment.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/chubby/views/comment.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/classic/collections/table.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/classic/collections/table.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/classic/elements/button.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/classic/elements/button.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/classic/elements/header.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/classic/elements/header.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/classic/modules/progress.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/classic/modules/progress.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/classic/views/card.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/classic/views/card.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/colored/modules/checkbox.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/colored/modules/checkbox.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/default/assets/fonts/icons.eot (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/default/assets/fonts/icons.svg (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/default/assets/fonts/icons.ttf (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/default/assets/fonts/icons.woff (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/default/assets/fonts/icons.woff2 (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/default/assets/images/flags.png (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/default/collections/breadcrumb.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/default/collections/breadcrumb.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/default/collections/form.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/default/collections/form.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/default/collections/grid.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/default/collections/grid.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/default/collections/menu.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/default/collections/menu.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/default/collections/message.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/default/collections/message.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/default/collections/table.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/default/collections/table.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/default/elements/button.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/default/elements/button.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/default/elements/container.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/default/elements/container.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/default/elements/divider.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/default/elements/divider.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/default/elements/flag.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/default/elements/flag.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/default/elements/header.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/default/elements/header.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/default/elements/icon.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/default/elements/icon.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/default/elements/image.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/default/elements/image.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/default/elements/input.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/default/elements/input.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/default/elements/label.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/default/elements/label.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/default/elements/list.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/default/elements/list.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/default/elements/loader.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/default/elements/loader.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/default/elements/rail.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/default/elements/rail.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/default/elements/reveal.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/default/elements/reveal.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/default/elements/segment.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/default/elements/segment.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/default/elements/step.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/default/elements/step.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/default/globals/reset.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/default/globals/reset.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/default/globals/site.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/default/globals/site.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/default/modules/accordion.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/default/modules/accordion.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/default/modules/chatroom.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/default/modules/chatroom.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/default/modules/checkbox.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/default/modules/checkbox.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/default/modules/dimmer.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/default/modules/dimmer.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/default/modules/dropdown.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/default/modules/dropdown.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/default/modules/embed.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/default/modules/embed.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/default/modules/modal.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/default/modules/modal.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/default/modules/nag.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/default/modules/nag.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/default/modules/popup.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/default/modules/popup.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/default/modules/progress.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/default/modules/progress.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/default/modules/rating.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/default/modules/rating.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/default/modules/search.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/default/modules/search.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/default/modules/shape.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/default/modules/shape.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/default/modules/sidebar.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/default/modules/sidebar.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/default/modules/sticky.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/default/modules/sticky.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/default/modules/tab.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/default/modules/tab.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/default/modules/transition.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/default/modules/transition.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/default/views/ad.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/default/views/ad.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/default/views/card.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/default/views/card.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/default/views/comment.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/default/views/comment.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/default/views/feed.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/default/views/feed.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/default/views/item.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/default/views/item.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/default/views/statistic.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/default/views/statistic.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/duo/elements/loader.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/duo/elements/loader.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/fixed-width/collections/grid.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/fixed-width/collections/grid.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/fixed-width/modules/modal.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/fixed-width/modules/modal.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/flat/collections/form.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/flat/collections/form.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/flat/globals/site.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/flat/globals/site.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/github/assets/fonts/octicons-local.ttf (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/github/assets/fonts/octicons.svg (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/github/assets/fonts/octicons.ttf (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/github/assets/fonts/octicons.woff (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/github/collections/breadcrumb.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/github/collections/form.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/github/collections/form.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/github/collections/grid.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/github/collections/menu.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/github/collections/menu.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/github/collections/message.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/github/collections/message.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/github/collections/table.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/github/elements/button.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/github/elements/button.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/github/elements/header.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/github/elements/icon.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/github/elements/icon.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/github/elements/image.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/github/elements/input.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/github/elements/input.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/github/elements/label.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/github/elements/label.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/github/elements/segment.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/github/elements/segment.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/github/elements/step.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/github/elements/step.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/github/globals/site.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/github/modules/dropdown.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/github/modules/dropdown.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/github/modules/popup.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/gmail/collections/message.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/gmail/collections/message.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/instagram/views/card.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/instagram/views/card.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/material/assets/fonts/icons.eot (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/material/assets/fonts/icons.svg (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/material/assets/fonts/icons.ttf (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/material/assets/fonts/icons.woff (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/material/collections/menu.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/material/collections/menu.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/material/elements/button.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/material/elements/button.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/material/elements/header.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/material/elements/header.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/material/elements/icon.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/material/elements/icon.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/material/globals/site.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/material/globals/site.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/material/modules/dropdown.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/material/modules/dropdown.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/material/modules/modal.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/material/modules/modal.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/pulsar/elements/loader.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/pulsar/elements/loader.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/raised/elements/button.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/raised/elements/button.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/resetcss/globals/reset.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/resetcss/globals/reset.variables (100%) create mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/round/elements/button.overrides rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/round/elements/button.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/rtl/globals/site.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/rtl/globals/site.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/striped/modules/progress.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/striped/modules/progress.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/timeline/views/feed.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/timeline/views/feed.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/twitter/elements/button.overrides (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/src/themes/twitter/elements/button.variables (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/tasks/README.md (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/tasks/admin/components/create.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/tasks/admin/components/init.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/tasks/admin/components/update.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/tasks/admin/distributions/create.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/tasks/admin/distributions/init.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/tasks/admin/distributions/update.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/tasks/admin/publish.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/tasks/admin/register.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/tasks/admin/release.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/tasks/build.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/tasks/check-install.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/tasks/clean.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/tasks/collections/README.md (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/tasks/collections/admin.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/tasks/collections/build.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/tasks/collections/internal.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/tasks/collections/rtl.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/tasks/config/admin/github.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/tasks/config/admin/oauth.example.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/tasks/config/admin/release.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/tasks/config/admin/templates/README.md (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/tasks/config/admin/templates/bower.json (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/tasks/config/admin/templates/component-package.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/tasks/config/admin/templates/composer.json (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/tasks/config/admin/templates/css-package.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/tasks/config/admin/templates/less-package.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/tasks/config/admin/templates/package.json (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/tasks/config/defaults.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/tasks/config/docs.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/tasks/config/npm/gulpfile.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/tasks/config/project/config.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/tasks/config/project/install.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/tasks/config/project/release.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/tasks/config/tasks.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/tasks/config/user.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/tasks/docs/build.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/tasks/docs/metadata.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/tasks/docs/serve.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/tasks/install.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/tasks/rtl/build.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/tasks/rtl/watch.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/tasks/version.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/bower_components/semantic/tasks/watch.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/controllers/CreateDeal.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/controllers/Deal.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/controllers/Home.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/require-config.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/routes.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/services/HttpErrorHandler.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/services/NodeApi.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/utils/dayCountBasisLookup.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/utils/semantic.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/viewmodel/Common.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/viewmodel/Deal.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/viewmodel/FixedLeg.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/viewmodel/FixedRate.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/js/viewmodel/FloatingLeg.js (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/view/create-deal.html (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/view/deal.html (100%) rename samples/irs-demo/{src/main/resources/irsweb => web/src/main/resources/static}/view/home.html (100%) create mode 100644 samples/irs-demo/web/src/test/kotlin/net/corda/irs/web/IrsDemoWebApplicationTests.kt rename samples/irs-demo/{src/test/kotlin/net/corda/irs => web/src/test/kotlin/net/corda/irs/web}/api/InterestRatesSwapDemoAPI.kt (62%) rename samples/irs-demo/{src/test/kotlin/net/corda/irs => web/src/test/kotlin/net/corda/irs/web/demo}/IRSDemo.kt (97%) rename samples/irs-demo/{src/test/kotlin/net/corda/irs => web/src/test/kotlin/net/corda/irs/web/demo}/IrsDemoClientApi.kt (87%) rename samples/irs-demo/{src/main/resources/net/corda/irs => web/src/test/resources/net/corda/irs/web}/simulation/example-irs-trade.json (100%) rename samples/irs-demo/{src/main/resources/net/corda/irs => web/src/test/resources/net/corda/irs/web}/simulation/trade.json (100%) create mode 100755 samples/irs-demo/web/src/test/resources/scripts/runwebapps.bat create mode 100755 samples/irs-demo/web/src/test/resources/scripts/runwebapps.scpt create mode 100755 samples/irs-demo/web/src/test/resources/scripts/runwebapps.sh diff --git a/.gitignore b/.gitignore index ad05b9958f..1eb2b271b4 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ tags .gradle local.properties +.gradletasknamecache # General build files **/build/* diff --git a/.idea/compiler.xml b/.idea/compiler.xml index 0c996385a3..ae994c7ef9 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -2,8 +2,6 @@ - - @@ -21,6 +19,7 @@ + @@ -43,11 +42,8 @@ - - - @@ -122,6 +118,8 @@ + + diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/modules/embed.variables b/docs/source/running-the-demos.rst similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/modules/embed.variables rename to docs/source/running-the-demos.rst diff --git a/samples/irs-demo/README.md b/samples/irs-demo/README.md index 38a24fc15e..b6576f192a 100644 --- a/samples/irs-demo/README.md +++ b/samples/irs-demo/README.md @@ -4,25 +4,28 @@ This demo brings up three nodes: Bank A, Bank B and a node that simultaneously r interest rates oracle. The two banks agree on an interest rate swap, and then do regular fixings of the deal as the time on a simulated clock passes. -To run from the command line in Unix: +Functionality is split into two parts - CordApp which provides actual distributed ledger backend and Spring Boot +webapp which provides REST API and web frontend. Application communicate using Corda RPC protocol. -1. Run ``./gradlew samples:irs-demo:deployNodes`` to install configs and a command line tool under +To run from the command line in Unix: +1. Run ``./gradlew samples:irs-demo:cordapp:deployNodes`` to install configs and a command line tool under ``samples/irs-demo/build`` -2. Run ``./gradlew samples:irs-demo:installDist`` -3. Move to the ``samples/irs-demo/build`` directory -4. Run ``./nodes/runnodes`` to open up three new terminals with the three nodes (you may have to install xterm) +2. Run ``./gradlew samples:irs-demo:web:deployWebapps`` to install configs and tools for running webservers +3. Move to the ``samples/irs-demo/`` directory +4. Run ``./cordapp/build/nodes/runnodes`` to open up three new terminals with the three nodes (you may have to install xterm) +5. Run ``./web/build/webapps/runwebapps`` to open three more terminals for associated webserver To run from the command line in Windows: -1. Run ``gradlew.bat samples:irs-demo:deployNodes`` to install configs and a command line tool under +1. Run ``gradlew.bat samples:irs-demo:cordapp:deployNodes`` to install configs and a command line tool under ``samples\irs-demo\build`` -2. Run ``gradlew.bat samples:irs-demo:installDist`` -3. Run ``cd samples\irs-demo\build`` to change current working directory -4. Run ``nodes\runnodes`` to open up several 6 terminals, 2 for each node. First terminal is a web-server associated - with every node and second one is Corda interactive shell for the node +2. Run ``gradlew.bat samples:irs-demo:web:deployWebapps`` to install configs and tools for running webservers +3. Run ``cd samples\irs-demo`` to change current working directory +4. Run ``cordapp\build\nodes\runnodes`` to open up several 3 terminals for each nodes +5. Run ``web\build\webapps\webapps`` to open up several 3 terminals for each nodes' webservers -This demo also has a web app. To use this, run nodes and then navigate to http://localhost:10007/web/irsdemo and -http://localhost:10010/web/irsdemo to see each node's view of the ledger. +This demo also has a web app. To use this, run nodes and then navigate to http://localhost:10007/ and +http://localhost:10010/ to see each node's view of the ledger. To use the web app, click the "Create Deal" button, fill in the form, then click the "Submit" button. You can then use the time controls at the top left of the home page to run the fixings. Click any individual trade in the blotter to diff --git a/samples/irs-demo/build.gradle b/samples/irs-demo/build.gradle index bcb20c214a..8a9ee0699e 100644 --- a/samples/irs-demo/build.gradle +++ b/samples/irs-demo/build.gradle @@ -1,6 +1,25 @@ +buildscript { + ext { + springBootVersion = '1.5.7.RELEASE' + } + repositories { + mavenCentral() + } + dependencies { + classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}") + } +} + +// Spring Boot plugin adds a numerous hardcoded dependencies in the version much lower then Corda expects +// causing the problems in runtime. Those can be changed by manipulating above properties +// See https://github.com/spring-gradle-plugins/dependency-management-plugin/blob/master/README.md#changing-the-value-of-a-version-property +ext['artemis.version'] = "$artemis_version" +ext['hibernate.version'] = "$hibernate_version" + apply plugin: 'java' apply plugin: 'kotlin' apply plugin: 'idea' +apply plugin: 'org.springframework.boot' apply plugin: 'net.corda.plugins.quasar-utils' apply plugin: 'net.corda.plugins.publish-utils' apply plugin: 'net.corda.plugins.cordapp' @@ -23,59 +42,25 @@ sourceSets { configurations { integrationTestCompile.extendsFrom testCompile integrationTestRuntime.extendsFrom testRuntime + demoArtifacts.extendsFrom testRuntime } dependencies { - compile "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version" - - // The irs demo CorDapp depends upon Cash CorDapp features - cordapp project(':finance') - - // Corda integration dependencies - cordaCompile project(path: ":node:capsule", configuration: 'runtimeArtifacts') - cordaCompile project(path: ":webserver:webcapsule", configuration: 'runtimeArtifacts') - cordaCompile project(':core') - cordaCompile project(':webserver') - - // Javax is required for webapis - compile "org.glassfish.jersey.core:jersey-server:${jersey_version}" - - // Cordapp dependencies - // Specify your cordapp's dependencies below, including dependent cordapps - compile "com.squareup.okhttp3:okhttp:$okhttp_version" + compile group: 'commons-io', name: 'commons-io', version: '2.5' + compile project(path: ":samples:irs-demo:cordapp", configuration: "demoArtifacts") + compile project(":samples:irs-demo:web") + compile('org.springframework.boot:spring-boot-starter-web') { + exclude module: "spring-boot-starter-logging" + exclude module: "logback-classic" + } testCompile project(':node-driver') testCompile "junit:junit:$junit_version" testCompile "org.assertj:assertj-core:${assertj_version}" } -task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) { - directory "./build/nodes" - node { - name "O=Notary Service,L=Zurich,C=CH" - notary = [validating : true] - p2pPort 10002 - rpcPort 10003 - webPort 10004 - cordapps = ["$project.group:finance:$corda_release_version"] - useTestClock true - } - node { - name "O=Bank A,L=London,C=GB" - p2pPort 10005 - rpcPort 10006 - webPort 10007 - cordapps = ["$project.group:finance:$corda_release_version"] - useTestClock true - } - node { - name "O=Bank B,L=New York,C=US" - p2pPort 10008 - rpcPort 10009 - webPort 10010 - cordapps = ["$project.group:finance:$corda_release_version"] - useTestClock true - } +bootRepackage { + enabled = false } task integrationTest(type: Test, dependsOn: []) { @@ -83,41 +68,9 @@ task integrationTest(type: Test, dependsOn: []) { classpath = sourceSets.integrationTest.runtimeClasspath } -// This fixes the "line too long" error when running this demo with windows CLI -// TODO: Automatically apply to all projects via a plugin -tasks.withType(CreateStartScripts).each { task -> - task.doLast { - String text = task.windowsScript.text - // Replaces the per file classpath (which are all jars in "lib") with a wildcard on lib - text = text.replaceFirst(/(set CLASSPATH=%APP_HOME%\\lib\\).*/, { "${it[1]}*" }) - task.windowsScript.write text - } -} - idea { module { downloadJavadoc = true // defaults to false downloadSources = true } -} - -publishing { - publications { - jarAndSources(MavenPublication) { - from components.java - artifactId 'irsdemo' - - artifact sourceJar - artifact javadocJar - } - } -} - -jar { - from sourceSets.test.output - manifest { - attributes( - 'Automatic-Module-Name': 'net.corda.samples.demos.irs' - ) - } -} +} \ No newline at end of file diff --git a/samples/irs-demo/cordapp/build.gradle b/samples/irs-demo/cordapp/build.gradle new file mode 100644 index 0000000000..9edccf8968 --- /dev/null +++ b/samples/irs-demo/cordapp/build.gradle @@ -0,0 +1,115 @@ +apply plugin: 'java' +apply plugin: 'kotlin' +apply plugin: 'idea' +apply plugin: 'net.corda.plugins.quasar-utils' +apply plugin: 'net.corda.plugins.publish-utils' +apply plugin: 'net.corda.plugins.cordformation' +apply plugin: 'net.corda.plugins.cordapp' +apply plugin: 'maven-publish' +apply plugin: 'application' + +mainClassName = 'net.corda.irs.IRSDemo' + +sourceSets { + integrationTest { + kotlin { + compileClasspath += main.output + test.output + runtimeClasspath += main.output + test.output + srcDir file('src/integration-test/kotlin') + } + } +} + +configurations { + integrationTestCompile.extendsFrom testCompile + integrationTestRuntime.extendsFrom testRuntime + demoArtifacts.extendsFrom integrationTestRuntime +} + +dependencies { + // The irs demo CorDapp depends upon Cash CorDapp features + cordapp project(':finance') + + // Corda integration dependencies + cordaCompile project(path: ":node:capsule", configuration: 'runtimeArtifacts') + cordaCompile project(':core') + + // Cordapp dependencies + // Specify your cordapp's dependencies below, including dependent cordapps + compile group: 'commons-io', name: 'commons-io', version: '2.5' + + testCompile project(':node-driver') + testCompile "junit:junit:$junit_version" + testCompile "org.assertj:assertj-core:${assertj_version}" +} + +task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) { + + ext.rpcUsers = [ + ['username' : "user", + 'password' : "password", + 'permissions' : [ + "StartFlow.net.corda.irs.flows.AutoOfferFlow\$Requester", + "StartFlow.net.corda.irs.flows.UpdateBusinessDayFlow\$Broadcast", + "StartFlow.net.corda.irs.api.NodeInterestRates\$UploadFixesFlow" + ]] + ] + + directory "./build/nodes" + node { + name "O=Notary Service,L=Zurich,C=CH" + notary = [validating : true] + p2pPort 10002 + rpcPort 10003 + cordapps = ["net.corda:finance:$corda_release_version"] + rpcUsers = ext.rpcUsers + useTestClock true + } + node { + name "O=Bank A,L=London,C=GB" + p2pPort 10005 + rpcPort 10006 + cordapps = ["net.corda:finance:$corda_release_version"] + rpcUsers = ext.rpcUsers + useTestClock true + } + node { + name "O=Bank B,L=New York,C=US" + p2pPort 10008 + rpcPort 10009 + cordapps = ["net.corda:finance:$corda_release_version"] + rpcUsers = ext.rpcUsers + useTestClock true + } +} + +task integrationTest(type: Test, dependsOn: []) { + testClassesDirs = sourceSets.integrationTest.output.classesDirs + classpath = sourceSets.integrationTest.runtimeClasspath +} + +// This fixes the "line too long" error when running this demo with windows CLI +// TODO: Automatically apply to all projects via a plugin +tasks.withType(CreateStartScripts).each { task -> + task.doLast { + String text = task.windowsScript.text + // Replaces the per file classpath (which are all jars in "lib") with a wildcard on lib + text = text.replaceFirst(/(set CLASSPATH=%APP_HOME%\\lib\\).*/, { "${it[1]}*" }) + task.windowsScript.write text + } +} + +idea { + module { + downloadJavadoc = true // defaults to false + downloadSources = true + } +} + +jar { + from sourceSets.test.output +} + +artifacts { + demoArtifacts jar +} \ No newline at end of file diff --git a/samples/irs-demo/src/main/kotlin/net/corda/irs/api/NodeInterestRates.kt b/samples/irs-demo/cordapp/src/main/kotlin/net/corda/irs/api/NodeInterestRates.kt similarity index 100% rename from samples/irs-demo/src/main/kotlin/net/corda/irs/api/NodeInterestRates.kt rename to samples/irs-demo/cordapp/src/main/kotlin/net/corda/irs/api/NodeInterestRates.kt diff --git a/samples/irs-demo/src/main/kotlin/net/corda/irs/contract/IRS.kt b/samples/irs-demo/cordapp/src/main/kotlin/net/corda/irs/contract/IRS.kt similarity index 100% rename from samples/irs-demo/src/main/kotlin/net/corda/irs/contract/IRS.kt rename to samples/irs-demo/cordapp/src/main/kotlin/net/corda/irs/contract/IRS.kt diff --git a/samples/irs-demo/src/main/kotlin/net/corda/irs/contract/IRSExport.kt b/samples/irs-demo/cordapp/src/main/kotlin/net/corda/irs/contract/IRSExport.kt similarity index 100% rename from samples/irs-demo/src/main/kotlin/net/corda/irs/contract/IRSExport.kt rename to samples/irs-demo/cordapp/src/main/kotlin/net/corda/irs/contract/IRSExport.kt diff --git a/samples/irs-demo/src/main/kotlin/net/corda/irs/contract/IRSUtils.kt b/samples/irs-demo/cordapp/src/main/kotlin/net/corda/irs/contract/IRSUtils.kt similarity index 100% rename from samples/irs-demo/src/main/kotlin/net/corda/irs/contract/IRSUtils.kt rename to samples/irs-demo/cordapp/src/main/kotlin/net/corda/irs/contract/IRSUtils.kt diff --git a/samples/irs-demo/src/main/kotlin/net/corda/irs/flows/AutoOfferFlow.kt b/samples/irs-demo/cordapp/src/main/kotlin/net/corda/irs/flows/AutoOfferFlow.kt similarity index 100% rename from samples/irs-demo/src/main/kotlin/net/corda/irs/flows/AutoOfferFlow.kt rename to samples/irs-demo/cordapp/src/main/kotlin/net/corda/irs/flows/AutoOfferFlow.kt diff --git a/samples/irs-demo/src/main/kotlin/net/corda/irs/flows/FixingFlow.kt b/samples/irs-demo/cordapp/src/main/kotlin/net/corda/irs/flows/FixingFlow.kt similarity index 100% rename from samples/irs-demo/src/main/kotlin/net/corda/irs/flows/FixingFlow.kt rename to samples/irs-demo/cordapp/src/main/kotlin/net/corda/irs/flows/FixingFlow.kt diff --git a/samples/irs-demo/src/main/kotlin/net/corda/irs/flows/RatesFixFlow.kt b/samples/irs-demo/cordapp/src/main/kotlin/net/corda/irs/flows/RatesFixFlow.kt similarity index 100% rename from samples/irs-demo/src/main/kotlin/net/corda/irs/flows/RatesFixFlow.kt rename to samples/irs-demo/cordapp/src/main/kotlin/net/corda/irs/flows/RatesFixFlow.kt diff --git a/samples/irs-demo/src/main/kotlin/net/corda/irs/utilities/OracleUtils.kt b/samples/irs-demo/cordapp/src/main/kotlin/net/corda/irs/utilities/OracleUtils.kt similarity index 100% rename from samples/irs-demo/src/main/kotlin/net/corda/irs/utilities/OracleUtils.kt rename to samples/irs-demo/cordapp/src/main/kotlin/net/corda/irs/utilities/OracleUtils.kt diff --git a/samples/irs-demo/src/main/resources/net/corda/irs/simulation/example.rates.txt b/samples/irs-demo/cordapp/src/main/resources/net/corda/irs/simulation/example.rates.txt similarity index 100% rename from samples/irs-demo/src/main/resources/net/corda/irs/simulation/example.rates.txt rename to samples/irs-demo/cordapp/src/main/resources/net/corda/irs/simulation/example.rates.txt diff --git a/samples/irs-demo/src/test/kotlin/net/corda/irs/Main.kt b/samples/irs-demo/cordapp/src/test/kotlin/net/corda/irs/Main.kt similarity index 100% rename from samples/irs-demo/src/test/kotlin/net/corda/irs/Main.kt rename to samples/irs-demo/cordapp/src/test/kotlin/net/corda/irs/Main.kt diff --git a/samples/irs-demo/src/test/kotlin/net/corda/irs/api/NodeInterestRatesTest.kt b/samples/irs-demo/cordapp/src/test/kotlin/net/corda/irs/api/NodeInterestRatesTest.kt similarity index 100% rename from samples/irs-demo/src/test/kotlin/net/corda/irs/api/NodeInterestRatesTest.kt rename to samples/irs-demo/cordapp/src/test/kotlin/net/corda/irs/api/NodeInterestRatesTest.kt diff --git a/samples/irs-demo/src/test/kotlin/net/corda/irs/contract/IRSTests.kt b/samples/irs-demo/cordapp/src/test/kotlin/net/corda/irs/contract/IRSTests.kt similarity index 100% rename from samples/irs-demo/src/test/kotlin/net/corda/irs/contract/IRSTests.kt rename to samples/irs-demo/cordapp/src/test/kotlin/net/corda/irs/contract/IRSTests.kt diff --git a/samples/irs-demo/src/test/kotlin/net/corda/irs/flows/UpdateBusinessDayFlow.kt b/samples/irs-demo/cordapp/src/test/kotlin/net/corda/irs/flows/UpdateBusinessDayFlow.kt similarity index 100% rename from samples/irs-demo/src/test/kotlin/net/corda/irs/flows/UpdateBusinessDayFlow.kt rename to samples/irs-demo/cordapp/src/test/kotlin/net/corda/irs/flows/UpdateBusinessDayFlow.kt diff --git a/samples/irs-demo/src/integration-test/kotlin/net/corda/irs/IRSDemoTest.kt b/samples/irs-demo/src/integration-test/kotlin/net/corda/irs/IRSDemoTest.kt index a598595f0b..f5c6feb921 100644 --- a/samples/irs-demo/src/integration-test/kotlin/net/corda/irs/IRSDemoTest.kt +++ b/samples/irs-demo/src/integration-test/kotlin/net/corda/irs/IRSDemoTest.kt @@ -13,23 +13,25 @@ import net.corda.core.contracts.UniqueIdentifier import net.corda.core.identity.Party import net.corda.core.messaging.vaultTrackBy import net.corda.core.toFuture -import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.loggerFor import net.corda.core.utilities.seconds import net.corda.finance.plugin.registerFinanceJSONMappers import net.corda.irs.contract.InterestRateSwap -import net.corda.irs.utilities.uploadFile +import net.corda.irs.web.IrsDemoWebApplication import net.corda.node.services.config.FullNodeConfiguration import net.corda.nodeapi.User -import net.corda.testing.* -import net.corda.testing.driver.driver +import net.corda.test.spring.springDriver +import net.corda.testing.DUMMY_BANK_A +import net.corda.testing.DUMMY_BANK_B +import net.corda.testing.DUMMY_NOTARY +import net.corda.testing.IntegrationTestCategory +import net.corda.testing.chooseIdentity import net.corda.testing.http.HttpApi import org.apache.commons.io.IOUtils import org.assertj.core.api.Assertions.assertThat import org.junit.Test import rx.Observable -import java.net.URL import java.time.Duration import java.time.LocalDate @@ -39,31 +41,34 @@ class IRSDemoTest : IntegrationTestCategory { val log = loggerFor() } - private val rpcUser = User("user", "password", setOf("ALL")) - private val currentDate: LocalDate = LocalDate.now() - private val futureDate: LocalDate = currentDate.plusMonths(6) - private val maxWaitTime: Duration = 60.seconds + val rpcUsers = listOf(User("user", "password", + setOf("StartFlow.net.corda.irs.flows.AutoOfferFlow\$Requester", + "StartFlow.net.corda.irs.flows.UpdateBusinessDayFlow\$Broadcast", + "StartFlow.net.corda.irs.api.NodeInterestRates\$UploadFixesFlow"))) + + val currentDate: LocalDate = LocalDate.now() + val futureDate: LocalDate = currentDate.plusMonths(6) + val maxWaitTime: Duration = 60.seconds @Test fun `runs IRS demo`() { - driver(useTestClock = true, isDebug = true) { + springDriver(useTestClock = true, isDebug = true, extraCordappPackagesToScan = listOf("net.corda.irs")) { val (controller, nodeA, nodeB) = listOf( - startNotaryNode(DUMMY_NOTARY.name, validating = false), - startNode(providedName = DUMMY_BANK_A.name, rpcUsers = listOf(rpcUser)), - startNode(providedName = DUMMY_BANK_B.name)) - .map { it.getOrThrow() } + startNotaryNode(DUMMY_NOTARY.name, validating = true, rpcUsers = rpcUsers), + startNode(providedName = DUMMY_BANK_A.name, rpcUsers = rpcUsers), + startNode(providedName = DUMMY_BANK_B.name, rpcUsers = rpcUsers)).map { it.getOrThrow() } log.info("All nodes started") - val (controllerAddr, nodeAAddr, nodeBAddr) = listOf( - startWebserver(controller), - startWebserver(nodeA), - startWebserver(nodeB)) - .map { it.getOrThrow().listenAddress } + val controllerAddrFuture = startSpringBootWebapp(IrsDemoWebApplication::class.java, controller, "/api/irs/demodate") + val nodeAAddrFuture = startSpringBootWebapp(IrsDemoWebApplication::class.java, nodeA, "/api/irs/demodate") + val nodeBAddrFuture = startSpringBootWebapp(IrsDemoWebApplication::class.java, nodeB, "/api/irs/demodate") + val (controllerAddr, nodeAAddr, nodeBAddr) = + listOf(controllerAddrFuture, nodeAAddrFuture, nodeBAddrFuture).map { it.getOrThrow().listenAddress } log.info("All webservers started") - val (_, nodeAApi, nodeBApi) = listOf(controller, nodeA, nodeB).zip(listOf(controllerAddr, nodeAAddr, nodeBAddr)).map { + val (controllerApi, nodeAApi, nodeBApi) = listOf(controller, nodeA, nodeB).zip(listOf(controllerAddr, nodeAAddr, nodeBAddr)).map { val mapper = net.corda.client.jackson.JacksonSupport.createDefaultMapper(it.first.rpc) registerFinanceJSONMappers(mapper) registerIRSModule(mapper) @@ -73,7 +78,7 @@ class IRSDemoTest : IntegrationTestCategory { val numADeals = getTradeCount(nodeAApi) val numBDeals = getTradeCount(nodeBApi) - runUploadRates(controllerAddr) + runUploadRates(controllerApi) runTrade(nodeAApi, controller.nodeInfo.chooseIdentity()) assertThat(getTradeCount(nodeAApi)).isEqualTo(numADeals + 1) @@ -89,9 +94,7 @@ class IRSDemoTest : IntegrationTestCategory { } } - private fun getFloatingLegFixCount(nodeApi: HttpApi): Int { - return getTrades(nodeApi)[0].calculation.floatingLegPaymentSchedule.count { it.value.rate.ratioUnit != null } - } + fun getFloatingLegFixCount(nodeApi: HttpApi) = getTrades(nodeApi)[0].calculation.floatingLegPaymentSchedule.count { it.value.rate.ratioUnit != null } private fun getFixingDateObservable(config: FullNodeConfiguration): Observable { val client = CordaRPCClient(config.rpcAddress!!) @@ -111,16 +114,15 @@ class IRSDemoTest : IntegrationTestCategory { private fun runTrade(nodeApi: HttpApi, oracle: Party) { log.info("Running trade against ${nodeApi.root}") - val fileContents = loadResourceFile("net/corda/irs/simulation/example-irs-trade.json") + val fileContents = loadResourceFile("net/corda/irs/web/simulation/example-irs-trade.json") val tradeFile = fileContents.replace("tradeXXX", "trade1").replace("oracleXXX", oracle.name.toString()) assertThat(nodeApi.postJson("deals", tradeFile)).isTrue() } - private fun runUploadRates(host: NetworkHostAndPort) { - log.info("Running upload rates against $host") + private fun runUploadRates(nodeApi: HttpApi) { + log.info("Running upload rates against ${nodeApi.root}") val fileContents = loadResourceFile("net/corda/irs/simulation/example.rates.txt") - val url = URL("http://$host/api/irs/fixes") - assertThat(uploadFile(url, fileContents)).isTrue() + assertThat(nodeApi.postPlain("fixes", fileContents)).isTrue() } private fun loadResourceFile(filename: String): String { diff --git a/samples/irs-demo/src/integration-test/kotlin/net/corda/test/spring/SpringDriver.kt b/samples/irs-demo/src/integration-test/kotlin/net/corda/test/spring/SpringDriver.kt new file mode 100644 index 0000000000..df5b25f480 --- /dev/null +++ b/samples/irs-demo/src/integration-test/kotlin/net/corda/test/spring/SpringDriver.kt @@ -0,0 +1,124 @@ +package net.corda.test.spring + +import net.corda.core.concurrent.CordaFuture +import net.corda.core.internal.concurrent.flatMap +import net.corda.core.internal.concurrent.fork +import net.corda.core.internal.concurrent.map +import net.corda.core.utilities.loggerFor +import net.corda.testing.driver.* +import okhttp3.OkHttpClient +import okhttp3.Request +import java.net.ConnectException +import java.net.URL +import java.nio.file.Path +import java.nio.file.Paths +import java.util.concurrent.ExecutorService +import java.util.concurrent.TimeUnit + +interface SpringDriverExposedDSLInterface : DriverDSLExposedInterface { + + /** + * Starts a Spring Boot application, passes the RPC connection data as parameters the process. + * Returns future which will complete after (and if) the server passes healthcheck. + * @param clazz Class with main method which is expected to run Spring application + * @param handle Corda Node handle this webapp is expected to connect to + * @param checkUrl URL path to use for server readiness check - uses [okhttp3.Response.isSuccessful] as qualifier + * + * TODO: Rather then expecting a given clazz to contain main method which start Spring app our own simple class can do this + */ + fun startSpringBootWebapp(clazz: Class<*>, handle: NodeHandle, checkUrl: String): CordaFuture +} + +interface SpringDriverInternalDSLInterface : DriverDSLInternalInterface, SpringDriverExposedDSLInterface + +fun springDriver( + defaultParameters: DriverParameters = DriverParameters(), + isDebug: Boolean = defaultParameters.isDebug, + driverDirectory: Path = defaultParameters.driverDirectory, + portAllocation: PortAllocation = defaultParameters.portAllocation, + debugPortAllocation: PortAllocation = defaultParameters.debugPortAllocation, + systemProperties: Map = defaultParameters.systemProperties, + useTestClock: Boolean = defaultParameters.useTestClock, + initialiseSerialization: Boolean = defaultParameters.initialiseSerialization, + startNodesInProcess: Boolean = defaultParameters.startNodesInProcess, + extraCordappPackagesToScan: List = defaultParameters.extraCordappPackagesToScan, + dsl: SpringDriverExposedDSLInterface.() -> A +) = genericDriver( + defaultParameters = defaultParameters, + isDebug = isDebug, + driverDirectory = driverDirectory, + portAllocation = portAllocation, + debugPortAllocation = debugPortAllocation, + systemProperties = systemProperties, + useTestClock = useTestClock, + initialiseSerialization = initialiseSerialization, + startNodesInProcess = startNodesInProcess, + extraCordappPackagesToScan = extraCordappPackagesToScan, + driverDslWrapper = { driverDSL:DriverDSL -> SpringBootDriverDSL(driverDSL) }, + coerce = { it }, + dsl = dsl +) + +data class SpringBootDriverDSL( + val driverDSL: DriverDSL +) : DriverDSLInternalInterface by driverDSL, SpringDriverInternalDSLInterface { + + val log = loggerFor() + + override fun startSpringBootWebapp(clazz: Class<*>, handle: NodeHandle, checkUrl: String): CordaFuture { + val debugPort = if (driverDSL.isDebug) driverDSL.debugPortAllocation.nextPort() else null + val processFuture = startApplication(driverDSL.executorService, handle, debugPort, clazz) + driverDSL.registerProcess(processFuture) + return processFuture.map { queryWebserver(handle, it, checkUrl) } + } + + private fun queryWebserver(handle: NodeHandle, process: Process, checkUrl: String): WebserverHandle { + val protocol = if (handle.configuration.useHTTPS) "https://" else "http://" + val url = URL(URL("$protocol${handle.webAddress}"), checkUrl) + val client = OkHttpClient.Builder().connectTimeout(5, TimeUnit.SECONDS).readTimeout(10, TimeUnit.SECONDS).build() + + var maxRetries = 30 + + while (process.isAlive && maxRetries > 0) try { + val response = client.newCall(Request.Builder().url(url).build()).execute() + response.use { + if (response.isSuccessful) { + return WebserverHandle(handle.webAddress, process) + } + } + + TimeUnit.SECONDS.sleep(2) + maxRetries-- + } catch (e: ConnectException) { + log.debug("Retrying webserver info at ${handle.webAddress}") + } + + throw IllegalStateException("Webserver at ${handle.webAddress} has died or was not reachable at URL ${url}") + } + + private fun startApplication(executorService: ExecutorService, handle: NodeHandle, debugPort: Int?, clazz: Class<*>): CordaFuture { + return executorService.fork { + val className = clazz.canonicalName + ProcessUtilities.startJavaProcessImpl( + className = className, // cannot directly get class for this, so just use string + jdwpPort = debugPort, + extraJvmArguments = listOf( + "-Dname=node-${handle.configuration.p2pAddress}-webserver", + "-Djava.io.tmpdir=${System.getProperty("java.io.tmpdir")}" + // Inherit from parent process + ), + classpath = ProcessUtilities.defaultClassPath, + workingDirectory = handle.configuration.baseDirectory, + errorLogPath = Paths.get("error.$className.log"), + arguments = listOf( + "--base-directory", handle.configuration.baseDirectory.toString(), + "--server.port=${handle.webAddress.port}", + "--corda.host=${handle.configuration.rpcAddress}", + "--corda.user=${handle.configuration.rpcUsers.first().username}", + "--corda.password=${handle.configuration.rpcUsers.first().password}" + ), + maximumHeapSize = null + ) + }.flatMap { process -> addressMustBeBoundFuture(driverDSL.executorService, handle.webAddress, process).map { process } } + } +} \ No newline at end of file diff --git a/samples/irs-demo/src/main/kotlin/net/corda/irs/plugin/IRSPlugin.kt b/samples/irs-demo/src/main/kotlin/net/corda/irs/plugin/IRSPlugin.kt deleted file mode 100644 index 38d5455e8f..0000000000 --- a/samples/irs-demo/src/main/kotlin/net/corda/irs/plugin/IRSPlugin.kt +++ /dev/null @@ -1,16 +0,0 @@ -package net.corda.irs.plugin - -import com.fasterxml.jackson.databind.ObjectMapper -import net.corda.finance.plugin.registerFinanceJSONMappers -import net.corda.irs.api.InterestRateSwapAPI -import net.corda.webserver.services.WebServerPluginRegistry -import java.util.function.Function - -class IRSPlugin : WebServerPluginRegistry { - override val webApis = listOf(Function(::InterestRateSwapAPI)) - override val staticServeDirs: Map = mapOf( - "irsdemo" to javaClass.classLoader.getResource("irsweb").toExternalForm() - ) - - override fun customizeJSONSerialization(om: ObjectMapper): Unit = registerFinanceJSONMappers(om) -} diff --git a/samples/irs-demo/src/main/kotlin/net/corda/irs/utilities/HttpUtils.kt b/samples/irs-demo/src/main/kotlin/net/corda/irs/utilities/HttpUtils.kt deleted file mode 100644 index 3337730462..0000000000 --- a/samples/irs-demo/src/main/kotlin/net/corda/irs/utilities/HttpUtils.kt +++ /dev/null @@ -1,42 +0,0 @@ -package net.corda.irs.utilities - -import okhttp3.MediaType -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.RequestBody -import java.net.URL -import java.util.concurrent.TimeUnit - -/** - * A small set of utilities for making HttpCalls, aimed at demos. - */ -private val client by lazy { - OkHttpClient.Builder() - .connectTimeout(5, TimeUnit.SECONDS) - .readTimeout(60, TimeUnit.SECONDS).build() -} - -fun putJson(url: URL, data: String): Boolean { - val body = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), data) - return makeRequest(Request.Builder().url(url).put(body).build()) -} - -fun postJson(url: URL, data: String): Boolean { - val body = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), data) - return makeRequest(Request.Builder().url(url).post(body).build()) -} - -fun uploadFile(url: URL, file: String): Boolean { - val body = RequestBody.create(MediaType.parse("text/plain; charset=utf-8"), file) - return makeRequest(Request.Builder().url(url).post(body).build()) -} - -private fun makeRequest(request: Request): Boolean { - val response = client.newCall(request).execute() - - if (!response.isSuccessful) { - println("Could not fulfill HTTP request. Status Code: ${response.code()}. Message: ${response.body().string()}") - } - response.close() - return response.isSuccessful -} diff --git a/samples/irs-demo/src/main/resources/META-INF/services/net.corda.webserver.services.WebServerPluginRegistry b/samples/irs-demo/src/main/resources/META-INF/services/net.corda.webserver.services.WebServerPluginRegistry deleted file mode 100644 index 5d1ad4fd3f..0000000000 --- a/samples/irs-demo/src/main/resources/META-INF/services/net.corda.webserver.services.WebServerPluginRegistry +++ /dev/null @@ -1,2 +0,0 @@ -# Register a ServiceLoader service extending from net.corda.webserver.services.WebServerPluginRegistry -net.corda.irs.plugin.IRSPlugin diff --git a/samples/irs-demo/src/test/kotlin/net/corda/irs/plugin/IRSDemoPlugin.kt b/samples/irs-demo/src/test/kotlin/net/corda/irs/plugin/IRSDemoPlugin.kt deleted file mode 100644 index c7d2e50474..0000000000 --- a/samples/irs-demo/src/test/kotlin/net/corda/irs/plugin/IRSDemoPlugin.kt +++ /dev/null @@ -1,9 +0,0 @@ -package net.corda.irs.plugin - -import net.corda.irs.api.InterestRatesSwapDemoAPI -import net.corda.webserver.services.WebServerPluginRegistry -import java.util.function.Function - -class IRSDemoPlugin : WebServerPluginRegistry { - override val webApis = listOf(Function(::InterestRatesSwapDemoAPI)) -} \ No newline at end of file diff --git a/samples/irs-demo/src/test/resources/META-INF/services/net.corda.webserver.services.WebServerPluginRegistry b/samples/irs-demo/src/test/resources/META-INF/services/net.corda.webserver.services.WebServerPluginRegistry deleted file mode 100644 index d19d0b4b92..0000000000 --- a/samples/irs-demo/src/test/resources/META-INF/services/net.corda.webserver.services.WebServerPluginRegistry +++ /dev/null @@ -1,2 +0,0 @@ -net.corda.irs.plugin.IRSPlugin -net.corda.irs.plugin.IRSDemoPlugin \ No newline at end of file diff --git a/samples/irs-demo/web/build.gradle b/samples/irs-demo/web/build.gradle new file mode 100644 index 0000000000..f2093facb1 --- /dev/null +++ b/samples/irs-demo/web/build.gradle @@ -0,0 +1,55 @@ +buildscript { + repositories { + mavenCentral() + } + dependencies { + classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}") + classpath("org.jetbrains.kotlin:kotlin-allopen:${kotlin_version}") + } +} + +// Spring Boot plugin adds a numerous hardcoded dependencies in the version much lower then Corda expects +// causing the problems in runtime. Those can be changed by manipulating above properties +// See https://github.com/spring-gradle-plugins/dependency-management-plugin/blob/master/README.md#changing-the-value-of-a-version-property +ext['artemis.version'] = "$artemis_version" +ext['hibernate.version'] = "$hibernate_version" + +apply plugin: 'kotlin' +apply plugin: 'kotlin-spring' +apply plugin: 'eclipse' +apply plugin: 'org.springframework.boot' +apply plugin: 'project-report' +apply plugin: 'application' + +dependencies { + compile('org.springframework.boot:spring-boot-starter-web') { + exclude module: "spring-boot-starter-logging" + exclude module: "logback-classic" + } + compile("com.fasterxml.jackson.module:jackson-module-kotlin:2.8.9") + compile project(":client:rpc") + compile project(":client:jackson") + compile project(":test-utils") + compile project(path: ":samples:irs-demo:cordapp", configuration: "demoArtifacts") + testCompile('org.springframework.boot:spring-boot-starter-test') { + exclude module: "spring-boot-starter-logging" + exclude module: "logback-classic" + } +} + +jar { + from sourceSets.test.output +} + +task deployWebapps(type: Copy, dependsOn: ['jar', 'bootRepackage']) { + ext.webappDir = file("build/webapps") + + from(jar.outputs) + from("src/test/resources/scripts/") { + filter { it + .replace('#JAR_PATH#', jar.archiveName) + .replace('#DIR#', ext.webappDir.getAbsolutePath()) + } + } + into ext.webappDir +} \ No newline at end of file diff --git a/samples/irs-demo/web/src/main/kotlin/net/corda/irs/web/IrsDemoWebApplication.kt b/samples/irs-demo/web/src/main/kotlin/net/corda/irs/web/IrsDemoWebApplication.kt new file mode 100644 index 0000000000..b6e81f928f --- /dev/null +++ b/samples/irs-demo/web/src/main/kotlin/net/corda/irs/web/IrsDemoWebApplication.kt @@ -0,0 +1,52 @@ +package net.corda.irs.web + +import com.fasterxml.jackson.databind.ObjectMapper +import net.corda.client.jackson.JacksonSupport +import net.corda.client.rpc.CordaRPCClient +import net.corda.core.messaging.CordaRPCOps +import net.corda.core.utilities.NetworkHostAndPort +import net.corda.finance.plugin.registerFinanceJSONMappers +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.beans.factory.annotation.Value +import org.springframework.boot.SpringApplication +import org.springframework.boot.autoconfigure.SpringBootApplication +import org.springframework.context.annotation.Bean + +/** + * Simple and sample SpringBoot web application which communicates with Corda node using RPC. + * [CordaRPCOps] instance can be managed simply as plain Spring bean. + * If support for (de)serializatin of Corda classes is required, [ObjectMapper] can be configured using helper + * functions, see [objectMapper] + */ +@SpringBootApplication +class IrsDemoWebApplication { + @Value("\${corda.host}") + lateinit var cordaHost:String + + @Value("\${corda.user}") + lateinit var cordaUser:String + + @Value("\${corda.password}") + lateinit var cordaPassword:String + + + @Bean + fun rpcClient(): CordaRPCOps { + return CordaRPCClient(NetworkHostAndPort.parse(cordaHost)).start(cordaUser, cordaPassword).proxy + } + + @Bean + fun objectMapper(@Autowired cordaRPCOps: CordaRPCOps): ObjectMapper { + val mapper = JacksonSupport.createDefaultMapper(cordaRPCOps) + registerFinanceJSONMappers(mapper) + return mapper + } + + // running as standalone java app + companion object { + @JvmStatic fun main(args: Array) { + SpringApplication.run(IrsDemoWebApplication::class.java, *args) + } + } +} + diff --git a/samples/irs-demo/src/main/kotlin/net/corda/irs/api/InterestRateSwapAPI.kt b/samples/irs-demo/web/src/main/kotlin/net/corda/irs/web/api/InterestSwapRestAPI.kt similarity index 68% rename from samples/irs-demo/src/main/kotlin/net/corda/irs/api/InterestRateSwapAPI.kt rename to samples/irs-demo/web/src/main/kotlin/net/corda/irs/web/api/InterestSwapRestAPI.kt index 2377a9c86a..1c1a564d19 100644 --- a/samples/irs-demo/src/main/kotlin/net/corda/irs/api/InterestRateSwapAPI.kt +++ b/samples/irs-demo/web/src/main/kotlin/net/corda/irs/web/api/InterestSwapRestAPI.kt @@ -1,4 +1,4 @@ -package net.corda.irs.api +package net.corda.irs.web.api import net.corda.core.contracts.filterStatesOfType import net.corda.core.messaging.CordaRPCOps @@ -7,11 +7,12 @@ import net.corda.core.messaging.vaultQueryBy import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.loggerFor import net.corda.irs.contract.InterestRateSwap -import net.corda.irs.flows.AutoOfferFlow +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.web.bind.annotation.* +import org.springframework.http.HttpStatus +import org.springframework.http.ResponseEntity import java.net.URI -import javax.ws.rs.* -import javax.ws.rs.core.MediaType -import javax.ws.rs.core.Response +import net.corda.irs.flows.AutoOfferFlow /** * This provides a simplified API, currently for demonstration use only. @@ -28,8 +29,10 @@ import javax.ws.rs.core.Response * * TODO: replace simulated date advancement with business event based implementation */ -@Path("irs") -class InterestRateSwapAPI(val rpc: CordaRPCOps) { + +@RestController +@RequestMapping("/api/irs") +class InterestRateSwapAPI { private val logger = loggerFor() @@ -44,6 +47,10 @@ class InterestRateSwapAPI(val rpc: CordaRPCOps) { } } + + @Autowired + lateinit var rpc: CordaRPCOps + private fun getAllDeals(): Array { val vault = rpc.vaultQueryBy().states val states = vault.filterStatesOfType() @@ -51,33 +58,30 @@ class InterestRateSwapAPI(val rpc: CordaRPCOps) { return swaps } - @GET - @Path("deals") - @Produces(MediaType.APPLICATION_JSON) + @GetMapping("/deals") fun fetchDeals(): Array = getAllDeals() - @POST - @Path("deals") - @Consumes(MediaType.APPLICATION_JSON) - fun storeDeal(newDeal: InterestRateSwap.State): Response { + @PostMapping("/deals") + fun storeDeal(@RequestBody newDeal: InterestRateSwap.State): ResponseEntity { return try { rpc.startFlow(AutoOfferFlow::Requester, newDeal).returnValue.getOrThrow() - Response.created(URI.create(generateDealLink(newDeal))).build() + ResponseEntity.created(URI.create(generateDealLink(newDeal))).build() } catch (ex: Throwable) { - logger.info("Exception when creating deal: $ex") - Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(ex.toString()).build() + logger.info("Exception when creating deal: $ex", ex) + ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(ex.toString()) } } - @GET - @Path("deals/{ref}") - @Produces(MediaType.APPLICATION_JSON) - fun fetchDeal(@PathParam("ref") ref: String): Response { - val deal = getDealByRef(ref) - if (deal == null) { - return Response.status(Response.Status.NOT_FOUND).build() + @GetMapping("/deals/{ref:.+}") + fun fetchDeal(@PathVariable ref: String?): ResponseEntity { + val deal = getDealByRef(ref!!) + return if (deal == null) { + ResponseEntity.notFound().build() } else { - return Response.ok().entity(deal).build() + ResponseEntity.ok(deal) } } + + @GetMapping("/deals/networksnapshot") + fun fetchDeal() = rpc.networkMapSnapshot().toString() } diff --git a/samples/irs-demo/web/src/main/resources/application-BankA.properties b/samples/irs-demo/web/src/main/resources/application-BankA.properties new file mode 100644 index 0000000000..600944e6a5 --- /dev/null +++ b/samples/irs-demo/web/src/main/resources/application-BankA.properties @@ -0,0 +1,2 @@ +corda.host=localhost:10006 +server.port=10007 \ No newline at end of file diff --git a/samples/irs-demo/web/src/main/resources/application-BankB.properties b/samples/irs-demo/web/src/main/resources/application-BankB.properties new file mode 100644 index 0000000000..187c6cc5c2 --- /dev/null +++ b/samples/irs-demo/web/src/main/resources/application-BankB.properties @@ -0,0 +1,2 @@ +corda.host=localhost:10009 +server.port=10010 \ No newline at end of file diff --git a/samples/irs-demo/web/src/main/resources/application-NotaryService.properties b/samples/irs-demo/web/src/main/resources/application-NotaryService.properties new file mode 100644 index 0000000000..e3de4502b7 --- /dev/null +++ b/samples/irs-demo/web/src/main/resources/application-NotaryService.properties @@ -0,0 +1,2 @@ +corda.host=localhost:10003 +server.port=10004 \ No newline at end of file diff --git a/samples/irs-demo/web/src/main/resources/application.properties b/samples/irs-demo/web/src/main/resources/application.properties new file mode 100644 index 0000000000..fae7da6e77 --- /dev/null +++ b/samples/irs-demo/web/src/main/resources/application.properties @@ -0,0 +1,2 @@ +corda.user=user +corda.password=password \ No newline at end of file diff --git a/samples/irs-demo/src/main/resources/irsweb/.bowerrc b/samples/irs-demo/web/src/main/resources/static/.bowerrc similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/.bowerrc rename to samples/irs-demo/web/src/main/resources/static/.bowerrc diff --git a/samples/irs-demo/src/main/resources/irsweb/bower.json b/samples/irs-demo/web/src/main/resources/static/bower.json similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/bower.json rename to samples/irs-demo/web/src/main/resources/static/bower.json diff --git a/samples/irs-demo/src/main/resources/irsweb/css/main.css b/samples/irs-demo/web/src/main/resources/static/css/main.css similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/css/main.css rename to samples/irs-demo/web/src/main/resources/static/css/main.css diff --git a/samples/irs-demo/src/main/resources/irsweb/index.html b/samples/irs-demo/web/src/main/resources/static/index.html similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/index.html rename to samples/irs-demo/web/src/main/resources/static/index.html diff --git a/samples/irs-demo/src/main/resources/irsweb/js/Deal.js b/samples/irs-demo/web/src/main/resources/static/js/Deal.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/Deal.js rename to samples/irs-demo/web/src/main/resources/static/js/Deal.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/app.js b/samples/irs-demo/web/src/main/resources/static/js/app.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/app.js rename to samples/irs-demo/web/src/main/resources/static/js/app.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/angular-fcsa-number/.bower.json b/samples/irs-demo/web/src/main/resources/static/js/bower_components/angular-fcsa-number/.bower.json similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/angular-fcsa-number/.bower.json rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/angular-fcsa-number/.bower.json diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/angular-fcsa-number/CHANGLOG.md b/samples/irs-demo/web/src/main/resources/static/js/bower_components/angular-fcsa-number/CHANGLOG.md similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/angular-fcsa-number/CHANGLOG.md rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/angular-fcsa-number/CHANGLOG.md diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/angular-fcsa-number/LICENSE b/samples/irs-demo/web/src/main/resources/static/js/bower_components/angular-fcsa-number/LICENSE similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/angular-fcsa-number/LICENSE rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/angular-fcsa-number/LICENSE diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/angular-fcsa-number/README.md b/samples/irs-demo/web/src/main/resources/static/js/bower_components/angular-fcsa-number/README.md similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/angular-fcsa-number/README.md rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/angular-fcsa-number/README.md diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/angular-fcsa-number/bower.json b/samples/irs-demo/web/src/main/resources/static/js/bower_components/angular-fcsa-number/bower.json similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/angular-fcsa-number/bower.json rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/angular-fcsa-number/bower.json diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/angular-fcsa-number/e2e/fcsaNumber.e2e.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/angular-fcsa-number/e2e/fcsaNumber.e2e.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/angular-fcsa-number/e2e/fcsaNumber.e2e.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/angular-fcsa-number/e2e/fcsaNumber.e2e.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/angular-fcsa-number/e2e/protractor.config b/samples/irs-demo/web/src/main/resources/static/js/bower_components/angular-fcsa-number/e2e/protractor.config similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/angular-fcsa-number/e2e/protractor.config rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/angular-fcsa-number/e2e/protractor.config diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/angular-fcsa-number/e2e/web/app.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/angular-fcsa-number/e2e/web/app.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/angular-fcsa-number/e2e/web/app.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/angular-fcsa-number/e2e/web/app.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/angular-fcsa-number/e2e/web/public/angular.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/angular-fcsa-number/e2e/web/public/angular.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/angular-fcsa-number/e2e/web/public/angular.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/angular-fcsa-number/e2e/web/public/angular.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/angular-fcsa-number/e2e/web/public/fcsaNumber.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/angular-fcsa-number/e2e/web/public/fcsaNumber.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/angular-fcsa-number/e2e/web/public/fcsaNumber.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/angular-fcsa-number/e2e/web/public/fcsaNumber.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/angular-fcsa-number/e2e/web/public/index.html b/samples/irs-demo/web/src/main/resources/static/js/bower_components/angular-fcsa-number/e2e/web/public/index.html similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/angular-fcsa-number/e2e/web/public/index.html rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/angular-fcsa-number/e2e/web/public/index.html diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/angular-fcsa-number/src/fcsaNumber.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/angular-fcsa-number/src/fcsaNumber.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/angular-fcsa-number/src/fcsaNumber.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/angular-fcsa-number/src/fcsaNumber.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/angular-fcsa-number/src/fcsaNumber.min.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/angular-fcsa-number/src/fcsaNumber.min.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/angular-fcsa-number/src/fcsaNumber.min.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/angular-fcsa-number/src/fcsaNumber.min.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/angular-route/.bower.json b/samples/irs-demo/web/src/main/resources/static/js/bower_components/angular-route/.bower.json similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/angular-route/.bower.json rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/angular-route/.bower.json diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/angular-route/LICENSE.md b/samples/irs-demo/web/src/main/resources/static/js/bower_components/angular-route/LICENSE.md similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/angular-route/LICENSE.md rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/angular-route/LICENSE.md diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/angular-route/README.md b/samples/irs-demo/web/src/main/resources/static/js/bower_components/angular-route/README.md similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/angular-route/README.md rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/angular-route/README.md diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/angular-route/angular-route.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/angular-route/angular-route.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/angular-route/angular-route.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/angular-route/angular-route.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/angular-route/angular-route.min.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/angular-route/angular-route.min.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/angular-route/angular-route.min.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/angular-route/angular-route.min.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/angular-route/angular-route.min.js.map b/samples/irs-demo/web/src/main/resources/static/js/bower_components/angular-route/angular-route.min.js.map similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/angular-route/angular-route.min.js.map rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/angular-route/angular-route.min.js.map diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/angular-route/bower.json b/samples/irs-demo/web/src/main/resources/static/js/bower_components/angular-route/bower.json similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/angular-route/bower.json rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/angular-route/bower.json diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/angular-route/index.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/angular-route/index.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/angular-route/index.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/angular-route/index.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/angular-route/package.json b/samples/irs-demo/web/src/main/resources/static/js/bower_components/angular-route/package.json similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/angular-route/package.json rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/angular-route/package.json diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/angular/.bower.json b/samples/irs-demo/web/src/main/resources/static/js/bower_components/angular/.bower.json similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/angular/.bower.json rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/angular/.bower.json diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/angular/LICENSE.md b/samples/irs-demo/web/src/main/resources/static/js/bower_components/angular/LICENSE.md similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/angular/LICENSE.md rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/angular/LICENSE.md diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/angular/README.md b/samples/irs-demo/web/src/main/resources/static/js/bower_components/angular/README.md similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/angular/README.md rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/angular/README.md diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/angular/angular-csp.css b/samples/irs-demo/web/src/main/resources/static/js/bower_components/angular/angular-csp.css similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/angular/angular-csp.css rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/angular/angular-csp.css diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/angular/angular.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/angular/angular.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/angular/angular.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/angular/angular.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/angular/angular.min.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/angular/angular.min.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/angular/angular.min.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/angular/angular.min.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/angular/angular.min.js.gzip b/samples/irs-demo/web/src/main/resources/static/js/bower_components/angular/angular.min.js.gzip similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/angular/angular.min.js.gzip rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/angular/angular.min.js.gzip diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/angular/angular.min.js.map b/samples/irs-demo/web/src/main/resources/static/js/bower_components/angular/angular.min.js.map similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/angular/angular.min.js.map rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/angular/angular.min.js.map diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/angular/bower.json b/samples/irs-demo/web/src/main/resources/static/js/bower_components/angular/bower.json similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/angular/bower.json rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/angular/bower.json diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/angular/index.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/angular/index.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/angular/index.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/angular/index.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/angular/package.json b/samples/irs-demo/web/src/main/resources/static/js/bower_components/angular/package.json similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/angular/package.json rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/angular/package.json diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery.maskedinput/.bower.json b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery.maskedinput/.bower.json similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery.maskedinput/.bower.json rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery.maskedinput/.bower.json diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery.maskedinput/CONTRIBUTING.md b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery.maskedinput/CONTRIBUTING.md similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery.maskedinput/CONTRIBUTING.md rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery.maskedinput/CONTRIBUTING.md diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery.maskedinput/LICENSE b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery.maskedinput/LICENSE similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery.maskedinput/LICENSE rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery.maskedinput/LICENSE diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery.maskedinput/README.md b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery.maskedinput/README.md similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery.maskedinput/README.md rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery.maskedinput/README.md diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery.maskedinput/bower.json b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery.maskedinput/bower.json similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery.maskedinput/bower.json rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery.maskedinput/bower.json diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery.maskedinput/demo/datepicker.html b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery.maskedinput/demo/datepicker.html similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery.maskedinput/demo/datepicker.html rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery.maskedinput/demo/datepicker.html diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery.maskedinput/demo/index.html b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery.maskedinput/demo/index.html similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery.maskedinput/demo/index.html rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery.maskedinput/demo/index.html diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery.maskedinput/dist/jquery.maskedinput.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery.maskedinput/dist/jquery.maskedinput.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery.maskedinput/dist/jquery.maskedinput.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery.maskedinput/dist/jquery.maskedinput.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery.maskedinput/dist/jquery.maskedinput.min.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery.maskedinput/dist/jquery.maskedinput.min.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery.maskedinput/dist/jquery.maskedinput.min.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery.maskedinput/dist/jquery.maskedinput.min.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery.maskedinput/gruntfile.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery.maskedinput/gruntfile.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery.maskedinput/gruntfile.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery.maskedinput/gruntfile.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery.maskedinput/jquery.maskedinput.nuspec b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery.maskedinput/jquery.maskedinput.nuspec similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery.maskedinput/jquery.maskedinput.nuspec rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery.maskedinput/jquery.maskedinput.nuspec diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery.maskedinput/package.json b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery.maskedinput/package.json similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery.maskedinput/package.json rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery.maskedinput/package.json diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery.maskedinput/src/jquery.maskedinput.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery.maskedinput/src/jquery.maskedinput.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery.maskedinput/src/jquery.maskedinput.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery.maskedinput/src/jquery.maskedinput.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/.bower.json b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/.bower.json similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/.bower.json rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/.bower.json diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/AUTHORS.txt b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/AUTHORS.txt similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/AUTHORS.txt rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/AUTHORS.txt diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/LICENSE.txt b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/LICENSE.txt similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/LICENSE.txt rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/LICENSE.txt diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/README.md b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/README.md similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/README.md rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/README.md diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/bower.json b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/bower.json similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/bower.json rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/bower.json diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/dist/core.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/dist/core.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/dist/core.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/dist/core.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/dist/jquery.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/dist/jquery.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/dist/jquery.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/dist/jquery.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/dist/jquery.min.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/dist/jquery.min.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/dist/jquery.min.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/dist/jquery.min.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/dist/jquery.min.map b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/dist/jquery.min.map similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/dist/jquery.min.map rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/dist/jquery.min.map diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/dist/jquery.slim.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/dist/jquery.slim.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/dist/jquery.slim.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/dist/jquery.slim.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/dist/jquery.slim.min.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/dist/jquery.slim.min.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/dist/jquery.slim.min.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/dist/jquery.slim.min.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/dist/jquery.slim.min.map b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/dist/jquery.slim.min.map similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/dist/jquery.slim.min.map rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/dist/jquery.slim.min.map diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/external/sizzle/LICENSE.txt b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/external/sizzle/LICENSE.txt similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/external/sizzle/LICENSE.txt rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/external/sizzle/LICENSE.txt diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/external/sizzle/dist/sizzle.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/external/sizzle/dist/sizzle.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/external/sizzle/dist/sizzle.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/external/sizzle/dist/sizzle.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/external/sizzle/dist/sizzle.min.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/external/sizzle/dist/sizzle.min.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/external/sizzle/dist/sizzle.min.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/external/sizzle/dist/sizzle.min.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/external/sizzle/dist/sizzle.min.map b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/external/sizzle/dist/sizzle.min.map similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/external/sizzle/dist/sizzle.min.map rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/external/sizzle/dist/sizzle.min.map diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/.eslintrc.json b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/.eslintrc.json similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/.eslintrc.json rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/.eslintrc.json diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/ajax.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/ajax.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/ajax.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/ajax.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/ajax/jsonp.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/ajax/jsonp.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/ajax/jsonp.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/ajax/jsonp.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/ajax/load.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/ajax/load.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/ajax/load.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/ajax/load.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/ajax/parseXML.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/ajax/parseXML.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/ajax/parseXML.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/ajax/parseXML.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/ajax/script.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/ajax/script.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/ajax/script.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/ajax/script.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/ajax/var/location.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/ajax/var/location.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/ajax/var/location.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/ajax/var/location.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/ajax/var/nonce.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/ajax/var/nonce.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/ajax/var/nonce.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/ajax/var/nonce.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/ajax/var/rquery.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/ajax/var/rquery.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/ajax/var/rquery.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/ajax/var/rquery.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/ajax/xhr.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/ajax/xhr.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/ajax/xhr.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/ajax/xhr.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/attributes.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/attributes.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/attributes.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/attributes.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/attributes/attr.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/attributes/attr.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/attributes/attr.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/attributes/attr.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/attributes/classes.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/attributes/classes.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/attributes/classes.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/attributes/classes.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/attributes/prop.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/attributes/prop.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/attributes/prop.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/attributes/prop.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/attributes/support.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/attributes/support.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/attributes/support.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/attributes/support.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/attributes/val.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/attributes/val.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/attributes/val.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/attributes/val.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/callbacks.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/callbacks.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/callbacks.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/callbacks.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/core.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/core.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/core.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/core.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/core/DOMEval.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/core/DOMEval.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/core/DOMEval.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/core/DOMEval.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/core/access.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/core/access.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/core/access.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/core/access.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/core/init.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/core/init.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/core/init.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/core/init.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/core/parseHTML.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/core/parseHTML.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/core/parseHTML.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/core/parseHTML.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/core/ready-no-deferred.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/core/ready-no-deferred.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/core/ready-no-deferred.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/core/ready-no-deferred.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/core/ready.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/core/ready.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/core/ready.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/core/ready.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/core/readyException.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/core/readyException.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/core/readyException.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/core/readyException.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/core/stripAndCollapse.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/core/stripAndCollapse.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/core/stripAndCollapse.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/core/stripAndCollapse.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/core/support.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/core/support.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/core/support.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/core/support.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/core/var/rsingleTag.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/core/var/rsingleTag.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/core/var/rsingleTag.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/core/var/rsingleTag.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/css.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/css.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/css.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/css.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/css/addGetHookIf.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/css/addGetHookIf.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/css/addGetHookIf.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/css/addGetHookIf.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/css/adjustCSS.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/css/adjustCSS.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/css/adjustCSS.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/css/adjustCSS.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/css/curCSS.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/css/curCSS.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/css/curCSS.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/css/curCSS.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/css/hiddenVisibleSelectors.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/css/hiddenVisibleSelectors.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/css/hiddenVisibleSelectors.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/css/hiddenVisibleSelectors.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/css/showHide.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/css/showHide.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/css/showHide.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/css/showHide.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/css/support.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/css/support.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/css/support.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/css/support.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/css/var/cssExpand.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/css/var/cssExpand.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/css/var/cssExpand.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/css/var/cssExpand.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/css/var/getStyles.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/css/var/getStyles.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/css/var/getStyles.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/css/var/getStyles.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/css/var/isHiddenWithinTree.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/css/var/isHiddenWithinTree.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/css/var/isHiddenWithinTree.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/css/var/isHiddenWithinTree.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/css/var/rmargin.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/css/var/rmargin.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/css/var/rmargin.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/css/var/rmargin.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/css/var/rnumnonpx.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/css/var/rnumnonpx.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/css/var/rnumnonpx.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/css/var/rnumnonpx.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/css/var/swap.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/css/var/swap.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/css/var/swap.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/css/var/swap.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/data.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/data.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/data.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/data.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/data/Data.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/data/Data.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/data/Data.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/data/Data.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/data/var/acceptData.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/data/var/acceptData.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/data/var/acceptData.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/data/var/acceptData.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/data/var/dataPriv.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/data/var/dataPriv.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/data/var/dataPriv.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/data/var/dataPriv.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/data/var/dataUser.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/data/var/dataUser.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/data/var/dataUser.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/data/var/dataUser.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/deferred.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/deferred.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/deferred.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/deferred.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/deferred/exceptionHook.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/deferred/exceptionHook.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/deferred/exceptionHook.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/deferred/exceptionHook.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/deprecated.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/deprecated.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/deprecated.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/deprecated.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/dimensions.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/dimensions.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/dimensions.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/dimensions.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/effects.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/effects.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/effects.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/effects.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/effects/Tween.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/effects/Tween.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/effects/Tween.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/effects/Tween.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/effects/animatedSelector.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/effects/animatedSelector.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/effects/animatedSelector.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/effects/animatedSelector.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/event.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/event.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/event.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/event.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/event/ajax.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/event/ajax.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/event/ajax.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/event/ajax.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/event/alias.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/event/alias.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/event/alias.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/event/alias.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/event/focusin.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/event/focusin.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/event/focusin.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/event/focusin.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/event/support.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/event/support.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/event/support.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/event/support.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/event/trigger.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/event/trigger.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/event/trigger.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/event/trigger.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/exports/amd.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/exports/amd.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/exports/amd.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/exports/amd.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/exports/global.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/exports/global.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/exports/global.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/exports/global.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/jquery.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/jquery.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/jquery.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/jquery.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/manipulation.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/manipulation.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/manipulation.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/manipulation.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/manipulation/_evalUrl.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/manipulation/_evalUrl.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/manipulation/_evalUrl.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/manipulation/_evalUrl.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/manipulation/buildFragment.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/manipulation/buildFragment.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/manipulation/buildFragment.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/manipulation/buildFragment.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/manipulation/getAll.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/manipulation/getAll.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/manipulation/getAll.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/manipulation/getAll.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/manipulation/setGlobalEval.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/manipulation/setGlobalEval.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/manipulation/setGlobalEval.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/manipulation/setGlobalEval.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/manipulation/support.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/manipulation/support.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/manipulation/support.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/manipulation/support.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/manipulation/var/rcheckableType.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/manipulation/var/rcheckableType.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/manipulation/var/rcheckableType.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/manipulation/var/rcheckableType.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/manipulation/var/rscriptType.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/manipulation/var/rscriptType.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/manipulation/var/rscriptType.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/manipulation/var/rscriptType.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/manipulation/var/rtagName.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/manipulation/var/rtagName.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/manipulation/var/rtagName.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/manipulation/var/rtagName.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/manipulation/wrapMap.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/manipulation/wrapMap.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/manipulation/wrapMap.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/manipulation/wrapMap.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/offset.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/offset.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/offset.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/offset.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/queue.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/queue.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/queue.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/queue.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/queue/delay.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/queue/delay.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/queue/delay.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/queue/delay.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/selector-native.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/selector-native.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/selector-native.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/selector-native.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/selector-sizzle.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/selector-sizzle.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/selector-sizzle.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/selector-sizzle.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/selector.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/selector.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/selector.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/selector.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/serialize.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/serialize.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/serialize.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/serialize.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/traversing.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/traversing.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/traversing.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/traversing.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/traversing/findFilter.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/traversing/findFilter.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/traversing/findFilter.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/traversing/findFilter.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/traversing/var/dir.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/traversing/var/dir.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/traversing/var/dir.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/traversing/var/dir.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/traversing/var/rneedsContext.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/traversing/var/rneedsContext.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/traversing/var/rneedsContext.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/traversing/var/rneedsContext.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/traversing/var/siblings.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/traversing/var/siblings.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/traversing/var/siblings.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/traversing/var/siblings.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/var/ObjectFunctionString.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/var/ObjectFunctionString.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/var/ObjectFunctionString.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/var/ObjectFunctionString.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/var/arr.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/var/arr.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/var/arr.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/var/arr.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/var/class2type.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/var/class2type.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/var/class2type.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/var/class2type.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/var/concat.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/var/concat.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/var/concat.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/var/concat.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/var/document.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/var/document.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/var/document.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/var/document.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/var/documentElement.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/var/documentElement.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/var/documentElement.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/var/documentElement.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/var/fnToString.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/var/fnToString.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/var/fnToString.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/var/fnToString.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/var/getProto.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/var/getProto.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/var/getProto.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/var/getProto.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/var/hasOwn.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/var/hasOwn.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/var/hasOwn.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/var/hasOwn.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/var/indexOf.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/var/indexOf.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/var/indexOf.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/var/indexOf.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/var/pnum.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/var/pnum.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/var/pnum.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/var/pnum.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/var/push.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/var/push.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/var/push.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/var/push.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/var/rcssNum.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/var/rcssNum.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/var/rcssNum.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/var/rcssNum.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/var/rnothtmlwhite.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/var/rnothtmlwhite.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/var/rnothtmlwhite.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/var/rnothtmlwhite.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/var/slice.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/var/slice.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/var/slice.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/var/slice.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/var/support.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/var/support.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/var/support.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/var/support.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/var/toString.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/var/toString.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/var/toString.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/var/toString.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/wrap.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/wrap.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/jquery/src/wrap.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/wrap.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/.bower.json b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/.bower.json similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/.bower.json rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/.bower.json diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/.editorconfig b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/.editorconfig similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/.editorconfig rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/.editorconfig diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/.gitattributes b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/.gitattributes similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/.gitattributes rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/.gitattributes diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/.github/CONTRIBUTING.md b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/.github/CONTRIBUTING.md similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/.github/CONTRIBUTING.md rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/.github/CONTRIBUTING.md diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/.gitignore b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/.gitignore similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/.gitignore rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/.gitignore diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/.jscsrc b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/.jscsrc similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/.jscsrc rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/.jscsrc diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/.markdown-doctest-setup.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/.markdown-doctest-setup.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/.markdown-doctest-setup.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/.markdown-doctest-setup.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/.travis.yml b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/.travis.yml similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/.travis.yml rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/.travis.yml diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/CHANGELOG b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/CHANGELOG similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/CHANGELOG rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/CHANGELOG diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/LICENSE b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/LICENSE similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/LICENSE rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/LICENSE diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/README.md b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/README.md similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/README.md rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/README.md diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/dist/lodash.core.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/dist/lodash.core.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/dist/lodash.core.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/dist/lodash.core.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/dist/lodash.core.min.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/dist/lodash.core.min.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/dist/lodash.core.min.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/dist/lodash.core.min.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/dist/lodash.fp.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/dist/lodash.fp.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/dist/lodash.fp.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/dist/lodash.fp.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/dist/lodash.fp.min.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/dist/lodash.fp.min.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/dist/lodash.fp.min.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/dist/lodash.fp.min.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/dist/lodash.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/dist/lodash.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/dist/lodash.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/dist/lodash.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/dist/lodash.min.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/dist/lodash.min.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/dist/lodash.min.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/dist/lodash.min.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/dist/mapping.fp.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/dist/mapping.fp.js similarity index 99% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/dist/mapping.fp.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/dist/mapping.fp.js index 8ff1cc3d6c..57d7642e9c 100644 --- a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/dist/mapping.fp.js +++ b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/dist/mapping.fp.js @@ -426,4 +426,4 @@ return /******/ (function(modules) { // webpackBootstrap /***/ } /******/ ]) }); -; \ No newline at end of file +; diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/doc/README.md b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/doc/README.md similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/doc/README.md rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/doc/README.md diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/fp/_baseConvert.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/fp/_baseConvert.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/fp/_baseConvert.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/fp/_baseConvert.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/fp/_convertBrowser.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/fp/_convertBrowser.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/fp/_convertBrowser.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/fp/_convertBrowser.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/fp/_mapping.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/fp/_mapping.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/fp/_mapping.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/fp/_mapping.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/fp/placeholder.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/fp/placeholder.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/fp/placeholder.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/fp/placeholder.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/lib/common/file.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/lib/common/file.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/lib/common/file.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/lib/common/file.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/lib/common/mapping.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/lib/common/mapping.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/lib/common/mapping.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/lib/common/mapping.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/lib/common/minify.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/lib/common/minify.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/lib/common/minify.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/lib/common/minify.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/lib/common/uglify.options.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/lib/common/uglify.options.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/lib/common/uglify.options.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/lib/common/uglify.options.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/lib/common/util.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/lib/common/util.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/lib/common/util.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/lib/common/util.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/lib/fp/build-dist.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/lib/fp/build-dist.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/lib/fp/build-dist.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/lib/fp/build-dist.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/lib/fp/build-doc.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/lib/fp/build-doc.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/lib/fp/build-doc.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/lib/fp/build-doc.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/lib/fp/build-modules.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/lib/fp/build-modules.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/lib/fp/build-modules.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/lib/fp/build-modules.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/lib/fp/template/doc/wiki.jst b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/lib/fp/template/doc/wiki.jst similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/lib/fp/template/doc/wiki.jst rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/lib/fp/template/doc/wiki.jst diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/lib/fp/template/modules/_falseOptions.jst b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/lib/fp/template/modules/_falseOptions.jst similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/lib/fp/template/modules/_falseOptions.jst rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/lib/fp/template/modules/_falseOptions.jst diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/lib/fp/template/modules/_util.jst b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/lib/fp/template/modules/_util.jst similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/lib/fp/template/modules/_util.jst rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/lib/fp/template/modules/_util.jst diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/lib/fp/template/modules/alias.jst b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/lib/fp/template/modules/alias.jst similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/lib/fp/template/modules/alias.jst rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/lib/fp/template/modules/alias.jst diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/lib/fp/template/modules/category.jst b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/lib/fp/template/modules/category.jst similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/lib/fp/template/modules/category.jst rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/lib/fp/template/modules/category.jst diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/lib/fp/template/modules/convert.jst b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/lib/fp/template/modules/convert.jst similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/lib/fp/template/modules/convert.jst rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/lib/fp/template/modules/convert.jst diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/lib/fp/template/modules/fp.jst b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/lib/fp/template/modules/fp.jst similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/lib/fp/template/modules/fp.jst rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/lib/fp/template/modules/fp.jst diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/lib/fp/template/modules/module.jst b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/lib/fp/template/modules/module.jst similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/lib/fp/template/modules/module.jst rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/lib/fp/template/modules/module.jst diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/lib/fp/template/modules/thru.jst b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/lib/fp/template/modules/thru.jst similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/lib/fp/template/modules/thru.jst rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/lib/fp/template/modules/thru.jst diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/lib/main/build-dist.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/lib/main/build-dist.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/lib/main/build-dist.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/lib/main/build-dist.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/lib/main/build-doc.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/lib/main/build-doc.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/lib/main/build-doc.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/lib/main/build-doc.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/lib/main/build-modules.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/lib/main/build-modules.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/lib/main/build-modules.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/lib/main/build-modules.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/lib/main/build-site.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/lib/main/build-site.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/lib/main/build-site.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/lib/main/build-site.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/lodash.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/lodash.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/lodash.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/lodash.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/package.json b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/package.json similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/package.json rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/package.json diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/perf/asset/perf-ui.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/perf/asset/perf-ui.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/perf/asset/perf-ui.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/perf/asset/perf-ui.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/perf/index.html b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/perf/index.html similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/perf/index.html rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/perf/index.html diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/perf/perf.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/perf/perf.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/perf/perf.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/perf/perf.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/test/asset/test-ui.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/test/asset/test-ui.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/test/asset/test-ui.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/test/asset/test-ui.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/test/asset/worker.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/test/asset/worker.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/test/asset/worker.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/test/asset/worker.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/test/backbone.html b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/test/backbone.html similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/test/backbone.html rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/test/backbone.html diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/test/fp.html b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/test/fp.html similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/test/fp.html rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/test/fp.html diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/test/index.html b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/test/index.html similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/test/index.html rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/test/index.html diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/test/remove.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/test/remove.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/test/remove.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/test/remove.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/test/saucelabs.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/test/saucelabs.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/test/saucelabs.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/test/saucelabs.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/test/test-fp.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/test/test-fp.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/test/test-fp.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/test/test-fp.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/test/test.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/test/test.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/test/test.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/test/test.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/test/underscore.html b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/test/underscore.html similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/test/underscore.html rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/test/underscore.html diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/backbone/LICENSE b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/backbone/LICENSE similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/backbone/LICENSE rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/backbone/LICENSE diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/backbone/backbone.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/backbone/backbone.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/backbone/backbone.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/backbone/backbone.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/backbone/test/collection.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/backbone/test/collection.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/backbone/test/collection.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/backbone/test/collection.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/backbone/test/events.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/backbone/test/events.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/backbone/test/events.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/backbone/test/events.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/backbone/test/model.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/backbone/test/model.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/backbone/test/model.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/backbone/test/model.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/backbone/test/noconflict.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/backbone/test/noconflict.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/backbone/test/noconflict.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/backbone/test/noconflict.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/backbone/test/router.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/backbone/test/router.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/backbone/test/router.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/backbone/test/router.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/backbone/test/setup/dom-setup.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/backbone/test/setup/dom-setup.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/backbone/test/setup/dom-setup.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/backbone/test/setup/dom-setup.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/backbone/test/setup/environment.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/backbone/test/setup/environment.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/backbone/test/setup/environment.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/backbone/test/setup/environment.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/backbone/test/sync.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/backbone/test/sync.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/backbone/test/sync.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/backbone/test/sync.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/backbone/test/view.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/backbone/test/view.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/backbone/test/view.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/backbone/test/view.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/license.txt b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/license.txt similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/license.txt rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/license.txt diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/blank.gif b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/blank.gif similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/blank.gif rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/blank.gif diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/buttonBg.png b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/buttonBg.png similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/buttonBg.png rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/buttonBg.png diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/buttonBgHover.png b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/buttonBgHover.png similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/buttonBgHover.png rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/buttonBgHover.png diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/debugger.css b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/debugger.css similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/debugger.css rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/debugger.css diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/detach.png b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/detach.png similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/detach.png rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/detach.png diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/detachHover.png b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/detachHover.png similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/detachHover.png rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/detachHover.png diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/disable.gif b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/disable.gif similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/disable.gif rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/disable.gif diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/disable.png b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/disable.png similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/disable.png rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/disable.png diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/disableHover.gif b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/disableHover.gif similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/disableHover.gif rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/disableHover.gif diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/disableHover.png b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/disableHover.png similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/disableHover.png rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/disableHover.png diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/down.png b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/down.png similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/down.png rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/down.png diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/downActive.png b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/downActive.png similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/downActive.png rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/downActive.png diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/downHover.png b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/downHover.png similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/downHover.png rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/downHover.png diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/errorIcon-sm.png b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/errorIcon-sm.png similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/errorIcon-sm.png rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/errorIcon-sm.png diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/errorIcon.gif b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/errorIcon.gif similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/errorIcon.gif rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/errorIcon.gif diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/errorIcon.png b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/errorIcon.png similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/errorIcon.png rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/errorIcon.png diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/firebug-1.3a2.css b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/firebug-1.3a2.css similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/firebug-1.3a2.css rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/firebug-1.3a2.css diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/firebug.IE6.css b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/firebug.IE6.css similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/firebug.IE6.css rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/firebug.IE6.css diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/firebug.css b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/firebug.css similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/firebug.css rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/firebug.css diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/firebug.html b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/firebug.html similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/firebug.html rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/firebug.html diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/firebug.png b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/firebug.png similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/firebug.png rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/firebug.png diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/group.gif b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/group.gif similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/group.gif rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/group.gif diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/html.css b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/html.css similarity index 99% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/html.css rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/html.css index 5b7c5f4baf..12742f4ce5 100644 --- a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/html.css +++ b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/html.css @@ -269,4 +269,4 @@ .twisty { background-position: 4px 4px; -} \ No newline at end of file +} diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/infoIcon.gif b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/infoIcon.gif similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/infoIcon.gif rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/infoIcon.gif diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/infoIcon.png b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/infoIcon.png similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/infoIcon.png rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/infoIcon.png diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/loading_16.gif b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/loading_16.gif similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/loading_16.gif rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/loading_16.gif diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/min.png b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/min.png similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/min.png rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/min.png diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/minHover.png b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/minHover.png similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/minHover.png rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/minHover.png diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/off.png b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/off.png similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/off.png rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/off.png diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/offHover.png b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/offHover.png similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/offHover.png rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/offHover.png diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/pixel_transparent.gif b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/pixel_transparent.gif similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/pixel_transparent.gif rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/pixel_transparent.gif diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/roundCorner.svg b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/roundCorner.svg similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/roundCorner.svg rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/roundCorner.svg diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/search.gif b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/search.gif similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/search.gif rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/search.gif diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/search.png b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/search.png similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/search.png rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/search.png diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/shadow.gif b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/shadow.gif similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/shadow.gif rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/shadow.gif diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/shadow2.gif b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/shadow2.gif similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/shadow2.gif rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/shadow2.gif diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/shadowAlpha.png b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/shadowAlpha.png similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/shadowAlpha.png rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/shadowAlpha.png diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/sprite.png b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/sprite.png similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/sprite.png rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/sprite.png diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/tabHoverLeft.png b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/tabHoverLeft.png similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/tabHoverLeft.png rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/tabHoverLeft.png diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/tabHoverMid.png b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/tabHoverMid.png similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/tabHoverMid.png rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/tabHoverMid.png diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/tabHoverRight.png b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/tabHoverRight.png similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/tabHoverRight.png rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/tabHoverRight.png diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/tabLeft.png b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/tabLeft.png similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/tabLeft.png rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/tabLeft.png diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/tabMenuCheckbox.png b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/tabMenuCheckbox.png similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/tabMenuCheckbox.png rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/tabMenuCheckbox.png diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/tabMenuPin.png b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/tabMenuPin.png similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/tabMenuPin.png rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/tabMenuPin.png diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/tabMenuRadio.png b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/tabMenuRadio.png similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/tabMenuRadio.png rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/tabMenuRadio.png diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/tabMenuTarget.png b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/tabMenuTarget.png similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/tabMenuTarget.png rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/tabMenuTarget.png diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/tabMenuTargetHover.png b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/tabMenuTargetHover.png similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/tabMenuTargetHover.png rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/tabMenuTargetHover.png diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/tabMid.png b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/tabMid.png similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/tabMid.png rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/tabMid.png diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/tabRight.png b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/tabRight.png similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/tabRight.png rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/tabRight.png diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/textEditorBorders.gif b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/textEditorBorders.gif similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/textEditorBorders.gif rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/textEditorBorders.gif diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/textEditorBorders.png b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/textEditorBorders.png similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/textEditorBorders.png rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/textEditorBorders.png diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/textEditorCorners.gif b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/textEditorCorners.gif similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/textEditorCorners.gif rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/textEditorCorners.gif diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/textEditorCorners.png b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/textEditorCorners.png similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/textEditorCorners.png rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/textEditorCorners.png diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/titlebarMid.png b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/titlebarMid.png similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/titlebarMid.png rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/titlebarMid.png diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/toolbarMid.png b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/toolbarMid.png similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/toolbarMid.png rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/toolbarMid.png diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/tree_close.gif b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/tree_close.gif similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/tree_close.gif rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/tree_close.gif diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/tree_open.gif b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/tree_open.gif similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/tree_open.gif rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/tree_open.gif diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/twistyClosed.png b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/twistyClosed.png similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/twistyClosed.png rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/twistyClosed.png diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/twistyOpen.png b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/twistyOpen.png similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/twistyOpen.png rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/twistyOpen.png diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/up.png b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/up.png similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/up.png rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/up.png diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/upActive.png b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/upActive.png similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/upActive.png rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/upActive.png diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/upHover.png b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/upHover.png similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/upHover.png rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/upHover.png diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/warningIcon.gif b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/warningIcon.gif similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/warningIcon.gif rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/warningIcon.gif diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/warningIcon.png b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/warningIcon.png similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/skin/xp/warningIcon.png rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/warningIcon.png diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/src/firebug-lite-debug.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/src/firebug-lite-debug.js similarity index 99% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/src/firebug-lite-debug.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/src/firebug-lite-debug.js index 40b1ae70cb..9cc2645cf8 100644 --- a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/firebug-lite/src/firebug-lite-debug.js +++ b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/src/firebug-lite-debug.js @@ -31173,4 +31173,4 @@ FirebugChrome.Skin = FBL.initialize(); // ************************************************************************************************ -})(); \ No newline at end of file +})(); diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/json-js/json2.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/json-js/json2.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/json-js/json2.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/json-js/json2.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/underscore/LICENSE b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/underscore/LICENSE similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/underscore/LICENSE rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/underscore/LICENSE diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/underscore/test/arrays.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/underscore/test/arrays.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/underscore/test/arrays.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/underscore/test/arrays.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/underscore/test/chaining.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/underscore/test/chaining.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/underscore/test/chaining.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/underscore/test/chaining.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/underscore/test/collections.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/underscore/test/collections.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/underscore/test/collections.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/underscore/test/collections.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/underscore/test/cross-document.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/underscore/test/cross-document.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/underscore/test/cross-document.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/underscore/test/cross-document.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/underscore/test/functions.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/underscore/test/functions.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/underscore/test/functions.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/underscore/test/functions.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/underscore/test/objects.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/underscore/test/objects.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/underscore/test/objects.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/underscore/test/objects.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/underscore/test/utility.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/underscore/test/utility.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/underscore/test/utility.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/underscore/test/utility.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/underscore/underscore-min.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/underscore/underscore-min.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/underscore/underscore-min.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/underscore/underscore-min.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/underscore/underscore.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/underscore/underscore.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/lodash/vendor/underscore/underscore.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/underscore/underscore.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/requirejs/.bower.json b/samples/irs-demo/web/src/main/resources/static/js/bower_components/requirejs/.bower.json similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/requirejs/.bower.json rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/requirejs/.bower.json diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/requirejs/LICENSE b/samples/irs-demo/web/src/main/resources/static/js/bower_components/requirejs/LICENSE similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/requirejs/LICENSE rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/requirejs/LICENSE diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/requirejs/README.md b/samples/irs-demo/web/src/main/resources/static/js/bower_components/requirejs/README.md similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/requirejs/README.md rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/requirejs/README.md diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/requirejs/bower.json b/samples/irs-demo/web/src/main/resources/static/js/bower_components/requirejs/bower.json similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/requirejs/bower.json rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/requirejs/bower.json diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/requirejs/require.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/requirejs/require.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/requirejs/require.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/requirejs/require.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/.bower.json b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/.bower.json similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/.bower.json rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/.bower.json diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/.csscomb.json b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/.csscomb.json similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/.csscomb.json rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/.csscomb.json diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/.csslintrc b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/.csslintrc similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/.csslintrc rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/.csslintrc diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/.gitignore b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/.gitignore similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/.gitignore rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/.gitignore diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/.jshintrc b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/.jshintrc similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/.jshintrc rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/.jshintrc diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/CONTRIBUTING.md b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/CONTRIBUTING.md similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/CONTRIBUTING.md rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/CONTRIBUTING.md diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/LICENSE.md b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/LICENSE.md similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/LICENSE.md rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/LICENSE.md diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/README.md b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/README.md similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/README.md rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/README.md diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/RELEASE-NOTES.md b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/RELEASE-NOTES.md similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/RELEASE-NOTES.md rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/RELEASE-NOTES.md diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/bower.json b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/bower.json similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/bower.json rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/bower.json diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/composer.json b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/composer.json similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/composer.json rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/composer.json diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/accordion.css b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/accordion.css similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/accordion.css rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/accordion.css diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/accordion.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/accordion.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/accordion.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/accordion.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/accordion.min.css b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/accordion.min.css similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/accordion.min.css rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/accordion.min.css diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/accordion.min.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/accordion.min.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/accordion.min.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/accordion.min.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/ad.css b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/ad.css similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/ad.css rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/ad.css diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/ad.min.css b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/ad.min.css similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/ad.min.css rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/ad.min.css diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/api.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/api.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/api.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/api.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/api.min.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/api.min.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/api.min.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/api.min.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/breadcrumb.css b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/breadcrumb.css similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/breadcrumb.css rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/breadcrumb.css diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/breadcrumb.min.css b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/breadcrumb.min.css similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/breadcrumb.min.css rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/breadcrumb.min.css diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/button.css b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/button.css similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/button.css rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/button.css diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/button.min.css b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/button.min.css similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/button.min.css rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/button.min.css diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/card.css b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/card.css similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/card.css rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/card.css diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/card.min.css b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/card.min.css similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/card.min.css rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/card.min.css diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/checkbox.css b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/checkbox.css similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/checkbox.css rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/checkbox.css diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/checkbox.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/checkbox.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/checkbox.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/checkbox.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/checkbox.min.css b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/checkbox.min.css similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/checkbox.min.css rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/checkbox.min.css diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/checkbox.min.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/checkbox.min.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/checkbox.min.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/checkbox.min.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/colorize.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/colorize.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/colorize.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/colorize.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/colorize.min.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/colorize.min.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/colorize.min.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/colorize.min.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/comment.css b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/comment.css similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/comment.css rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/comment.css diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/comment.min.css b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/comment.min.css similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/comment.min.css rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/comment.min.css diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/container.css b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/container.css similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/container.css rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/container.css diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/container.min.css b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/container.min.css similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/container.min.css rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/container.min.css diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/dimmer.css b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/dimmer.css similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/dimmer.css rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/dimmer.css diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/dimmer.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/dimmer.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/dimmer.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/dimmer.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/dimmer.min.css b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/dimmer.min.css similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/dimmer.min.css rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/dimmer.min.css diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/dimmer.min.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/dimmer.min.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/dimmer.min.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/dimmer.min.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/divider.css b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/divider.css similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/divider.css rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/divider.css diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/divider.min.css b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/divider.min.css similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/divider.min.css rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/divider.min.css diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/dropdown.css b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/dropdown.css similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/dropdown.css rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/dropdown.css diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/dropdown.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/dropdown.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/dropdown.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/dropdown.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/dropdown.min.css b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/dropdown.min.css similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/dropdown.min.css rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/dropdown.min.css diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/dropdown.min.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/dropdown.min.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/dropdown.min.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/dropdown.min.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/embed.css b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/embed.css similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/embed.css rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/embed.css diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/embed.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/embed.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/embed.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/embed.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/embed.min.css b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/embed.min.css similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/embed.min.css rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/embed.min.css diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/embed.min.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/embed.min.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/embed.min.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/embed.min.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/feed.css b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/feed.css similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/feed.css rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/feed.css diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/feed.min.css b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/feed.min.css similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/feed.min.css rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/feed.min.css diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/flag.css b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/flag.css similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/flag.css rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/flag.css diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/flag.min.css b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/flag.min.css similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/flag.min.css rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/flag.min.css diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/form.css b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/form.css similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/form.css rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/form.css diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/form.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/form.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/form.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/form.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/form.min.css b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/form.min.css similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/form.min.css rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/form.min.css diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/form.min.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/form.min.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/form.min.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/form.min.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/grid.css b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/grid.css similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/grid.css rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/grid.css diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/grid.min.css b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/grid.min.css similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/grid.min.css rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/grid.min.css diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/header.css b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/header.css similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/header.css rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/header.css diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/header.min.css b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/header.min.css similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/header.min.css rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/header.min.css diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/icon.css b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/icon.css similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/icon.css rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/icon.css diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/icon.min.css b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/icon.min.css similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/icon.min.css rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/icon.min.css diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/image.css b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/image.css similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/image.css rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/image.css diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/image.min.css b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/image.min.css similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/image.min.css rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/image.min.css diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/input.css b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/input.css similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/input.css rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/input.css diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/input.min.css b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/input.min.css similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/input.min.css rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/input.min.css diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/item.css b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/item.css similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/item.css rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/item.css diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/item.min.css b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/item.min.css similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/item.min.css rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/item.min.css diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/label.css b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/label.css similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/label.css rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/label.css diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/label.min.css b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/label.min.css similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/label.min.css rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/label.min.css diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/list.css b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/list.css similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/list.css rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/list.css diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/list.min.css b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/list.min.css similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/list.min.css rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/list.min.css diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/loader.css b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/loader.css similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/loader.css rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/loader.css diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/loader.min.css b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/loader.min.css similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/loader.min.css rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/loader.min.css diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/menu.css b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/menu.css similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/menu.css rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/menu.css diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/menu.min.css b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/menu.min.css similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/menu.min.css rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/menu.min.css diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/message.css b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/message.css similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/message.css rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/message.css diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/message.min.css b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/message.min.css similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/message.min.css rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/message.min.css diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/modal.css b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/modal.css similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/modal.css rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/modal.css diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/modal.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/modal.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/modal.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/modal.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/modal.min.css b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/modal.min.css similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/modal.min.css rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/modal.min.css diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/modal.min.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/modal.min.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/modal.min.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/modal.min.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/nag.css b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/nag.css similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/nag.css rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/nag.css diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/nag.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/nag.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/nag.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/nag.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/nag.min.css b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/nag.min.css similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/nag.min.css rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/nag.min.css diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/nag.min.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/nag.min.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/nag.min.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/nag.min.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/popup.css b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/popup.css similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/popup.css rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/popup.css diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/popup.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/popup.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/popup.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/popup.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/popup.min.css b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/popup.min.css similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/popup.min.css rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/popup.min.css diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/popup.min.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/popup.min.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/popup.min.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/popup.min.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/progress.css b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/progress.css similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/progress.css rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/progress.css diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/progress.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/progress.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/progress.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/progress.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/progress.min.css b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/progress.min.css similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/progress.min.css rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/progress.min.css diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/progress.min.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/progress.min.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/progress.min.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/progress.min.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/rail.css b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/rail.css similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/rail.css rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/rail.css diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/rail.min.css b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/rail.min.css similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/rail.min.css rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/rail.min.css diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/rating.css b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/rating.css similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/rating.css rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/rating.css diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/rating.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/rating.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/rating.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/rating.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/rating.min.css b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/rating.min.css similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/rating.min.css rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/rating.min.css diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/rating.min.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/rating.min.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/rating.min.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/rating.min.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/reset.css b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/reset.css similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/reset.css rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/reset.css diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/reset.min.css b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/reset.min.css similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/reset.min.css rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/reset.min.css diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/reveal.css b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/reveal.css similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/reveal.css rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/reveal.css diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/reveal.min.css b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/reveal.min.css similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/reveal.min.css rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/reveal.min.css diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/search.css b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/search.css similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/search.css rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/search.css diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/search.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/search.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/search.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/search.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/search.min.css b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/search.min.css similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/search.min.css rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/search.min.css diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/search.min.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/search.min.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/search.min.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/search.min.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/segment.css b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/segment.css similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/segment.css rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/segment.css diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/segment.min.css b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/segment.min.css similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/segment.min.css rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/segment.min.css diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/shape.css b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/shape.css similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/shape.css rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/shape.css diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/shape.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/shape.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/shape.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/shape.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/shape.min.css b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/shape.min.css similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/shape.min.css rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/shape.min.css diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/shape.min.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/shape.min.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/shape.min.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/shape.min.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/sidebar.css b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/sidebar.css similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/sidebar.css rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/sidebar.css diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/sidebar.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/sidebar.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/sidebar.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/sidebar.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/sidebar.min.css b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/sidebar.min.css similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/sidebar.min.css rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/sidebar.min.css diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/sidebar.min.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/sidebar.min.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/sidebar.min.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/sidebar.min.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/site.css b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/site.css similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/site.css rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/site.css diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/site.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/site.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/site.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/site.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/site.min.css b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/site.min.css similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/site.min.css rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/site.min.css diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/site.min.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/site.min.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/site.min.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/site.min.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/state.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/state.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/state.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/state.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/state.min.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/state.min.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/state.min.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/state.min.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/statistic.css b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/statistic.css similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/statistic.css rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/statistic.css diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/statistic.min.css b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/statistic.min.css similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/statistic.min.css rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/statistic.min.css diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/step.css b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/step.css similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/step.css rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/step.css diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/step.min.css b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/step.min.css similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/step.min.css rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/step.min.css diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/sticky.css b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/sticky.css similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/sticky.css rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/sticky.css diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/sticky.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/sticky.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/sticky.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/sticky.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/sticky.min.css b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/sticky.min.css similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/sticky.min.css rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/sticky.min.css diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/sticky.min.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/sticky.min.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/sticky.min.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/sticky.min.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/tab.css b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/tab.css similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/tab.css rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/tab.css diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/tab.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/tab.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/tab.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/tab.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/tab.min.css b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/tab.min.css similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/tab.min.css rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/tab.min.css diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/tab.min.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/tab.min.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/tab.min.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/tab.min.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/table.css b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/table.css similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/table.css rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/table.css diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/table.min.css b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/table.min.css similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/table.min.css rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/table.min.css diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/transition.css b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/transition.css similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/transition.css rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/transition.css diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/transition.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/transition.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/transition.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/transition.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/transition.min.css b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/transition.min.css similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/transition.min.css rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/transition.min.css diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/transition.min.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/transition.min.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/transition.min.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/transition.min.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/video.css b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/video.css similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/video.css rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/video.css diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/video.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/video.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/video.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/video.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/video.min.css b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/video.min.css similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/video.min.css rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/video.min.css diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/video.min.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/video.min.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/video.min.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/video.min.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/visibility.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/visibility.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/visibility.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/visibility.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/visibility.min.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/visibility.min.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/visibility.min.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/visibility.min.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/visit.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/visit.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/visit.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/visit.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/visit.min.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/visit.min.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/components/visit.min.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/visit.min.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/semantic.css b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/semantic.css similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/semantic.css rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/semantic.css diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/semantic.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/semantic.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/semantic.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/semantic.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/semantic.min.css b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/semantic.min.css similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/semantic.min.css rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/semantic.min.css diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/semantic.min.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/semantic.min.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/semantic.min.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/semantic.min.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/themes/basic/assets/fonts/icons.eot b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/themes/basic/assets/fonts/icons.eot similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/themes/basic/assets/fonts/icons.eot rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/themes/basic/assets/fonts/icons.eot diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/themes/basic/assets/fonts/icons.svg b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/themes/basic/assets/fonts/icons.svg similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/themes/basic/assets/fonts/icons.svg rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/themes/basic/assets/fonts/icons.svg diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/themes/basic/assets/fonts/icons.ttf b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/themes/basic/assets/fonts/icons.ttf similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/themes/basic/assets/fonts/icons.ttf rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/themes/basic/assets/fonts/icons.ttf diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/themes/basic/assets/fonts/icons.woff b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/themes/basic/assets/fonts/icons.woff similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/themes/basic/assets/fonts/icons.woff rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/themes/basic/assets/fonts/icons.woff diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/themes/default/assets/fonts/icons.eot b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/themes/default/assets/fonts/icons.eot similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/themes/default/assets/fonts/icons.eot rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/themes/default/assets/fonts/icons.eot diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/themes/default/assets/fonts/icons.otf b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/themes/default/assets/fonts/icons.otf similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/themes/default/assets/fonts/icons.otf rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/themes/default/assets/fonts/icons.otf diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/themes/default/assets/fonts/icons.svg b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/themes/default/assets/fonts/icons.svg similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/themes/default/assets/fonts/icons.svg rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/themes/default/assets/fonts/icons.svg diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/themes/default/assets/fonts/icons.ttf b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/themes/default/assets/fonts/icons.ttf similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/themes/default/assets/fonts/icons.ttf rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/themes/default/assets/fonts/icons.ttf diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/themes/default/assets/fonts/icons.woff b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/themes/default/assets/fonts/icons.woff similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/themes/default/assets/fonts/icons.woff rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/themes/default/assets/fonts/icons.woff diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/themes/default/assets/fonts/icons.woff2 b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/themes/default/assets/fonts/icons.woff2 similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/themes/default/assets/fonts/icons.woff2 rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/themes/default/assets/fonts/icons.woff2 diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/themes/default/assets/images/flags.png b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/themes/default/assets/images/flags.png similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/themes/default/assets/images/flags.png rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/themes/default/assets/images/flags.png diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/themes/github/assets/fonts/octicons-local.ttf b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/themes/github/assets/fonts/octicons-local.ttf similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/themes/github/assets/fonts/octicons-local.ttf rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/themes/github/assets/fonts/octicons-local.ttf diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/themes/github/assets/fonts/octicons.svg b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/themes/github/assets/fonts/octicons.svg similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/themes/github/assets/fonts/octicons.svg rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/themes/github/assets/fonts/octicons.svg diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/themes/github/assets/fonts/octicons.ttf b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/themes/github/assets/fonts/octicons.ttf similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/themes/github/assets/fonts/octicons.ttf rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/themes/github/assets/fonts/octicons.ttf diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/themes/github/assets/fonts/octicons.woff b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/themes/github/assets/fonts/octicons.woff similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/themes/github/assets/fonts/octicons.woff rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/themes/github/assets/fonts/octicons.woff diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/themes/material/assets/fonts/icons.eot b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/themes/material/assets/fonts/icons.eot similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/themes/material/assets/fonts/icons.eot rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/themes/material/assets/fonts/icons.eot diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/themes/material/assets/fonts/icons.svg b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/themes/material/assets/fonts/icons.svg similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/themes/material/assets/fonts/icons.svg rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/themes/material/assets/fonts/icons.svg diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/themes/material/assets/fonts/icons.ttf b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/themes/material/assets/fonts/icons.ttf similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/themes/material/assets/fonts/icons.ttf rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/themes/material/assets/fonts/icons.ttf diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/themes/material/assets/fonts/icons.woff b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/themes/material/assets/fonts/icons.woff similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/dist/themes/material/assets/fonts/icons.woff rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/themes/material/assets/fonts/icons.woff diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/examples/assets/images/avatar/nan.jpg b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/examples/assets/images/avatar/nan.jpg similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/examples/assets/images/avatar/nan.jpg rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/examples/assets/images/avatar/nan.jpg diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/examples/assets/images/avatar/tom.jpg b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/examples/assets/images/avatar/tom.jpg similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/examples/assets/images/avatar/tom.jpg rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/examples/assets/images/avatar/tom.jpg diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/examples/assets/images/bg.jpg b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/examples/assets/images/bg.jpg similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/examples/assets/images/bg.jpg rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/examples/assets/images/bg.jpg diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/examples/assets/images/logo.png b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/examples/assets/images/logo.png similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/examples/assets/images/logo.png rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/examples/assets/images/logo.png diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/examples/assets/images/wireframe/centered-paragraph.png b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/examples/assets/images/wireframe/centered-paragraph.png similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/examples/assets/images/wireframe/centered-paragraph.png rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/examples/assets/images/wireframe/centered-paragraph.png diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/examples/assets/images/wireframe/image-square.png b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/examples/assets/images/wireframe/image-square.png similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/examples/assets/images/wireframe/image-square.png rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/examples/assets/images/wireframe/image-square.png diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/examples/assets/images/wireframe/image-text.png b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/examples/assets/images/wireframe/image-text.png similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/examples/assets/images/wireframe/image-text.png rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/examples/assets/images/wireframe/image-text.png diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/examples/assets/images/wireframe/image.png b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/examples/assets/images/wireframe/image.png similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/examples/assets/images/wireframe/image.png rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/examples/assets/images/wireframe/image.png diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/examples/assets/images/wireframe/media-paragraph-alt.png b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/examples/assets/images/wireframe/media-paragraph-alt.png similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/examples/assets/images/wireframe/media-paragraph-alt.png rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/examples/assets/images/wireframe/media-paragraph-alt.png diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/examples/assets/images/wireframe/media-paragraph.png b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/examples/assets/images/wireframe/media-paragraph.png similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/examples/assets/images/wireframe/media-paragraph.png rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/examples/assets/images/wireframe/media-paragraph.png diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/examples/assets/images/wireframe/paragraph.png b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/examples/assets/images/wireframe/paragraph.png similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/examples/assets/images/wireframe/paragraph.png rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/examples/assets/images/wireframe/paragraph.png diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/examples/assets/images/wireframe/short-paragraph.png b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/examples/assets/images/wireframe/short-paragraph.png similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/examples/assets/images/wireframe/short-paragraph.png rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/examples/assets/images/wireframe/short-paragraph.png diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/examples/assets/images/wireframe/square-image.png b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/examples/assets/images/wireframe/square-image.png similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/examples/assets/images/wireframe/square-image.png rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/examples/assets/images/wireframe/square-image.png diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/examples/assets/images/wireframe/text-image.png b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/examples/assets/images/wireframe/text-image.png similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/examples/assets/images/wireframe/text-image.png rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/examples/assets/images/wireframe/text-image.png diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/examples/assets/images/wireframe/white-image.png b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/examples/assets/images/wireframe/white-image.png similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/examples/assets/images/wireframe/white-image.png rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/examples/assets/images/wireframe/white-image.png diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/examples/assets/library/iframe-content.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/examples/assets/library/iframe-content.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/examples/assets/library/iframe-content.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/examples/assets/library/iframe-content.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/examples/assets/library/iframe.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/examples/assets/library/iframe.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/examples/assets/library/iframe.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/examples/assets/library/iframe.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/examples/assets/library/jquery.min.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/examples/assets/library/jquery.min.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/examples/assets/library/jquery.min.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/examples/assets/library/jquery.min.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/examples/assets/show-examples.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/examples/assets/show-examples.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/examples/assets/show-examples.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/examples/assets/show-examples.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/examples/attached.html b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/examples/attached.html similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/examples/attached.html rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/examples/attached.html diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/examples/bootstrap.html b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/examples/bootstrap.html similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/examples/bootstrap.html rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/examples/bootstrap.html diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/examples/components/button.html b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/examples/components/button.html similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/examples/components/button.html rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/examples/components/button.html diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/examples/components/card.html b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/examples/components/card.html similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/examples/components/card.html rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/examples/components/card.html diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/examples/components/input.html b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/examples/components/input.html similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/examples/components/input.html rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/examples/components/input.html diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/examples/components/menu.html b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/examples/components/menu.html similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/examples/components/menu.html rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/examples/components/menu.html diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/examples/components/site.html b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/examples/components/site.html similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/examples/components/site.html rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/examples/components/site.html diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/examples/components/table.html b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/examples/components/table.html similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/examples/components/table.html rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/examples/components/table.html diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/examples/fixed.html b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/examples/fixed.html similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/examples/fixed.html rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/examples/fixed.html diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/examples/grid.html b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/examples/grid.html similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/examples/grid.html rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/examples/grid.html diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/examples/homepage.html b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/examples/homepage.html similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/examples/homepage.html rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/examples/homepage.html diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/examples/login.html b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/examples/login.html similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/examples/login.html rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/examples/login.html diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/examples/responsive.html b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/examples/responsive.html similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/examples/responsive.html rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/examples/responsive.html diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/examples/sticky.html b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/examples/sticky.html similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/examples/sticky.html rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/examples/sticky.html diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/examples/theming.html b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/examples/theming.html similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/examples/theming.html rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/examples/theming.html diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/gulpfile.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/gulpfile.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/gulpfile.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/gulpfile.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/karma.conf.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/karma.conf.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/karma.conf.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/karma.conf.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/logo.png b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/logo.png similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/logo.png rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/logo.png diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/package.json b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/package.json similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/package.json rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/package.json diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/semantic.json.example b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/semantic.json.example similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/semantic.json.example rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/semantic.json.example diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/README.md b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/README.md similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/README.md rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/README.md diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/collections/breadcrumb.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/collections/breadcrumb.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/collections/breadcrumb.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/collections/breadcrumb.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/collections/breadcrumb.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/collections/breadcrumb.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/collections/breadcrumb.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/collections/breadcrumb.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/collections/form.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/collections/form.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/collections/form.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/collections/form.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/collections/form.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/collections/form.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/collections/form.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/collections/form.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/collections/grid.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/collections/grid.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/collections/grid.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/collections/grid.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/collections/grid.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/collections/grid.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/collections/grid.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/collections/grid.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/collections/menu.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/collections/menu.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/collections/menu.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/collections/menu.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/collections/menu.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/collections/menu.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/collections/menu.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/collections/menu.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/collections/message.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/collections/message.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/collections/message.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/collections/message.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/collections/message.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/collections/message.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/collections/message.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/collections/message.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/collections/table.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/collections/table.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/collections/table.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/collections/table.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/collections/table.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/collections/table.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/collections/table.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/collections/table.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/elements/button.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/elements/button.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/elements/button.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/elements/button.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/elements/button.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/elements/button.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/elements/button.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/elements/button.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/elements/container.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/elements/container.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/elements/container.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/elements/container.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/elements/container.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/elements/container.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/elements/container.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/elements/container.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/elements/divider.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/elements/divider.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/elements/divider.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/elements/divider.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/elements/divider.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/elements/divider.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/elements/divider.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/elements/divider.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/elements/flag.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/elements/flag.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/elements/flag.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/elements/flag.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/elements/flag.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/elements/flag.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/elements/flag.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/elements/flag.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/elements/header.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/elements/header.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/elements/header.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/elements/header.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/elements/header.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/elements/header.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/elements/header.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/elements/header.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/elements/icon.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/elements/icon.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/elements/icon.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/elements/icon.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/elements/icon.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/elements/icon.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/elements/icon.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/elements/icon.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/elements/image.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/elements/image.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/elements/image.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/elements/image.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/elements/image.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/elements/image.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/elements/image.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/elements/image.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/elements/input.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/elements/input.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/elements/input.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/elements/input.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/elements/input.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/elements/input.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/elements/input.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/elements/input.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/elements/label.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/elements/label.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/elements/label.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/elements/label.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/elements/label.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/elements/label.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/elements/label.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/elements/label.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/elements/list.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/elements/list.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/elements/list.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/elements/list.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/elements/list.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/elements/list.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/elements/list.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/elements/list.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/elements/loader.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/elements/loader.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/elements/loader.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/elements/loader.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/elements/loader.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/elements/loader.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/elements/loader.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/elements/loader.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/elements/rail.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/elements/rail.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/elements/rail.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/elements/rail.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/elements/rail.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/elements/rail.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/elements/rail.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/elements/rail.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/elements/reveal.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/elements/reveal.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/elements/reveal.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/elements/reveal.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/elements/reveal.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/elements/reveal.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/elements/reveal.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/elements/reveal.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/elements/segment.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/elements/segment.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/elements/segment.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/elements/segment.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/elements/segment.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/elements/segment.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/elements/segment.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/elements/segment.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/elements/step.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/elements/step.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/elements/step.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/elements/step.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/elements/step.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/elements/step.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/elements/step.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/elements/step.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/globals/reset.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/globals/reset.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/globals/reset.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/globals/reset.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/globals/reset.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/globals/reset.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/globals/reset.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/globals/reset.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/globals/site.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/globals/site.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/globals/site.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/globals/site.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/globals/site.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/globals/site.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/globals/site.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/globals/site.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/modules/accordion.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/modules/accordion.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/modules/accordion.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/modules/accordion.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/modules/accordion.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/modules/accordion.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/modules/accordion.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/modules/accordion.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/modules/chatroom.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/modules/chatroom.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/modules/chatroom.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/modules/chatroom.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/modules/chatroom.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/modules/chatroom.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/modules/chatroom.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/modules/chatroom.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/modules/checkbox.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/modules/checkbox.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/modules/checkbox.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/modules/checkbox.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/modules/checkbox.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/modules/checkbox.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/modules/checkbox.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/modules/checkbox.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/modules/dimmer.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/modules/dimmer.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/modules/dimmer.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/modules/dimmer.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/modules/dimmer.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/modules/dimmer.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/modules/dimmer.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/modules/dimmer.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/modules/dropdown.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/modules/dropdown.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/modules/dropdown.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/modules/dropdown.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/modules/dropdown.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/modules/dropdown.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/modules/dropdown.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/modules/dropdown.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/modules/embed.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/modules/embed.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/modules/embed.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/modules/embed.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/bootstrap3/elements/button.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/modules/embed.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/bootstrap3/elements/button.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/modules/embed.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/modules/modal.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/modules/modal.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/modules/modal.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/modules/modal.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/modules/modal.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/modules/modal.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/modules/modal.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/modules/modal.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/modules/nag.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/modules/nag.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/modules/nag.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/modules/nag.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/modules/nag.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/modules/nag.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/modules/nag.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/modules/nag.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/modules/popup.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/modules/popup.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/modules/popup.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/modules/popup.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/modules/popup.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/modules/popup.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/modules/popup.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/modules/popup.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/modules/progress.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/modules/progress.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/modules/progress.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/modules/progress.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/modules/progress.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/modules/progress.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/modules/progress.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/modules/progress.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/modules/rating.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/modules/rating.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/modules/rating.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/modules/rating.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/modules/rating.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/modules/rating.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/modules/rating.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/modules/rating.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/modules/search.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/modules/search.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/modules/search.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/modules/search.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/modules/search.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/modules/search.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/modules/search.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/modules/search.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/modules/shape.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/modules/shape.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/modules/shape.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/modules/shape.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/modules/shape.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/modules/shape.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/modules/shape.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/modules/shape.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/modules/sidebar.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/modules/sidebar.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/modules/sidebar.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/modules/sidebar.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/modules/sidebar.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/modules/sidebar.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/modules/sidebar.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/modules/sidebar.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/modules/sticky.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/modules/sticky.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/modules/sticky.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/modules/sticky.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/modules/sticky.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/modules/sticky.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/modules/sticky.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/modules/sticky.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/modules/tab.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/modules/tab.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/modules/tab.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/modules/tab.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/modules/tab.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/modules/tab.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/modules/tab.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/modules/tab.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/modules/transition.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/modules/transition.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/modules/transition.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/modules/transition.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/modules/transition.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/modules/transition.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/modules/transition.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/modules/transition.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/views/ad.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/views/ad.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/views/ad.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/views/ad.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/views/ad.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/views/ad.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/views/ad.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/views/ad.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/views/card.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/views/card.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/views/card.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/views/card.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/views/card.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/views/card.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/views/card.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/views/card.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/views/comment.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/views/comment.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/views/comment.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/views/comment.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/views/comment.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/views/comment.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/views/comment.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/views/comment.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/views/feed.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/views/feed.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/views/feed.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/views/feed.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/views/feed.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/views/feed.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/views/feed.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/views/feed.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/views/item.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/views/item.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/views/item.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/views/item.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/views/item.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/views/item.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/views/item.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/views/item.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/views/statistic.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/views/statistic.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/views/statistic.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/views/statistic.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/views/statistic.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/views/statistic.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/_site/views/statistic.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/views/statistic.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/behaviors/api.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/behaviors/api.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/behaviors/api.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/behaviors/api.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/behaviors/colorize.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/behaviors/colorize.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/behaviors/colorize.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/behaviors/colorize.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/behaviors/form.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/behaviors/form.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/behaviors/form.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/behaviors/form.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/behaviors/state.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/behaviors/state.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/behaviors/state.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/behaviors/state.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/behaviors/visibility.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/behaviors/visibility.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/behaviors/visibility.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/behaviors/visibility.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/behaviors/visit.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/behaviors/visit.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/behaviors/visit.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/behaviors/visit.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/collections/breadcrumb.less b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/collections/breadcrumb.less similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/collections/breadcrumb.less rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/collections/breadcrumb.less diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/collections/form.less b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/collections/form.less similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/collections/form.less rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/collections/form.less diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/collections/grid.less b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/collections/grid.less similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/collections/grid.less rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/collections/grid.less diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/collections/menu.less b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/collections/menu.less similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/collections/menu.less rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/collections/menu.less diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/collections/message.less b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/collections/message.less similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/collections/message.less rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/collections/message.less diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/collections/table.less b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/collections/table.less similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/collections/table.less rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/collections/table.less diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/elements/button.less b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/elements/button.less similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/elements/button.less rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/elements/button.less diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/elements/container.less b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/elements/container.less similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/elements/container.less rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/elements/container.less diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/elements/divider.less b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/elements/divider.less similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/elements/divider.less rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/elements/divider.less diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/elements/flag.less b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/elements/flag.less similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/elements/flag.less rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/elements/flag.less diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/elements/header.less b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/elements/header.less similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/elements/header.less rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/elements/header.less diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/elements/icon.less b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/elements/icon.less similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/elements/icon.less rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/elements/icon.less diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/elements/image.less b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/elements/image.less similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/elements/image.less rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/elements/image.less diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/elements/input.less b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/elements/input.less similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/elements/input.less rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/elements/input.less diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/elements/label.less b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/elements/label.less similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/elements/label.less rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/elements/label.less diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/elements/list.less b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/elements/list.less similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/elements/list.less rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/elements/list.less diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/elements/loader.less b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/elements/loader.less similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/elements/loader.less rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/elements/loader.less diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/elements/rail.less b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/elements/rail.less similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/elements/rail.less rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/elements/rail.less diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/elements/reveal.less b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/elements/reveal.less similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/elements/reveal.less rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/elements/reveal.less diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/elements/segment.less b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/elements/segment.less similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/elements/segment.less rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/elements/segment.less diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/elements/step.less b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/elements/step.less similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/elements/step.less rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/elements/step.less diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/globals/reset.less b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/globals/reset.less similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/globals/reset.less rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/globals/reset.less diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/globals/site.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/globals/site.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/globals/site.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/globals/site.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/globals/site.less b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/globals/site.less similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/globals/site.less rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/globals/site.less diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/modules/accordion.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/modules/accordion.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/modules/accordion.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/modules/accordion.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/modules/accordion.less b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/modules/accordion.less similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/modules/accordion.less rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/modules/accordion.less diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/modules/checkbox.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/modules/checkbox.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/modules/checkbox.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/modules/checkbox.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/modules/checkbox.less b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/modules/checkbox.less similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/modules/checkbox.less rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/modules/checkbox.less diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/modules/dimmer.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/modules/dimmer.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/modules/dimmer.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/modules/dimmer.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/modules/dimmer.less b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/modules/dimmer.less similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/modules/dimmer.less rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/modules/dimmer.less diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/modules/dropdown.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/modules/dropdown.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/modules/dropdown.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/modules/dropdown.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/modules/dropdown.less b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/modules/dropdown.less similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/modules/dropdown.less rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/modules/dropdown.less diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/modules/embed.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/modules/embed.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/modules/embed.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/modules/embed.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/modules/embed.less b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/modules/embed.less similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/modules/embed.less rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/modules/embed.less diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/modules/modal.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/modules/modal.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/modules/modal.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/modules/modal.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/modules/modal.less b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/modules/modal.less similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/modules/modal.less rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/modules/modal.less diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/modules/nag.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/modules/nag.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/modules/nag.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/modules/nag.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/modules/nag.less b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/modules/nag.less similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/modules/nag.less rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/modules/nag.less diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/modules/popup.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/modules/popup.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/modules/popup.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/modules/popup.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/modules/popup.less b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/modules/popup.less similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/modules/popup.less rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/modules/popup.less diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/modules/progress.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/modules/progress.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/modules/progress.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/modules/progress.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/modules/progress.less b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/modules/progress.less similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/modules/progress.less rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/modules/progress.less diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/modules/rating.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/modules/rating.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/modules/rating.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/modules/rating.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/modules/rating.less b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/modules/rating.less similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/modules/rating.less rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/modules/rating.less diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/modules/search.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/modules/search.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/modules/search.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/modules/search.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/modules/search.less b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/modules/search.less similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/modules/search.less rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/modules/search.less diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/modules/shape.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/modules/shape.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/modules/shape.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/modules/shape.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/modules/shape.less b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/modules/shape.less similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/modules/shape.less rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/modules/shape.less diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/modules/sidebar.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/modules/sidebar.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/modules/sidebar.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/modules/sidebar.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/modules/sidebar.less b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/modules/sidebar.less similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/modules/sidebar.less rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/modules/sidebar.less diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/modules/sticky.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/modules/sticky.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/modules/sticky.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/modules/sticky.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/modules/sticky.less b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/modules/sticky.less similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/modules/sticky.less rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/modules/sticky.less diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/modules/tab.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/modules/tab.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/modules/tab.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/modules/tab.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/modules/tab.less b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/modules/tab.less similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/modules/tab.less rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/modules/tab.less diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/modules/transition.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/modules/transition.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/modules/transition.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/modules/transition.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/modules/transition.less b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/modules/transition.less similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/modules/transition.less rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/modules/transition.less diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/views/ad.less b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/views/ad.less similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/views/ad.less rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/views/ad.less diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/views/card.less b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/views/card.less similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/views/card.less rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/views/card.less diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/views/comment.less b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/views/comment.less similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/views/comment.less rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/views/comment.less diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/views/feed.less b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/views/feed.less similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/views/feed.less rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/views/feed.less diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/views/item.less b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/views/item.less similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/views/item.less rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/views/item.less diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/views/statistic.less b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/views/statistic.less similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/definitions/views/statistic.less rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/views/statistic.less diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/semantic.less b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/semantic.less similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/semantic.less rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/semantic.less diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/theme.config.example b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/theme.config.example similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/theme.config.example rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/theme.config.example diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/theme.less b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/theme.less similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/theme.less rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/theme.less diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/amazon/elements/button.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/amazon/elements/button.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/amazon/elements/button.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/amazon/elements/button.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/amazon/elements/button.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/amazon/elements/button.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/amazon/elements/button.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/amazon/elements/button.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/amazon/globals/site.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/amazon/globals/site.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/amazon/globals/site.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/amazon/globals/site.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/basic/assets/fonts/icons.eot b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/basic/assets/fonts/icons.eot similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/basic/assets/fonts/icons.eot rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/basic/assets/fonts/icons.eot diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/basic/assets/fonts/icons.svg b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/basic/assets/fonts/icons.svg similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/basic/assets/fonts/icons.svg rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/basic/assets/fonts/icons.svg diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/basic/assets/fonts/icons.ttf b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/basic/assets/fonts/icons.ttf similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/basic/assets/fonts/icons.ttf rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/basic/assets/fonts/icons.ttf diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/basic/assets/fonts/icons.woff b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/basic/assets/fonts/icons.woff similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/basic/assets/fonts/icons.woff rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/basic/assets/fonts/icons.woff diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/basic/collections/table.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/basic/collections/table.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/basic/collections/table.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/basic/collections/table.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/basic/collections/table.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/basic/collections/table.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/basic/collections/table.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/basic/collections/table.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/basic/elements/button.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/basic/elements/button.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/basic/elements/button.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/basic/elements/button.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/basic/elements/button.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/basic/elements/button.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/basic/elements/button.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/basic/elements/button.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/basic/elements/icon.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/basic/elements/icon.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/basic/elements/icon.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/basic/elements/icon.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/basic/elements/icon.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/basic/elements/icon.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/basic/elements/icon.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/basic/elements/icon.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/basic/elements/step.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/basic/elements/step.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/basic/elements/step.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/basic/elements/step.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/basic/elements/step.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/basic/elements/step.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/basic/elements/step.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/basic/elements/step.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/basic/globals/reset.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/basic/globals/reset.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/basic/globals/reset.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/basic/globals/reset.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/basic/globals/reset.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/basic/globals/reset.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/basic/globals/reset.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/basic/globals/reset.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/basic/modules/progress.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/basic/modules/progress.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/basic/modules/progress.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/basic/modules/progress.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/basic/modules/progress.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/basic/modules/progress.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/basic/modules/progress.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/basic/modules/progress.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/basic/views/card.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/basic/views/card.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/basic/views/card.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/basic/views/card.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/basic/views/card.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/basic/views/card.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/basic/views/card.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/basic/views/card.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/bookish/elements/header.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/bookish/elements/header.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/bookish/elements/header.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/bookish/elements/header.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/bookish/elements/header.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/bookish/elements/header.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/bookish/elements/header.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/bookish/elements/header.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/round/elements/button.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/bootstrap3/elements/button.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/round/elements/button.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/bootstrap3/elements/button.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/bootstrap3/elements/button.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/bootstrap3/elements/button.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/bootstrap3/elements/button.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/bootstrap3/elements/button.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/chubby/collections/form.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/chubby/collections/form.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/chubby/collections/form.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/chubby/collections/form.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/chubby/collections/form.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/chubby/collections/form.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/chubby/collections/form.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/chubby/collections/form.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/chubby/collections/menu.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/chubby/collections/menu.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/chubby/collections/menu.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/chubby/collections/menu.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/chubby/collections/menu.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/chubby/collections/menu.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/chubby/collections/menu.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/chubby/collections/menu.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/chubby/elements/button.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/chubby/elements/button.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/chubby/elements/button.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/chubby/elements/button.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/chubby/elements/button.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/chubby/elements/button.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/chubby/elements/button.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/chubby/elements/button.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/chubby/elements/header.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/chubby/elements/header.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/chubby/elements/header.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/chubby/elements/header.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/chubby/elements/header.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/chubby/elements/header.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/chubby/elements/header.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/chubby/elements/header.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/chubby/modules/accordion.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/chubby/modules/accordion.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/chubby/modules/accordion.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/chubby/modules/accordion.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/chubby/modules/accordion.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/chubby/modules/accordion.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/chubby/modules/accordion.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/chubby/modules/accordion.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/chubby/views/comment.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/chubby/views/comment.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/chubby/views/comment.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/chubby/views/comment.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/chubby/views/comment.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/chubby/views/comment.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/chubby/views/comment.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/chubby/views/comment.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/classic/collections/table.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/classic/collections/table.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/classic/collections/table.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/classic/collections/table.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/classic/collections/table.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/classic/collections/table.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/classic/collections/table.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/classic/collections/table.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/classic/elements/button.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/classic/elements/button.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/classic/elements/button.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/classic/elements/button.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/classic/elements/button.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/classic/elements/button.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/classic/elements/button.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/classic/elements/button.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/classic/elements/header.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/classic/elements/header.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/classic/elements/header.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/classic/elements/header.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/classic/elements/header.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/classic/elements/header.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/classic/elements/header.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/classic/elements/header.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/classic/modules/progress.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/classic/modules/progress.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/classic/modules/progress.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/classic/modules/progress.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/classic/modules/progress.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/classic/modules/progress.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/classic/modules/progress.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/classic/modules/progress.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/classic/views/card.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/classic/views/card.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/classic/views/card.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/classic/views/card.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/classic/views/card.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/classic/views/card.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/classic/views/card.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/classic/views/card.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/colored/modules/checkbox.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/colored/modules/checkbox.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/colored/modules/checkbox.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/colored/modules/checkbox.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/colored/modules/checkbox.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/colored/modules/checkbox.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/colored/modules/checkbox.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/colored/modules/checkbox.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/assets/fonts/icons.eot b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/assets/fonts/icons.eot similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/assets/fonts/icons.eot rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/assets/fonts/icons.eot diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/assets/fonts/icons.svg b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/assets/fonts/icons.svg similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/assets/fonts/icons.svg rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/assets/fonts/icons.svg diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/assets/fonts/icons.ttf b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/assets/fonts/icons.ttf similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/assets/fonts/icons.ttf rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/assets/fonts/icons.ttf diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/assets/fonts/icons.woff b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/assets/fonts/icons.woff similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/assets/fonts/icons.woff rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/assets/fonts/icons.woff diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/assets/fonts/icons.woff2 b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/assets/fonts/icons.woff2 similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/assets/fonts/icons.woff2 rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/assets/fonts/icons.woff2 diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/assets/images/flags.png b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/assets/images/flags.png similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/assets/images/flags.png rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/assets/images/flags.png diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/collections/breadcrumb.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/collections/breadcrumb.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/collections/breadcrumb.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/collections/breadcrumb.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/collections/breadcrumb.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/collections/breadcrumb.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/collections/breadcrumb.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/collections/breadcrumb.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/collections/form.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/collections/form.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/collections/form.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/collections/form.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/collections/form.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/collections/form.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/collections/form.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/collections/form.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/collections/grid.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/collections/grid.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/collections/grid.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/collections/grid.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/collections/grid.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/collections/grid.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/collections/grid.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/collections/grid.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/collections/menu.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/collections/menu.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/collections/menu.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/collections/menu.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/collections/menu.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/collections/menu.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/collections/menu.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/collections/menu.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/collections/message.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/collections/message.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/collections/message.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/collections/message.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/collections/message.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/collections/message.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/collections/message.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/collections/message.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/collections/table.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/collections/table.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/collections/table.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/collections/table.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/collections/table.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/collections/table.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/collections/table.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/collections/table.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/elements/button.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/elements/button.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/elements/button.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/elements/button.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/elements/button.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/elements/button.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/elements/button.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/elements/button.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/elements/container.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/elements/container.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/elements/container.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/elements/container.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/elements/container.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/elements/container.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/elements/container.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/elements/container.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/elements/divider.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/elements/divider.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/elements/divider.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/elements/divider.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/elements/divider.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/elements/divider.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/elements/divider.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/elements/divider.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/elements/flag.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/elements/flag.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/elements/flag.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/elements/flag.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/elements/flag.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/elements/flag.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/elements/flag.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/elements/flag.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/elements/header.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/elements/header.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/elements/header.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/elements/header.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/elements/header.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/elements/header.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/elements/header.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/elements/header.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/elements/icon.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/elements/icon.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/elements/icon.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/elements/icon.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/elements/icon.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/elements/icon.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/elements/icon.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/elements/icon.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/elements/image.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/elements/image.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/elements/image.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/elements/image.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/elements/image.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/elements/image.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/elements/image.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/elements/image.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/elements/input.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/elements/input.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/elements/input.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/elements/input.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/elements/input.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/elements/input.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/elements/input.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/elements/input.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/elements/label.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/elements/label.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/elements/label.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/elements/label.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/elements/label.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/elements/label.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/elements/label.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/elements/label.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/elements/list.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/elements/list.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/elements/list.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/elements/list.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/elements/list.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/elements/list.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/elements/list.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/elements/list.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/elements/loader.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/elements/loader.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/elements/loader.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/elements/loader.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/elements/loader.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/elements/loader.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/elements/loader.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/elements/loader.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/elements/rail.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/elements/rail.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/elements/rail.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/elements/rail.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/elements/rail.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/elements/rail.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/elements/rail.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/elements/rail.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/elements/reveal.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/elements/reveal.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/elements/reveal.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/elements/reveal.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/elements/reveal.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/elements/reveal.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/elements/reveal.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/elements/reveal.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/elements/segment.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/elements/segment.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/elements/segment.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/elements/segment.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/elements/segment.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/elements/segment.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/elements/segment.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/elements/segment.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/elements/step.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/elements/step.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/elements/step.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/elements/step.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/elements/step.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/elements/step.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/elements/step.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/elements/step.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/globals/reset.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/globals/reset.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/globals/reset.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/globals/reset.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/globals/reset.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/globals/reset.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/globals/reset.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/globals/reset.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/globals/site.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/globals/site.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/globals/site.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/globals/site.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/globals/site.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/globals/site.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/globals/site.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/globals/site.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/modules/accordion.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/modules/accordion.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/modules/accordion.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/modules/accordion.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/modules/accordion.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/modules/accordion.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/modules/accordion.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/modules/accordion.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/modules/chatroom.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/modules/chatroom.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/modules/chatroom.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/modules/chatroom.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/modules/chatroom.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/modules/chatroom.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/modules/chatroom.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/modules/chatroom.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/modules/checkbox.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/modules/checkbox.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/modules/checkbox.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/modules/checkbox.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/modules/checkbox.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/modules/checkbox.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/modules/checkbox.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/modules/checkbox.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/modules/dimmer.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/modules/dimmer.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/modules/dimmer.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/modules/dimmer.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/modules/dimmer.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/modules/dimmer.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/modules/dimmer.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/modules/dimmer.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/modules/dropdown.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/modules/dropdown.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/modules/dropdown.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/modules/dropdown.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/modules/dropdown.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/modules/dropdown.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/modules/dropdown.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/modules/dropdown.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/modules/embed.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/modules/embed.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/modules/embed.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/modules/embed.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/modules/embed.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/modules/embed.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/modules/embed.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/modules/embed.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/modules/modal.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/modules/modal.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/modules/modal.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/modules/modal.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/modules/modal.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/modules/modal.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/modules/modal.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/modules/modal.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/modules/nag.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/modules/nag.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/modules/nag.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/modules/nag.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/modules/nag.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/modules/nag.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/modules/nag.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/modules/nag.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/modules/popup.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/modules/popup.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/modules/popup.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/modules/popup.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/modules/popup.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/modules/popup.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/modules/popup.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/modules/popup.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/modules/progress.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/modules/progress.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/modules/progress.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/modules/progress.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/modules/progress.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/modules/progress.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/modules/progress.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/modules/progress.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/modules/rating.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/modules/rating.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/modules/rating.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/modules/rating.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/modules/rating.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/modules/rating.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/modules/rating.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/modules/rating.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/modules/search.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/modules/search.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/modules/search.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/modules/search.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/modules/search.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/modules/search.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/modules/search.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/modules/search.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/modules/shape.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/modules/shape.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/modules/shape.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/modules/shape.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/modules/shape.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/modules/shape.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/modules/shape.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/modules/shape.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/modules/sidebar.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/modules/sidebar.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/modules/sidebar.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/modules/sidebar.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/modules/sidebar.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/modules/sidebar.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/modules/sidebar.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/modules/sidebar.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/modules/sticky.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/modules/sticky.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/modules/sticky.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/modules/sticky.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/modules/sticky.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/modules/sticky.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/modules/sticky.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/modules/sticky.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/modules/tab.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/modules/tab.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/modules/tab.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/modules/tab.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/modules/tab.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/modules/tab.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/modules/tab.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/modules/tab.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/modules/transition.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/modules/transition.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/modules/transition.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/modules/transition.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/modules/transition.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/modules/transition.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/modules/transition.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/modules/transition.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/views/ad.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/views/ad.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/views/ad.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/views/ad.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/views/ad.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/views/ad.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/views/ad.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/views/ad.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/views/card.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/views/card.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/views/card.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/views/card.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/views/card.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/views/card.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/views/card.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/views/card.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/views/comment.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/views/comment.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/views/comment.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/views/comment.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/views/comment.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/views/comment.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/views/comment.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/views/comment.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/views/feed.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/views/feed.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/views/feed.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/views/feed.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/views/feed.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/views/feed.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/views/feed.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/views/feed.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/views/item.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/views/item.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/views/item.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/views/item.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/views/item.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/views/item.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/views/item.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/views/item.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/views/statistic.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/views/statistic.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/views/statistic.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/views/statistic.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/views/statistic.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/views/statistic.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/default/views/statistic.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/views/statistic.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/duo/elements/loader.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/duo/elements/loader.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/duo/elements/loader.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/duo/elements/loader.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/duo/elements/loader.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/duo/elements/loader.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/duo/elements/loader.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/duo/elements/loader.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/fixed-width/collections/grid.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/fixed-width/collections/grid.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/fixed-width/collections/grid.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/fixed-width/collections/grid.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/fixed-width/collections/grid.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/fixed-width/collections/grid.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/fixed-width/collections/grid.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/fixed-width/collections/grid.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/fixed-width/modules/modal.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/fixed-width/modules/modal.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/fixed-width/modules/modal.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/fixed-width/modules/modal.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/fixed-width/modules/modal.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/fixed-width/modules/modal.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/fixed-width/modules/modal.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/fixed-width/modules/modal.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/flat/collections/form.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/flat/collections/form.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/flat/collections/form.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/flat/collections/form.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/flat/collections/form.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/flat/collections/form.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/flat/collections/form.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/flat/collections/form.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/flat/globals/site.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/flat/globals/site.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/flat/globals/site.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/flat/globals/site.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/flat/globals/site.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/flat/globals/site.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/flat/globals/site.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/flat/globals/site.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/github/assets/fonts/octicons-local.ttf b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/github/assets/fonts/octicons-local.ttf similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/github/assets/fonts/octicons-local.ttf rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/github/assets/fonts/octicons-local.ttf diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/github/assets/fonts/octicons.svg b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/github/assets/fonts/octicons.svg similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/github/assets/fonts/octicons.svg rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/github/assets/fonts/octicons.svg diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/github/assets/fonts/octicons.ttf b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/github/assets/fonts/octicons.ttf similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/github/assets/fonts/octicons.ttf rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/github/assets/fonts/octicons.ttf diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/github/assets/fonts/octicons.woff b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/github/assets/fonts/octicons.woff similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/github/assets/fonts/octicons.woff rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/github/assets/fonts/octicons.woff diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/github/collections/breadcrumb.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/github/collections/breadcrumb.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/github/collections/breadcrumb.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/github/collections/breadcrumb.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/github/collections/form.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/github/collections/form.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/github/collections/form.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/github/collections/form.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/github/collections/form.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/github/collections/form.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/github/collections/form.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/github/collections/form.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/github/collections/grid.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/github/collections/grid.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/github/collections/grid.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/github/collections/grid.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/github/collections/menu.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/github/collections/menu.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/github/collections/menu.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/github/collections/menu.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/github/collections/menu.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/github/collections/menu.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/github/collections/menu.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/github/collections/menu.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/github/collections/message.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/github/collections/message.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/github/collections/message.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/github/collections/message.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/github/collections/message.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/github/collections/message.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/github/collections/message.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/github/collections/message.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/github/collections/table.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/github/collections/table.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/github/collections/table.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/github/collections/table.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/github/elements/button.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/github/elements/button.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/github/elements/button.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/github/elements/button.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/github/elements/button.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/github/elements/button.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/github/elements/button.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/github/elements/button.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/github/elements/header.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/github/elements/header.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/github/elements/header.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/github/elements/header.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/github/elements/icon.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/github/elements/icon.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/github/elements/icon.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/github/elements/icon.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/github/elements/icon.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/github/elements/icon.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/github/elements/icon.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/github/elements/icon.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/github/elements/image.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/github/elements/image.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/github/elements/image.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/github/elements/image.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/github/elements/input.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/github/elements/input.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/github/elements/input.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/github/elements/input.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/github/elements/input.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/github/elements/input.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/github/elements/input.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/github/elements/input.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/github/elements/label.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/github/elements/label.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/github/elements/label.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/github/elements/label.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/github/elements/label.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/github/elements/label.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/github/elements/label.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/github/elements/label.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/github/elements/segment.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/github/elements/segment.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/github/elements/segment.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/github/elements/segment.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/github/elements/segment.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/github/elements/segment.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/github/elements/segment.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/github/elements/segment.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/github/elements/step.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/github/elements/step.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/github/elements/step.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/github/elements/step.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/github/elements/step.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/github/elements/step.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/github/elements/step.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/github/elements/step.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/github/globals/site.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/github/globals/site.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/github/globals/site.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/github/globals/site.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/github/modules/dropdown.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/github/modules/dropdown.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/github/modules/dropdown.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/github/modules/dropdown.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/github/modules/dropdown.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/github/modules/dropdown.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/github/modules/dropdown.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/github/modules/dropdown.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/github/modules/popup.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/github/modules/popup.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/github/modules/popup.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/github/modules/popup.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/gmail/collections/message.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/gmail/collections/message.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/gmail/collections/message.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/gmail/collections/message.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/gmail/collections/message.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/gmail/collections/message.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/gmail/collections/message.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/gmail/collections/message.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/instagram/views/card.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/instagram/views/card.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/instagram/views/card.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/instagram/views/card.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/instagram/views/card.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/instagram/views/card.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/instagram/views/card.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/instagram/views/card.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/material/assets/fonts/icons.eot b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/material/assets/fonts/icons.eot similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/material/assets/fonts/icons.eot rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/material/assets/fonts/icons.eot diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/material/assets/fonts/icons.svg b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/material/assets/fonts/icons.svg similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/material/assets/fonts/icons.svg rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/material/assets/fonts/icons.svg diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/material/assets/fonts/icons.ttf b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/material/assets/fonts/icons.ttf similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/material/assets/fonts/icons.ttf rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/material/assets/fonts/icons.ttf diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/material/assets/fonts/icons.woff b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/material/assets/fonts/icons.woff similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/material/assets/fonts/icons.woff rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/material/assets/fonts/icons.woff diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/material/collections/menu.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/material/collections/menu.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/material/collections/menu.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/material/collections/menu.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/material/collections/menu.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/material/collections/menu.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/material/collections/menu.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/material/collections/menu.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/material/elements/button.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/material/elements/button.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/material/elements/button.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/material/elements/button.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/material/elements/button.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/material/elements/button.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/material/elements/button.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/material/elements/button.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/material/elements/header.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/material/elements/header.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/material/elements/header.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/material/elements/header.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/material/elements/header.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/material/elements/header.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/material/elements/header.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/material/elements/header.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/material/elements/icon.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/material/elements/icon.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/material/elements/icon.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/material/elements/icon.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/material/elements/icon.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/material/elements/icon.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/material/elements/icon.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/material/elements/icon.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/material/globals/site.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/material/globals/site.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/material/globals/site.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/material/globals/site.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/material/globals/site.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/material/globals/site.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/material/globals/site.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/material/globals/site.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/material/modules/dropdown.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/material/modules/dropdown.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/material/modules/dropdown.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/material/modules/dropdown.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/material/modules/dropdown.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/material/modules/dropdown.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/material/modules/dropdown.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/material/modules/dropdown.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/material/modules/modal.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/material/modules/modal.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/material/modules/modal.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/material/modules/modal.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/material/modules/modal.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/material/modules/modal.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/material/modules/modal.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/material/modules/modal.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/pulsar/elements/loader.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/pulsar/elements/loader.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/pulsar/elements/loader.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/pulsar/elements/loader.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/pulsar/elements/loader.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/pulsar/elements/loader.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/pulsar/elements/loader.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/pulsar/elements/loader.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/raised/elements/button.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/raised/elements/button.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/raised/elements/button.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/raised/elements/button.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/raised/elements/button.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/raised/elements/button.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/raised/elements/button.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/raised/elements/button.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/resetcss/globals/reset.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/resetcss/globals/reset.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/resetcss/globals/reset.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/resetcss/globals/reset.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/resetcss/globals/reset.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/resetcss/globals/reset.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/resetcss/globals/reset.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/resetcss/globals/reset.variables diff --git a/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/round/elements/button.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/round/elements/button.overrides new file mode 100644 index 0000000000..e69de29bb2 diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/round/elements/button.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/round/elements/button.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/round/elements/button.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/round/elements/button.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/rtl/globals/site.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/rtl/globals/site.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/rtl/globals/site.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/rtl/globals/site.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/rtl/globals/site.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/rtl/globals/site.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/rtl/globals/site.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/rtl/globals/site.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/striped/modules/progress.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/striped/modules/progress.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/striped/modules/progress.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/striped/modules/progress.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/striped/modules/progress.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/striped/modules/progress.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/striped/modules/progress.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/striped/modules/progress.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/timeline/views/feed.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/timeline/views/feed.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/timeline/views/feed.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/timeline/views/feed.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/timeline/views/feed.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/timeline/views/feed.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/timeline/views/feed.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/timeline/views/feed.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/twitter/elements/button.overrides b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/twitter/elements/button.overrides similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/twitter/elements/button.overrides rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/twitter/elements/button.overrides diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/twitter/elements/button.variables b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/twitter/elements/button.variables similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/src/themes/twitter/elements/button.variables rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/twitter/elements/button.variables diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/tasks/README.md b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/README.md similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/tasks/README.md rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/README.md diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/tasks/admin/components/create.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/admin/components/create.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/tasks/admin/components/create.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/admin/components/create.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/tasks/admin/components/init.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/admin/components/init.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/tasks/admin/components/init.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/admin/components/init.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/tasks/admin/components/update.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/admin/components/update.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/tasks/admin/components/update.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/admin/components/update.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/tasks/admin/distributions/create.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/admin/distributions/create.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/tasks/admin/distributions/create.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/admin/distributions/create.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/tasks/admin/distributions/init.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/admin/distributions/init.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/tasks/admin/distributions/init.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/admin/distributions/init.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/tasks/admin/distributions/update.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/admin/distributions/update.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/tasks/admin/distributions/update.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/admin/distributions/update.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/tasks/admin/publish.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/admin/publish.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/tasks/admin/publish.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/admin/publish.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/tasks/admin/register.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/admin/register.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/tasks/admin/register.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/admin/register.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/tasks/admin/release.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/admin/release.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/tasks/admin/release.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/admin/release.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/tasks/build.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/build.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/tasks/build.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/build.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/tasks/check-install.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/check-install.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/tasks/check-install.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/check-install.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/tasks/clean.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/clean.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/tasks/clean.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/clean.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/tasks/collections/README.md b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/collections/README.md similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/tasks/collections/README.md rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/collections/README.md diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/tasks/collections/admin.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/collections/admin.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/tasks/collections/admin.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/collections/admin.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/tasks/collections/build.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/collections/build.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/tasks/collections/build.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/collections/build.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/tasks/collections/internal.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/collections/internal.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/tasks/collections/internal.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/collections/internal.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/tasks/collections/rtl.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/collections/rtl.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/tasks/collections/rtl.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/collections/rtl.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/tasks/config/admin/github.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/config/admin/github.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/tasks/config/admin/github.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/config/admin/github.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/tasks/config/admin/oauth.example.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/config/admin/oauth.example.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/tasks/config/admin/oauth.example.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/config/admin/oauth.example.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/tasks/config/admin/release.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/config/admin/release.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/tasks/config/admin/release.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/config/admin/release.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/tasks/config/admin/templates/README.md b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/config/admin/templates/README.md similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/tasks/config/admin/templates/README.md rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/config/admin/templates/README.md diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/tasks/config/admin/templates/bower.json b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/config/admin/templates/bower.json similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/tasks/config/admin/templates/bower.json rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/config/admin/templates/bower.json diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/tasks/config/admin/templates/component-package.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/config/admin/templates/component-package.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/tasks/config/admin/templates/component-package.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/config/admin/templates/component-package.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/tasks/config/admin/templates/composer.json b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/config/admin/templates/composer.json similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/tasks/config/admin/templates/composer.json rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/config/admin/templates/composer.json diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/tasks/config/admin/templates/css-package.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/config/admin/templates/css-package.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/tasks/config/admin/templates/css-package.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/config/admin/templates/css-package.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/tasks/config/admin/templates/less-package.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/config/admin/templates/less-package.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/tasks/config/admin/templates/less-package.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/config/admin/templates/less-package.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/tasks/config/admin/templates/package.json b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/config/admin/templates/package.json similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/tasks/config/admin/templates/package.json rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/config/admin/templates/package.json diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/tasks/config/defaults.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/config/defaults.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/tasks/config/defaults.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/config/defaults.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/tasks/config/docs.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/config/docs.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/tasks/config/docs.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/config/docs.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/tasks/config/npm/gulpfile.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/config/npm/gulpfile.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/tasks/config/npm/gulpfile.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/config/npm/gulpfile.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/tasks/config/project/config.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/config/project/config.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/tasks/config/project/config.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/config/project/config.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/tasks/config/project/install.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/config/project/install.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/tasks/config/project/install.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/config/project/install.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/tasks/config/project/release.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/config/project/release.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/tasks/config/project/release.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/config/project/release.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/tasks/config/tasks.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/config/tasks.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/tasks/config/tasks.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/config/tasks.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/tasks/config/user.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/config/user.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/tasks/config/user.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/config/user.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/tasks/docs/build.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/docs/build.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/tasks/docs/build.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/docs/build.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/tasks/docs/metadata.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/docs/metadata.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/tasks/docs/metadata.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/docs/metadata.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/tasks/docs/serve.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/docs/serve.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/tasks/docs/serve.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/docs/serve.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/tasks/install.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/install.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/tasks/install.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/install.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/tasks/rtl/build.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/rtl/build.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/tasks/rtl/build.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/rtl/build.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/tasks/rtl/watch.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/rtl/watch.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/tasks/rtl/watch.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/rtl/watch.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/tasks/version.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/version.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/tasks/version.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/version.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/tasks/watch.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/watch.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/bower_components/semantic/tasks/watch.js rename to samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/watch.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/controllers/CreateDeal.js b/samples/irs-demo/web/src/main/resources/static/js/controllers/CreateDeal.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/controllers/CreateDeal.js rename to samples/irs-demo/web/src/main/resources/static/js/controllers/CreateDeal.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/controllers/Deal.js b/samples/irs-demo/web/src/main/resources/static/js/controllers/Deal.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/controllers/Deal.js rename to samples/irs-demo/web/src/main/resources/static/js/controllers/Deal.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/controllers/Home.js b/samples/irs-demo/web/src/main/resources/static/js/controllers/Home.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/controllers/Home.js rename to samples/irs-demo/web/src/main/resources/static/js/controllers/Home.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/require-config.js b/samples/irs-demo/web/src/main/resources/static/js/require-config.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/require-config.js rename to samples/irs-demo/web/src/main/resources/static/js/require-config.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/routes.js b/samples/irs-demo/web/src/main/resources/static/js/routes.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/routes.js rename to samples/irs-demo/web/src/main/resources/static/js/routes.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/services/HttpErrorHandler.js b/samples/irs-demo/web/src/main/resources/static/js/services/HttpErrorHandler.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/services/HttpErrorHandler.js rename to samples/irs-demo/web/src/main/resources/static/js/services/HttpErrorHandler.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/services/NodeApi.js b/samples/irs-demo/web/src/main/resources/static/js/services/NodeApi.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/services/NodeApi.js rename to samples/irs-demo/web/src/main/resources/static/js/services/NodeApi.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/utils/dayCountBasisLookup.js b/samples/irs-demo/web/src/main/resources/static/js/utils/dayCountBasisLookup.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/utils/dayCountBasisLookup.js rename to samples/irs-demo/web/src/main/resources/static/js/utils/dayCountBasisLookup.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/utils/semantic.js b/samples/irs-demo/web/src/main/resources/static/js/utils/semantic.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/utils/semantic.js rename to samples/irs-demo/web/src/main/resources/static/js/utils/semantic.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/viewmodel/Common.js b/samples/irs-demo/web/src/main/resources/static/js/viewmodel/Common.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/viewmodel/Common.js rename to samples/irs-demo/web/src/main/resources/static/js/viewmodel/Common.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/viewmodel/Deal.js b/samples/irs-demo/web/src/main/resources/static/js/viewmodel/Deal.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/viewmodel/Deal.js rename to samples/irs-demo/web/src/main/resources/static/js/viewmodel/Deal.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/viewmodel/FixedLeg.js b/samples/irs-demo/web/src/main/resources/static/js/viewmodel/FixedLeg.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/viewmodel/FixedLeg.js rename to samples/irs-demo/web/src/main/resources/static/js/viewmodel/FixedLeg.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/viewmodel/FixedRate.js b/samples/irs-demo/web/src/main/resources/static/js/viewmodel/FixedRate.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/viewmodel/FixedRate.js rename to samples/irs-demo/web/src/main/resources/static/js/viewmodel/FixedRate.js diff --git a/samples/irs-demo/src/main/resources/irsweb/js/viewmodel/FloatingLeg.js b/samples/irs-demo/web/src/main/resources/static/js/viewmodel/FloatingLeg.js similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/js/viewmodel/FloatingLeg.js rename to samples/irs-demo/web/src/main/resources/static/js/viewmodel/FloatingLeg.js diff --git a/samples/irs-demo/src/main/resources/irsweb/view/create-deal.html b/samples/irs-demo/web/src/main/resources/static/view/create-deal.html similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/view/create-deal.html rename to samples/irs-demo/web/src/main/resources/static/view/create-deal.html diff --git a/samples/irs-demo/src/main/resources/irsweb/view/deal.html b/samples/irs-demo/web/src/main/resources/static/view/deal.html similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/view/deal.html rename to samples/irs-demo/web/src/main/resources/static/view/deal.html diff --git a/samples/irs-demo/src/main/resources/irsweb/view/home.html b/samples/irs-demo/web/src/main/resources/static/view/home.html similarity index 100% rename from samples/irs-demo/src/main/resources/irsweb/view/home.html rename to samples/irs-demo/web/src/main/resources/static/view/home.html diff --git a/samples/irs-demo/web/src/test/kotlin/net/corda/irs/web/IrsDemoWebApplicationTests.kt b/samples/irs-demo/web/src/test/kotlin/net/corda/irs/web/IrsDemoWebApplicationTests.kt new file mode 100644 index 0000000000..cbddd8a4b3 --- /dev/null +++ b/samples/irs-demo/web/src/test/kotlin/net/corda/irs/web/IrsDemoWebApplicationTests.kt @@ -0,0 +1,19 @@ +package net.corda.irs.web + +import net.corda.core.messaging.CordaRPCOps +import org.junit.Test +import org.junit.runner.RunWith +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.boot.test.mock.mockito.MockBean +import org.springframework.test.context.junit4.SpringRunner + +@RunWith(SpringRunner::class) +@SpringBootTest(properties = arrayOf("corda.host=localhost:12345", "corda.user=user", "corda.password=password")) +class IrsDemoWebApplicationTests { + @MockBean + lateinit var rpc: CordaRPCOps + + @Test + fun contextLoads() { + } +} diff --git a/samples/irs-demo/src/test/kotlin/net/corda/irs/api/InterestRatesSwapDemoAPI.kt b/samples/irs-demo/web/src/test/kotlin/net/corda/irs/web/api/InterestRatesSwapDemoAPI.kt similarity index 62% rename from samples/irs-demo/src/test/kotlin/net/corda/irs/api/InterestRatesSwapDemoAPI.kt rename to samples/irs-demo/web/src/test/kotlin/net/corda/irs/web/api/InterestRatesSwapDemoAPI.kt index 0ea9bfda2f..6a54266e28 100644 --- a/samples/irs-demo/src/test/kotlin/net/corda/irs/api/InterestRatesSwapDemoAPI.kt +++ b/samples/irs-demo/web/src/test/kotlin/net/corda/irs/web/api/InterestRatesSwapDemoAPI.kt @@ -1,55 +1,55 @@ -package net.corda.irs.api +package net.corda.irs.web.api import net.corda.core.messaging.CordaRPCOps import net.corda.core.messaging.startFlow import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.loggerFor +import net.corda.irs.api.NodeInterestRates import net.corda.irs.flows.UpdateBusinessDayFlow +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.http.HttpStatus +import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.* import java.time.LocalDate import java.time.LocalDateTime import java.time.ZoneId -import javax.ws.rs.* -import javax.ws.rs.core.MediaType -import javax.ws.rs.core.Response /** * GET /api/irs/demodate - return the current date as viewed by the system in YYYY-MM-DD format. * PUT /api/irs/demodate - put date in format YYYY-MM-DD to advance the current date as viewed by the system and * POST /api/irs/fixes - store the fixing data as a text file */ -@Path("irs") -class InterestRatesSwapDemoAPI(val rpc: CordaRPCOps) { +@RestController +@RequestMapping("/api/irs") +class InterestRatesSwapDemoAPI { companion object { private val logger = loggerFor() } - @PUT - @Path("demodate") - @Consumes(MediaType.APPLICATION_JSON) - fun storeDemoDate(newDemoDate: LocalDate): Response { + @Autowired + lateinit var rpc: CordaRPCOps + + @PutMapping("demodate") + fun storeDemoDate(@RequestBody newDemoDate: LocalDate): ResponseEntity { val priorDemoDate = fetchDemoDate() // Can only move date forwards if (newDemoDate.isAfter(priorDemoDate)) { rpc.startFlow(UpdateBusinessDayFlow::Broadcast, newDemoDate).returnValue.getOrThrow() - return Response.ok().build() + return ResponseEntity.ok().build() } val msg = "demodate is already $priorDemoDate and can only be updated with a later date" logger.error("Attempt to set demodate to $newDemoDate but $msg") - return Response.status(Response.Status.CONFLICT).entity(msg).build() + return ResponseEntity.status(HttpStatus.CONFLICT).body(msg) } - @GET - @Path("demodate") - @Produces(MediaType.APPLICATION_JSON) + @GetMapping("demodate") fun fetchDemoDate(): LocalDate { return LocalDateTime.ofInstant(rpc.currentNodeTime(), ZoneId.systemDefault()).toLocalDate() } - @POST - @Path("fixes") - @Consumes(MediaType.TEXT_PLAIN) - fun storeFixes(file: String): Response { + @PostMapping("fixes") + fun storeFixes(@RequestBody file: String): ResponseEntity { rpc.startFlow(NodeInterestRates::UploadFixesFlow, file).returnValue.getOrThrow() - return Response.ok().build() + return ResponseEntity.ok().build() } } \ No newline at end of file diff --git a/samples/irs-demo/src/test/kotlin/net/corda/irs/IRSDemo.kt b/samples/irs-demo/web/src/test/kotlin/net/corda/irs/web/demo/IRSDemo.kt similarity index 97% rename from samples/irs-demo/src/test/kotlin/net/corda/irs/IRSDemo.kt rename to samples/irs-demo/web/src/test/kotlin/net/corda/irs/web/demo/IRSDemo.kt index aac368f305..d2793e6785 100644 --- a/samples/irs-demo/src/test/kotlin/net/corda/irs/IRSDemo.kt +++ b/samples/irs-demo/web/src/test/kotlin/net/corda/irs/web/demo/IRSDemo.kt @@ -1,6 +1,6 @@ @file:JvmName("IRSDemo") -package net.corda.irs +package net.corda.irs.web.demo import joptsimple.OptionParser import net.corda.core.identity.CordaX500Name diff --git a/samples/irs-demo/src/test/kotlin/net/corda/irs/IrsDemoClientApi.kt b/samples/irs-demo/web/src/test/kotlin/net/corda/irs/web/demo/IrsDemoClientApi.kt similarity index 87% rename from samples/irs-demo/src/test/kotlin/net/corda/irs/IrsDemoClientApi.kt rename to samples/irs-demo/web/src/test/kotlin/net/corda/irs/web/demo/IrsDemoClientApi.kt index f4268f57ef..5c434cdb19 100644 --- a/samples/irs-demo/src/test/kotlin/net/corda/irs/IrsDemoClientApi.kt +++ b/samples/irs-demo/web/src/test/kotlin/net/corda/irs/web/demo/IrsDemoClientApi.kt @@ -1,11 +1,9 @@ -package net.corda.irs +package net.corda.irs.web.demo import net.corda.core.identity.CordaX500Name import net.corda.core.utilities.NetworkHostAndPort -import net.corda.irs.utilities.uploadFile import net.corda.testing.http.HttpApi import org.apache.commons.io.IOUtils -import java.net.URL /** * Interface for communicating with nodes running the IRS demo. @@ -26,8 +24,7 @@ class IRSDemoClientApi(private val hostAndPort: NetworkHostAndPort) { // TODO: Add uploading of files to the HTTP API fun runUploadRates() { val fileContents = IOUtils.toString(Thread.currentThread().contextClassLoader.getResourceAsStream("net/corda/irs/simulation/example.rates.txt"), Charsets.UTF_8.name()) - val url = URL("http://$hostAndPort/api/irs/fixes") - check(uploadFile(url, fileContents)) + check(api.postPlain("fixes", fileContents)) println("Rates successfully uploaded!") } diff --git a/samples/irs-demo/src/main/resources/net/corda/irs/simulation/example-irs-trade.json b/samples/irs-demo/web/src/test/resources/net/corda/irs/web/simulation/example-irs-trade.json similarity index 100% rename from samples/irs-demo/src/main/resources/net/corda/irs/simulation/example-irs-trade.json rename to samples/irs-demo/web/src/test/resources/net/corda/irs/web/simulation/example-irs-trade.json diff --git a/samples/irs-demo/src/main/resources/net/corda/irs/simulation/trade.json b/samples/irs-demo/web/src/test/resources/net/corda/irs/web/simulation/trade.json similarity index 100% rename from samples/irs-demo/src/main/resources/net/corda/irs/simulation/trade.json rename to samples/irs-demo/web/src/test/resources/net/corda/irs/web/simulation/trade.json diff --git a/samples/irs-demo/web/src/test/resources/scripts/runwebapps.bat b/samples/irs-demo/web/src/test/resources/scripts/runwebapps.bat new file mode 100755 index 0000000000..c92810b5ef --- /dev/null +++ b/samples/irs-demo/web/src/test/resources/scripts/runwebapps.bat @@ -0,0 +1,3 @@ +cmd /C start java -Dspring.profiles.active=NotaryService -jar #JAR_PATH# +cmd /C start java -Dspring.profiles.active=BankA -jar #JAR_PATH# +cmd /C start java -Dspring.profiles.active=BankB -jar #JAR_PATH# \ No newline at end of file diff --git a/samples/irs-demo/web/src/test/resources/scripts/runwebapps.scpt b/samples/irs-demo/web/src/test/resources/scripts/runwebapps.scpt new file mode 100755 index 0000000000..7441118a1a --- /dev/null +++ b/samples/irs-demo/web/src/test/resources/scripts/runwebapps.scpt @@ -0,0 +1,12 @@ +tell app "Terminal" + activate + tell app "System Events" to tell process "Terminal" to keystroke "t" using command down + delay 0.5 + do script "bash -c 'cd \"#DIR#\" && java -Dspring.profiles.active=NotaryService -jar #JAR_PATH# && exit'" in selected tab of the front window + tell app "System Events" to tell process "Terminal" to keystroke "t" using command down + delay 0.5 + do script "bash -c 'cd \"#DIR#\" && java -Dspring.profiles.active=BankA -jar #JAR_PATH# && exit'" in selected tab of the front window + tell app "System Events" to tell process "Terminal" to keystroke "t" using command down + delay 0.5 + do script "bash -c 'cd \"#DIR#\" && java -Dspring.profiles.active=BankB -jar #JAR_PATH# && exit'" in selected tab of the front window +end tell \ No newline at end of file diff --git a/samples/irs-demo/web/src/test/resources/scripts/runwebapps.sh b/samples/irs-demo/web/src/test/resources/scripts/runwebapps.sh new file mode 100755 index 0000000000..c87f95821c --- /dev/null +++ b/samples/irs-demo/web/src/test/resources/scripts/runwebapps.sh @@ -0,0 +1,14 @@ +#!/bin/bash -x + +function run_webapp() { + if [ ! -z "$TMUX" ]; then + tmux new-window -n $1 $2; [ $? -eq 0 -o $? -eq 143 ] || sh + else + xterm -T $1 -e $2; [ $? -eq 0 -o $? -eq 143 ] || sh + fi; +} + +run_webapp "NotaryService" "cd \"#DIR#\" && java -Dspring.profiles.active=NotaryService -jar #JAR_PATH#" +run_webapp "BankA" "cd \"#DIR#\" && java -Dspring.profiles.active=BankA -jar #JAR_PATH#" +run_webapp "BankB" "cd \"#DIR#\" && java -Dspring.profiles.active=BankB -jar #JAR_PATH#" + diff --git a/samples/network-visualiser/build.gradle b/samples/network-visualiser/build.gradle index d0ee4ea3e3..b5b20986d6 100644 --- a/samples/network-visualiser/build.gradle +++ b/samples/network-visualiser/build.gradle @@ -1,15 +1,30 @@ +buildscript { + ext { + springBootVersion = '1.5.7.RELEASE' + } + repositories { + mavenCentral() + } + dependencies { + classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}") + } +} + apply plugin: 'java' apply plugin: 'kotlin' apply plugin: 'idea' apply plugin: 'application' +apply plugin: 'org.springframework.boot' apply plugin: 'net.corda.plugins.quasar-utils' apply plugin: 'us.kirchmeier.capsule' +// Spring Boot plugin have to be reimported, however it picks up the settings from irs-demo, so there is no need to +// reconfigure + // Warning: The network visualiser is not a Cordapp so please do not use it as an example of how // to build a cordapp dependencies { - compile "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version" testCompile "junit:junit:$junit_version" // Corda integration dependencies diff --git a/samples/network-visualiser/src/main/kotlin/net/corda/netmap/simulation/IRSSimulation.kt b/samples/network-visualiser/src/main/kotlin/net/corda/netmap/simulation/IRSSimulation.kt index 95b203c7a2..5b73073155 100644 --- a/samples/network-visualiser/src/main/kotlin/net/corda/netmap/simulation/IRSSimulation.kt +++ b/samples/network-visualiser/src/main/kotlin/net/corda/netmap/simulation/IRSSimulation.kt @@ -130,7 +130,7 @@ class IRSSimulation(networkSendManuallyPumped: Boolean, runAsync: Boolean, laten // have the convenient copy() method that'd let us make small adjustments. Instead they're partly mutable. // TODO: We should revisit this in post-Excalibur cleanup and fix, e.g. by introducing an interface. - val irs = om.readValue(javaClass.classLoader.getResourceAsStream("net/corda/irs/simulation/trade.json") + val irs = om.readValue(javaClass.classLoader.getResourceAsStream("net/corda/irs/web/simulation/trade.json") .reader() .readText() .replace("oracleXXX", RatesOracleFactory.RATES_SERVICE_NAME.toString())) diff --git a/settings.gradle b/settings.gradle index ec0d5adcc6..953e3ae510 100644 --- a/settings.gradle +++ b/settings.gradle @@ -37,6 +37,8 @@ include 'docs/source/example-code' // Note that we are deliberately choosing to include 'samples:attachment-demo' include 'samples:trader-demo' include 'samples:irs-demo' +include 'samples:irs-demo:cordapp' +include 'samples:irs-demo:web' include 'samples:network-visualiser' include 'samples:simm-valuation-demo' include 'samples:notary-demo' diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt index ff8bdc3e14..7de6c3fa5e 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt @@ -418,6 +418,56 @@ fun genericD } } +/** + * This is a helper method to allow extending of the DSL, along the lines of + * interface SomeOtherExposedDSLInterface : DriverDSLExposedInterface + * interface SomeOtherInternalDSLInterface : DriverDSLInternalInterface, SomeOtherExposedDSLInterface + * class SomeOtherDSL(val driverDSL : DriverDSL) : DriverDSLInternalInterface by driverDSL, SomeOtherInternalDSLInterface + * + * @param coerce We need this explicit coercion witness because we can't put an extra DI : D bound in a `where` clause. + */ +fun genericDriver( + defaultParameters: DriverParameters = DriverParameters(), + isDebug: Boolean = defaultParameters.isDebug, + driverDirectory: Path = defaultParameters.driverDirectory, + portAllocation: PortAllocation = defaultParameters.portAllocation, + debugPortAllocation: PortAllocation = defaultParameters.debugPortAllocation, + systemProperties: Map = defaultParameters.systemProperties, + useTestClock: Boolean = defaultParameters.useTestClock, + initialiseSerialization: Boolean = defaultParameters.initialiseSerialization, + startNodesInProcess: Boolean = defaultParameters.startNodesInProcess, + extraCordappPackagesToScan: List = defaultParameters.extraCordappPackagesToScan, + driverDslWrapper: (DriverDSL) -> D, + coerce: (D) -> DI, + dsl: DI.() -> A +): A { + if (initialiseSerialization) initialiseTestSerialization() + val driverDsl = driverDslWrapper( + DriverDSL( + portAllocation = portAllocation, + debugPortAllocation = debugPortAllocation, + systemProperties = systemProperties, + driverDirectory = driverDirectory.toAbsolutePath(), + useTestClock = useTestClock, + isDebug = isDebug, + startNodesInProcess = startNodesInProcess, + extraCordappPackagesToScan = extraCordappPackagesToScan + ) + ) + val shutdownHook = addShutdownHook(driverDsl::shutdown) + try { + driverDsl.start() + return dsl(coerce(driverDsl)) + } catch (exception: Throwable) { + log.error("Driver shutting down because of exception", exception) + throw exception + } finally { + driverDsl.shutdown() + shutdownHook.cancel() + if (initialiseSerialization) resetTestSerialization() + } +} + fun getTimestampAsDirectoryName(): String { return DateTimeFormatter.ofPattern("yyyyMMddHHmmss").withZone(UTC).format(Instant.now()) } diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/driver/ProcessUtilities.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/driver/ProcessUtilities.kt index d0a5c6492d..4df2380c73 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/driver/ProcessUtilities.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/driver/ProcessUtilities.kt @@ -49,9 +49,12 @@ object ProcessUtilities { addAll(arguments) } return ProcessBuilder(command).apply { - if (errorLogPath != null) redirectError(errorLogPath.toFile()) // FIXME: Undone by inheritIO. inheritIO() - if (workingDirectory != null) directory(workingDirectory.toFile()) + if (workingDirectory != null) { + redirectError((workingDirectory / "$className.stderr.log").toFile()) + redirectOutput((workingDirectory / "$className.stdout.log").toFile()) + directory(workingDirectory.toFile()) + } }.start() } diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/http/HttpApi.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/http/HttpApi.kt index 0c283fd844..7e948285dc 100644 --- a/testing/test-utils/src/main/kotlin/net/corda/testing/http/HttpApi.kt +++ b/testing/test-utils/src/main/kotlin/net/corda/testing/http/HttpApi.kt @@ -19,6 +19,13 @@ class HttpApi(val root: URL, val mapper: ObjectMapper = defaultMapper) { */ fun postJson(path: String, data: Any = Unit) = HttpUtils.postJson(URL(root, path), toJson(data)) + /** + * Send a POST with a payload to the path on the API specified. + * + * @param data String payload + */ + fun postPlain(path: String, data: String = "") = HttpUtils.postPlain(URL(root, path), data) + /** * Send a GET request to the path on the API specified. */ diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/http/HttpUtils.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/http/HttpUtils.kt index 6a65441726..b07d55db86 100644 --- a/testing/test-utils/src/main/kotlin/net/corda/testing/http/HttpUtils.kt +++ b/testing/test-utils/src/main/kotlin/net/corda/testing/http/HttpUtils.kt @@ -1,8 +1,6 @@ package net.corda.testing.http import com.fasterxml.jackson.databind.ObjectMapper -import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule -import com.fasterxml.jackson.module.kotlin.KotlinModule import net.corda.core.utilities.loggerFor import okhttp3.MediaType import okhttp3.OkHttpClient @@ -35,6 +33,11 @@ object HttpUtils { return makeRequest(Request.Builder().url(url).header("Content-Type", "application/json").post(body).build()) } + fun postPlain(url: URL, data: String): Boolean { + val body = RequestBody.create(MediaType.parse("text/plain; charset=utf-8"), data) + return makeRequest(Request.Builder().url(url).post(body).build()) + } + inline fun getJson(url: URL, params: Map = mapOf(), mapper: ObjectMapper = defaultMapper): T { val paramString = if (params.isEmpty()) "" else "?" + params.map { "${it.key}=${it.value}" }.joinToString("&") val parameterisedUrl = URL(url.toExternalForm() + paramString) From dcca3179d9065eab6e311949955100a34bc6b830 Mon Sep 17 00:00:00 2001 From: Viktor Kolomeyko Date: Wed, 25 Oct 2017 19:25:19 +0100 Subject: [PATCH 44/94] Perform sensible currency conversion (#1941) It is kind of sad to see 1 GBP to be equal to 1 USD and also it looks misleading in the blotter. --- .../client/jfx/model/ExchangeRateModel.kt | 23 +++++++++- .../client/jfx/model/ExchangeRateModelTest.kt | 44 +++++++++++++++++++ 2 files changed, 65 insertions(+), 2 deletions(-) create mode 100644 client/jfx/src/test/kotlin/net/corda/client/jfx/model/ExchangeRateModelTest.kt diff --git a/client/jfx/src/main/kotlin/net/corda/client/jfx/model/ExchangeRateModel.kt b/client/jfx/src/main/kotlin/net/corda/client/jfx/model/ExchangeRateModel.kt index 636603d1ab..92870b671b 100644 --- a/client/jfx/src/main/kotlin/net/corda/client/jfx/model/ExchangeRateModel.kt +++ b/client/jfx/src/main/kotlin/net/corda/client/jfx/model/ExchangeRateModel.kt @@ -3,8 +3,12 @@ package net.corda.client.jfx.model import javafx.beans.property.SimpleObjectProperty import javafx.beans.value.ObservableValue import net.corda.core.contracts.Amount +import net.corda.finance.CHF +import net.corda.finance.EUR +import net.corda.finance.GBP +import net.corda.finance.USD import java.math.BigDecimal -import java.math.RoundingMode +import java.math.MathContext import java.util.* /** @@ -24,9 +28,24 @@ abstract class ExchangeRate { /** * Default implementation of an exchange rate model, which uses a fixed exchange rate. */ +private val usdExchangeRates: Map = mapOf( + GBP to BigDecimal(1.31), + EUR to BigDecimal(1.18), + CHF to BigDecimal(1.01) +) + +private fun safeFetchRate(currency: Currency) = + usdExchangeRates[currency] ?: throw IllegalArgumentException("No exchange rate for $currency") + // TODO hook up an actual oracle class ExchangeRateModel { val exchangeRate: ObservableValue = SimpleObjectProperty(object : ExchangeRate() { - override fun rate(from: Currency, to: Currency) = BigDecimal.ONE + override fun rate(from: Currency, to: Currency): BigDecimal = + when { + from == to -> BigDecimal.ONE + USD == to -> safeFetchRate(from) + USD == from -> BigDecimal.ONE.divide(safeFetchRate(to), MathContext.DECIMAL64) + else -> safeFetchRate(from).divide(safeFetchRate(to), MathContext.DECIMAL64) + } }) } diff --git a/client/jfx/src/test/kotlin/net/corda/client/jfx/model/ExchangeRateModelTest.kt b/client/jfx/src/test/kotlin/net/corda/client/jfx/model/ExchangeRateModelTest.kt new file mode 100644 index 0000000000..6b1a1a9ae2 --- /dev/null +++ b/client/jfx/src/test/kotlin/net/corda/client/jfx/model/ExchangeRateModelTest.kt @@ -0,0 +1,44 @@ +package net.corda.client.jfx.model + +import net.corda.core.contracts.Amount +import net.corda.finance.CHF +import net.corda.finance.GBP +import net.corda.finance.RUB +import net.corda.finance.USD +import org.assertj.core.api.Assertions +import org.junit.Test +import java.math.BigDecimal +import java.util.* +import kotlin.test.assertEquals +import kotlin.test.assertTrue + +class ExchangeRateModelTest { + + companion object { + private val instance = ExchangeRateModel().exchangeRate.value + + private fun assertEquals(one: Amount, another: Amount) { + assertEquals(one.token, another.token) + assertTrue("$one != $another", {(one.toDecimal() - another.toDecimal()).abs() < BigDecimal(0.01) }) + } + } + @Test + fun `perform fx testing`() { + val tenSwissies = Amount(10, BigDecimal.ONE, CHF) + assertEquals(instance.exchangeAmount(tenSwissies, CHF), tenSwissies) + + val tenSwissiesInUsd = Amount(101, BigDecimal.ONE.divide(BigDecimal.TEN), USD) + assertEquals(instance.exchangeAmount(tenSwissies, USD), tenSwissiesInUsd) + + assertEquals(instance.exchangeAmount(tenSwissiesInUsd, CHF), tenSwissies) + + val tenQuidInSwissies = Amount(1297, BigDecimal.ONE.divide(BigDecimal(100)), CHF) + val tenQuid = Amount(10, BigDecimal.ONE, GBP) + assertEquals(instance.exchangeAmount(tenQuid, CHF), tenQuidInSwissies) + + assertEquals(instance.exchangeAmount(tenQuidInSwissies, GBP), tenQuid) + + Assertions.assertThatThrownBy { instance.exchangeAmount(tenQuid, RUB) }.isInstanceOf(IllegalArgumentException::class.java) + .hasMessage("No exchange rate for RUB") + } +} \ No newline at end of file From a3be4d0dadee6c02f71f382e34cbf6c56f6d9ba7 Mon Sep 17 00:00:00 2001 From: Christian Sailer Date: Thu, 26 Oct 2017 11:47:37 +0100 Subject: [PATCH 45/94] Add extraPackages to NodePerformanceTest so it runs successfully --- .../kotlin/net/corda/node/NodePerformanceTests.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/node/src/integration-test/kotlin/net/corda/node/NodePerformanceTests.kt b/node/src/integration-test/kotlin/net/corda/node/NodePerformanceTests.kt index 211f0e1f15..6cddc95bed 100644 --- a/node/src/integration-test/kotlin/net/corda/node/NodePerformanceTests.kt +++ b/node/src/integration-test/kotlin/net/corda/node/NodePerformanceTests.kt @@ -102,7 +102,7 @@ class NodePerformanceTests { @Test fun `self pay rate`() { - driver(startNodesInProcess = true) { + driver(startNodesInProcess = true, extraCordappPackagesToScan = listOf("net.corda.finance")) { val a = startNotaryNode( DUMMY_NOTARY.name, rpcUsers = listOf(User("A", "A", setOf(startFlowPermission(), startFlowPermission()))) From 04d8260e0fd3e68c1ffe79d761989a5b324e47fb Mon Sep 17 00:00:00 2001 From: bpaunescu Date: Thu, 26 Oct 2017 12:16:57 +0100 Subject: [PATCH 46/94] =?UTF-8?q?CORDA-351:=20force=20update=20dependencie?= =?UTF-8?q?s=20and=20suppress=20vulnerabilities=20not=E2=80=A6=20(#1944)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * CORDA-351: force update dependencies and suppress vulnerabilities not affecting corda * CORDA-351: force update dependencies and suppress vulnerabilities not affecting corda --- .../suppressedLibraries.xml | 19 ++++++++++++++++++- build.gradle | 12 +++++++----- client/jfx/build.gradle | 3 +++ node-api/build.gradle | 4 ++++ node/build.gradle | 3 +++ verifier/build.gradle | 4 ++++ webserver/build.gradle | 3 +++ 7 files changed, 42 insertions(+), 6 deletions(-) diff --git a/.ci/dependency-checker/suppressedLibraries.xml b/.ci/dependency-checker/suppressedLibraries.xml index 1150abff87..529b911945 100644 --- a/.ci/dependency-checker/suppressedLibraries.xml +++ b/.ci/dependency-checker/suppressedLibraries.xml @@ -10,5 +10,22 @@ cpe:/a:apache:struts:2.0.0 --> - + + + + ^io\.atomix\.catalyst:catalyst-netty:.*$ + CVE-2014-3488 + + + + + ^commons-cli:commons-cli:.*$ + CVE-2016-6497 + + + + + ^commons-cli:commons-cli:.*$ + CVE-2015-3253 + diff --git a/build.gradle b/build.gradle index 0541f3fadf..6b0f7e5764 100644 --- a/build.gradle +++ b/build.gradle @@ -22,19 +22,19 @@ buildscript { ext.asm_version = '0.5.3' ext.artemis_version = '2.1.0' - ext.jackson_version = '2.8.5' - ext.jetty_version = '9.3.9.v20160517' + ext.jackson_version = '2.9.2' + ext.jetty_version = '9.4.7.v20170914' ext.jersey_version = '2.25' ext.jolokia_version = '2.0.0-M3' - ext.assertj_version = '3.6.1' + ext.assertj_version = '3.8.0' ext.slf4j_version = '1.7.25' - ext.log4j_version = '2.7' + ext.log4j_version = '2.9.1' ext.bouncycastle_version = constants.getProperty("bouncycastleVersion") ext.guava_version = constants.getProperty("guavaVersion") ext.okhttp_version = '3.5.0' ext.netty_version = '4.1.9.Final' ext.typesafe_config_version = constants.getProperty("typesafeConfigVersion") - ext.fileupload_version = '1.3.2' + ext.fileupload_version = '1.3.3' ext.junit_version = '4.12' ext.mockito_version = '2.10.0' ext.jopt_simple_version = '5.0.2' @@ -46,6 +46,8 @@ buildscript { ext.dokka_version = '0.9.14' ext.eddsa_version = '0.2.0' ext.dependency_checker_version = '3.0.1' + ext.commons_collections_version = '4.1' + ext.beanutils_version = '1.9.3' // Update 121 is required for ObjectInputFilter and at time of writing 131 was latest: ext.java8_minUpdateVersion = '131' diff --git a/client/jfx/build.gradle b/client/jfx/build.gradle index aaf1d5fd14..0e84e78f37 100644 --- a/client/jfx/build.gradle +++ b/client/jfx/build.gradle @@ -37,6 +37,9 @@ dependencies { compile 'org.fxmisc.easybind:easybind:1.0.3' // Artemis Client: ability to connect to an Artemis broker and control it. + // TODO: remove the forced update of commons-collections and beanutils when artemis updates them + compile "org.apache.commons:commons-collections4:${commons_collections_version}" + compile "commons-beanutils:commons-beanutils:${beanutils_version}" compile "org.apache.activemq:artemis-core-client:${artemis_version}" // Unit testing helpers. diff --git a/node-api/build.gradle b/node-api/build.gradle index a9384bd4ab..76abc37bea 100644 --- a/node-api/build.gradle +++ b/node-api/build.gradle @@ -11,6 +11,10 @@ dependencies { compile "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version" compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" testCompile "org.jetbrains.kotlin:kotlin-test:$kotlin_version" + + // TODO: remove the forced update of commons-collections and beanutils when artemis updates them + compile "org.apache.commons:commons-collections4:${commons_collections_version}" + compile "commons-beanutils:commons-beanutils:${beanutils_version}" compile "org.apache.activemq:artemis-core-client:${artemis_version}" compile "org.apache.activemq:artemis-commons:${artemis_version}" diff --git a/node/build.gradle b/node/build.gradle index 7d3313d454..9bc48d5ca8 100644 --- a/node/build.gradle +++ b/node/build.gradle @@ -95,6 +95,9 @@ dependencies { compile "net.sf.jopt-simple:jopt-simple:$jopt_simple_version" // Artemis: for reliable p2p message queues. + // TODO: remove the forced update of commons-collections and beanutils when artemis updates them + compile "org.apache.commons:commons-collections4:${commons_collections_version}" + compile "commons-beanutils:commons-beanutils:${beanutils_version}" compile "org.apache.activemq:artemis-server:${artemis_version}" compile "org.apache.activemq:artemis-core-client:${artemis_version}" runtime ("org.apache.activemq:artemis-amqp-protocol:${artemis_version}") { diff --git a/verifier/build.gradle b/verifier/build.gradle index 33e4869dfd..ad0f9ff686 100644 --- a/verifier/build.gradle +++ b/verifier/build.gradle @@ -33,6 +33,10 @@ dependencies { compile "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version" compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" testCompile "org.jetbrains.kotlin:kotlin-test:$kotlin_version" + + // TODO: remove the forced update of commons-collections and beanutils when artemis updates them + compile "org.apache.commons:commons-collections4:${commons_collections_version}" + compile "commons-beanutils:commons-beanutils:${beanutils_version}" compile "org.apache.activemq:artemis-core-client:${artemis_version}" // Log4J: logging framework (with SLF4J bindings) diff --git a/webserver/build.gradle b/webserver/build.gradle index 15fa109983..166a8fe3aa 100644 --- a/webserver/build.gradle +++ b/webserver/build.gradle @@ -49,6 +49,9 @@ dependencies { compile "net.sf.jopt-simple:jopt-simple:$jopt_simple_version" // Jersey for JAX-RS implementation for use in Jetty + // TODO: remove force upgrade when jersey catches up + compile "org.eclipse.jetty:jetty-continuation:${jetty_version}" + compile "org.glassfish.jersey.core:jersey-server:$jersey_version" compile "org.glassfish.jersey.containers:jersey-container-servlet-core:$jersey_version" compile "org.glassfish.jersey.containers:jersey-container-jetty-http:$jersey_version" From 71fe0daa08d97e1e4cf2bead7b943fd9ac0215e7 Mon Sep 17 00:00:00 2001 From: Christian Sailer Date: Tue, 24 Oct 2017 14:13:04 +0100 Subject: [PATCH 47/94] Give the checkpoint checker thread a meaningful name --- .../node/utilities/NamedThreadFactory.kt | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 node/src/main/kotlin/net/corda/node/utilities/NamedThreadFactory.kt diff --git a/node/src/main/kotlin/net/corda/node/utilities/NamedThreadFactory.kt b/node/src/main/kotlin/net/corda/node/utilities/NamedThreadFactory.kt new file mode 100644 index 0000000000..9b4044686c --- /dev/null +++ b/node/src/main/kotlin/net/corda/node/utilities/NamedThreadFactory.kt @@ -0,0 +1,19 @@ +package net.corda.node.utilities + +import java.util.concurrent.ExecutorService +import java.util.concurrent.Executors +import java.util.concurrent.ThreadFactory +import java.util.concurrent.atomic.AtomicInteger + +class NamedThreadFactory(private val name:String, private val underlyingFactory: ThreadFactory) : ThreadFactory{ + val threadNumber = AtomicInteger(1) + override fun newThread(r: Runnable?): Thread { + val t = underlyingFactory.newThread(r) + t.name = name + "-" + threadNumber.getAndIncrement() + return t + } +} + +fun newNamedSinleThreadExecutor(name: String): ExecutorService { + return Executors.newSingleThreadExecutor(NamedThreadFactory(name, Executors.defaultThreadFactory())) +} From 74595c65c91f04327575a3cba7c97aa8823de8c4 Mon Sep 17 00:00:00 2001 From: Christian Sailer Date: Tue, 24 Oct 2017 15:58:17 +0100 Subject: [PATCH 48/94] Add optional debug options to config and allow to switch checkpointChecking off in dev mode --- .../node/services/config/NodeConfiguration.kt | 3 + .../statemachine/StateMachineManagerImpl.kt | 7 +- .../node/utilities/NamedThreadFactory.kt | 2 +- .../config/FullNodeConfigurationTest.kt | 72 +++++++++++++++++++ 4 files changed, 78 insertions(+), 6 deletions(-) diff --git a/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt b/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt index 6d150a3ab3..30e5e6f7a4 100644 --- a/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt +++ b/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt @@ -28,6 +28,7 @@ interface NodeConfiguration : NodeSSLConfiguration { val database: Properties? val rpcUsers: List val devMode: Boolean + val debugOptions: Properties? val certificateSigningService: URL val certificateChainCheckPolicies: List val verifierType: VerifierType @@ -93,6 +94,7 @@ data class FullNodeConfiguration( override val notary: NotaryConfig?, override val certificateChainCheckPolicies: List, override val devMode: Boolean = false, + override val debugOptions: Properties? = null, val useTestClock: Boolean = false, val detectPublicIp: Boolean = true, override val activeMQServer: ActiveMqServerConfiguration, @@ -103,6 +105,7 @@ data class FullNodeConfiguration( init { // This is a sanity feature do not remove. require(!useTestClock || devMode) { "Cannot use test clock outside of dev mode" } + require(debugOptions == null || devMode){"Cannot use debugOptions outside of dev mode"} // TODO Move this to ArtemisMessagingServer rpcUsers.forEach { require(it.username.matches("\\w+".toRegex())) { "Username ${it.username} contains invalid characters" } diff --git a/node/src/main/kotlin/net/corda/node/services/statemachine/StateMachineManagerImpl.kt b/node/src/main/kotlin/net/corda/node/services/statemachine/StateMachineManagerImpl.kt index 7fdd7f920c..cf96c9a0d2 100644 --- a/node/src/main/kotlin/net/corda/node/services/statemachine/StateMachineManagerImpl.kt +++ b/node/src/main/kotlin/net/corda/node/services/statemachine/StateMachineManagerImpl.kt @@ -33,10 +33,7 @@ import net.corda.node.services.api.CheckpointStorage import net.corda.node.services.api.ServiceHubInternal import net.corda.node.services.messaging.ReceivedMessage import net.corda.node.services.messaging.TopicSession -import net.corda.node.utilities.AffinityExecutor -import net.corda.node.utilities.CordaPersistence -import net.corda.node.utilities.bufferUntilDatabaseCommit -import net.corda.node.utilities.wrapWithDatabaseTransaction +import net.corda.node.utilities.* import net.corda.nodeapi.internal.serialization.SerializeAsTokenContextImpl import net.corda.nodeapi.internal.serialization.withTokenContext import org.apache.activemq.artemis.utils.ReusableLatch @@ -92,7 +89,7 @@ class StateMachineManagerImpl( private val scheduler = FiberScheduler() private val mutex = ThreadBox(InnerState()) // This thread (only enabled in dev mode) deserialises checkpoints in the background to shake out bugs in checkpoint restore. - private val checkpointCheckerThread = if (serviceHub.configuration.devMode) Executors.newSingleThreadExecutor() else null + private val checkpointCheckerThread = if (serviceHub.configuration.devMode && serviceHub.configuration.debugOptions?.getProperty("disableCheckpointChecking") != "true") newNamedSingleThreadExecutor("CheckpointChecker") else null @Volatile private var unrestorableCheckpoints = false diff --git a/node/src/main/kotlin/net/corda/node/utilities/NamedThreadFactory.kt b/node/src/main/kotlin/net/corda/node/utilities/NamedThreadFactory.kt index 9b4044686c..bd93f57470 100644 --- a/node/src/main/kotlin/net/corda/node/utilities/NamedThreadFactory.kt +++ b/node/src/main/kotlin/net/corda/node/utilities/NamedThreadFactory.kt @@ -14,6 +14,6 @@ class NamedThreadFactory(private val name:String, private val underlyingFactory: } } -fun newNamedSinleThreadExecutor(name: String): ExecutorService { +fun newNamedSingleThreadExecutor(name: String): ExecutorService { return Executors.newSingleThreadExecutor(NamedThreadFactory(name, Executors.defaultThreadFactory())) } diff --git a/node/src/test/kotlin/net/corda/node/services/config/FullNodeConfigurationTest.kt b/node/src/test/kotlin/net/corda/node/services/config/FullNodeConfigurationTest.kt index bc40165c06..8a5ca1547c 100644 --- a/node/src/test/kotlin/net/corda/node/services/config/FullNodeConfigurationTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/config/FullNodeConfigurationTest.kt @@ -10,6 +10,8 @@ import org.assertj.core.api.Assertions.assertThatThrownBy import org.junit.Test import java.net.URL import java.nio.file.Paths +import java.util.* +import kotlin.test.assertFalse class FullNodeConfigurationTest { @Test @@ -43,4 +45,74 @@ class FullNodeConfigurationTest { assertThatThrownBy { configWithRPCUsername("user*1") }.hasMessageContaining("*") assertThatThrownBy { configWithRPCUsername("user#1") }.hasMessageContaining("#") } + + @Test + fun `Can't have debug options if not in dev mode`() { + val testConfiguration = FullNodeConfiguration( + baseDirectory = Paths.get("."), + myLegalName = ALICE.name, + networkMapService = null, + emailAddress = "", + keyStorePassword = "cordacadevpass", + trustStorePassword = "trustpass", + dataSourceProperties = makeTestDataSourceProperties(ALICE.name.organisation), + database = makeTestDatabaseProperties(), + certificateSigningService = URL("http://localhost"), + rpcUsers = emptyList(), + verifierType = VerifierType.InMemory, + useHTTPS = false, + p2pAddress = NetworkHostAndPort("localhost", 0), + rpcAddress = NetworkHostAndPort("localhost", 1), + messagingServerAddress = null, + notary = null, + certificateChainCheckPolicies = emptyList(), + devMode = true, + activeMQServer = ActiveMqServerConfiguration(BridgeConfiguration(0, 0, 0.0)), + additionalNodeInfoPollingFrequencyMsec = 5.seconds.toMillis()) + + fun configDebugOptions(devMode: Boolean, debugOptions: Properties?) { + testConfiguration.copy(devMode = devMode, debugOptions = debugOptions) + } + val debugOptions = Properties() + configDebugOptions(true, debugOptions) + configDebugOptions(true,null) + assertThatThrownBy{configDebugOptions(false, debugOptions)}.hasMessageMatching("Cannot use debugOptions outside of dev mode") + configDebugOptions(false,null) + } + + @Test + fun `check properties behave as expected`() + { + val testConfiguration = FullNodeConfiguration( + baseDirectory = Paths.get("."), + myLegalName = ALICE.name, + networkMapService = null, + emailAddress = "", + keyStorePassword = "cordacadevpass", + trustStorePassword = "trustpass", + dataSourceProperties = makeTestDataSourceProperties(ALICE.name.organisation), + database = makeTestDatabaseProperties(), + certificateSigningService = URL("http://localhost"), + rpcUsers = emptyList(), + verifierType = VerifierType.InMemory, + useHTTPS = false, + p2pAddress = NetworkHostAndPort("localhost", 0), + rpcAddress = NetworkHostAndPort("localhost", 1), + messagingServerAddress = null, + notary = null, + certificateChainCheckPolicies = emptyList(), + devMode = true, + activeMQServer = ActiveMqServerConfiguration(BridgeConfiguration(0, 0, 0.0)), + additionalNodeInfoPollingFrequencyMsec = 5.seconds.toMillis()) + + fun configDebugOptions(devMode: Boolean, debugOptions: Properties?) : NodeConfiguration { + return testConfiguration.copy(devMode = devMode, debugOptions = debugOptions) + } + val debugOptions = Properties() + assertFalse { configDebugOptions(true, debugOptions).debugOptions?.getProperty("foo") == "bar"} + assertFalse { configDebugOptions(true,null).debugOptions?.getProperty("foo") == "bar"} + debugOptions.setProperty("foo", "bar") + assert( configDebugOptions(true, debugOptions).debugOptions?.getProperty("foo") == "bar") + } + } From 0cbee046f9b06aed66b749978399346322ab6531 Mon Sep 17 00:00:00 2001 From: Christian Sailer Date: Thu, 26 Oct 2017 14:36:24 +0100 Subject: [PATCH 49/94] Add extension method to check for flags devModeOptions, formatting --- .../statemachine/StateMachineManagerImpl.kt | 4 ++-- .../services/config/FullNodeConfigurationTest.kt | 16 ++++++++-------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/node/src/main/kotlin/net/corda/node/services/statemachine/StateMachineManagerImpl.kt b/node/src/main/kotlin/net/corda/node/services/statemachine/StateMachineManagerImpl.kt index cf96c9a0d2..be4f991313 100644 --- a/node/src/main/kotlin/net/corda/node/services/statemachine/StateMachineManagerImpl.kt +++ b/node/src/main/kotlin/net/corda/node/services/statemachine/StateMachineManagerImpl.kt @@ -31,6 +31,7 @@ import net.corda.node.internal.InitiatedFlowFactory import net.corda.node.services.api.Checkpoint import net.corda.node.services.api.CheckpointStorage import net.corda.node.services.api.ServiceHubInternal +import net.corda.node.services.config.isDevModeOptionsFlagSet import net.corda.node.services.messaging.ReceivedMessage import net.corda.node.services.messaging.TopicSession import net.corda.node.utilities.* @@ -43,7 +44,6 @@ import rx.subjects.PublishSubject import java.io.NotSerializableException import java.util.* import java.util.concurrent.ConcurrentHashMap -import java.util.concurrent.Executors import java.util.concurrent.TimeUnit.SECONDS import javax.annotation.concurrent.ThreadSafe @@ -89,7 +89,7 @@ class StateMachineManagerImpl( private val scheduler = FiberScheduler() private val mutex = ThreadBox(InnerState()) // This thread (only enabled in dev mode) deserialises checkpoints in the background to shake out bugs in checkpoint restore. - private val checkpointCheckerThread = if (serviceHub.configuration.devMode && serviceHub.configuration.debugOptions?.getProperty("disableCheckpointChecking") != "true") newNamedSingleThreadExecutor("CheckpointChecker") else null + private val checkpointCheckerThread = if (serviceHub.configuration.devMode && !serviceHub.configuration.isDevModeOptionsFlagSet("disableCheckpointChecking")) newNamedSingleThreadExecutor("CheckpointChecker") else null @Volatile private var unrestorableCheckpoints = false diff --git a/node/src/test/kotlin/net/corda/node/services/config/FullNodeConfigurationTest.kt b/node/src/test/kotlin/net/corda/node/services/config/FullNodeConfigurationTest.kt index 8a5ca1547c..db1a61b6fb 100644 --- a/node/src/test/kotlin/net/corda/node/services/config/FullNodeConfigurationTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/config/FullNodeConfigurationTest.kt @@ -71,17 +71,17 @@ class FullNodeConfigurationTest { additionalNodeInfoPollingFrequencyMsec = 5.seconds.toMillis()) fun configDebugOptions(devMode: Boolean, debugOptions: Properties?) { - testConfiguration.copy(devMode = devMode, debugOptions = debugOptions) + testConfiguration.copy(devMode = devMode, devModeOptions = debugOptions) } val debugOptions = Properties() configDebugOptions(true, debugOptions) configDebugOptions(true,null) - assertThatThrownBy{configDebugOptions(false, debugOptions)}.hasMessageMatching("Cannot use debugOptions outside of dev mode") + assertThatThrownBy{configDebugOptions(false, debugOptions)}.hasMessageMatching( "Cannot use devModeOptions outside of dev mode" ) configDebugOptions(false,null) } @Test - fun `check properties behave as expected`() + fun `check devModeOptions flag helper`() { val testConfiguration = FullNodeConfiguration( baseDirectory = Paths.get("."), @@ -106,13 +106,13 @@ class FullNodeConfigurationTest { additionalNodeInfoPollingFrequencyMsec = 5.seconds.toMillis()) fun configDebugOptions(devMode: Boolean, debugOptions: Properties?) : NodeConfiguration { - return testConfiguration.copy(devMode = devMode, debugOptions = debugOptions) + return testConfiguration.copy(devMode = devMode, devModeOptions = debugOptions) } val debugOptions = Properties() - assertFalse { configDebugOptions(true, debugOptions).debugOptions?.getProperty("foo") == "bar"} - assertFalse { configDebugOptions(true,null).debugOptions?.getProperty("foo") == "bar"} - debugOptions.setProperty("foo", "bar") - assert( configDebugOptions(true, debugOptions).debugOptions?.getProperty("foo") == "bar") + assertFalse { configDebugOptions(true,null).isDevModeOptionsFlagSet("foo")} + assertFalse { configDebugOptions(true,debugOptions).isDevModeOptionsFlagSet("foo")} + debugOptions.setProperty("foo", "tRuE") + assert( configDebugOptions(true, debugOptions).isDevModeOptionsFlagSet("foo")) } } From d275ab41a83190c9dd2404497ac51c1c016b793f Mon Sep 17 00:00:00 2001 From: Christian Sailer Date: Thu, 26 Oct 2017 14:40:19 +0100 Subject: [PATCH 50/94] Rename debugOptions to devModeOptions, extension method --- .../corda/node/services/config/NodeConfiguration.kt | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt b/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt index 30e5e6f7a4..d5c93dafbb 100644 --- a/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt +++ b/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt @@ -28,7 +28,7 @@ interface NodeConfiguration : NodeSSLConfiguration { val database: Properties? val rpcUsers: List val devMode: Boolean - val debugOptions: Properties? + val devModeOptions: Properties? val certificateSigningService: URL val certificateChainCheckPolicies: List val verifierType: VerifierType @@ -38,6 +38,10 @@ interface NodeConfiguration : NodeSSLConfiguration { val additionalNodeInfoPollingFrequencyMsec: Long } +fun NodeConfiguration.isDevModeOptionsFlagSet(flag: String):Boolean{ + return this.devModeOptions?.get(flag).toString().toLowerCase() == "true" +} + data class NotaryConfig(val validating: Boolean, val raft: RaftConfig? = null, val bftSMaRt: BFTSMaRtConfiguration? = null, @@ -94,7 +98,7 @@ data class FullNodeConfiguration( override val notary: NotaryConfig?, override val certificateChainCheckPolicies: List, override val devMode: Boolean = false, - override val debugOptions: Properties? = null, + override val devModeOptions: Properties? = null, val useTestClock: Boolean = false, val detectPublicIp: Boolean = true, override val activeMQServer: ActiveMqServerConfiguration, @@ -105,7 +109,7 @@ data class FullNodeConfiguration( init { // This is a sanity feature do not remove. require(!useTestClock || devMode) { "Cannot use test clock outside of dev mode" } - require(debugOptions == null || devMode){"Cannot use debugOptions outside of dev mode"} + require(devModeOptions == null || devMode){ "Cannot use devModeOptions outside of dev mode" } // TODO Move this to ArtemisMessagingServer rpcUsers.forEach { require(it.username.matches("\\w+".toRegex())) { "Username ${it.username} contains invalid characters" } From a4636b0d53e12f5f437538139644dc617a225f31 Mon Sep 17 00:00:00 2001 From: Christian Sailer Date: Thu, 26 Oct 2017 15:11:22 +0100 Subject: [PATCH 51/94] Fix mock for new NodeConfigutation property --- .../src/main/kotlin/net/corda/testing/NodeTestUtils.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/NodeTestUtils.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/NodeTestUtils.kt index 768b7771c6..f781935fe6 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/NodeTestUtils.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/NodeTestUtils.kt @@ -78,6 +78,7 @@ fun testNodeConfiguration( doReturn(5).whenever(it).messageRedeliveryDelaySeconds doReturn(0L).whenever(it).additionalNodeInfoPollingFrequencyMsec doReturn(null).whenever(it).networkMapService + doReturn(null).whenever(it).devModeOptions doCallRealMethod().whenever(it).certificatesDirectory doCallRealMethod().whenever(it).trustStoreFile doCallRealMethod().whenever(it).sslKeystore From 59fe7a196bff0c301d84bf9ed456ffc0d66cab95 Mon Sep 17 00:00:00 2001 From: Mat Rizzo Date: Mon, 23 Oct 2017 17:23:29 +0100 Subject: [PATCH 52/94] CORDA-728 fixed the issue with node runner on mac. Also did a tiny bit of readability refactoring. CORDA-728 clinton's requested changes to move all property getters onto one line CORDA-728 fixed the issue with node runner on mac. Also did a tiny bit of readability refactoring. CORDA-728 clinton's requested changes to move all property getters onto one line CORDA-728 Tiny bit of readability refactoring, change of flow control around error occuring on TC, renaming of log file to work with Rick's TC change. CORDA-728 clinton's requested changes to move all property getters onto one line CORDA-728 clinton's requested changes to move all property getters onto one line CORDA-728 Fixing some simple compilation errors. CORDA-728 deparallelizing in attempt to fix capsule error --- .../main/kotlin/net/corda/plugins/Cordform.kt | 26 ++++++++++++------- .../kotlin/net/corda/plugins/Cordformation.kt | 10 ++++--- .../src/main/kotlin/net/corda/plugins/Node.kt | 9 +++++-- .../kotlin/net/corda/plugins/NodeRunner.kt | 2 +- 4 files changed, 30 insertions(+), 17 deletions(-) diff --git a/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordform.kt b/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordform.kt index 5193703c59..c9bb038c69 100644 --- a/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordform.kt +++ b/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordform.kt @@ -148,26 +148,22 @@ open class Cordform : DefaultTask() { installNodeInfos() } + private fun generateNodeInfos() { project.logger.info("Generating node infos") val generateTimeoutSeconds = 60L val processes = nodes.map { node -> project.logger.info("Generating node info for ${fullNodePath(node)}") val logDir = File(fullNodePath(node).toFile(), "logs") - logDir.mkdirs() // Directory may not exist at this point - Pair(node, ProcessBuilder("java", "-jar", Node.nodeJarName, "--just-generate-node-info") - .directory(fullNodePath(node).toFile()) - .redirectErrorStream(true) - // InheritIO causes hangs on windows due the gradle buffer also not being flushed. - // Must redirect to output or logger (node log is still written, this is just startup banner) - .redirectOutput(File(logDir, "generate-info-log.txt")) - .start()) + logDir.mkdirs() + Pair(node, buildNodeProcess(node, logDir)) } try { - processes.parallelStream().forEach { (node, process) -> + processes.forEach { (node, process) -> if (!process.waitFor(generateTimeoutSeconds, TimeUnit.SECONDS)) { throw GradleException("Node took longer $generateTimeoutSeconds seconds than too to generate node info - see node log at ${fullNodePath(node)}/logs") - } else if (process.exitValue() != 0) { + } + if (process.exitValue() != 0) { throw GradleException("Node exited with ${process.exitValue()} when generating node infos - see node log at ${fullNodePath(node)}/logs") } } @@ -179,6 +175,16 @@ open class Cordform : DefaultTask() { } } + private fun buildNodeProcess(node: Node, logDir: File): Process { + return ProcessBuilder("java", "-jar", Node.nodeJarName, "--just-generate-node-info") + .directory(fullNodePath(node).toFile()) + .redirectErrorStream(true) + // InheritIO causes hangs on windows due the gradle buffer also not being flushed. + // Must redirect to output or logger (node log is still written, this is just startup banner) + .redirectOutput(File(logDir, "generate-info.log")) + .start() + } + private fun installNodeInfos() { project.logger.info("Installing node infos") for (source in nodes) { diff --git a/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordformation.kt b/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordformation.kt index b0e09ad2da..c28c8b645e 100644 --- a/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordformation.kt +++ b/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordformation.kt @@ -18,10 +18,12 @@ class Cordformation : Plugin { * @return A file handle to the file in the JAR. */ fun getPluginFile(project: Project, filePathInJar: String): File { - val archive: File? = project.rootProject.buildscript.configurations.single { it.name == "classpath" }.find { - it.name.contains("cordformation") - } - return project.rootProject.resources.text.fromArchiveEntry(archive, filePathInJar).asFile() + val archive: File? = project.rootProject.buildscript.configurations + .single { it.name == "classpath" } + .find { it.name.contains("cordformation") } + return project.rootProject.resources.text + .fromArchiveEntry(archive, filePathInJar) + .asFile() } val executableFileMode = "0755".toInt(8) diff --git a/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Node.kt b/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Node.kt index b0660a9245..cc19007617 100644 --- a/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Node.kt +++ b/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Node.kt @@ -128,7 +128,7 @@ class Node(private val project: Project) : CordformNode() { // Can't parse as an X500 name, use the full string name } - nodeDir = File(rootDir.toFile(), dirName.replace("\\s", "")) + nodeDir = File(rootDir.toFile(), dirName) } private fun configureProperties() { @@ -201,7 +201,12 @@ class Node(private val project: Project) : CordformNode() { * Installs the configuration file to this node's directory and detokenises it. */ private fun installConfig() { - val options = ConfigRenderOptions.defaults().setOriginComments(false).setComments(false).setFormatted(false).setJson(false) + val options = ConfigRenderOptions + .defaults() + .setOriginComments(false) + .setComments(false) + .setFormatted(false) + .setJson(false) val configFileText = config.root().render(options).split("\n").toList() // Need to write a temporary file first to use the project.copy, which resolves directories correctly. diff --git a/gradle-plugins/cordformation/src/noderunner/kotlin/net/corda/plugins/NodeRunner.kt b/gradle-plugins/cordformation/src/noderunner/kotlin/net/corda/plugins/NodeRunner.kt index 83462c2deb..04b01654f3 100644 --- a/gradle-plugins/cordformation/src/noderunner/kotlin/net/corda/plugins/NodeRunner.kt +++ b/gradle-plugins/cordformation/src/noderunner/kotlin/net/corda/plugins/NodeRunner.kt @@ -104,7 +104,7 @@ private class TerminalWindowJavaCommand(jarName: String, dir: File, debugPort: I delay 0.5 tell app "System Events" to tell process "Terminal" to keystroke "t" using command down delay 0.5 - do script "bash -c 'cd $dir; ${command.joinToString(" ")} && exit'" in selected tab of the front window + do script "bash -c 'cd \"$dir\" ; \"${command.joinToString("""\" \"""")}\" && exit'" in selected tab of the front window end tell""") } OS.WINDOWS -> { From 75ba4712388becaf77518c6a6b40d2963daea2ff Mon Sep 17 00:00:00 2001 From: Mat Rizzo Date: Tue, 24 Oct 2017 16:56:57 +0100 Subject: [PATCH 53/94] CORDA-728 Turning on capsule debug --- .../cordformation/src/main/kotlin/net/corda/plugins/Cordform.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordform.kt b/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordform.kt index c9bb038c69..5bdb90a884 100644 --- a/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordform.kt +++ b/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordform.kt @@ -176,7 +176,7 @@ open class Cordform : DefaultTask() { } private fun buildNodeProcess(node: Node, logDir: File): Process { - return ProcessBuilder("java", "-jar", Node.nodeJarName, "--just-generate-node-info") + return ProcessBuilder("java", "-Dcapsule.log=verbose", "-jar", Node.nodeJarName, "--just-generate-node-info") .directory(fullNodePath(node).toFile()) .redirectErrorStream(true) // InheritIO causes hangs on windows due the gradle buffer also not being flushed. From 91bba5e2636999e3789ed08d8d38b04cbec0d304 Mon Sep 17 00:00:00 2001 From: Mat Rizzo Date: Tue, 24 Oct 2017 16:07:01 +0100 Subject: [PATCH 54/94] CORDA-741 changed formatted to true --- .../cordformation/src/main/kotlin/net/corda/plugins/Node.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Node.kt b/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Node.kt index cc19007617..322a1febcc 100644 --- a/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Node.kt +++ b/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Node.kt @@ -205,7 +205,7 @@ class Node(private val project: Project) : CordformNode() { .defaults() .setOriginComments(false) .setComments(false) - .setFormatted(false) + .setFormatted(true) .setJson(false) val configFileText = config.root().render(options).split("\n").toList() From 170fde9d71adaacd0198673ae4ba9750243c052e Mon Sep 17 00:00:00 2001 From: Christian Sailer Date: Thu, 26 Oct 2017 16:09:58 +0100 Subject: [PATCH 55/94] Code rewiew: - add comments - clearer variable names - use constants instead of literal --- .../node/services/config/NodeConfiguration.kt | 4 ++++ .../statemachine/StateMachineManagerImpl.kt | 5 ++++- .../corda/node/utilities/NamedThreadFactory.kt | 18 ++++++++++++++---- 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt b/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt index d5c93dafbb..2fcba96f70 100644 --- a/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt +++ b/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt @@ -36,6 +36,10 @@ interface NodeConfiguration : NodeSSLConfiguration { val notary: NotaryConfig? val activeMQServer: ActiveMqServerConfiguration val additionalNodeInfoPollingFrequencyMsec: Long + + companion object { + val disableCheckpointCheckerFlag = "disableCheckpointChecker" + } } fun NodeConfiguration.isDevModeOptionsFlagSet(flag: String):Boolean{ diff --git a/node/src/main/kotlin/net/corda/node/services/statemachine/StateMachineManagerImpl.kt b/node/src/main/kotlin/net/corda/node/services/statemachine/StateMachineManagerImpl.kt index be4f991313..ed559f3411 100644 --- a/node/src/main/kotlin/net/corda/node/services/statemachine/StateMachineManagerImpl.kt +++ b/node/src/main/kotlin/net/corda/node/services/statemachine/StateMachineManagerImpl.kt @@ -31,6 +31,7 @@ import net.corda.node.internal.InitiatedFlowFactory import net.corda.node.services.api.Checkpoint import net.corda.node.services.api.CheckpointStorage import net.corda.node.services.api.ServiceHubInternal +import net.corda.node.services.config.NodeConfiguration import net.corda.node.services.config.isDevModeOptionsFlagSet import net.corda.node.services.messaging.ReceivedMessage import net.corda.node.services.messaging.TopicSession @@ -89,7 +90,9 @@ class StateMachineManagerImpl( private val scheduler = FiberScheduler() private val mutex = ThreadBox(InnerState()) // This thread (only enabled in dev mode) deserialises checkpoints in the background to shake out bugs in checkpoint restore. - private val checkpointCheckerThread = if (serviceHub.configuration.devMode && !serviceHub.configuration.isDevModeOptionsFlagSet("disableCheckpointChecking")) newNamedSingleThreadExecutor("CheckpointChecker") else null + private val checkpointCheckerThread = if (serviceHub.configuration.devMode + && !serviceHub.configuration.isDevModeOptionsFlagSet(NodeConfiguration.disableCheckpointCheckerFlag)) + newNamedSingleThreadExecutor("CheckpointChecker") else null @Volatile private var unrestorableCheckpoints = false diff --git a/node/src/main/kotlin/net/corda/node/utilities/NamedThreadFactory.kt b/node/src/main/kotlin/net/corda/node/utilities/NamedThreadFactory.kt index bd93f57470..c7fd6c0186 100644 --- a/node/src/main/kotlin/net/corda/node/utilities/NamedThreadFactory.kt +++ b/node/src/main/kotlin/net/corda/node/utilities/NamedThreadFactory.kt @@ -5,15 +5,25 @@ import java.util.concurrent.Executors import java.util.concurrent.ThreadFactory import java.util.concurrent.atomic.AtomicInteger + +/** + * Utility class that allows to give threads arbitrary name prefixes when they are created + * via an executor. It will use an underlying thread factory to create the actual thread + * and then override the thread name with the prefix and an ever increasing number + */ class NamedThreadFactory(private val name:String, private val underlyingFactory: ThreadFactory) : ThreadFactory{ val threadNumber = AtomicInteger(1) - override fun newThread(r: Runnable?): Thread { - val t = underlyingFactory.newThread(r) - t.name = name + "-" + threadNumber.getAndIncrement() - return t + override fun newThread(runnable: Runnable?): Thread { + val thread = underlyingFactory.newThread(runnable) + thread.name = name + "-" + threadNumber.getAndIncrement() + return thread } } +/** + * Create a single thread executor with a NamedThreadFactory based on the default thread factory + * defined in java.util.concurrent.Executors + */ fun newNamedSingleThreadExecutor(name: String): ExecutorService { return Executors.newSingleThreadExecutor(NamedThreadFactory(name, Executors.defaultThreadFactory())) } From b2e88099167cf8d8538732f22788452f0d63d5d0 Mon Sep 17 00:00:00 2001 From: Mike Hearn Date: Wed, 25 Oct 2017 13:28:37 +0200 Subject: [PATCH 56/94] Minor: InitiatedBy is a documented annotation. --- core/src/main/kotlin/net/corda/core/flows/InitiatedBy.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/core/src/main/kotlin/net/corda/core/flows/InitiatedBy.kt b/core/src/main/kotlin/net/corda/core/flows/InitiatedBy.kt index a5f9c709a2..8ad0fb2fc0 100644 --- a/core/src/main/kotlin/net/corda/core/flows/InitiatedBy.kt +++ b/core/src/main/kotlin/net/corda/core/flows/InitiatedBy.kt @@ -15,4 +15,5 @@ import kotlin.reflect.KClass * @see InitiatingFlow */ @Target(CLASS) +@MustBeDocumented annotation class InitiatedBy(val value: KClass>) \ No newline at end of file From 1b4a5f3d4c54f227f4e989ced9c980b7ae715440 Mon Sep 17 00:00:00 2001 From: Mike Hearn Date: Thu, 26 Oct 2017 16:22:57 +0200 Subject: [PATCH 57/94] Add a toString to FlowSessionImpl --- .../corda/node/services/statemachine/FlowSessionImpl.kt | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/node/src/main/kotlin/net/corda/node/services/statemachine/FlowSessionImpl.kt b/node/src/main/kotlin/net/corda/node/services/statemachine/FlowSessionImpl.kt index 054d7c5d01..479bbe86da 100644 --- a/node/src/main/kotlin/net/corda/node/services/statemachine/FlowSessionImpl.kt +++ b/node/src/main/kotlin/net/corda/node/services/statemachine/FlowSessionImpl.kt @@ -8,10 +8,7 @@ import net.corda.core.identity.Party import net.corda.core.internal.FlowStateMachine import net.corda.core.utilities.UntrustworthyData -class FlowSessionImpl( - override val counterparty: Party -) : FlowSession() { - +class FlowSessionImpl(override val counterparty: Party) : FlowSession() { internal lateinit var stateMachine: FlowStateMachine<*> internal lateinit var sessionFlow: FlowLogic<*> @@ -57,5 +54,7 @@ class FlowSessionImpl( @Suspendable override fun send(payload: Any) = send(payload, maySkipCheckpoint = false) + + override fun toString() = "Flow session with $counterparty" } From d7e4aa1783ff80ce69843ace5e1abe9e1bddc0df Mon Sep 17 00:00:00 2001 From: Mike Hearn Date: Wed, 25 Oct 2017 13:32:31 +0200 Subject: [PATCH 58/94] Add more formal support for observer/regulator nodes. This is a simplistic approach which assumes a dedicated node for observers: states that are reported to the node will appear in the database and update feeds as normal. Apps that expect all updates to be relevant to themselves may need adjusting if they run on an observer node too, but this is likely to be rare. --- .idea/compiler.xml | 1 + .../core/flows/ReceiveTransactionFlow.kt | 33 +++++++++--- .../core/internal/ResolveTransactionsFlow.kt | 3 +- .../kotlin/net/corda/core/node/ServiceHub.kt | 38 +++++++++++-- docs/source/changelog.rst | 12 +++-- docs/source/release-notes.rst | 3 ++ docs/source/tutorial-observer-nodes.rst | 36 +++++++++++++ docs/source/tutorials-index.rst | 3 +- .../net/corda/node/internal/AbstractNode.kt | 9 ++-- .../node/internal/cordapp/CordappLoader.kt | 1 + .../corda/node/services/CoreFlowHandlers.kt | 4 +- .../node/services/api/ServiceHubInternal.kt | 7 +-- .../node/services/api/VaultServiceInternal.kt | 5 +- .../node/services/vault/NodeVaultService.kt | 45 ++++++++++------ .../events/NodeSchedulerServiceTest.kt | 8 +-- .../persistence/DBTransactionStorageTests.kt | 5 +- .../persistence/HibernateConfigurationTest.kt | 5 +- .../services/vault/NodeVaultServiceTest.kt | 54 ++++++++++++++----- .../net/corda/irs/flows/AutoOfferFlow.kt | 43 +++++++++++++-- .../net/corda/testing/node/MockServices.kt | 16 +++--- 20 files changed, 251 insertions(+), 80 deletions(-) create mode 100644 docs/source/tutorial-observer-nodes.rst diff --git a/.idea/compiler.xml b/.idea/compiler.xml index ae994c7ef9..9197de4b88 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -42,6 +42,7 @@ + diff --git a/core/src/main/kotlin/net/corda/core/flows/ReceiveTransactionFlow.kt b/core/src/main/kotlin/net/corda/core/flows/ReceiveTransactionFlow.kt index 2e686e8746..76dc39dba7 100644 --- a/core/src/main/kotlin/net/corda/core/flows/ReceiveTransactionFlow.kt +++ b/core/src/main/kotlin/net/corda/core/flows/ReceiveTransactionFlow.kt @@ -3,6 +3,7 @@ package net.corda.core.flows import co.paralleluniverse.fibers.Suspendable import net.corda.core.contracts.* import net.corda.core.internal.ResolveTransactionsFlow +import net.corda.core.node.StatesToRecord import net.corda.core.transactions.SignedTransaction import net.corda.core.utilities.unwrap import java.security.SignatureException @@ -14,25 +15,41 @@ import java.security.SignatureException * [SignedTransaction] and perform the resolution back-and-forth required to check the dependencies and download any missing * attachments. The flow will return the [SignedTransaction] after it is resolved and then verified using [SignedTransaction.verify]. * - * @param otherSideSession session to the other side which is calling [SendTransactionFlow]. - * @param checkSufficientSignatures if true checks all required signatures are present. See [SignedTransaction.verify]. + * Please note that it will *not* store the transaction to the vault unless that is explicitly requested. + * + * @property otherSideSession session to the other side which is calling [SendTransactionFlow]. + * @property checkSufficientSignatures if true checks all required signatures are present. See [SignedTransaction.verify]. + * @property statesToRecord which transaction states should be recorded in the vault, if any. */ -class ReceiveTransactionFlow(private val otherSideSession: FlowSession, - private val checkSufficientSignatures: Boolean) : FlowLogic() { - /** Receives a [SignedTransaction] from [otherSideSession], verifies it and then records it in the vault. */ - constructor(otherSideSession: FlowSession) : this(otherSideSession, true) - +class ReceiveTransactionFlow @JvmOverloads constructor(private val otherSideSession: FlowSession, + private val checkSufficientSignatures: Boolean = true, + private val statesToRecord: StatesToRecord = StatesToRecord.NONE) : FlowLogic() { + @Suppress("KDocMissingDocumentation") @Suspendable @Throws(SignatureException::class, AttachmentResolutionException::class, TransactionResolutionException::class, TransactionVerificationException::class) override fun call(): SignedTransaction { - return otherSideSession.receive().unwrap { + if (checkSufficientSignatures) { + logger.trace("Receiving a transaction from ${otherSideSession.counterparty}") + } else { + logger.trace("Receiving a transaction (but without checking the signatures) from ${otherSideSession.counterparty}") + } + + val stx = otherSideSession.receive().unwrap { subFlow(ResolveTransactionsFlow(it, otherSideSession)) it.verify(serviceHub, checkSufficientSignatures) it } + + if (checkSufficientSignatures) { + // We should only send a transaction to the vault for processing if we did in fact fully verify it, and + // there are no missing signatures. We don't want partly signed stuff in the vault. + logger.trace("Successfully received fully signed tx ${stx.id}, sending to the vault for processing") + serviceHub.recordTransactions(statesToRecord, setOf(stx)) + } + return stx } } diff --git a/core/src/main/kotlin/net/corda/core/internal/ResolveTransactionsFlow.kt b/core/src/main/kotlin/net/corda/core/internal/ResolveTransactionsFlow.kt index e0fec0f7ac..3b531d3cf6 100644 --- a/core/src/main/kotlin/net/corda/core/internal/ResolveTransactionsFlow.kt +++ b/core/src/main/kotlin/net/corda/core/internal/ResolveTransactionsFlow.kt @@ -5,6 +5,7 @@ import net.corda.core.crypto.SecureHash import net.corda.core.flows.FlowException import net.corda.core.flows.FlowLogic import net.corda.core.flows.FlowSession +import net.corda.core.node.StatesToRecord import net.corda.core.serialization.CordaSerializable import net.corda.core.transactions.SignedTransaction import net.corda.core.utilities.exactAdd @@ -94,7 +95,7 @@ class ResolveTransactionsFlow(private val txHashes: Set, // half way through, it's no big deal, although it might result in us attempting to re-download data // redundantly next time we attempt verification. it.verify(serviceHub) - serviceHub.recordTransactions(false, it) + serviceHub.recordTransactions(StatesToRecord.NONE, listOf(it)) } return signedTransaction?.let { diff --git a/core/src/main/kotlin/net/corda/core/node/ServiceHub.kt b/core/src/main/kotlin/net/corda/core/node/ServiceHub.kt index ce08e56881..41d0980651 100644 --- a/core/src/main/kotlin/net/corda/core/node/ServiceHub.kt +++ b/core/src/main/kotlin/net/corda/core/node/ServiceHub.kt @@ -50,6 +50,26 @@ interface ServicesForResolution : StateLoader { val cordappProvider: CordappProvider } +/** + * Controls whether the transaction is sent to the vault at all, and if so whether states have to be relevant + * or not in order to be recorded. Used in [ServiceHub.recordTransactions] + */ +enum class StatesToRecord { + /** The received transaction is not sent to the vault at all. This is used within transaction resolution. */ + NONE, + /** + * All states that can be seen in the transaction will be recorded by the vault, even if none of the identities + * on this node are a participant or owner. + */ + ALL_VISIBLE, + /** + * Only states that involve one of our public keys will be stored in the vault. This is the default. A public + * key is involved (relevant) if it's in the [OwnableState.owner] field, or appears in the [ContractState.participants] + * collection. This is usually equivalent to "can I change the contents of this state by signing a transaction". + */ + ONLY_RELEVANT +} + /** * A service hub is the starting point for most operations you can do inside the node. You are provided with one * when a class annotated with [CordaService] is constructed, and you have access to one inside flows. Most RPCs @@ -132,7 +152,9 @@ interface ServiceHub : ServicesForResolution { * @param txs The transactions to record. * @param notifyVault indicate if the vault should be notified for the update. */ - fun recordTransactions(notifyVault: Boolean, txs: Iterable) + fun recordTransactions(notifyVault: Boolean, txs: Iterable) { + recordTransactions(if (notifyVault) StatesToRecord.ONLY_RELEVANT else StatesToRecord.NONE, txs) + } /** * Stores the given [SignedTransaction]s in the local transaction storage and then sends them to the vault for @@ -142,12 +164,22 @@ interface ServiceHub : ServicesForResolution { recordTransactions(notifyVault, listOf(first, *remaining)) } + /** + * Stores the given [SignedTransaction]s in the local transaction storage and then sends them to the vault for + * further processing if [statesToRecord] is not [StatesToRecord.NONE]. + * This is expected to be run within a database transaction. + * + * @param txs The transactions to record. + * @param statesToRecord how the vault should treat the output states of the transaction. + */ + fun recordTransactions(statesToRecord: StatesToRecord, txs: Iterable) + /** * Stores the given [SignedTransaction]s in the local transaction storage and then sends them to the vault for * further processing. This is expected to be run within a database transaction. */ fun recordTransactions(first: SignedTransaction, vararg remaining: SignedTransaction) { - recordTransactions(true, first, *remaining) + recordTransactions(listOf(first, *remaining)) } /** @@ -155,7 +187,7 @@ interface ServiceHub : ServicesForResolution { * further processing. This is expected to be run within a database transaction. */ fun recordTransactions(txs: Iterable) { - recordTransactions(true, txs) + recordTransactions(StatesToRecord.ONLY_RELEVANT, txs) } /** diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst index 5702e9763a..f3f5e0be7c 100644 --- a/docs/source/changelog.rst +++ b/docs/source/changelog.rst @@ -6,6 +6,7 @@ from the previous milestone release. UNRELEASED ---------- + * ``OpaqueBytes.bytes`` now returns a clone of its underlying ``ByteArray``, and has been redeclared as ``final``. This is a minor change to the public API, but is required to ensure that classes like ``SecureHash`` are immutable. @@ -37,17 +38,17 @@ UNRELEASED ``notaryNodeAddress``, ``notaryClusterAddresses`` and ``bftSMaRt`` have also been removed and replaced by a single ``notary`` config object. See :doc:`corda-configuration-file` for more details. -* Gradle task ``deployNodes`` can have an additional parameter `configFile` with the path to a properties file +* Gradle task ``deployNodes`` can have an additional parameter ``configFile`` with the path to a properties file to be appended to node.conf. -* Cordformation node building DSL can have an additional parameter `configFile` with the path to a properties file +* Cordformation node building DSL can have an additional parameter ``configFile`` with the path to a properties file to be appended to node.conf. * ``FlowLogic`` now has a static method called ``sleep`` which can be used in certain circumstances to help with resolving contention over states in flows. This should be used in place of any other sleep primitive since these are not compatible with flows and their use will be prevented at some point in the future. Pay attention to the warnings and limitations described in the documentation for this method. This helps resolve a bug in ``Cash`` coin selection. - A new static property `currentTopLevel` returns the top most `FlowLogic` instance, or null if not in a flow. + A new static property ``currentTopLevel`` returns the top most ``FlowLogic`` instance, or null if not in a flow. * ``CordaService`` annotated classes should be upgraded to take a constructor parameter of type ``AppServiceHub`` which allows services to start flows marked with the ``StartableByService`` annotation. For backwards compatability @@ -65,7 +66,10 @@ UNRELEASED * A new function ``checkCommandVisibility(publicKey: PublicKey)`` has been added to ``FilteredTransaction`` to check if every command that a signer should receive (e.g. an Oracle) is indeed visible. -* Change the AMQP serialiser to use the oficially assigned R3 identifier rather than a placeholder. +* Changed the AMQP serialiser to use the oficially assigned R3 identifier rather than a placeholder. + +* The ``ReceiveTransactionFlow`` can now be told to record the transaction at the same time as receiving it. Using this + feature, better support for observer/regulator nodes has been added. See :doc:`tutorial-observer-nodes`. .. _changelog_v1: diff --git a/docs/source/release-notes.rst b/docs/source/release-notes.rst index 72baf8cd6f..d0a7fa14fd 100644 --- a/docs/source/release-notes.rst +++ b/docs/source/release-notes.rst @@ -6,6 +6,9 @@ Here are release notes for each snapshot release from M9 onwards. Unreleased ---------- +Support for observer/regulator nodes has returned. Read :doc:`tutorial-observer-nodes` to learn more or examine the +interest rate swaps demo. + Release 1.0 ----------- Corda 1.0 is finally here! diff --git a/docs/source/tutorial-observer-nodes.rst b/docs/source/tutorial-observer-nodes.rst new file mode 100644 index 0000000000..6a77cc1fa7 --- /dev/null +++ b/docs/source/tutorial-observer-nodes.rst @@ -0,0 +1,36 @@ +.. highlight:: kotlin +.. raw:: html + + + + +Observer nodes +============== + +Posting transactions to an observer node is a common requirement in finance, where regulators often want +to receive comprehensive reporting on all actions taken. By running their own node, regulators can receive a stream +of digitally signed, de-duplicated reports useful for later processing. + +Adding support for observer nodes to your application is easy. The IRS (interest rate swap) demo shows to do it. + +Just define a new flow that wraps the SendTransactionFlow/ReceiveTransactionFlow, as follows: + +.. container:: codeset + + .. literalinclude:: ../../samples/irs-demo/cordapp/src/main/kotlin/net/corda/irs/flows/AutoOfferFlow.kt + :language: kotlin + :start-after: DOCSTART 1 + :end-before: DOCEND 1 + +In this example, the ``AutoOfferFlow`` is the business logic, and we define two very short and simple flows to send +the transaction to the regulator. There are two important aspects to note here: + +1. The ``ReportToRegulatorFlow`` is marked as an ``@InitiatingFlow`` because it will start a new conversation, context + free, with the regulator. +2. The ``ReceiveRegulatoryReportFlow`` uses ``ReceiveTransactionFlow`` in a special way - it tells it to send the + transaction to the vault for processing, including all states even if not involving our public keys. This is required + because otherwise the vault will ignore states that don't list any of the node's public keys, but in this case, + we do want to passively observe states we can't change. So overriding this behaviour is required. + +If the states define a relational mapping (see :doc:`api-persistence`) then the regulator will be able to query the +reports from their database and observe new transactions coming in via RPC. \ No newline at end of file diff --git a/docs/source/tutorials-index.rst b/docs/source/tutorials-index.rst index f14be4385f..d2b3dfce9d 100644 --- a/docs/source/tutorials-index.rst +++ b/docs/source/tutorials-index.rst @@ -19,4 +19,5 @@ Tutorials tutorial-custom-notary tutorial-tear-offs tutorial-attachments - event-scheduling \ No newline at end of file + event-scheduling + tutorial-observer-nodes \ No newline at end of file diff --git a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt index f7adde2604..badeb6a9f9 100644 --- a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt +++ b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt @@ -16,10 +16,7 @@ import net.corda.core.internal.concurrent.doneFuture import net.corda.core.internal.concurrent.flatMap import net.corda.core.internal.concurrent.openFuture import net.corda.core.messaging.* -import net.corda.core.node.AppServiceHub -import net.corda.core.node.NodeInfo -import net.corda.core.node.ServiceHub -import net.corda.core.node.StateLoader +import net.corda.core.node.* import net.corda.core.node.services.* import net.corda.core.serialization.SerializationWhitelist import net.corda.core.serialization.SerializeAsToken @@ -803,9 +800,9 @@ abstract class AbstractNode(config: NodeConfiguration, return flowFactories[initiatingFlowClass] } - override fun recordTransactions(notifyVault: Boolean, txs: Iterable) { + override fun recordTransactions(statesToRecord: StatesToRecord, txs: Iterable) { database.transaction { - super.recordTransactions(notifyVault, txs) + super.recordTransactions(statesToRecord, txs) } } diff --git a/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappLoader.kt b/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappLoader.kt index 5154c592b1..50daaa1d8d 100644 --- a/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappLoader.kt +++ b/node/src/main/kotlin/net/corda/node/internal/cordapp/CordappLoader.kt @@ -274,6 +274,7 @@ class CordappLoader private constructor(private val cordappJarPaths: List() { @Suspendable override fun call() { - val stx = subFlow(ReceiveTransactionFlow(sender)) - serviceHub.recordTransactions(stx) + subFlow(ReceiveTransactionFlow(sender, true, StatesToRecord.ONLY_RELEVANT)) } } diff --git a/node/src/main/kotlin/net/corda/node/services/api/ServiceHubInternal.kt b/node/src/main/kotlin/net/corda/node/services/api/ServiceHubInternal.kt index de12960304..345766ee41 100644 --- a/node/src/main/kotlin/net/corda/node/services/api/ServiceHubInternal.kt +++ b/node/src/main/kotlin/net/corda/node/services/api/ServiceHubInternal.kt @@ -15,6 +15,7 @@ import net.corda.core.messaging.SingleMessageRecipient import net.corda.core.messaging.StateMachineTransactionMapping import net.corda.core.node.NodeInfo import net.corda.core.node.ServiceHub +import net.corda.core.node.StatesToRecord import net.corda.core.node.services.NetworkMapCache import net.corda.core.node.services.NetworkMapCacheBase import net.corda.core.node.services.TransactionStorage @@ -93,7 +94,7 @@ interface ServiceHubInternal : ServiceHub { val database: CordaPersistence val configuration: NodeConfiguration override val cordappProvider: CordappProviderInternal - override fun recordTransactions(notifyVault: Boolean, txs: Iterable) { + override fun recordTransactions(statesToRecord: StatesToRecord, txs: Iterable) { require(txs.any()) { "No transactions passed in for recording" } val recordedTransactions = txs.filter { validatedTransactions.addTransaction(it) } val stateMachineRunId = FlowStateMachineImpl.currentStateMachine()?.id @@ -105,9 +106,9 @@ interface ServiceHubInternal : ServiceHub { log.warn("Transactions recorded from outside of a state machine") } - if (notifyVault) { + if (statesToRecord != StatesToRecord.NONE) { val toNotify = recordedTransactions.map { if (it.isNotaryChangeTransaction()) it.notaryChangeTx else it.tx } - vaultService.notifyAll(toNotify) + vaultService.notifyAll(statesToRecord, toNotify) } } diff --git a/node/src/main/kotlin/net/corda/node/services/api/VaultServiceInternal.kt b/node/src/main/kotlin/net/corda/node/services/api/VaultServiceInternal.kt index fde38dfe05..b24978f202 100644 --- a/node/src/main/kotlin/net/corda/node/services/api/VaultServiceInternal.kt +++ b/node/src/main/kotlin/net/corda/node/services/api/VaultServiceInternal.kt @@ -1,5 +1,6 @@ package net.corda.node.services.api +import net.corda.core.node.StatesToRecord import net.corda.core.node.services.VaultService import net.corda.core.transactions.CoreTransaction import net.corda.core.transactions.NotaryChangeWireTransaction @@ -12,8 +13,8 @@ interface VaultServiceInternal : VaultService { * indicate whether an update consists entirely of regular or notary change transactions, which may require * different processing logic. */ - fun notifyAll(txns: Iterable) + fun notifyAll(statesToRecord: StatesToRecord, txns: Iterable) /** Same as notifyAll but with a single transaction. */ - fun notify(tx: CoreTransaction) = notifyAll(listOf(tx)) + fun notify(statesToRecord: StatesToRecord, tx: CoreTransaction) = notifyAll(statesToRecord, listOf(tx)) } diff --git a/node/src/main/kotlin/net/corda/node/services/vault/NodeVaultService.kt b/node/src/main/kotlin/net/corda/node/services/vault/NodeVaultService.kt index 934cc2cb83..818f0d58bc 100644 --- a/node/src/main/kotlin/net/corda/node/services/vault/NodeVaultService.kt +++ b/node/src/main/kotlin/net/corda/node/services/vault/NodeVaultService.kt @@ -13,6 +13,7 @@ import net.corda.core.node.services.vault.QueryCriteria import net.corda.core.node.services.vault.Sort import net.corda.core.node.services.vault.SortAttribute import net.corda.core.messaging.DataFeed +import net.corda.core.node.StatesToRecord import net.corda.core.node.services.VaultQueryException import net.corda.core.node.services.vault.* import net.corda.core.schemas.PersistentStateRef @@ -50,8 +51,7 @@ private fun CriteriaBuilder.executeUpdate(session: Session, configure: Root<*>.( } /** - * Currently, the node vault service is a very simple RDBMS backed implementation. It will change significantly when - * we add further functionality as the design for the vault and vault service matures. + * The vault service handles storage, retrieval and querying of states. * * This class needs database transactions to be in-flight during method calls and init, and will throw exceptions if * this is not the case. @@ -59,8 +59,12 @@ private fun CriteriaBuilder.executeUpdate(session: Session, configure: Root<*>.( * TODO: keep an audit trail with time stamps of previously unconsumed states "as of" a particular point in time. * TODO: have transaction storage do some caching. */ -class NodeVaultService(private val clock: Clock, private val keyManagementService: KeyManagementService, private val stateLoader: StateLoader, hibernateConfig: HibernateConfiguration) : SingletonSerializeAsToken(), VaultServiceInternal { - +class NodeVaultService( + private val clock: Clock, + private val keyManagementService: KeyManagementService, + private val stateLoader: StateLoader, + hibernateConfig: HibernateConfiguration +) : SingletonSerializeAsToken(), VaultServiceInternal { private companion object { val log = loggerFor() } @@ -118,7 +122,10 @@ class NodeVaultService(private val clock: Clock, private val keyManagementServic override val updates: Observable> get() = mutex.locked { _updatesInDbTx } - override fun notifyAll(txns: Iterable) { + override fun notifyAll(statesToRecord: StatesToRecord, txns: Iterable) { + if (statesToRecord == StatesToRecord.NONE) + return + // It'd be easier to just group by type, but then we'd lose ordering. val regularTxns = mutableListOf() val notaryChangeTxns = mutableListOf() @@ -128,30 +135,33 @@ class NodeVaultService(private val clock: Clock, private val keyManagementServic is WireTransaction -> { regularTxns.add(tx) if (notaryChangeTxns.isNotEmpty()) { - notifyNotaryChange(notaryChangeTxns.toList()) + notifyNotaryChange(notaryChangeTxns.toList(), statesToRecord) notaryChangeTxns.clear() } } is NotaryChangeWireTransaction -> { notaryChangeTxns.add(tx) if (regularTxns.isNotEmpty()) { - notifyRegular(regularTxns.toList()) + notifyRegular(regularTxns.toList(), statesToRecord) regularTxns.clear() } } } } - if (regularTxns.isNotEmpty()) notifyRegular(regularTxns.toList()) - if (notaryChangeTxns.isNotEmpty()) notifyNotaryChange(notaryChangeTxns.toList()) + if (regularTxns.isNotEmpty()) notifyRegular(regularTxns.toList(), statesToRecord) + if (notaryChangeTxns.isNotEmpty()) notifyNotaryChange(notaryChangeTxns.toList(), statesToRecord) } - private fun notifyRegular(txns: Iterable) { + private fun notifyRegular(txns: Iterable, statesToRecord: StatesToRecord) { fun makeUpdate(tx: WireTransaction): Vault.Update { val myKeys = keyManagementService.filterMyKeys(tx.outputs.flatMap { it.data.participants.map { it.owningKey } }) - val ourNewStates = tx.outputs. - filter { isRelevant(it.data, myKeys.toSet()) }. - map { tx.outRef(it.data) } + + val ourNewStates = when (statesToRecord) { + StatesToRecord.NONE -> throw AssertionError("Should not reach here") + StatesToRecord.ONLY_RELEVANT -> tx.outputs.filter { isRelevant(it.data, myKeys.toSet()) } + StatesToRecord.ALL_VISIBLE -> tx.outputs + }.map { tx.outRef(it.data) } // Retrieve all unconsumed states for this transaction's inputs val consumedStates = loadStates(tx.inputs) @@ -169,7 +179,7 @@ class NodeVaultService(private val clock: Clock, private val keyManagementServic processAndNotify(netDelta) } - private fun notifyNotaryChange(txns: Iterable) { + private fun notifyNotaryChange(txns: Iterable, statesToRecord: StatesToRecord) { fun makeUpdate(tx: NotaryChangeWireTransaction): Vault.Update { // We need to resolve the full transaction here because outputs are calculated from inputs // We also can't do filtering beforehand, since output encumbrance pointers get recalculated based on @@ -178,7 +188,12 @@ class NodeVaultService(private val clock: Clock, private val keyManagementServic val myKeys = keyManagementService.filterMyKeys(ltx.outputs.flatMap { it.data.participants.map { it.owningKey } }) val (consumedStateAndRefs, producedStates) = ltx.inputs. zip(ltx.outputs). - filter { (_, output) -> isRelevant(output.data, myKeys.toSet()) }. + filter { (_, output) -> + if (statesToRecord == StatesToRecord.ONLY_RELEVANT) + isRelevant(output.data, myKeys.toSet()) + else + true + }. unzip() val producedStateAndRefs = producedStates.map { ltx.outRef(it.data) } diff --git a/node/src/test/kotlin/net/corda/node/services/events/NodeSchedulerServiceTest.kt b/node/src/test/kotlin/net/corda/node/services/events/NodeSchedulerServiceTest.kt index 446976faf2..31a77812c2 100644 --- a/node/src/test/kotlin/net/corda/node/services/events/NodeSchedulerServiceTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/events/NodeSchedulerServiceTest.kt @@ -12,6 +12,7 @@ import net.corda.core.identity.CordaX500Name import net.corda.core.identity.Party import net.corda.core.node.NodeInfo import net.corda.core.node.ServiceHub +import net.corda.core.node.StatesToRecord import net.corda.core.serialization.SingletonSerializeAsToken import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.TransactionBuilder @@ -107,11 +108,12 @@ class NodeSchedulerServiceTest : SingletonSerializeAsToken() { doReturn(myInfo).whenever(it).myInfo doReturn(kms).whenever(it).keyManagementService doReturn(CordappProviderImpl(CordappLoader.createWithTestPackages(listOf("net.corda.testing.contracts")), MockAttachmentStorage())).whenever(it).cordappProvider - doCallRealMethod().whenever(it).recordTransactions(any()) - doCallRealMethod().whenever(it).recordTransactions(any(), any()) - doCallRealMethod().whenever(it).recordTransactions(any(), any>()) + doCallRealMethod().whenever(it).recordTransactions(any(), any>()) + doCallRealMethod().whenever(it).recordTransactions(any>()) + doCallRealMethod().whenever(it).recordTransactions(any(), anyVararg()) doReturn(NodeVaultService(testClock, kms, stateLoader, database.hibernateConfig)).whenever(it).vaultService doReturn(this@NodeSchedulerServiceTest).whenever(it).testReference + } smmExecutor = AffinityExecutor.ServiceAffinityExecutor("test", 1) mockSMM = StateMachineManagerImpl(services, DBCheckpointStorage(), smmExecutor, database) diff --git a/node/src/test/kotlin/net/corda/node/services/persistence/DBTransactionStorageTests.kt b/node/src/test/kotlin/net/corda/node/services/persistence/DBTransactionStorageTests.kt index 160ea87bf3..5ee2454c98 100644 --- a/node/src/test/kotlin/net/corda/node/services/persistence/DBTransactionStorageTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/persistence/DBTransactionStorageTests.kt @@ -5,13 +5,13 @@ import net.corda.core.crypto.Crypto import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SignatureMetadata import net.corda.core.crypto.TransactionSignature +import net.corda.core.node.StatesToRecord import net.corda.core.node.services.VaultService import net.corda.core.toFuture import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.WireTransaction import net.corda.node.services.api.VaultServiceInternal import net.corda.node.services.schema.HibernateObserver -import net.corda.node.services.schema.NodeSchemaService import net.corda.node.services.transactions.PersistentUniquenessProvider import net.corda.node.services.vault.NodeVaultService import net.corda.node.utilities.CordaPersistence @@ -40,7 +40,6 @@ class DBTransactionStorageTests : TestDependencyInjectionBase() { val dataSourceProps = makeTestDataSourceProperties() database = configureDatabase(dataSourceProps, makeTestDatabaseProperties(), ::makeTestIdentityService) database.transaction { - services = object : MockServices(BOB_KEY) { override val vaultService: VaultServiceInternal get() { @@ -54,7 +53,7 @@ class DBTransactionStorageTests : TestDependencyInjectionBase() { validatedTransactions.addTransaction(stx) } // Refactored to use notifyAll() as we have no other unit test for that method with multiple transactions. - vaultService.notifyAll(txs.map { it.tx }) + vaultService.notifyAll(StatesToRecord.ONLY_RELEVANT, txs.map { it.tx }) } } } diff --git a/node/src/test/kotlin/net/corda/node/services/persistence/HibernateConfigurationTest.kt b/node/src/test/kotlin/net/corda/node/services/persistence/HibernateConfigurationTest.kt index 9f6fe30d11..b1eb668ec7 100644 --- a/node/src/test/kotlin/net/corda/node/services/persistence/HibernateConfigurationTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/persistence/HibernateConfigurationTest.kt @@ -5,6 +5,7 @@ import net.corda.core.contracts.StateAndRef import net.corda.core.contracts.StateRef import net.corda.core.contracts.TransactionState import net.corda.core.crypto.SecureHash +import net.corda.core.node.StatesToRecord import net.corda.core.utilities.toBase58String import net.corda.core.node.services.Vault import net.corda.core.node.services.VaultService @@ -82,12 +83,12 @@ class HibernateConfigurationTest : TestDependencyInjectionBase() { hibernateConfig = database.hibernateConfig services = object : MockServices(cordappPackages, BOB_KEY, BOC_KEY, DUMMY_NOTARY_KEY) { override val vaultService = makeVaultService(database.hibernateConfig) - override fun recordTransactions(notifyVault: Boolean, txs: Iterable) { + override fun recordTransactions(statesToRecord: StatesToRecord, txs: Iterable) { for (stx in txs) { validatedTransactions.addTransaction(stx) } // Refactored to use notifyAll() as we have no other unit test for that method with multiple transactions. - vaultService.notifyAll(txs.map { it.tx }) + vaultService.notifyAll(statesToRecord, txs.map { it.tx }) } override fun jdbcSession() = database.createSession() diff --git a/node/src/test/kotlin/net/corda/node/services/vault/NodeVaultServiceTest.kt b/node/src/test/kotlin/net/corda/node/services/vault/NodeVaultServiceTest.kt index 46802e8a7f..e08732c9bf 100644 --- a/node/src/test/kotlin/net/corda/node/services/vault/NodeVaultServiceTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/vault/NodeVaultServiceTest.kt @@ -1,16 +1,15 @@ package net.corda.node.services.vault import co.paralleluniverse.fibers.Suspendable -import net.corda.core.contracts.Amount -import net.corda.core.contracts.Issued -import net.corda.core.contracts.StateAndRef -import net.corda.core.contracts.StateRef +import net.corda.core.contracts.* import net.corda.core.crypto.generateKeyPair import net.corda.core.identity.AbstractParty import net.corda.core.identity.AnonymousParty import net.corda.core.identity.Party import net.corda.core.internal.packageName +import net.corda.core.node.StatesToRecord import net.corda.core.node.services.* +import net.corda.core.node.services.vault.PageSpecification import net.corda.core.node.services.vault.QueryCriteria import net.corda.core.node.services.vault.QueryCriteria.* import net.corda.core.transactions.NotaryChangeWireTransaction @@ -30,7 +29,6 @@ import net.corda.node.utilities.CordaPersistence import net.corda.testing.* import net.corda.testing.contracts.fillWithSomeTestCash import net.corda.testing.node.MockServices -import net.corda.testing.node.MockServices.Companion.makeTestDatabaseAndMockServices import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThatExceptionOfType import org.junit.After @@ -58,8 +56,10 @@ class NodeVaultServiceTest : TestDependencyInjectionBase() { @Before fun setUp() { LogHelper.setLevel(NodeVaultService::class) - val databaseAndServices = makeTestDatabaseAndMockServices(keys = listOf(BOC_KEY, DUMMY_CASH_ISSUER_KEY), - cordappPackages = cordappPackages) + val databaseAndServices = MockServices.makeTestDatabaseAndMockServices( + keys = listOf(BOC_KEY, DUMMY_CASH_ISSUER_KEY), + cordappPackages = cordappPackages + ) database = databaseAndServices.first services = databaseAndServices.second issuerServices = MockServices(cordappPackages, DUMMY_CASH_ISSUER_KEY, BOC_KEY) @@ -102,10 +102,10 @@ class NodeVaultServiceTest : TestDependencyInjectionBase() { val originalVault = vaultService val services2 = object : MockServices() { override val vaultService: NodeVaultService get() = originalVault - override fun recordTransactions(notifyVault: Boolean, txs: Iterable) { + override fun recordTransactions(statesToRecord: StatesToRecord, txs: Iterable) { for (stx in txs) { validatedTransactions.addTransaction(stx) - vaultService.notify(stx.tx) + vaultService.notify(statesToRecord, stx.tx) } } } @@ -512,14 +512,14 @@ class NodeVaultServiceTest : TestDependencyInjectionBase() { }.toWireTransaction(services) val cashState = StateAndRef(issueTx.outputs.single(), StateRef(issueTx.id, 0)) - database.transaction { service.notify(issueTx) } + database.transaction { service.notify(StatesToRecord.ONLY_RELEVANT, issueTx) } val expectedIssueUpdate = Vault.Update(emptySet(), setOf(cashState), null) database.transaction { val moveTx = TransactionBuilder(services.myInfo.chooseIdentity()).apply { Cash.generateSpend(services, this, Amount(1000, GBP), thirdPartyIdentity) }.toWireTransaction(services) - service.notify(moveTx) + service.notify(StatesToRecord.ONLY_RELEVANT, moveTx) } val expectedMoveUpdate = Vault.Update(setOf(cashState), emptySet(), null) @@ -556,7 +556,7 @@ class NodeVaultServiceTest : TestDependencyInjectionBase() { val cashStateWithNewNotary = StateAndRef(initialCashState.state.copy(notary = newNotary), StateRef(changeNotaryTx.id, 0)) database.transaction { - service.notifyAll(listOf(issueStx.tx, changeNotaryTx)) + service.notifyAll(StatesToRecord.ONLY_RELEVANT, listOf(issueStx.tx, changeNotaryTx)) } // Move cash @@ -567,7 +567,7 @@ class NodeVaultServiceTest : TestDependencyInjectionBase() { } database.transaction { - service.notify(moveTx) + service.notify(StatesToRecord.ONLY_RELEVANT, moveTx) } val expectedIssueUpdate = Vault.Update(emptySet(), setOf(initialCashState), null) @@ -577,4 +577,32 @@ class NodeVaultServiceTest : TestDependencyInjectionBase() { val observedUpdates = vaultSubscriber.onNextEvents assertEquals(observedUpdates, listOf(expectedIssueUpdate, expectedNotaryChangeUpdate, expectedMoveUpdate)) } + + @Test + fun observerMode() { + fun countCash(): Long { + return database.transaction { + vaultService.queryBy(Cash.State::class.java, QueryCriteria.VaultQueryCriteria(), PageSpecification(1)).totalStatesAvailable + } + } + val currentCashStates = countCash() + + // Send some minimalist dummy transaction. + val txb = TransactionBuilder(DUMMY_NOTARY) + txb.addOutputState(Cash.State(MEGA_CORP.ref(0), 100.DOLLARS, MINI_CORP), Cash::class.java.name) + txb.addCommand(Cash.Commands.Move(), MEGA_CORP_PUBKEY) + val wtx = txb.toWireTransaction(services) + database.transaction { + vaultService.notify(StatesToRecord.ONLY_RELEVANT, wtx) + } + + // Check that it was ignored as irrelevant. + assertEquals(currentCashStates, countCash()) + + // Now try again and check it was accepted. + database.transaction { + vaultService.notify(StatesToRecord.ALL_VISIBLE, wtx) + } + assertEquals(currentCashStates + 1, countCash()) + } } diff --git a/samples/irs-demo/cordapp/src/main/kotlin/net/corda/irs/flows/AutoOfferFlow.kt b/samples/irs-demo/cordapp/src/main/kotlin/net/corda/irs/flows/AutoOfferFlow.kt index 3500e75eb4..9da48655a9 100644 --- a/samples/irs-demo/cordapp/src/main/kotlin/net/corda/irs/flows/AutoOfferFlow.kt +++ b/samples/irs-demo/cordapp/src/main/kotlin/net/corda/irs/flows/AutoOfferFlow.kt @@ -2,8 +2,10 @@ package net.corda.irs.flows import co.paralleluniverse.fibers.Suspendable import net.corda.core.flows.* +import net.corda.core.identity.Party import net.corda.core.identity.excludeHostNode import net.corda.core.identity.groupAbstractPartyByWellKnownParty +import net.corda.core.node.StatesToRecord import net.corda.core.transactions.SignedTransaction import net.corda.core.utilities.ProgressTracker import net.corda.finance.contracts.DealState @@ -23,7 +25,6 @@ object AutoOfferFlow { @InitiatingFlow @StartableByRPC class Requester(val dealToBeOffered: DealState) : FlowLogic() { - companion object { object RECEIVED : ProgressTracker.Step("Received API call") object DEALING : ProgressTracker.Step("Starting the deal flow") { @@ -55,11 +56,45 @@ object AutoOfferFlow { AutoOffer(notary, dealToBeOffered), progressTracker.getChildProgressTracker(DEALING)!! ) - val stx = subFlow(instigator) - return stx + return subFlow(instigator) } } + // DOCSTART 1 @InitiatedBy(Requester::class) - class AutoOfferAcceptor(otherSideSession: FlowSession) : Acceptor(otherSideSession) + class AutoOfferAcceptor(otherSideSession: FlowSession) : Acceptor(otherSideSession) { + @Suspendable + override fun call(): SignedTransaction { + val finalTx = super.call() + // Our transaction is now committed to the ledger, so report it to our regulator. We use a custom flow + // that wraps SendTransactionFlow to allow the receiver to customise how ReceiveTransactionFlow is run, + // and because in a real life app you'd probably have more complex logic here e.g. describing why the report + // was filed, checking that the reportee is a regulated entity and not some random node from the wrong + // country and so on. + val regulator = serviceHub.identityService.partiesFromName("Regulator", true).single() + subFlow(ReportToRegulatorFlow(regulator, finalTx)) + return finalTx + } + + } + + @InitiatingFlow + class ReportToRegulatorFlow(private val regulator: Party, private val finalTx: SignedTransaction) : FlowLogic() { + @Suspendable + override fun call() { + val session = initiateFlow(regulator) + subFlow(SendTransactionFlow(session, finalTx)) + } + } + + @InitiatedBy(ReportToRegulatorFlow::class) + class ReceiveRegulatoryReportFlow(private val otherSideSession: FlowSession) : FlowLogic() { + @Suspendable + override fun call() { + // Start the matching side of SendTransactionFlow above, but tell it to record all visible states even + // though they (as far as the node can tell) are nothing to do with us. + subFlow(ReceiveTransactionFlow(otherSideSession, true, StatesToRecord.ALL_VISIBLE)) + } + } + // DOCEND 1 } diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockServices.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockServices.kt index b0c962232a..41249c8062 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockServices.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockServices.kt @@ -9,10 +9,7 @@ import net.corda.core.identity.PartyAndCertificate import net.corda.core.messaging.DataFeed import net.corda.core.messaging.FlowHandle import net.corda.core.messaging.FlowProgressHandle -import net.corda.core.node.AppServiceHub -import net.corda.core.node.NodeInfo -import net.corda.core.node.ServiceHub -import net.corda.core.node.StateLoader +import net.corda.core.node.* import net.corda.core.node.services.* import net.corda.core.serialization.SerializeAsToken import net.corda.core.serialization.SingletonSerializeAsToken @@ -30,7 +27,6 @@ import net.corda.node.services.persistence.HibernateConfiguration import net.corda.node.services.persistence.InMemoryStateMachineRecordedTransactionMappingStorage import net.corda.node.services.schema.HibernateObserver import net.corda.node.services.schema.NodeSchemaService -import net.corda.node.services.statemachine.FlowStateMachineImpl import net.corda.node.services.transactions.InMemoryTransactionVerifierService import net.corda.node.services.vault.NodeVaultService import net.corda.node.utilities.CordaPersistence @@ -101,7 +97,7 @@ open class MockServices( /** * Makes database and mock services appropriate for unit tests. - * @param keys a list of [KeyPair] instances to be used by [MockServices]. Defualts to [MEGA_CORP_KEY] + * @param keys a list of [KeyPair] instances to be used by [MockServices]. Defaults to [MEGA_CORP_KEY] * @param createIdentityService a lambda function returning an instance of [IdentityService]. Defauts to [InMemoryIdentityService]. * * @return a pair where the first element is the instance of [CordaPersistence] and the second is [MockServices]. @@ -118,14 +114,14 @@ open class MockServices( val mockService = database.transaction { object : MockServices(cordappLoader, *(keys.toTypedArray())) { override val identityService: IdentityService = database.transaction { identityServiceRef } - override val vaultService = makeVaultService(database.hibernateConfig) + override val vaultService: VaultServiceInternal = makeVaultService(database.hibernateConfig) - override fun recordTransactions(notifyVault: Boolean, txs: Iterable) { + override fun recordTransactions(statesToRecord: StatesToRecord, txs: Iterable) { for (stx in txs) { validatedTransactions.addTransaction(stx) } // Refactored to use notifyAll() as we have no other unit test for that method with multiple transactions. - vaultService.notifyAll(txs.map { it.tx }) + vaultService.notifyAll(statesToRecord, txs.map { it.tx }) } override fun jdbcSession(): Connection = database.createSession() @@ -142,7 +138,7 @@ open class MockServices( val key: KeyPair get() = keys.first() - override fun recordTransactions(notifyVault: Boolean, txs: Iterable) { + override fun recordTransactions(statesToRecord: StatesToRecord, txs: Iterable) { txs.forEach { stateMachineRecordedTransactionMapping.addMapping(StateMachineRunId.createRandom(), it.id) } From 428eb43abefcdb5f6c6f659fd939d94dfe100bd7 Mon Sep 17 00:00:00 2001 From: Mike Hearn Date: Thu, 26 Oct 2017 16:36:46 +0200 Subject: [PATCH 59/94] Minor: add a discussion to recordTransactions. Discuss the interaction between coin selection and observer nodes and why it's designed the way it is. --- docs/source/tutorial-observer-nodes.rst | 8 ++++- .../node/services/api/ServiceHubInternal.kt | 34 +++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/docs/source/tutorial-observer-nodes.rst b/docs/source/tutorial-observer-nodes.rst index 6a77cc1fa7..ecb5f04089 100644 --- a/docs/source/tutorial-observer-nodes.rst +++ b/docs/source/tutorial-observer-nodes.rst @@ -33,4 +33,10 @@ the transaction to the regulator. There are two important aspects to note here: we do want to passively observe states we can't change. So overriding this behaviour is required. If the states define a relational mapping (see :doc:`api-persistence`) then the regulator will be able to query the -reports from their database and observe new transactions coming in via RPC. \ No newline at end of file +reports from their database and observe new transactions coming in via RPC. + +.. warning:: Nodes which act as both observers and which directly take part in the ledger are not supported at this + time. In particular, coin selection may return states which you do not have the private keys to be able to sign + for. Future versions of Corda may address this issue, but for now, if you wish to both participate in the ledger + and also observe transactions that you can't sign for you will need to run two nodes and have two separate + identities. \ No newline at end of file diff --git a/node/src/main/kotlin/net/corda/node/services/api/ServiceHubInternal.kt b/node/src/main/kotlin/net/corda/node/services/api/ServiceHubInternal.kt index 345766ee41..81ad1d57dd 100644 --- a/node/src/main/kotlin/net/corda/node/services/api/ServiceHubInternal.kt +++ b/node/src/main/kotlin/net/corda/node/services/api/ServiceHubInternal.kt @@ -108,6 +108,40 @@ interface ServiceHubInternal : ServiceHub { if (statesToRecord != StatesToRecord.NONE) { val toNotify = recordedTransactions.map { if (it.isNotaryChangeTransaction()) it.notaryChangeTx else it.tx } + // When the user has requested StatesToRecord.ALL we may end up recording and relationally mapping states + // that do not involve us and that we cannot sign for. This will break coin selection and thus a warning + // is present in the documentation for this feature (see the "Observer nodes" tutorial on docs.corda.net). + // + // The reason for this is three-fold: + // + // 1) We are putting in place the observer mode feature relatively quickly to meet specific customer + // launch target dates. + // + // 2) The right design for vaults which mix observations and relevant states isn't entirely clear yet. + // + // 3) If we get the design wrong it could create security problems and business confusions. + // + // Back in the bitcoinj days I did add support for "watching addresses" to the wallet code, which is the + // Bitcoin equivalent of observer nodes: + // + // https://bitcoinj.github.io/working-with-the-wallet#watching-wallets + // + // The ability to have a wallet containing both irrelevant and relevant states complicated everything quite + // dramatically, even methods as basic as the getBalance() API which required additional modes to let you + // query "balance I can spend" vs "balance I am observing". In the end it might have been better to just + // require the user to create an entirely separate wallet for observing with. + // + // In Corda we don't support a single node having multiple vaults (at the time of writing), and it's not + // clear that's the right way to go: perhaps adding an "origin" column to the VAULT_STATES table is a better + // solution. Then you could select subsets of states depending on where the report came from. + // + // The risk of doing this is that apps/developers may use 'canned SQL queries' not written by us that forget + // to add a WHERE clause for the origin column. Those queries will seem to work most of the time until + // they're run on an observer node and mix in irrelevant data. In the worst case this may result in + // erroneous data being reported to the user, which could cause security problems. + // + // Because the primary use case for recording irrelevant states is observer/regulator nodes, who are unlikely + // to make writes to the ledger very often or at all, we choose to punt this issue for the time being. vaultService.notifyAll(statesToRecord, toNotify) } } From 4e07add161077b9ff4862d679ad00a98d94144e0 Mon Sep 17 00:00:00 2001 From: Mat Rizzo Date: Wed, 25 Oct 2017 14:14:48 +0100 Subject: [PATCH 60/94] CORDA-742 Change capsule cache directory for testing purposes --- .../cordformation/src/main/kotlin/net/corda/plugins/Cordform.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordform.kt b/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordform.kt index 5bdb90a884..7e2bb261f9 100644 --- a/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordform.kt +++ b/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordform.kt @@ -176,7 +176,7 @@ open class Cordform : DefaultTask() { } private fun buildNodeProcess(node: Node, logDir: File): Process { - return ProcessBuilder("java", "-Dcapsule.log=verbose", "-jar", Node.nodeJarName, "--just-generate-node-info") + return ProcessBuilder("java", "-Dcapsule.log=verbose", "-DCAPSULE_CACHE_DIR=./cache", "-jar", Node.nodeJarName, "--just-generate-node-info") .directory(fullNodePath(node).toFile()) .redirectErrorStream(true) // InheritIO causes hangs on windows due the gradle buffer also not being flushed. From 3bcf526f470fa59bcf71c9fc2f4a70ea487afcd3 Mon Sep 17 00:00:00 2001 From: Mat Rizzo Date: Wed, 25 Oct 2017 16:30:21 +0100 Subject: [PATCH 61/94] CORDA-742 Test capsule cache directory --- .../cordformation/src/main/kotlin/net/corda/plugins/Cordform.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordform.kt b/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordform.kt index 7e2bb261f9..197ed1f609 100644 --- a/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordform.kt +++ b/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordform.kt @@ -176,7 +176,7 @@ open class Cordform : DefaultTask() { } private fun buildNodeProcess(node: Node, logDir: File): Process { - return ProcessBuilder("java", "-Dcapsule.log=verbose", "-DCAPSULE_CACHE_DIR=./cache", "-jar", Node.nodeJarName, "--just-generate-node-info") + return ProcessBuilder("java", "-Dcapsule.log=verbose", "-DCAPSULE_CACHE_DIR=`pwd`/cache", "-jar", Node.nodeJarName, "--just-generate-node-info") .directory(fullNodePath(node).toFile()) .redirectErrorStream(true) // InheritIO causes hangs on windows due the gradle buffer also not being flushed. From e10ed1bdcfd31d8471cc8b2af782fa21dd009ce5 Mon Sep 17 00:00:00 2001 From: Mat Rizzo Date: Wed, 25 Oct 2017 16:42:43 +0100 Subject: [PATCH 62/94] CORDA-742 Test capsule cache directory --- .../cordformation/src/main/kotlin/net/corda/plugins/Cordform.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordform.kt b/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordform.kt index 197ed1f609..d46356bb5e 100644 --- a/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordform.kt +++ b/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordform.kt @@ -176,7 +176,7 @@ open class Cordform : DefaultTask() { } private fun buildNodeProcess(node: Node, logDir: File): Process { - return ProcessBuilder("java", "-Dcapsule.log=verbose", "-DCAPSULE_CACHE_DIR=`pwd`/cache", "-jar", Node.nodeJarName, "--just-generate-node-info") + return ProcessBuilder("java", "-Dcapsule.log=verbose", "-Dcapsule.dir=./cache", "-jar", Node.nodeJarName, "--just-generate-node-info") .directory(fullNodePath(node).toFile()) .redirectErrorStream(true) // InheritIO causes hangs on windows due the gradle buffer also not being flushed. From 127c8629820372c6cbc2f6b5f6937a5e01dcb43b Mon Sep 17 00:00:00 2001 From: Mat Rizzo Date: Wed, 25 Oct 2017 17:19:06 +0100 Subject: [PATCH 63/94] CORDA-742 Test capsule cache directory --- .../src/main/kotlin/net/corda/plugins/Cordform.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordform.kt b/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordform.kt index d46356bb5e..ac0431a551 100644 --- a/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordform.kt +++ b/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordform.kt @@ -176,7 +176,9 @@ open class Cordform : DefaultTask() { } private fun buildNodeProcess(node: Node, logDir: File): Process { - return ProcessBuilder("java", "-Dcapsule.log=verbose", "-Dcapsule.dir=./cache", "-jar", Node.nodeJarName, "--just-generate-node-info") + val command = listOf("java", "-Dcapsule.log=verbose", "-Dcapsule.dir=./cache", "-jar", Node.nodeJarName, "--just-generate-node-info") + println(command.joinToString(" ")) + return ProcessBuilder(command) .directory(fullNodePath(node).toFile()) .redirectErrorStream(true) // InheritIO causes hangs on windows due the gradle buffer also not being flushed. From f5a8e11411fe6d0af78ce21e04fc78579e33b776 Mon Sep 17 00:00:00 2001 From: Mat Rizzo Date: Thu, 26 Oct 2017 10:05:36 +0100 Subject: [PATCH 64/94] CORDA-742 Test capsule cache directory --- .../main/kotlin/net/corda/plugins/Cordform.kt | 27 +++++++++---------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordform.kt b/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordform.kt index ac0431a551..fa79a40755 100644 --- a/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordform.kt +++ b/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordform.kt @@ -152,14 +152,12 @@ open class Cordform : DefaultTask() { private fun generateNodeInfos() { project.logger.info("Generating node infos") val generateTimeoutSeconds = 60L - val processes = nodes.map { node -> + for (node in nodes) { project.logger.info("Generating node info for ${fullNodePath(node)}") - val logDir = File(fullNodePath(node).toFile(), "logs") + var logDir = File(fullNodePath(node).toFile(), "logs") logDir.mkdirs() - Pair(node, buildNodeProcess(node, logDir)) - } - try { - processes.forEach { (node, process) -> + var process = buildNodeProcess(node, logDir) + try { if (!process.waitFor(generateTimeoutSeconds, TimeUnit.SECONDS)) { throw GradleException("Node took longer $generateTimeoutSeconds seconds than too to generate node info - see node log at ${fullNodePath(node)}/logs") } @@ -167,24 +165,25 @@ open class Cordform : DefaultTask() { throw GradleException("Node exited with ${process.exitValue()} when generating node infos - see node log at ${fullNodePath(node)}/logs") } } - } finally { - // This will be a no-op on success - abort remaining on failure - processes.forEach { - it.second.destroyForcibly() + finally { + if (process.isAlive()) { + process.destroyForcibly() + } } } } private fun buildNodeProcess(node: Node, logDir: File): Process { - val command = listOf("java", "-Dcapsule.log=verbose", "-Dcapsule.dir=./cache", "-jar", Node.nodeJarName, "--just-generate-node-info") - println(command.joinToString(" ")) - return ProcessBuilder(command) + val javaPathString = Paths.get(System.getProperty("java.home"), "bin", "java").toString() + val command = listOf(javaPathString, "-Dcapsule.log=verbose", "-Dcapsule.dir=./cache", "-jar", Node.nodeJarName, "--just-generate-node-info") + var pb = ProcessBuilder(command) .directory(fullNodePath(node).toFile()) .redirectErrorStream(true) // InheritIO causes hangs on windows due the gradle buffer also not being flushed. // Must redirect to output or logger (node log is still written, this is just startup banner) .redirectOutput(File(logDir, "generate-info.log")) - .start() + pb.environment().put("CAPSULE_CACHE_DIR", "./cache") + return pb.start() } private fun installNodeInfos() { From ac1dfec1ffc961c17c1cad06ae80c89803baa331 Mon Sep 17 00:00:00 2001 From: Mat Rizzo Date: Thu, 26 Oct 2017 11:16:16 +0100 Subject: [PATCH 65/94] CORDA-742 Refactored back to more parallel execution --- .../main/kotlin/net/corda/plugins/Cordform.kt | 58 ++++++++++--------- 1 file changed, 32 insertions(+), 26 deletions(-) diff --git a/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordform.kt b/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordform.kt index fa79a40755..ec9e07f22b 100644 --- a/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordform.kt +++ b/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordform.kt @@ -148,42 +148,48 @@ open class Cordform : DefaultTask() { installNodeInfos() } - private fun generateNodeInfos() { project.logger.info("Generating node infos") - val generateTimeoutSeconds = 60L - for (node in nodes) { - project.logger.info("Generating node info for ${fullNodePath(node)}") - var logDir = File(fullNodePath(node).toFile(), "logs") - logDir.mkdirs() - var process = buildNodeProcess(node, logDir) - try { - if (!process.waitFor(generateTimeoutSeconds, TimeUnit.SECONDS)) { - throw GradleException("Node took longer $generateTimeoutSeconds seconds than too to generate node info - see node log at ${fullNodePath(node)}/logs") - } - if (process.exitValue() != 0) { - throw GradleException("Node exited with ${process.exitValue()} when generating node infos - see node log at ${fullNodePath(node)}/logs") - } - } - finally { - if (process.isAlive()) { - process.destroyForcibly() - } - } + var nodeProcesses = buildNodeProcesses() + try { + nodeProcesses.forEach { (node, process) -> validateNodeProcess(node, process) } + } + finally { + nodeProcesses.forEach { it.second.destroyForcibly() } } } - private fun buildNodeProcess(node: Node, logDir: File): Process { - val javaPathString = Paths.get(System.getProperty("java.home"), "bin", "java").toString() - val command = listOf(javaPathString, "-Dcapsule.log=verbose", "-Dcapsule.dir=./cache", "-jar", Node.nodeJarName, "--just-generate-node-info") - var pb = ProcessBuilder(command) + private fun buildNodeProcesses(): List> { + return nodes.map { buildNodeProcess(it, makeLogDirectory(it)) } + } + + private fun makeLogDirectory(node: Node): File { + var logDir = File(fullNodePath(node).toFile(), "logs") + logDir.mkdirs() + return logDir + } + + private fun buildNodeProcess(node: Node, logDir: File): Pair { + val command = listOf("java", "-Dcapsule.log=verbose", "-Dcapsule.dir=./cache", "-jar", Node.nodeJarName, "--just-generate-node-info") + var builder = ProcessBuilder(command) .directory(fullNodePath(node).toFile()) .redirectErrorStream(true) // InheritIO causes hangs on windows due the gradle buffer also not being flushed. // Must redirect to output or logger (node log is still written, this is just startup banner) .redirectOutput(File(logDir, "generate-info.log")) - pb.environment().put("CAPSULE_CACHE_DIR", "./cache") - return pb.start() + builder.environment().put("CAPSULE_CACHE_DIR", "./cache") + return Pair(node, builder.start()) + } + + private fun validateNodeProcess(node: Node, process: Process) { + val generateTimeoutSeconds = 60L + if (!process.waitFor(generateTimeoutSeconds, TimeUnit.SECONDS)) { + throw GradleException("Node took longer $generateTimeoutSeconds seconds than too to generate node info - see node log at ${fullNodePath(node)}/logs") + } + if (process.exitValue() != 0) { + throw GradleException("Node exited with ${process.exitValue()} when generating node infos - see node log at ${fullNodePath(node)}/logs") + } + project.logger.info("Generated node info for ${fullNodePath(node)}") } private fun installNodeInfos() { From 788d8ef65f2fa169fa04de2ff0ab19f8182c7b14 Mon Sep 17 00:00:00 2001 From: Mat Rizzo Date: Thu, 26 Oct 2017 11:48:15 +0100 Subject: [PATCH 66/94] CORDA-742 Minor refactor --- .../src/main/kotlin/net/corda/plugins/Cordform.kt | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordform.kt b/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordform.kt index ec9e07f22b..cb358043d8 100644 --- a/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordform.kt +++ b/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordform.kt @@ -152,10 +152,10 @@ open class Cordform : DefaultTask() { project.logger.info("Generating node infos") var nodeProcesses = buildNodeProcesses() try { - nodeProcesses.forEach { (node, process) -> validateNodeProcess(node, process) } + validateNodeProcessess(nodeProcesses) } finally { - nodeProcesses.forEach { it.second.destroyForcibly() } + destroyNodeProcesses(nodeProcesses) } } @@ -163,6 +163,14 @@ open class Cordform : DefaultTask() { return nodes.map { buildNodeProcess(it, makeLogDirectory(it)) } } + private fun validateNodeProcessess(processes: List>) { + nodeProcesses.forEach { (node, process) -> validateNodeProcess(node, process) } + } + + private fun destroyNodeProcesses(processes: List>) { + nodeProcesses.forEach { it.second.destroyForcibly() } + } + private fun makeLogDirectory(node: Node): File { var logDir = File(fullNodePath(node).toFile(), "logs") logDir.mkdirs() From 8021cdd364ab5b41ba4a29abd915fd30ab770d32 Mon Sep 17 00:00:00 2001 From: Mat Rizzo Date: Thu, 26 Oct 2017 12:21:39 +0100 Subject: [PATCH 67/94] CORDA-742 Refactors --- .../main/kotlin/net/corda/plugins/Cordform.kt | 70 +++++++++++-------- 1 file changed, 42 insertions(+), 28 deletions(-) diff --git a/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordform.kt b/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordform.kt index cb358043d8..5fcf310f56 100644 --- a/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordform.kt +++ b/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordform.kt @@ -11,6 +11,7 @@ import org.gradle.api.tasks.SourceSet.MAIN_SOURCE_SET_NAME import org.gradle.api.tasks.TaskAction import java.io.File import java.net.URLClassLoader +import java.nio.file.Files import java.nio.file.Path import java.nio.file.Paths import java.util.concurrent.TimeUnit @@ -141,8 +142,6 @@ open class Cordform : DefaultTask() { } } - private fun fullNodePath(node: Node): Path = project.projectDir.toPath().resolve(node.nodeDir.toPath()) - private fun generateAndInstallNodeInfos() { generateNodeInfos() installNodeInfos() @@ -153,51 +152,60 @@ open class Cordform : DefaultTask() { var nodeProcesses = buildNodeProcesses() try { validateNodeProcessess(nodeProcesses) - } - finally { + } finally { destroyNodeProcesses(nodeProcesses) } } - private fun buildNodeProcesses(): List> { - return nodes.map { buildNodeProcess(it, makeLogDirectory(it)) } + private fun buildNodeProcesses(): Map { + return nodes + .map { buildNodeProcess(it) } + .toMap() } - private fun validateNodeProcessess(processes: List>) { - nodeProcesses.forEach { (node, process) -> validateNodeProcess(node, process) } + private fun validateNodeProcessess(nodeProcesses: Map) { + nodeProcesses.forEach { (node, process) -> + validateNodeProcess(node, process) + } } - private fun destroyNodeProcesses(processes: List>) { - nodeProcesses.forEach { it.second.destroyForcibly() } + private fun destroyNodeProcesses(nodeProcesses: Map) { + nodeProcesses.forEach { (_, process) -> + process.destroyForcibly() + } } - private fun makeLogDirectory(node: Node): File { - var logDir = File(fullNodePath(node).toFile(), "logs") - logDir.mkdirs() - return logDir - } - - private fun buildNodeProcess(node: Node, logDir: File): Pair { - val command = listOf("java", "-Dcapsule.log=verbose", "-Dcapsule.dir=./cache", "-jar", Node.nodeJarName, "--just-generate-node-info") - var builder = ProcessBuilder(command) - .directory(fullNodePath(node).toFile()) + private fun buildNodeProcess(node: Node): Pair { + node.makeLogDirectory() + var process = ProcessBuilder(generateNodeInfoCommand()) + .directory(node.fullPath().toFile()) .redirectErrorStream(true) // InheritIO causes hangs on windows due the gradle buffer also not being flushed. // Must redirect to output or logger (node log is still written, this is just startup banner) - .redirectOutput(File(logDir, "generate-info.log")) - builder.environment().put("CAPSULE_CACHE_DIR", "./cache") - return Pair(node, builder.start()) + .redirectOutput(node.logFile().toFile()) + .addEnvironment("CAPSULE_CACHE_DIR", "./cache") + .start() + return Pair(node, process) } + private fun generateNodeInfoCommand(): List = listOf( + "java", + "-Dcapsule.log=verbose", + "-Dcapsule.dir=./cache", + "-jar", + Node.nodeJarName, + "--just-generate-node-info" + ) + private fun validateNodeProcess(node: Node, process: Process) { val generateTimeoutSeconds = 60L if (!process.waitFor(generateTimeoutSeconds, TimeUnit.SECONDS)) { - throw GradleException("Node took longer $generateTimeoutSeconds seconds than too to generate node info - see node log at ${fullNodePath(node)}/logs") + throw GradleException("Node took longer $generateTimeoutSeconds seconds than too to generate node info - see node log at ${node.fullPath()}/logs") } if (process.exitValue() != 0) { - throw GradleException("Node exited with ${process.exitValue()} when generating node infos - see node log at ${fullNodePath(node)}/logs") + throw GradleException("Node exited with ${process.exitValue()} when generating node infos - see node log at ${node.fullPath()}/logs") } - project.logger.info("Generated node info for ${fullNodePath(node)}") + project.logger.info("Generated node info for ${node.fullPath()}") } private fun installNodeInfos() { @@ -207,13 +215,19 @@ open class Cordform : DefaultTask() { if (source.nodeDir != destination.nodeDir) { project.copy { it.apply { - from(fullNodePath(source).toString()) + from(source.fullPath().toString()) include("nodeInfo-*") - into(fullNodePath(destination).resolve(CordformNode.NODE_INFO_DIRECTORY).toString()) + into(destination.fullPath().resolve(CordformNode.NODE_INFO_DIRECTORY).toString()) } } } } } } + + private fun Node.fullPath(): Path = project.projectDir.toPath().resolve(this.nodeDir.toPath()) + private fun Node.logDirectory(): Path = this.fullPath().resolve("logs") + private fun Node.makeLogDirectory() = Files.createDirectories(this.logDirectory()) + private fun Node.logFile(): Path = this.logDirectory().resolve("generate-info.log") + private fun ProcessBuilder.addEnvironment(key: String, value: String) = this.apply { environment().put(key, value) } } From a3c553d44aa2c18ffa6c751b58e381df52cf938f Mon Sep 17 00:00:00 2001 From: Mat Rizzo Date: Thu, 26 Oct 2017 15:38:02 +0100 Subject: [PATCH 68/94] CORDA-742 Added constant for cache directory. --- .../src/main/kotlin/net/corda/plugins/Cordform.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordform.kt b/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordform.kt index 5fcf310f56..0410721a3b 100644 --- a/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordform.kt +++ b/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordform.kt @@ -183,7 +183,7 @@ open class Cordform : DefaultTask() { // InheritIO causes hangs on windows due the gradle buffer also not being flushed. // Must redirect to output or logger (node log is still written, this is just startup banner) .redirectOutput(node.logFile().toFile()) - .addEnvironment("CAPSULE_CACHE_DIR", "./cache") + .addEnvironment("CAPSULE_CACHE_DIR", capsuleCacheDir) .start() return Pair(node, process) } @@ -191,7 +191,7 @@ open class Cordform : DefaultTask() { private fun generateNodeInfoCommand(): List = listOf( "java", "-Dcapsule.log=verbose", - "-Dcapsule.dir=./cache", + "-Dcapsule.dir=$capsuleCacheDir", "-jar", Node.nodeJarName, "--just-generate-node-info" @@ -225,6 +225,7 @@ open class Cordform : DefaultTask() { } } + private const val capsuleCacheDir: String = "./cache" private fun Node.fullPath(): Path = project.projectDir.toPath().resolve(this.nodeDir.toPath()) private fun Node.logDirectory(): Path = this.fullPath().resolve("logs") private fun Node.makeLogDirectory() = Files.createDirectories(this.logDirectory()) From 92bbce711b31af5cb3eff32cde41547b3493c87d Mon Sep 17 00:00:00 2001 From: Mat Rizzo Date: Thu, 26 Oct 2017 16:29:45 +0100 Subject: [PATCH 69/94] CORDA-742 PR #1951 Moved extension methods into node. --- .../src/main/kotlin/net/corda/plugins/Cordform.kt | 9 ++------- .../src/main/kotlin/net/corda/plugins/Node.kt | 6 ++++++ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordform.kt b/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordform.kt index 0410721a3b..2b208a2ce9 100644 --- a/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordform.kt +++ b/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordform.kt @@ -183,7 +183,7 @@ open class Cordform : DefaultTask() { // InheritIO causes hangs on windows due the gradle buffer also not being flushed. // Must redirect to output or logger (node log is still written, this is just startup banner) .redirectOutput(node.logFile().toFile()) - .addEnvironment("CAPSULE_CACHE_DIR", capsuleCacheDir) + .addEnvironment("CAPSULE_CACHE_DIR", Node.capsuleCacheDir) .start() return Pair(node, process) } @@ -191,7 +191,7 @@ open class Cordform : DefaultTask() { private fun generateNodeInfoCommand(): List = listOf( "java", "-Dcapsule.log=verbose", - "-Dcapsule.dir=$capsuleCacheDir", + "-Dcapsule.dir=${Node.capsuleCacheDir}", "-jar", Node.nodeJarName, "--just-generate-node-info" @@ -225,10 +225,5 @@ open class Cordform : DefaultTask() { } } - private const val capsuleCacheDir: String = "./cache" - private fun Node.fullPath(): Path = project.projectDir.toPath().resolve(this.nodeDir.toPath()) - private fun Node.logDirectory(): Path = this.fullPath().resolve("logs") - private fun Node.makeLogDirectory() = Files.createDirectories(this.logDirectory()) - private fun Node.logFile(): Path = this.logDirectory().resolve("generate-info.log") private fun ProcessBuilder.addEnvironment(key: String, value: String) = this.apply { environment().put(key, value) } } diff --git a/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Node.kt b/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Node.kt index 322a1febcc..3c97fb140f 100644 --- a/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Node.kt +++ b/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Node.kt @@ -21,8 +21,14 @@ class Node(private val project: Project) : CordformNode() { @JvmStatic val webJarName = "corda-webserver.jar" private val configFileProperty = "configFile" + val capsuleCacheDir: String = "./cache" } + fun fullPath(): Path = project.projectDir.toPath().resolve(this.nodeDir.toPath()) + fun logDirectory(): Path = this.fullPath().resolve("logs") + fun makeLogDirectory() = Files.createDirectories(this.logDirectory()) + fun logFile(): Path = this.logDirectory().resolve("generate-info.log") + /** * Set the list of CorDapps to install to the plugins directory. Each cordapp is a fully qualified Maven * dependency name, eg: com.example:product-name:0.1 From ee3106fd5a82227d799bc70723ed8abb40a523c7 Mon Sep 17 00:00:00 2001 From: Mat Rizzo Date: Thu, 26 Oct 2017 16:49:15 +0100 Subject: [PATCH 70/94] CORDA-742 PR #1951 Moved extension methods back. --- .../src/main/kotlin/net/corda/plugins/Cordform.kt | 2 +- .../src/main/kotlin/net/corda/plugins/Node.kt | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordform.kt b/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordform.kt index 2b208a2ce9..48642e4312 100644 --- a/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordform.kt +++ b/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordform.kt @@ -224,6 +224,6 @@ open class Cordform : DefaultTask() { } } } - + private fun logFile(): Path = this.logDirectory().resolve("generate-info.log") private fun ProcessBuilder.addEnvironment(key: String, value: String) = this.apply { environment().put(key, value) } } diff --git a/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Node.kt b/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Node.kt index 3c97fb140f..ef98848e18 100644 --- a/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Node.kt +++ b/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Node.kt @@ -24,10 +24,9 @@ class Node(private val project: Project) : CordformNode() { val capsuleCacheDir: String = "./cache" } - fun fullPath(): Path = project.projectDir.toPath().resolve(this.nodeDir.toPath()) - fun logDirectory(): Path = this.fullPath().resolve("logs") - fun makeLogDirectory() = Files.createDirectories(this.logDirectory()) - fun logFile(): Path = this.logDirectory().resolve("generate-info.log") + fun fullPath(): Path = project.projectDir.toPath().resolve(nodeDir.toPath()) + fun logDirectory(): Path = fullPath().resolve("logs") + fun makeLogDirectory() = Files.createDirectories(logDirectory()) /** * Set the list of CorDapps to install to the plugins directory. Each cordapp is a fully qualified Maven From cc0380470181a6c9e19e81ea5bbd843d9c6bc72e Mon Sep 17 00:00:00 2001 From: Mat Rizzo Date: Thu, 26 Oct 2017 16:55:25 +0100 Subject: [PATCH 71/94] CORDA-742 PR #1951 Fixed compilation error --- .../cordformation/src/main/kotlin/net/corda/plugins/Cordform.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordform.kt b/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordform.kt index 48642e4312..057b1861b5 100644 --- a/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordform.kt +++ b/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordform.kt @@ -224,6 +224,6 @@ open class Cordform : DefaultTask() { } } } - private fun logFile(): Path = this.logDirectory().resolve("generate-info.log") + private fun Node.logFile(): Path = this.logDirectory().resolve("generate-info.log") private fun ProcessBuilder.addEnvironment(key: String, value: String) = this.apply { environment().put(key, value) } } From 01f80fb187f682e3e8c81b1dc1cfa01fa2b4f04b Mon Sep 17 00:00:00 2001 From: Alberto Arri <30873160+al-r3@users.noreply.github.com> Date: Fri, 27 Oct 2017 09:27:23 +0100 Subject: [PATCH 72/94] remove outdated test (#1949) --- .../node/services/statemachine/FlowFrameworkTests.kt | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/node/src/test/kotlin/net/corda/node/services/statemachine/FlowFrameworkTests.kt b/node/src/test/kotlin/net/corda/node/services/statemachine/FlowFrameworkTests.kt index 044c953422..6d478a8f03 100644 --- a/node/src/test/kotlin/net/corda/node/services/statemachine/FlowFrameworkTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/statemachine/FlowFrameworkTests.kt @@ -145,17 +145,7 @@ class FlowFrameworkTests { val restoredFlow = bobNode.restartAndGetRestoredFlow() assertThat(restoredFlow.receivedPayloads[0]).isEqualTo("Hello") } - - @Test - fun `flow added before network map does run after init`() { - val charlieNode = mockNet.createNode() //create vanilla node - val flow = NoOpFlow() - charlieNode.services.startFlow(flow) - assertEquals(false, flow.flowStarted) // Not started yet as no network activity has been allowed yet - mockNet.runNetwork() // Allow network map messages to flow - assertEquals(true, flow.flowStarted) // Now we should have run the flow - } - + @Test fun `flow loaded from checkpoint will respond to messages from before start`() { aliceNode.registerFlowFactory(ReceiveFlow::class) { InitiatedSendFlow("Hello", it) } From 79f080368b963f029629ffa90e62c97d847e587d Mon Sep 17 00:00:00 2001 From: Christian Sailer Date: Fri, 27 Oct 2017 09:37:39 +0100 Subject: [PATCH 73/94] Code rewiew: constant naming --- .../kotlin/net/corda/node/services/config/NodeConfiguration.kt | 2 +- .../corda/node/services/statemachine/StateMachineManagerImpl.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt b/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt index 2fcba96f70..cce2e7d6d2 100644 --- a/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt +++ b/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt @@ -38,7 +38,7 @@ interface NodeConfiguration : NodeSSLConfiguration { val additionalNodeInfoPollingFrequencyMsec: Long companion object { - val disableCheckpointCheckerFlag = "disableCheckpointChecker" + const val DISABLE_CHECKPOINT_CHECKER = "disableCheckpointChecker" } } diff --git a/node/src/main/kotlin/net/corda/node/services/statemachine/StateMachineManagerImpl.kt b/node/src/main/kotlin/net/corda/node/services/statemachine/StateMachineManagerImpl.kt index ed559f3411..3e05a45e1f 100644 --- a/node/src/main/kotlin/net/corda/node/services/statemachine/StateMachineManagerImpl.kt +++ b/node/src/main/kotlin/net/corda/node/services/statemachine/StateMachineManagerImpl.kt @@ -91,7 +91,7 @@ class StateMachineManagerImpl( private val mutex = ThreadBox(InnerState()) // This thread (only enabled in dev mode) deserialises checkpoints in the background to shake out bugs in checkpoint restore. private val checkpointCheckerThread = if (serviceHub.configuration.devMode - && !serviceHub.configuration.isDevModeOptionsFlagSet(NodeConfiguration.disableCheckpointCheckerFlag)) + && !serviceHub.configuration.isDevModeOptionsFlagSet(NodeConfiguration.DISABLE_CHECKPOINT_CHECKER)) newNamedSingleThreadExecutor("CheckpointChecker") else null @Volatile private var unrestorableCheckpoints = false From 4a4500811fc569e4744b3fc2a42141117295df5a Mon Sep 17 00:00:00 2001 From: Christian Sailer Date: Fri, 27 Oct 2017 10:24:03 +0100 Subject: [PATCH 74/94] Code rewiew: reformat --- .../net/corda/node/services/config/NodeConfiguration.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt b/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt index cce2e7d6d2..a47a9bc1d5 100644 --- a/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt +++ b/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt @@ -42,7 +42,7 @@ interface NodeConfiguration : NodeSSLConfiguration { } } -fun NodeConfiguration.isDevModeOptionsFlagSet(flag: String):Boolean{ +fun NodeConfiguration.isDevModeOptionsFlagSet(flag: String): Boolean { return this.devModeOptions?.get(flag).toString().toLowerCase() == "true" } @@ -113,7 +113,7 @@ data class FullNodeConfiguration( init { // This is a sanity feature do not remove. require(!useTestClock || devMode) { "Cannot use test clock outside of dev mode" } - require(devModeOptions == null || devMode){ "Cannot use devModeOptions outside of dev mode" } + require(devModeOptions == null || devMode) { "Cannot use devModeOptions outside of dev mode" } // TODO Move this to ArtemisMessagingServer rpcUsers.forEach { require(it.username.matches("\\w+".toRegex())) { "Username ${it.username} contains invalid characters" } From 3633624dc63ab1fa26bdba876ace1dd79bbd2a10 Mon Sep 17 00:00:00 2001 From: Katelyn Baker Date: Tue, 10 Oct 2017 23:09:25 +0100 Subject: [PATCH 75/94] CORDA-553 - First steps towards evolvability Define the two transforms that will be useful for enum evolvability (see design document for more details). Furthermore, define the generic mechanism by which transform annotations on classes are encoded into the AMQP envelope With nothing to check for these annotations at either end, this is mostly a no op, but an important step toward getting evolvability in place --- .gitignore | 1 + .../CordaSerializationTransformEnumDefault.kt | 18 + .../CordaSerializationTransformRename.kt | 17 + .../amqp/AMQPDescriptorRegistry.kt | 31 ++ .../internal/serialization/amqp/Envelope.kt | 60 +++ .../internal/serialization/amqp/Schema.kt | 73 +--- .../serialization/amqp/SerializationOutput.kt | 10 +- .../serialization/amqp/SupportedTransforms.kt | 66 +++ .../serialization/amqp/TansformTypes.kt | 55 +++ .../serialization/amqp/TransformsSchema.kt | 268 ++++++++++++ .../serialization/amqp/AMQPTestUtils.kt | 19 +- .../amqp/EnumEvolvabilityTests.kt | 380 ++++++++++++++++++ .../amqp/SerializationOutputTests.kt | 2 + 13 files changed, 928 insertions(+), 72 deletions(-) create mode 100644 core/src/main/kotlin/net/corda/core/serialization/CordaSerializationTransformEnumDefault.kt create mode 100644 core/src/main/kotlin/net/corda/core/serialization/CordaSerializationTransformRename.kt create mode 100644 node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/AMQPDescriptorRegistry.kt create mode 100644 node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/Envelope.kt create mode 100644 node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SupportedTransforms.kt create mode 100644 node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/TansformTypes.kt create mode 100644 node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/TransformsSchema.kt create mode 100644 node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/EnumEvolvabilityTests.kt diff --git a/.gitignore b/.gitignore index 1eb2b271b4..2bc98f9dfd 100644 --- a/.gitignore +++ b/.gitignore @@ -36,6 +36,7 @@ lib/quasar.jar .idea/dataSources .idea/markdown-navigator .idea/runConfigurations +.idea/dictionaries /gradle-plugins/.idea/ # Include the -parameters compiler option by default in IntelliJ required for serialization. diff --git a/core/src/main/kotlin/net/corda/core/serialization/CordaSerializationTransformEnumDefault.kt b/core/src/main/kotlin/net/corda/core/serialization/CordaSerializationTransformEnumDefault.kt new file mode 100644 index 0000000000..8cce317ed4 --- /dev/null +++ b/core/src/main/kotlin/net/corda/core/serialization/CordaSerializationTransformEnumDefault.kt @@ -0,0 +1,18 @@ +package net.corda.core.serialization + +/** + * + */ +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +annotation class CordaSerializationTransformEnumDefaults(vararg val value: CordaSerializationTransformEnumDefault) + +/** + * + */ +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +// When Kotlin starts writing 1.8 class files enable this, it removes the need for the wrapping annotation +//@Repeatable +annotation class CordaSerializationTransformEnumDefault(val new: String, val old: String) + diff --git a/core/src/main/kotlin/net/corda/core/serialization/CordaSerializationTransformRename.kt b/core/src/main/kotlin/net/corda/core/serialization/CordaSerializationTransformRename.kt new file mode 100644 index 0000000000..379505bf1b --- /dev/null +++ b/core/src/main/kotlin/net/corda/core/serialization/CordaSerializationTransformRename.kt @@ -0,0 +1,17 @@ +package net.corda.core.serialization + +/** + * + */ +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +annotation class CordaSerializationTransformRenames(vararg val value: CordaSerializationTransformRename) + +/** + * + */ +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +// When Kotlin starts writing 1.8 class files enable this, it removes the need for the wrapping annotation +//@Repeatable +annotation class CordaSerializationTransformRename(val to: String, val from: String) diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/AMQPDescriptorRegistry.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/AMQPDescriptorRegistry.kt new file mode 100644 index 0000000000..4ffeb57764 --- /dev/null +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/AMQPDescriptorRegistry.kt @@ -0,0 +1,31 @@ +package net.corda.nodeapi.internal.serialization.amqp + +import org.apache.qpid.proton.amqp.UnsignedLong + +/** + * R3 AMQP assigned enterprise number + * + * see [here](https://www.iana.org/assignments/enterprise-numbers/enterprise-numbers) + * + * Repeated here for brevity: + * 50530 - R3 - Mike Hearn - mike&r3.com + */ +const val DESCRIPTOR_TOP_32BITS: Long = 0xc5620000 + +enum class AMQPDescriptorRegistry(val id: Long) { + + ENVELOPE(1), + SCHEMA(2), + OBJECT_DESCRIPTOR(3), + FIELD(4), + COMPOSITE_TYPE(5), + RESTRICTED_TYPE(6), + CHOICE(7), + REFERENCED_OBJECT(8), + TRANSFORM_SCHEMA(9), + TRANSFORM_ELEMENT(10), + TRANSFORM_ELEMENT_KEY(11) + ; + + val amqpDescriptor = UnsignedLong(id or DESCRIPTOR_TOP_32BITS) +} diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/Envelope.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/Envelope.kt new file mode 100644 index 0000000000..d2f73e0575 --- /dev/null +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/Envelope.kt @@ -0,0 +1,60 @@ +package net.corda.nodeapi.internal.serialization.amqp + +import org.apache.qpid.proton.amqp.DescribedType +import org.apache.qpid.proton.codec.Data +import org.apache.qpid.proton.codec.DescribedTypeConstructor + +import java.io.NotSerializableException + +/** + * This class wraps all serialized data, so that the schema can be carried along with it. We will provide various + * internal utilities to decompose and recompose with/without schema etc so that e.g. we can store objects with a + * (relationally) normalised out schema to avoid excessive duplication. + */ +// TODO: make the schema parsing lazy since mostly schemas will have been seen before and we only need it if we +// TODO: don't recognise a type descriptor. +data class Envelope(val obj: Any?, val schema: Schema, val transformsSchema: TransformsSchema) : DescribedType { + companion object : DescribedTypeConstructor { + val DESCRIPTOR = AMQPDescriptorRegistry.ENVELOPE.amqpDescriptor + val DESCRIPTOR_OBJECT = Descriptor(null, DESCRIPTOR) + + fun get(data: Data): Envelope { + val describedType = data.`object` as DescribedType + if (describedType.descriptor != DESCRIPTOR) { + throw NotSerializableException("Unexpected descriptor ${describedType.descriptor}.") + } + val list = describedType.described as List<*> + + // We need to cope with objects serialised without the transforms header element in the + // envelope + val transformSchema : Any? = when (list.size) { + 2 -> null + 3 -> list[2] + else -> throw NotSerializableException("Malformed list, bad length of ${list.size} (should be 2 or 3)") + } + + return newInstance(listOf(list[0], Schema.get(list[1]!!), TransformsSchema.newInstance(transformSchema))) + } + + // This seperation of functions is needed as this will be the entry point for the default + // AMQP decoder if one is used (see the unit tests) + override fun newInstance(described: Any?): Envelope { + val list = described as? List<*> ?: throw IllegalStateException("Was expecting a list") + + // We need to cope with objects serialised without the transforms header element in the + // envelope + val transformSchema = when (list.size) { + 2 -> TransformsSchema.newInstance(null) + 3 -> list[2] as TransformsSchema + else -> throw NotSerializableException("Malformed list, bad length of ${list.size} (should be 2 or 3)") + } + return Envelope(list[0], list[1] as Schema, transformSchema) + } + + override fun getTypeClass(): Class<*> = Envelope::class.java + } + + override fun getDescriptor(): Any = DESCRIPTOR + + override fun getDescribed(): Any = listOf(obj, schema, transformsSchema) +} diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/Schema.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/Schema.kt index cfb505ab6c..0d20ca0822 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/Schema.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/Schema.kt @@ -10,7 +10,6 @@ import org.apache.qpid.proton.amqp.DescribedType import org.apache.qpid.proton.amqp.Symbol import org.apache.qpid.proton.amqp.UnsignedInteger import org.apache.qpid.proton.amqp.UnsignedLong -import org.apache.qpid.proton.codec.Data import org.apache.qpid.proton.codec.DescribedTypeConstructor import java.io.NotSerializableException import java.lang.reflect.* @@ -18,68 +17,10 @@ import java.util.* import net.corda.nodeapi.internal.serialization.carpenter.Field as CarpenterField import net.corda.nodeapi.internal.serialization.carpenter.Schema as CarpenterSchema -/** - * R3 AMQP assigned enterprise number - * - * see [here](https://www.iana.org/assignments/enterprise-numbers/enterprise-numbers) - * - * Repeated here for brevity: - * 50530 - R3 - Mike Hearn - mike&r3.com - */ -const val DESCRIPTOR_TOP_32BITS: Long = 0xc5620000 - -const val DESCRIPTOR_DOMAIN: String = "net.corda" // "corda" + majorVersionByte + minorVersionMSB + minorVersionLSB val AmqpHeaderV1_0: OpaqueBytes = OpaqueBytes("corda\u0001\u0000\u0000".toByteArray()) -private enum class DescriptorRegistry(val id: Long) { - - ENVELOPE(1), - SCHEMA(2), - OBJECT_DESCRIPTOR(3), - FIELD(4), - COMPOSITE_TYPE(5), - RESTRICTED_TYPE(6), - CHOICE(7), - REFERENCED_OBJECT(8), - ; - - val amqpDescriptor = UnsignedLong(id or DESCRIPTOR_TOP_32BITS) -} - -/** - * This class wraps all serialized data, so that the schema can be carried along with it. We will provide various internal utilities - * to decompose and recompose with/without schema etc so that e.g. we can store objects with a (relationally) normalised out schema to - * avoid excessive duplication. - */ -// TODO: make the schema parsing lazy since mostly schemas will have been seen before and we only need it if we don't recognise a type descriptor. -data class Envelope(val obj: Any?, val schema: Schema) : DescribedType { - companion object : DescribedTypeConstructor { - val DESCRIPTOR = DescriptorRegistry.ENVELOPE.amqpDescriptor - val DESCRIPTOR_OBJECT = Descriptor(null, DESCRIPTOR) - - fun get(data: Data): Envelope { - val describedType = data.`object` as DescribedType - if (describedType.descriptor != DESCRIPTOR) { - throw NotSerializableException("Unexpected descriptor ${describedType.descriptor}.") - } - val list = describedType.described as List<*> - return newInstance(listOf(list[0], Schema.get(list[1]!!))) - } - - override fun getTypeClass(): Class<*> = Envelope::class.java - - override fun newInstance(described: Any?): Envelope { - val list = described as? List<*> ?: throw IllegalStateException("Was expecting a list") - return Envelope(list[0], list[1] as Schema) - } - } - - override fun getDescriptor(): Any = DESCRIPTOR - - override fun getDescribed(): Any = listOf(obj, schema) -} /** * This and the classes below are OO representations of the AMQP XML schema described in the specification. Their @@ -87,7 +28,7 @@ data class Envelope(val obj: Any?, val schema: Schema) : DescribedType { */ data class Schema(val types: List) : DescribedType { companion object : DescribedTypeConstructor { - val DESCRIPTOR = DescriptorRegistry.SCHEMA.amqpDescriptor + val DESCRIPTOR = AMQPDescriptorRegistry.SCHEMA.amqpDescriptor fun get(obj: Any): Schema { val describedType = obj as DescribedType @@ -117,7 +58,7 @@ data class Descriptor(val name: Symbol?, val code: UnsignedLong? = null) : Descr constructor(name: String?) : this(Symbol.valueOf(name)) companion object : DescribedTypeConstructor { - val DESCRIPTOR = DescriptorRegistry.OBJECT_DESCRIPTOR.amqpDescriptor + val DESCRIPTOR = AMQPDescriptorRegistry.OBJECT_DESCRIPTOR.amqpDescriptor fun get(obj: Any): Descriptor { val describedType = obj as DescribedType @@ -155,7 +96,7 @@ data class Descriptor(val name: Symbol?, val code: UnsignedLong? = null) : Descr data class Field(val name: String, val type: String, val requires: List, val default: String?, val label: String?, val mandatory: Boolean, val multiple: Boolean) : DescribedType { companion object : DescribedTypeConstructor { - val DESCRIPTOR = DescriptorRegistry.FIELD.amqpDescriptor + val DESCRIPTOR = AMQPDescriptorRegistry.FIELD.amqpDescriptor fun get(obj: Any): Field { val describedType = obj as DescribedType @@ -215,7 +156,7 @@ sealed class TypeNotation : DescribedType { data class CompositeType(override val name: String, override val label: String?, override val provides: List, override val descriptor: Descriptor, val fields: List) : TypeNotation() { companion object : DescribedTypeConstructor { - val DESCRIPTOR = DescriptorRegistry.COMPOSITE_TYPE.amqpDescriptor + val DESCRIPTOR = AMQPDescriptorRegistry.COMPOSITE_TYPE.amqpDescriptor fun get(describedType: DescribedType): CompositeType { if (describedType.descriptor != DESCRIPTOR) { @@ -264,7 +205,7 @@ data class RestrictedType(override val name: String, override val descriptor: Descriptor, val choices: List) : TypeNotation() { companion object : DescribedTypeConstructor { - val DESCRIPTOR = DescriptorRegistry.RESTRICTED_TYPE.amqpDescriptor + val DESCRIPTOR = AMQPDescriptorRegistry.RESTRICTED_TYPE.amqpDescriptor fun get(describedType: DescribedType): RestrictedType { if (describedType.descriptor != DESCRIPTOR) { @@ -309,7 +250,7 @@ data class RestrictedType(override val name: String, data class Choice(val name: String, val value: String) : DescribedType { companion object : DescribedTypeConstructor { - val DESCRIPTOR = DescriptorRegistry.CHOICE.amqpDescriptor + val DESCRIPTOR = AMQPDescriptorRegistry.CHOICE.amqpDescriptor fun get(obj: Any): Choice { val describedType = obj as DescribedType @@ -338,7 +279,7 @@ data class Choice(val name: String, val value: String) : DescribedType { data class ReferencedObject(private val refCounter: Int) : DescribedType { companion object : DescribedTypeConstructor { - val DESCRIPTOR = DescriptorRegistry.REFERENCED_OBJECT.amqpDescriptor + val DESCRIPTOR = AMQPDescriptorRegistry.REFERENCED_OBJECT.amqpDescriptor fun get(obj: Any): ReferencedObject { val describedType = obj as DescribedType diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationOutput.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationOutput.kt index 87a19376c9..2c0c4ece9f 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationOutput.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationOutput.kt @@ -45,10 +45,10 @@ open class SerializationOutput(internal val serializerFactory: SerializerFactory val data = Data.Factory.create() data.withDescribed(Envelope.DESCRIPTOR_OBJECT) { withList { - // Our object writeObject(obj, this) - // The schema - writeSchema(Schema(schemaHistory.toList()), this) + val schema = Schema(schemaHistory.toList()) + writeSchema(schema, this) + writeTransformSchema(TransformsSchema.build(schema, serializerFactory), this) } } val bytes = ByteArray(data.encodedSize().toInt() + 8) @@ -66,6 +66,10 @@ open class SerializationOutput(internal val serializerFactory: SerializerFactory data.putObject(schema) } + open fun writeTransformSchema(transformsSchema: TransformsSchema, data: Data) { + data.putObject(transformsSchema) + } + internal fun writeObjectOrNull(obj: Any?, data: Data, type: Type) { if (obj == null) { data.putNull() diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SupportedTransforms.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SupportedTransforms.kt new file mode 100644 index 0000000000..3b8f3b843a --- /dev/null +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SupportedTransforms.kt @@ -0,0 +1,66 @@ +package net.corda.nodeapi.internal.serialization.amqp + +import net.corda.core.serialization.CordaSerializationTransformEnumDefaults +import net.corda.core.serialization.CordaSerializationTransformEnumDefault +import net.corda.core.serialization.CordaSerializationTransformRenames +import net.corda.core.serialization.CordaSerializationTransformRename + +/** + * Utility class that defines an instance of a transform we support + * + * @property type The transform annotation + * @property enum Maps the annotaiton onto a transform type, we expect there are multiple annotations that + * would map to a single transform type + * @property f Anonymous function that should return a list of Annotations encapsualted by the parent annotation + * that reference the transform. Notionally this allows the code that extracts transforms to work on single instances + * of a transform or a meta list of them + */ +data class SupportedTransform( + val type: Class, + val enum: TransformTypes, + val getAnnotations: (Annotation) -> List) + +/** + * Extract from an annotated class the list of annotations that refer to a particular + * transformation type when that class has multiple transforms wrapped in an + * outer annotation + */ +@Suppress("UNCHECKED_CAST") +private val wrapperExtract = { x: Annotation -> + (x::class.java.getDeclaredMethod("value").invoke(x) as Array).toList() +} + +/** + * Extract from an annotated class the list of annotations that refer to a particular + * transformation type when that class has a single decorator applied + */ +private val singleExtract = { x: Annotation -> listOf(x) } + +/** + * Utility list of all transforms we support that simplifies our generator + * + * NOTE: We have to support single instances of the transform annotations as well as the wrapping annotation + * when many instances are repeated + */ +val supportedTransforms = listOf( + SupportedTransform( + CordaSerializationTransformEnumDefaults::class.java, + TransformTypes.EnumDefault, + wrapperExtract + ), + SupportedTransform( + CordaSerializationTransformEnumDefault::class.java, + TransformTypes.EnumDefault, + singleExtract + ), + SupportedTransform( + CordaSerializationTransformRenames::class.java, + TransformTypes.Rename, + wrapperExtract + ), + SupportedTransform( + CordaSerializationTransformRename::class.java, + TransformTypes.Rename, + singleExtract + ) +) diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/TansformTypes.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/TansformTypes.kt new file mode 100644 index 0000000000..65e49c14de --- /dev/null +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/TansformTypes.kt @@ -0,0 +1,55 @@ +package net.corda.nodeapi.internal.serialization.amqp + +import org.apache.qpid.proton.amqp.DescribedType + +import net.corda.core.serialization.CordaSerializationTransformEnumDefault +import net.corda.core.serialization.CordaSerializationTransformEnumDefaults +import net.corda.core.serialization.CordaSerializationTransformRename +import org.apache.qpid.proton.codec.DescribedTypeConstructor +import java.io.NotSerializableException + +/** + * Enumerated type that represents each transform that can be applied to a class. Used as the key type in + * the [TransformsSchema] map for each class. + * + * @property build should be a function that takes a transform [Annotation] (currently one of + * [CordaSerializationTransformRename] or [CordaSerializationTransformEnumDefaults]) + * and constructs an instance of the corresponding [Transform] type + */ +// TODO: it would be awesome to auto build this list by scanning for transform annotations themselves +// TODO: annotated with some annotation +enum class TransformTypes(val build: (Annotation) -> Transform) : DescribedType { + EnumDefault({ a -> EnumDefaultSchemeTransform((a as CordaSerializationTransformEnumDefault).old, a.new) }) { + override fun getDescriptor(): Any = DESCRIPTOR + override fun getDescribed(): Any = ordinal + }, + Rename({ a -> RenameSchemaTransform((a as CordaSerializationTransformRename).from, a.to) }) { + override fun getDescriptor(): Any = DESCRIPTOR + override fun getDescribed(): Any = ordinal + }; + + companion object : DescribedTypeConstructor { + val DESCRIPTOR = AMQPDescriptorRegistry.TRANSFORM_ELEMENT_KEY.amqpDescriptor + + /** + * Used to construct an instance of the object from the serialised bytes + * + * @param obj the serialised byte object from the AMQP serialised stream + */ + override fun newInstance(obj: Any?): TransformTypes { + val describedType = obj as DescribedType + + if (describedType.descriptor != DESCRIPTOR) { + throw NotSerializableException("Unexpected descriptor ${describedType.descriptor}.") + } + + try { + return values()[describedType.described as Int] + } catch (e: IndexOutOfBoundsException) { + throw NotSerializableException("Bad ordinal value ${describedType.described}.") + } + } + + override fun getTypeClass(): Class<*> = TransformTypes::class.java + } +} diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/TransformsSchema.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/TransformsSchema.kt new file mode 100644 index 0000000000..d7f959e1f9 --- /dev/null +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/TransformsSchema.kt @@ -0,0 +1,268 @@ +package net.corda.nodeapi.internal.serialization.amqp + +import java.util.* +import net.corda.core.serialization.CordaSerializationTransformEnumDefault +import net.corda.core.serialization.CordaSerializationTransformRename +import org.apache.qpid.proton.amqp.DescribedType +import org.apache.qpid.proton.codec.DescribedTypeConstructor +import java.io.NotSerializableException + +// NOTE: We are effectively going to replicate the annotations, we need to do this because +// we can't instantiate instances of those annotation classes and this code needs to +// work at the de-serialising end +/** + * Base class for representations of specific types of transforms as applied to a type within the + * Corda serialisation framework + */ +sealed class Transform : DescribedType { + companion object : DescribedTypeConstructor { + val DESCRIPTOR = AMQPDescriptorRegistry.TRANSFORM_ELEMENT.amqpDescriptor + + /** + * @param obj: a serialized instance of a described type, should be one of the + * descendants of this class + */ + private fun checkDescribed(obj: Any?): Any? { + val describedType = obj as DescribedType + + if (describedType.descriptor != DESCRIPTOR) { + throw NotSerializableException("Unexpected descriptor ${describedType.descriptor}.") + } + + return describedType.described + } + + /** + * From an encoded descendant return an instance of the specific type. Transforms are encoded into + * the schema as a list of class name and parameters.Using the class name (list element 0) + * create the appropriate class instance + * + * @param obj: a serialized instance of a described type, should be one of the + * descendants of this class + */ + override fun newInstance(obj: Any?): Transform { + val described = Transform.checkDescribed(obj) as List<*> + return when (described[0]) { + EnumDefaultSchemeTransform.typeName -> EnumDefaultSchemeTransform.newInstance(described) + RenameSchemaTransform.typeName -> RenameSchemaTransform.newInstance(described) + else -> throw NotSerializableException("Unexpected transform type ${described[0]}") + } + } + + override fun getTypeClass(): Class<*> = Transform::class.java + } + + override fun getDescriptor(): Any = DESCRIPTOR + + /** + * Return a string representation of a transform in terms of key / value pairs, used + * by the serializer to encode arbitrary transforms + */ + abstract fun params(): String + + abstract val name: String +} + +/** + * Transform to be used on an Enumerated Type whenever a new element is added + * + * @property old The value the [new] instance should default to when not available + * @property new the value (as a String) that has been added + */ +class EnumDefaultSchemeTransform(val old: String, val new: String) : Transform() { + companion object : DescribedTypeConstructor { + /** + * Value encoded into the schema that identifies a transform as this type + */ + val typeName = "EnumDefault" + + override fun newInstance(obj: Any?): EnumDefaultSchemeTransform { + val described = obj as List<*> + val old = described[1] as? String ?: throw IllegalStateException("Was expecting \"old\" as a String") + val new = described[2] as? String ?: throw IllegalStateException("Was expecting \"new\" as a String") + return EnumDefaultSchemeTransform(old, new) + } + + override fun getTypeClass(): Class<*> = EnumDefaultSchemeTransform::class.java + } + + @Suppress("UNUSED") + constructor (annotation: CordaSerializationTransformEnumDefault) : this(annotation.old, annotation.new) + + override fun getDescribed(): Any = listOf(name, old, new) + override fun params() = "old=${old.esc()} new=${new.esc()}" + + override fun equals(other: Any?): Boolean { + val o = other as? EnumDefaultSchemeTransform ?: return super.equals(other) + return o.new == new && o.old == old + } + + override val name: String get() = typeName +} + +/** + * Transform applied to either a class or enum where a property is renamed + * + * @property from the name at time of change of the property + * @property to the new name of the property + */ +class RenameSchemaTransform(val from: String, val to: String) : Transform() { + companion object : DescribedTypeConstructor { + /** + * Value encoded into the schema that identifies a transform as this type + */ + val typeName = "Rename" + + override fun newInstance(obj: Any?): RenameSchemaTransform { + val described = obj as List<*> + val from = described[1] as? String ?: throw IllegalStateException("Was expecting \"from\" as a String") + val to = described[2] as? String ?: throw IllegalStateException("Was expecting \"to\" as a String") + return RenameSchemaTransform(from, to) + } + + override fun getTypeClass(): Class<*> = RenameSchemaTransform::class.java + } + + @Suppress("UNUSED") + constructor (annotation: CordaSerializationTransformRename) : this(annotation.from, annotation.to) + + override fun getDescribed(): Any = listOf(name, from, to) + + override fun params() = "from=${from.esc()} to=${to.esc()}" + + override fun equals(other: Any?): Boolean { + val o = other as? RenameSchemaTransform ?: return super.equals(other) + return o.from == from && o.to == to + } + + override val name: String get() = typeName +} + + +/** + * @property types is a list of serialised types that have transforms, each list element is a + */ +data class TransformsSchema(val types: Map>>) : DescribedType { + companion object : DescribedTypeConstructor { + val DESCRIPTOR = AMQPDescriptorRegistry.TRANSFORM_SCHEMA.amqpDescriptor + + /** + * Prepare a schema for encoding, takes all of the types being transmitted and inspects each + * one for any transform annotations. If there are any build up a set that can be + * encoded into the AMQP [Envelope] + * + * @param schema should be a [Schema] generated for a serialised data structure + * @param sf should be provided by the same serialization context that generated the schema + */ + fun build(schema: Schema, sf: SerializerFactory): TransformsSchema { + val rtn = mutableMapOf>>() + + schema.types.forEach { type -> + val clazz = try { + sf.classloader.loadClass(type.name) + } catch (e: ClassNotFoundException) { + return@forEach + } + + supportedTransforms.forEach { transform -> + clazz.getAnnotation(transform.type)?.let { list -> + transform.getAnnotations(list).forEach { + val t = transform.enum.build(it) + + val m = rtn.computeIfAbsent(type.name) { + EnumMap>(TransformTypes::class.java) + } + + // we're explicitly rejecting repeated annotations, whilst it's fine and we'd just + // 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 (m.computeIfAbsent(transform.enum) { mutableListOf() }.filter { it == t }.isNotEmpty()) { + throw NotSerializableException( + "Repeated unique transformation annotation of type ${t.name}") + } + + m[transform.enum]!!.add(t) + } + } + } + } + + return TransformsSchema(rtn) + } + + override fun getTypeClass(): Class<*> = TransformsSchema::class.java + + /** + * Constructs an instance of the object from the serialised form of an instance + * of this object + */ + override fun newInstance(described: Any?): TransformsSchema { + val rtn = mutableMapOf>>() + + val describedType = described as? DescribedType ?: return TransformsSchema(rtn) + + if (describedType.descriptor != DESCRIPTOR) { + throw NotSerializableException("Unexpected descriptor ${describedType.descriptor}.") + } + + val map = describedType.described as? Map<*, *> ?: + throw NotSerializableException("Transform schema must be encoded as a map") + + + map.forEach { type -> + val fingerprint = type.key as? String ?: + throw NotSerializableException("Fingerprint must be encoded as a string") + + rtn[fingerprint] = EnumMap>(TransformTypes::class.java) + + (type.value as Map<*, *>).forEach { transformType, transforms -> + val transform = TransformTypes.newInstance(transformType) + + rtn[fingerprint]!![transform] = mutableListOf() + (transforms as List<*>).forEach { + rtn[fingerprint]!![TransformTypes.newInstance(transformType)]?.add(Transform.newInstance(it)) ?: + throw NotSerializableException("De-serialization error with transform for class " + + "${type.key} ${transform.name}") + } + } + } + + return TransformsSchema(rtn) + } + } + + override fun getDescriptor(): Any = DESCRIPTOR + + override fun getDescribed(): Any = types + + override fun toString(): String { + data class Indent(val indent: String) { + @Suppress("UNUSED") constructor(i: Indent) : this(" ${i.indent}") + override fun toString() = indent + } + + val sb = StringBuilder("") + val indent = Indent("") + + sb.appendln("$indent") + types.forEach { type -> + val indent = Indent(indent) + sb.appendln("$indent") + type.value.forEach { transform -> + val indent = Indent(indent) + sb.appendln("$indent") + transform.value.forEach { + val indent = Indent(indent) + sb.appendln("$indent") + } + sb.appendln("$indent") + } + sb.appendln("$indent") + } + sb.appendln("$indent") + + return sb.toString() + } +} + +private fun String.esc() = "\"$this\"" diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/AMQPTestUtils.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/AMQPTestUtils.kt index 4f9b7b6872..43fe82a6ee 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/AMQPTestUtils.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/AMQPTestUtils.kt @@ -18,18 +18,31 @@ class TestSerializationOutput( if (verbose) println(schema) super.writeSchema(schema, data) } + + override fun writeTransformSchema(transformsSchema: TransformsSchema, data: Data) { + if(verbose) { + println ("Writing Transform Schema") + println (transformsSchema) + } + super.writeTransformSchema(transformsSchema, data) + } } fun testName(): String = Thread.currentThread().stackTrace[2].methodName -data class BytesAndSchema(val obj: SerializedBytes, val schema: Schema) +data class BytesAndSchemas( + val obj: SerializedBytes, + val schema: Schema, + val transformsSchema: TransformsSchema) // Extension for the serialize routine that returns the scheme encoded into the // bytes as well as the bytes for simple testing @Throws(NotSerializableException::class) -fun SerializationOutput.serializeAndReturnSchema(obj: T): BytesAndSchema { +fun SerializationOutput.serializeAndReturnSchema(obj: T): BytesAndSchemas { try { - return BytesAndSchema(_serialize(obj), Schema(schemaHistory.toList())) + val blob = _serialize(obj) + val schema = Schema(schemaHistory.toList()) + return BytesAndSchemas(blob, schema, TransformsSchema.build(schema, serializerFactory)) } finally { andFinally() } diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/EnumEvolvabilityTests.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/EnumEvolvabilityTests.kt new file mode 100644 index 0000000000..f2b0d7dec4 --- /dev/null +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/EnumEvolvabilityTests.kt @@ -0,0 +1,380 @@ +package net.corda.nodeapi.internal.serialization.amqp + +import net.corda.core.serialization.CordaSerializationTransformEnumDefault +import net.corda.core.serialization.CordaSerializationTransformEnumDefaults +import net.corda.core.serialization.CordaSerializationTransformRename +import net.corda.core.serialization.CordaSerializationTransformRenames +import org.assertj.core.api.Assertions +import org.junit.Test +import java.io.NotSerializableException +import kotlin.test.assertEquals +import kotlin.test.assertTrue + +class EnumEvolvabilityTests { + companion object { + val VERBOSE = false + } + + enum class NotAnnotated { + A, B, C, D + } + + @CordaSerializationTransformEnumDefaults() + enum class MissingDefaults { + A, B, C, D + } + + @CordaSerializationTransformRenames() + enum class MissingRenames { + A, B, C, D + } + + @CordaSerializationTransformEnumDefault("D", "A") + enum class AnnotatedEnumOnce { + A, B, C, D + } + + @CordaSerializationTransformEnumDefaults( + CordaSerializationTransformEnumDefault("E", "D"), + CordaSerializationTransformEnumDefault("D", "A")) + enum class AnnotatedEnumTwice { + A, B, C, D, E + } + + @CordaSerializationTransformRename("E", "D") + enum class RenameEnumOnce { + A, B, C, E + } + + @CordaSerializationTransformRenames( + CordaSerializationTransformRename("E", "C"), + CordaSerializationTransformRename("F", "D")) + enum class RenameEnumTwice { + A, B, E, F + } + + @Test + fun noAnnotation() { + data class C (val n: NotAnnotated) + + val sf = testDefaultFactory() + val bAndS = TestSerializationOutput(VERBOSE, sf).serializeAndReturnSchema(C(NotAnnotated.A)) + + assertEquals(2, bAndS.schema.types.size) + assertEquals(0, bAndS.transformsSchema.types.size) + } + + @Test + fun missingDefaults() { + data class C (val m: MissingDefaults) + + val sf = testDefaultFactory() + val bAndS = TestSerializationOutput(VERBOSE, sf).serializeAndReturnSchema(C(MissingDefaults.A)) + + assertEquals(2, bAndS.schema.types.size) + assertEquals(0, bAndS.transformsSchema.types.size) + } + + @Test + fun missingRenames() { + data class C (val m: MissingRenames) + + val sf = testDefaultFactory() + val bAndS = TestSerializationOutput(VERBOSE, sf).serializeAndReturnSchema(C(MissingRenames.A)) + + assertEquals(2, bAndS.schema.types.size) + assertEquals(0, bAndS.transformsSchema.types.size) + + } + + @Test + fun defaultAnnotationIsAddedToEnvelope() { + data class C (val annotatedEnum: AnnotatedEnumOnce) + + val sf = testDefaultFactory() + val bAndS = TestSerializationOutput(VERBOSE, sf).serializeAndReturnSchema(C(AnnotatedEnumOnce.D)) + + // only the enum is decorated so schema sizes should be different (2 objects, only one evolved) + assertEquals(2, bAndS.schema.types.size) + assertEquals(1, bAndS.transformsSchema.types.size) + assertEquals (AnnotatedEnumOnce::class.java.name, bAndS.transformsSchema.types.keys.first()) + + val schema = bAndS.transformsSchema.types.values.first() + + assertEquals(1, schema.size) + assertTrue (schema.keys.contains(TransformTypes.EnumDefault)) + assertEquals (1, schema[TransformTypes.EnumDefault]!!.size) + assertTrue (schema[TransformTypes.EnumDefault]!![0] is EnumDefaultSchemeTransform) + assertEquals ("D", (schema[TransformTypes.EnumDefault]!![0] as EnumDefaultSchemeTransform).new) + assertEquals ("A", (schema[TransformTypes.EnumDefault]!![0] as EnumDefaultSchemeTransform).old) + } + + @Test + fun doubleDefaultAnnotationIsAddedToEnvelope() { + data class C (val annotatedEnum: AnnotatedEnumTwice) + + val sf = testDefaultFactory() + val bAndS = TestSerializationOutput(VERBOSE, sf).serializeAndReturnSchema(C(AnnotatedEnumTwice.E)) + + assertEquals(2, bAndS.schema.types.size) + assertEquals(1, bAndS.transformsSchema.types.size) + assertEquals (AnnotatedEnumTwice::class.java.name, bAndS.transformsSchema.types.keys.first()) + + val schema = bAndS.transformsSchema.types.values.first() + + assertEquals(1, schema.size) + assertTrue (schema.keys.contains(TransformTypes.EnumDefault)) + assertEquals (2, schema[TransformTypes.EnumDefault]!!.size) + assertTrue (schema[TransformTypes.EnumDefault]!![0] is EnumDefaultSchemeTransform) + assertEquals ("E", (schema[TransformTypes.EnumDefault]!![0] as EnumDefaultSchemeTransform).new) + assertEquals ("D", (schema[TransformTypes.EnumDefault]!![0] as EnumDefaultSchemeTransform).old) + assertTrue (schema[TransformTypes.EnumDefault]!![1] is EnumDefaultSchemeTransform) + assertEquals ("D", (schema[TransformTypes.EnumDefault]!![1] as EnumDefaultSchemeTransform).new) + assertEquals ("A", (schema[TransformTypes.EnumDefault]!![1] as EnumDefaultSchemeTransform).old) + } + + @Test + fun defaultAnnotationIsAddedToEnvelopeAndDeserialised() { + data class C (val annotatedEnum: AnnotatedEnumOnce) + + val sf = testDefaultFactory() + val sb = TestSerializationOutput(VERBOSE, sf).serialize(C(AnnotatedEnumOnce.D)) + val db = DeserializationInput(sf).deserializeAndReturnEnvelope(sb) + + // as with the serialisation stage, de-serialising the object we should see two + // types described in the header with one of those having transforms + assertEquals(2, db.envelope.schema.types.size) + assertEquals(1, db.envelope.transformsSchema.types.size) + + val eName = AnnotatedEnumOnce::class.java.name + val types = db.envelope.schema.types + val transforms = db.envelope.transformsSchema.types + + assertEquals(1, types.filter { it.name == eName }.size) + assertTrue(eName in transforms) + + val schema = transforms[eName] + + assertTrue (schema!!.keys.contains(TransformTypes.EnumDefault)) + assertEquals (1, schema[TransformTypes.EnumDefault]!!.size) + assertTrue (schema[TransformTypes.EnumDefault]!![0] is EnumDefaultSchemeTransform) + assertEquals ("D", (schema[TransformTypes.EnumDefault]!![0] as EnumDefaultSchemeTransform).new) + assertEquals ("A", (schema[TransformTypes.EnumDefault]!![0] as EnumDefaultSchemeTransform).old) + } + + @Test + fun doubleDefaultAnnotationIsAddedToEnvelopeAndDeserialised() { + data class C(val annotatedEnum: AnnotatedEnumTwice) + + val sf = testDefaultFactory() + val sb = TestSerializationOutput(VERBOSE, sf).serialize(C(AnnotatedEnumTwice.E)) + val db = DeserializationInput(sf).deserializeAndReturnEnvelope(sb) + + // as with the serialisation stage, de-serialising the object we should see two + // types described in the header with one of those having transforms + assertEquals(2, db.envelope.schema.types.size) + assertEquals(1, db.envelope.transformsSchema.types.size) + + val transforms = db.envelope.transformsSchema.types + + assertTrue (transforms.contains(AnnotatedEnumTwice::class.java.name)) + assertTrue (transforms[AnnotatedEnumTwice::class.java.name]!!.contains(TransformTypes.EnumDefault)) + assertEquals (2, transforms[AnnotatedEnumTwice::class.java.name]!![TransformTypes.EnumDefault]!!.size) + + val enumDefaults = transforms[AnnotatedEnumTwice::class.java.name]!![TransformTypes.EnumDefault]!! + + assertEquals("E", (enumDefaults[0] as EnumDefaultSchemeTransform).new) + assertEquals("D", (enumDefaults[0] as EnumDefaultSchemeTransform).old) + assertEquals("D", (enumDefaults[1] as EnumDefaultSchemeTransform).new) + assertEquals("A", (enumDefaults[1] as EnumDefaultSchemeTransform).old) + } + + @Test + fun renameAnnotationIsAdded() { + data class C (val annotatedEnum: RenameEnumOnce) + + val sf = testDefaultFactory() + + // Serialise the object + val bAndS = TestSerializationOutput(VERBOSE, sf).serializeAndReturnSchema(C(RenameEnumOnce.E)) + + assertEquals(2, bAndS.schema.types.size) + assertEquals(1, bAndS.transformsSchema.types.size) + assertEquals (RenameEnumOnce::class.java.name, bAndS.transformsSchema.types.keys.first()) + + val serialisedSchema = bAndS.transformsSchema.types[RenameEnumOnce::class.java.name]!! + + assertEquals(1, serialisedSchema.size) + assertTrue(serialisedSchema.containsKey(TransformTypes.Rename)) + assertEquals(1, serialisedSchema[TransformTypes.Rename]!!.size) + assertEquals("D", (serialisedSchema[TransformTypes.Rename]!![0] as RenameSchemaTransform).from) + assertEquals("E", (serialisedSchema[TransformTypes.Rename]!![0] as RenameSchemaTransform).to) + + // Now de-serialise the blob + val cAndS = DeserializationInput(sf).deserializeAndReturnEnvelope(bAndS.obj) + + assertEquals(2, cAndS.envelope.schema.types.size) + assertEquals(1, cAndS.envelope.transformsSchema.types.size) + assertEquals (RenameEnumOnce::class.java.name, cAndS.envelope.transformsSchema.types.keys.first()) + + val deserialisedSchema = cAndS.envelope.transformsSchema.types[RenameEnumOnce::class.java.name]!! + + assertEquals(1, deserialisedSchema.size) + assertTrue(deserialisedSchema.containsKey(TransformTypes.Rename)) + assertEquals(1, deserialisedSchema[TransformTypes.Rename]!!.size) + assertEquals("D", (deserialisedSchema[TransformTypes.Rename]!![0] as RenameSchemaTransform).from) + assertEquals("E", (deserialisedSchema[TransformTypes.Rename]!![0] as RenameSchemaTransform).to) + } + + @Test + fun doubleRenameAnnotationIsAdded() { + data class C (val annotatedEnum: RenameEnumTwice) + + val sf = testDefaultFactory() + + // Serialise the object + val bAndS = TestSerializationOutput(VERBOSE, sf).serializeAndReturnSchema(C(RenameEnumTwice.F)) + + assertEquals(2, bAndS.schema.types.size) + assertEquals(1, bAndS.transformsSchema.types.size) + assertEquals (RenameEnumTwice::class.java.name, bAndS.transformsSchema.types.keys.first()) + + val serialisedSchema = bAndS.transformsSchema.types[RenameEnumTwice::class.java.name]!! + + assertEquals(1, serialisedSchema.size) + assertTrue(serialisedSchema.containsKey(TransformTypes.Rename)) + assertEquals(2, serialisedSchema[TransformTypes.Rename]!!.size) + assertEquals("C", (serialisedSchema[TransformTypes.Rename]!![0] as RenameSchemaTransform).from) + assertEquals("E", (serialisedSchema[TransformTypes.Rename]!![0] as RenameSchemaTransform).to) + assertEquals("D", (serialisedSchema[TransformTypes.Rename]!![1] as RenameSchemaTransform).from) + assertEquals("F", (serialisedSchema[TransformTypes.Rename]!![1] as RenameSchemaTransform).to) + + // Now de-serialise the blob + val cAndS = DeserializationInput(sf).deserializeAndReturnEnvelope(bAndS.obj) + + assertEquals(2, cAndS.envelope.schema.types.size) + assertEquals(1, cAndS.envelope.transformsSchema.types.size) + assertEquals (RenameEnumTwice::class.java.name, cAndS.envelope.transformsSchema.types.keys.first()) + + val deserialisedSchema = cAndS.envelope.transformsSchema.types[RenameEnumTwice::class.java.name]!! + + assertEquals(1, deserialisedSchema.size) + assertTrue(deserialisedSchema.containsKey(TransformTypes.Rename)) + assertEquals(2, deserialisedSchema[TransformTypes.Rename]!!.size) + assertEquals("C", (deserialisedSchema[TransformTypes.Rename]!![0] as RenameSchemaTransform).from) + assertEquals("E", (deserialisedSchema[TransformTypes.Rename]!![0] as RenameSchemaTransform).to) + assertEquals("D", (deserialisedSchema[TransformTypes.Rename]!![1] as RenameSchemaTransform).from) + assertEquals("F", (deserialisedSchema[TransformTypes.Rename]!![1] as RenameSchemaTransform).to) + } + + @CordaSerializationTransformRename(from="A", to="X") + @CordaSerializationTransformEnumDefault(old = "X", new="E") + enum class RenameAndExtendEnum { + X, B, C, D, E + } + + @Test + fun bothAnnotationTypes() { + data class C (val annotatedEnum: RenameAndExtendEnum) + + val sf = testDefaultFactory() + + // Serialise the object + val bAndS = TestSerializationOutput(VERBOSE, sf).serializeAndReturnSchema(C(RenameAndExtendEnum.X)) + + assertEquals(2, bAndS.schema.types.size) + assertEquals(1, bAndS.transformsSchema.types.size) + assertEquals (RenameAndExtendEnum::class.java.name, bAndS.transformsSchema.types.keys.first()) + + val serialisedSchema = bAndS.transformsSchema.types[RenameAndExtendEnum::class.java.name]!! + + // This time there should be two distinct transform types (all previous tests have had only + // a single type + assertEquals(2, serialisedSchema.size) + assertTrue (serialisedSchema.containsKey(TransformTypes.Rename)) + assertTrue (serialisedSchema.containsKey(TransformTypes.EnumDefault)) + + assertEquals(1, serialisedSchema[TransformTypes.Rename]!!.size) + assertEquals("A", (serialisedSchema[TransformTypes.Rename]!![0] as RenameSchemaTransform).from) + assertEquals("X", (serialisedSchema[TransformTypes.Rename]!![0] as RenameSchemaTransform).to) + + assertEquals(1, serialisedSchema[TransformTypes.EnumDefault]!!.size) + assertEquals("E", (serialisedSchema[TransformTypes.EnumDefault]!![0] as EnumDefaultSchemeTransform).new) + assertEquals("X", (serialisedSchema[TransformTypes.EnumDefault]!![0] as EnumDefaultSchemeTransform).old) + } + + @CordaSerializationTransformEnumDefaults ( + CordaSerializationTransformEnumDefault("D", "A"), + CordaSerializationTransformEnumDefault("D", "A")) + enum class RepeatedAnnotation { + A, B, C, D, E + } + + @Test + fun repeatedAnnotation() { + data class C (val a: RepeatedAnnotation) + + val sf = testDefaultFactory() + + Assertions.assertThatThrownBy { + TestSerializationOutput(VERBOSE, sf).serializeAndReturnSchema(C(RepeatedAnnotation.A)) + }.isInstanceOf(NotSerializableException::class.java) + } + + @CordaSerializationTransformEnumDefault("D", "A") + enum class E1 { + A, B, C, D + } + + @CordaSerializationTransformEnumDefaults ( + CordaSerializationTransformEnumDefault("D", "A"), + CordaSerializationTransformEnumDefault("E", "A")) + enum class E2 { + A, B, C, D, E + } + + @CordaSerializationTransformEnumDefaults (CordaSerializationTransformEnumDefault("D", "A")) + enum class E3 { + A, B, C, D + } + + @Test + fun multiEnums() { + data class A (val a: E1, val b: E2) + data class B (val a: E3, val b: A, val c: E1) + data class C (val a: B, val b: E2, val c: E3) + + val c = C(B(E3.A,A(E1.A,E2.B),E1.C),E2.B,E3.A) + + val sf = testDefaultFactory() + + // Serialise the object + val bAndS = TestSerializationOutput(VERBOSE, sf).serializeAndReturnSchema(c) + + println (bAndS.transformsSchema) + + // we have six types and three of those, the enums, should have transforms + assertEquals(6, bAndS.schema.types.size) + assertEquals(3, bAndS.transformsSchema.types.size) + + assertTrue (E1::class.java.name in bAndS.transformsSchema.types) + assertTrue (E2::class.java.name in bAndS.transformsSchema.types) + assertTrue (E3::class.java.name in bAndS.transformsSchema.types) + + val e1S = bAndS.transformsSchema.types[E1::class.java.name]!! + val e2S = bAndS.transformsSchema.types[E2::class.java.name]!! + val e3S = bAndS.transformsSchema.types[E3::class.java.name]!! + + assertEquals(1, e1S.size) + assertEquals(1, e2S.size) + assertEquals(1, e3S.size) + + assertTrue(TransformTypes.EnumDefault in e1S) + assertTrue(TransformTypes.EnumDefault in e2S) + assertTrue(TransformTypes.EnumDefault in e3S) + + assertEquals(1, e1S[TransformTypes.EnumDefault]!!.size) + assertEquals(2, e2S[TransformTypes.EnumDefault]!!.size) + assertEquals(1, e3S[TransformTypes.EnumDefault]!!.size) + } +} diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationOutputTests.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationOutputTests.kt index 31136f0723..038234548f 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationOutputTests.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationOutputTests.kt @@ -164,6 +164,8 @@ class SerializationOutputTests { this.register(Choice.DESCRIPTOR, Choice.Companion) this.register(RestrictedType.DESCRIPTOR, RestrictedType.Companion) this.register(ReferencedObject.DESCRIPTOR, ReferencedObject.Companion) + this.register(TransformsSchema.DESCRIPTOR, TransformsSchema.Companion) + this.register(TransformTypes.DESCRIPTOR, TransformTypes.Companion) } EncoderImpl(decoder) decoder.setByteBuffer(ByteBuffer.wrap(bytes.bytes, 8, bytes.size - 8)) From bc12f87a2448c88165e2eac25040de9127cf022c Mon Sep 17 00:00:00 2001 From: Katelyn Baker Date: Thu, 19 Oct 2017 16:17:35 +0100 Subject: [PATCH 76/94] CORDA-553 - Review Comments --- .../CordaSerializationTransformEnumDefault.kt | 75 ++++++++++++++++++ .../CordaSerializationTransformRename.kt | 18 +++++ .../amqp/AMQPDescriptorRegistry.kt | 11 ++- .../internal/serialization/amqp/Envelope.kt | 27 ++++--- .../internal/serialization/amqp/Schema.kt | 2 +- .../serialization/amqp/SerializerFactory.kt | 2 + .../serialization/amqp/SupportedTransforms.kt | 16 ++-- .../serialization/amqp/TansformTypes.kt | 3 +- .../serialization/amqp/TransformsSchema.kt | 78 +++++++++++-------- .../amqp/JavaSerializationOutputTests.java | 2 + .../amqp/EnumEvolvabilityTests.kt | 27 +++++++ 11 files changed, 205 insertions(+), 56 deletions(-) diff --git a/core/src/main/kotlin/net/corda/core/serialization/CordaSerializationTransformEnumDefault.kt b/core/src/main/kotlin/net/corda/core/serialization/CordaSerializationTransformEnumDefault.kt index 8cce317ed4..aa1e9c7154 100644 --- a/core/src/main/kotlin/net/corda/core/serialization/CordaSerializationTransformEnumDefault.kt +++ b/core/src/main/kotlin/net/corda/core/serialization/CordaSerializationTransformEnumDefault.kt @@ -1,14 +1,89 @@ package net.corda.core.serialization /** + * This annotation is used to mark an enumerated type as having had multiple members added, It acts + * as a container annotation for instances of [CordaSerializationTransformEnumDefault], each of which + * details individual additions. * + * @property value an array of [CordaSerializationTransformEnumDefault]. + * + * NOTE: Order is important, new values should always be added before any others + * + * ``` + * // initial implementation + * enum class ExampleEnum { + * A, B, C + * } + * + * // First alteration + * @CordaSerializationTransformEnumDefaults( + * CordaSerializationTransformEnumDefault("D", "C")) + * enum class ExampleEnum { + * A, B, C, D + * } + * + * // Second alteration, new transform is placed at the head of the list + * @CordaSerializationTransformEnumDefaults( + * CordaSerializationTransformEnumDefault("E", "C"), + * CordaSerializationTransformEnumDefault("D", "C")) + * enum class ExampleEnum { + * A, B, C, D, E + * } + * ``` + * + * IMPORTANT - Once added (and in production) do NOT remove old annotations. See documentation for + * more discussion on this point!. */ @Target(AnnotationTarget.CLASS) @Retention(AnnotationRetention.RUNTIME) annotation class CordaSerializationTransformEnumDefaults(vararg val value: CordaSerializationTransformEnumDefault) /** + * This annotation is used to mark an enumerated type as having had a new constant appended to it. For + * each additional constant added a new annotation should be appended to the class. If more than one + * is required the wrapper annotation [CordaSerializationTransformEnumDefaults] should be used to + * encapsulate them * + * @property new [String] equivalent of the value of the new constant + * @property old [String] equivalent of the value of the existing constant that deserialisers should + * favour when de-serialising a value they have no corresponding value for + * + * For Example + * + * Enum before modification: + * ``` + * enum class ExampleEnum { + * A, B, C + * } + * ``` + * + * Assuming at some point a new constant is added it is required we have some mechanism by which to tell + * nodes with an older version of the class on their Class Path what to do if they attempt to deserialize + * an example of the class with that new value + * + * ``` + * @CordaSerializationTransformEnumDefault("D", "C") + * enum class ExampleEnum { + * A, B, C, D + * } + * ``` + * + * So, on deserialisation treat any instance of the enum that is encoded as D as C + * + * Adding a second new constant requires the wrapper annotation [CordaSerializationTransformEnumDefaults] + * + * ``` + * @CordaSerializationTransformEnumDefaults( + * CordaSerializationTransformEnumDefault("E", "D"), + * CordaSerializationTransformEnumDefault("D", "C")) + * enum class ExampleEnum { + * A, B, C, D, E + * } + * ``` + * + * It's fine to assign the second new value a default that may not be present in all versions as in this + * case it will work down the transform hierarchy until it finds a value it can apply, in this case it would + * try E -> D -> C (when E -> D fails) */ @Target(AnnotationTarget.CLASS) @Retention(AnnotationRetention.RUNTIME) diff --git a/core/src/main/kotlin/net/corda/core/serialization/CordaSerializationTransformRename.kt b/core/src/main/kotlin/net/corda/core/serialization/CordaSerializationTransformRename.kt index 379505bf1b..c8ae7e2aec 100644 --- a/core/src/main/kotlin/net/corda/core/serialization/CordaSerializationTransformRename.kt +++ b/core/src/main/kotlin/net/corda/core/serialization/CordaSerializationTransformRename.kt @@ -1,13 +1,31 @@ package net.corda.core.serialization /** + * This annotation is used to mark a class as having had multiple elements renamed as a container annotation for + * instances of [CordaSerializationTransformRename], each of which details an individual rename. * + * @property value an array of [CordaSerializationTransformRename] + * + * NOTE: Order is important, new values should always be added before existing + * + * IMPORTANT - Once added (and in production) do NOT remove old annotations. See documentation for + * more discussion on this point!. */ @Target(AnnotationTarget.CLASS) @Retention(AnnotationRetention.RUNTIME) annotation class CordaSerializationTransformRenames(vararg val value: CordaSerializationTransformRename) +// TODO When we have class renaming update the docs /** + * This annotation is used to mark a class has having had a property element. It is used by the + * AMQP deserialiser to allow instances with different versions of the class on their Class Path + * to successfully deserialize the object + * + * NOTE: Renaming of the class itself is not be done with this annotation. For class renaming + * see ??? + * + * @property to [String] representation of the properties new name + * @property from [String] representation of the properties old new * */ @Target(AnnotationTarget.CLASS) diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/AMQPDescriptorRegistry.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/AMQPDescriptorRegistry.kt index 4ffeb57764..0c9a06b8fd 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/AMQPDescriptorRegistry.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/AMQPDescriptorRegistry.kt @@ -10,10 +10,17 @@ import org.apache.qpid.proton.amqp.UnsignedLong * Repeated here for brevity: * 50530 - R3 - Mike Hearn - mike&r3.com */ -const val DESCRIPTOR_TOP_32BITS: Long = 0xc5620000 +const val DESCRIPTOR_TOP_32BITS: Long = 0xc562 shl(32 + 16) +/** + * AMQP desriptor ID's for our custom types. + * + * NEVER DELETE OR CHANGE THE ID ASSOCIATED WITH A TYPE + * + * these are encoded as part of a serialised blob and doing so would render us unable to + * de-serialise that blob!!! + */ enum class AMQPDescriptorRegistry(val id: Long) { - ENVELOPE(1), SCHEMA(2), OBJECT_DESCRIPTOR(3), diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/Envelope.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/Envelope.kt index d2f73e0575..358cecbb48 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/Envelope.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/Envelope.kt @@ -18,6 +18,14 @@ data class Envelope(val obj: Any?, val schema: Schema, val transformsSchema: Tra val DESCRIPTOR = AMQPDescriptorRegistry.ENVELOPE.amqpDescriptor val DESCRIPTOR_OBJECT = Descriptor(null, DESCRIPTOR) + // described list should either be two or three elements long + private const val ENVELOPE_WITHOUT_TRANSFORMS = 2 + private const val ENVELOPE_WITH_TRANSFORMS = 3 + + private const val BLOB_IDX = 0 + private const val SCHEMA_IDX = 1 + private const val TRANSFORMS_SCHEMA_IDX = 2 + fun get(data: Data): Envelope { val describedType = data.`object` as DescribedType if (describedType.descriptor != DESCRIPTOR) { @@ -27,28 +35,29 @@ data class Envelope(val obj: Any?, val schema: Schema, val transformsSchema: Tra // We need to cope with objects serialised without the transforms header element in the // envelope - val transformSchema : Any? = when (list.size) { - 2 -> null - 3 -> list[2] + 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)") } - return newInstance(listOf(list[0], Schema.get(list[1]!!), TransformsSchema.newInstance(transformSchema))) + return newInstance(listOf(list[BLOB_IDX], Schema.get(list[SCHEMA_IDX]!!), + TransformsSchema.newInstance(transformSchema))) } - // This seperation of functions is needed as this will be the entry point for the default - // AMQP decoder if one is used (see the unit tests) + // This separation of functions is needed as this will be the entry point for the default + // AMQP decoder if one is used (see the unit tests). override fun newInstance(described: Any?): Envelope { val list = described as? List<*> ?: throw IllegalStateException("Was expecting a list") // We need to cope with objects serialised without the transforms header element in the // envelope val transformSchema = when (list.size) { - 2 -> TransformsSchema.newInstance(null) - 3 -> list[2] as TransformsSchema + 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)") } - return Envelope(list[0], list[1] as Schema, transformSchema) + return Envelope(list[BLOB_IDX], list[SCHEMA_IDX] as Schema, transformSchema) } override fun getTypeClass(): Class<*> = Envelope::class.java diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/Schema.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/Schema.kt index 0d20ca0822..bb38933ed0 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/Schema.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/Schema.kt @@ -17,11 +17,11 @@ import java.util.* import net.corda.nodeapi.internal.serialization.carpenter.Field as CarpenterField import net.corda.nodeapi.internal.serialization.carpenter.Schema as CarpenterSchema +const val DESCRIPTOR_DOMAIN: String = "net.corda" // "corda" + majorVersionByte + minorVersionMSB + minorVersionLSB val AmqpHeaderV1_0: OpaqueBytes = OpaqueBytes("corda\u0001\u0000\u0000".toByteArray()) - /** * This and the classes below are OO representations of the AMQP XML schema described in the specification. Their * [toString] representations generate the associated XML form. diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializerFactory.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializerFactory.kt index da8ae1afc6..5075593ace 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializerFactory.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializerFactory.kt @@ -34,6 +34,8 @@ open class SerializerFactory(val whitelist: ClassWhitelist, cl: ClassLoader) { private val serializersByType = ConcurrentHashMap>() private val serializersByDescriptor = ConcurrentHashMap>() private val customSerializers = CopyOnWriteArrayList>() + val transformsCache = ConcurrentHashMap>>() + open val classCarpenter = ClassCarpenter(cl, whitelist) val classloader: ClassLoader get() = classCarpenter.classloader diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SupportedTransforms.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SupportedTransforms.kt index 3b8f3b843a..90ef03d79b 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SupportedTransforms.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SupportedTransforms.kt @@ -1,19 +1,19 @@ package net.corda.nodeapi.internal.serialization.amqp -import net.corda.core.serialization.CordaSerializationTransformEnumDefaults import net.corda.core.serialization.CordaSerializationTransformEnumDefault -import net.corda.core.serialization.CordaSerializationTransformRenames +import net.corda.core.serialization.CordaSerializationTransformEnumDefaults import net.corda.core.serialization.CordaSerializationTransformRename +import net.corda.core.serialization.CordaSerializationTransformRenames /** - * Utility class that defines an instance of a transform we support + * Utility class that defines an instance of a transform we support. * - * @property type The transform annotation + * @property type The transform annotation. * @property enum Maps the annotaiton onto a transform type, we expect there are multiple annotations that - * would map to a single transform type - * @property f Anonymous function that should return a list of Annotations encapsualted by the parent annotation + * would map to a single transform type. + * @property getAnnotations Anonymous function that should return a list of Annotations encapsualted by the parent annotation * that reference the transform. Notionally this allows the code that extracts transforms to work on single instances - * of a transform or a meta list of them + * of a transform or a meta list of them. */ data class SupportedTransform( val type: Class, @@ -37,7 +37,7 @@ private val wrapperExtract = { x: Annotation -> private val singleExtract = { x: Annotation -> listOf(x) } /** - * Utility list of all transforms we support that simplifies our generator + * Utility list of all transforms we support that simplifies our generation code * * NOTE: We have to support single instances of the transform annotations as well as the wrapping annotation * when many instances are repeated diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/TansformTypes.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/TansformTypes.kt index 65e49c14de..28f5b7e27c 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/TansformTypes.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/TansformTypes.kt @@ -1,10 +1,9 @@ package net.corda.nodeapi.internal.serialization.amqp -import org.apache.qpid.proton.amqp.DescribedType - import net.corda.core.serialization.CordaSerializationTransformEnumDefault import net.corda.core.serialization.CordaSerializationTransformEnumDefaults import net.corda.core.serialization.CordaSerializationTransformRename +import org.apache.qpid.proton.amqp.DescribedType import org.apache.qpid.proton.codec.DescribedTypeConstructor import java.io.NotSerializableException diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/TransformsSchema.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/TransformsSchema.kt index d7f959e1f9..54836bc158 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/TransformsSchema.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/TransformsSchema.kt @@ -1,11 +1,11 @@ package net.corda.nodeapi.internal.serialization.amqp -import java.util.* import net.corda.core.serialization.CordaSerializationTransformEnumDefault import net.corda.core.serialization.CordaSerializationTransformRename import org.apache.qpid.proton.amqp.DescribedType import org.apache.qpid.proton.codec.DescribedTypeConstructor import java.io.NotSerializableException +import java.util.* // NOTE: We are effectively going to replicate the annotations, we need to do this because // we can't instantiate instances of those annotation classes and this code needs to @@ -14,7 +14,7 @@ import java.io.NotSerializableException * Base class for representations of specific types of transforms as applied to a type within the * Corda serialisation framework */ -sealed class Transform : DescribedType { +abstract class Transform : DescribedType { companion object : DescribedTypeConstructor { val DESCRIPTOR = AMQPDescriptorRegistry.TRANSFORM_ELEMENT.amqpDescriptor @@ -92,10 +92,10 @@ class EnumDefaultSchemeTransform(val old: String, val new: String) : Transform() override fun getDescribed(): Any = listOf(name, old, new) override fun params() = "old=${old.esc()} new=${new.esc()}" - override fun equals(other: Any?): Boolean { - val o = other as? EnumDefaultSchemeTransform ?: return super.equals(other) - return o.new == new && o.old == old - } + override fun equals(other: Any?) = ( + (other is EnumDefaultSchemeTransform && other.new == new && other.old == old) || super.equals(other)) + + override fun hashCode() = (17 * new.hashCode()) + old.hashCode() override val name: String get() = typeName } @@ -130,17 +130,21 @@ class RenameSchemaTransform(val from: String, val to: String) : Transform() { override fun params() = "from=${from.esc()} to=${to.esc()}" - override fun equals(other: Any?): Boolean { - val o = other as? RenameSchemaTransform ?: return super.equals(other) - return o.from == from && o.to == to - } + override fun equals(other: Any?) = ( + (other is RenameSchemaTransform && other.from == from && other.to == to) || super.equals(other)) + + override fun hashCode() = (11 * from.hashCode()) + to.hashCode() override val name: String get() = typeName } /** - * @property types is a list of serialised types that have transforms, each list element is a + * Represents the set of all transforms that can be a applied to all classes represented as part of + * an AMQP schema. It forms a part of the AMQP envelope alongside the [Schema] and the serialized bytes + * + * @property types maps class names to a map of transformation types. In turn those transformation types + * are each a list of instances o that transform. */ data class TransformsSchema(val types: Map>>) : DescribedType { companion object : DescribedTypeConstructor { @@ -158,31 +162,38 @@ data class TransformsSchema(val types: Map>>() schema.types.forEach { type -> - val clazz = try { - sf.classloader.loadClass(type.name) - } catch (e: ClassNotFoundException) { - return@forEach - } + sf.transformsCache.computeIfAbsent(type.name) { + val transforms = EnumMap>(TransformTypes::class.java) + try { + val clazz = sf.classloader.loadClass(type.name) - supportedTransforms.forEach { transform -> - clazz.getAnnotation(transform.type)?.let { list -> - transform.getAnnotations(list).forEach { - val t = transform.enum.build(it) + supportedTransforms.forEach { transform -> + clazz.getAnnotation(transform.type)?.let { list -> + transform.getAnnotations(list).forEach { annotation -> + val t = transform.enum.build(annotation) - val m = rtn.computeIfAbsent(type.name) { - EnumMap>(TransformTypes::class.java) + // we're explicitly rejecting repeated annotations, whilst it's fine and we'd just + // 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() } + .filter { t == it }.isNotEmpty()) { + throw NotSerializableException( + "Repeated unique transformation annotation of type ${t.name}") + } + + transforms[transform.enum]!!.add(t) + } } - - // we're explicitly rejecting repeated annotations, whilst it's fine and we'd just - // 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 (m.computeIfAbsent(transform.enum) { mutableListOf() }.filter { it == t }.isNotEmpty()) { - throw NotSerializableException( - "Repeated unique transformation annotation of type ${t.name}") - } - - m[transform.enum]!!.add(t) } + } catch (_: ClassNotFoundException) { + // if we can't load the class we'll end up caching an empty list which is fine as that + // list, on lookup, won't be included in the schema because it's empty + } + + transforms + }.apply { + if (isNotEmpty()) { + rtn[type.name] = this } } } @@ -198,7 +209,6 @@ data class TransformsSchema(val types: Map>>() - val describedType = described as? DescribedType ?: return TransformsSchema(rtn) if (describedType.descriptor != DESCRIPTOR) { @@ -208,7 +218,6 @@ data class TransformsSchema(val types: Map ?: throw NotSerializableException("Transform schema must be encoded as a map") - map.forEach { type -> val fingerprint = type.key as? String ?: throw NotSerializableException("Fingerprint must be encoded as a string") @@ -238,6 +247,7 @@ data class TransformsSchema(val types: Map Date: Thu, 26 Oct 2017 16:54:36 +0100 Subject: [PATCH 77/94] CORDA-553 - Cope with future transforms --- .../internal/serialization/amqp/Envelope.kt | 1 + .../serialization/amqp/SupportedTransforms.kt | 14 +- .../serialization/amqp/TansformTypes.kt | 25 ++- .../serialization/amqp/TransformsSchema.kt | 57 +++++-- .../amqp/EnumEvolvabilityTests.kt | 73 ++++++--- .../serialization/amqp/EvolvabilityTests.kt | 146 ++++++++---------- ...EnumEvolvabilityTests.testUnknownTransform | Bin 0 -> 785 bytes ...bilityTests.addAdditionalParamNotMandatory | Bin 273 -> 292 bytes .../EvolvabilityTests.addAndRemoveParameters | Bin 390 -> 403 bytes ...yTests.addMandatoryFieldWithAltConstructor | Bin 285 -> 298 bytes ...dMandatoryFieldWithAltReorderedConstructor | Bin 387 -> 400 bytes ...FieldWithAltReorderedConstructorAndRemoval | Bin 403 -> 416 bytes .../amqp/EvolvabilityTests.changeSubType | Bin 616 -> 629 bytes .../amqp/EvolvabilityTests.multiVersion.1 | Bin 294 -> 307 bytes .../amqp/EvolvabilityTests.multiVersion.2 | Bin 327 -> 340 bytes .../amqp/EvolvabilityTests.multiVersion.3 | Bin 372 -> 385 bytes ...volvabilityTests.multiVersionWithRemoval.1 | Bin 338 -> 351 bytes ...volvabilityTests.multiVersionWithRemoval.2 | Bin 392 -> 405 bytes ...volvabilityTests.multiVersionWithRemoval.3 | Bin 425 -> 438 bytes .../amqp/EvolvabilityTests.removeParameters | Bin 378 -> 391 bytes ...vabilityTests.simpleOrderSwapDifferentType | Bin 311 -> 324 bytes .../EvolvabilityTests.simpleOrderSwapSameType | Bin 302 -> 315 bytes 22 files changed, 198 insertions(+), 118 deletions(-) create mode 100644 node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EnumEvolvabilityTests.testUnknownTransform diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/Envelope.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/Envelope.kt index 358cecbb48..5b489c5d84 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/Envelope.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/Envelope.kt @@ -57,6 +57,7 @@ data class Envelope(val obj: Any?, val schema: Schema, val transformsSchema: Tra ENVELOPE_WITH_TRANSFORMS -> list[TRANSFORMS_SCHEMA_IDX] as TransformsSchema else -> throw NotSerializableException("Malformed list, bad length of ${list.size} (should be 2 or 3)") } + return Envelope(list[BLOB_IDX], list[SCHEMA_IDX] as Schema, transformSchema) } diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SupportedTransforms.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SupportedTransforms.kt index 90ef03d79b..df3f4f8d45 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SupportedTransforms.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SupportedTransforms.kt @@ -36,11 +36,17 @@ private val wrapperExtract = { x: Annotation -> */ private val singleExtract = { x: Annotation -> listOf(x) } +// Transform annotation used to test the handling of transforms the de-serialising node doesn't understand. At +// some point test cases will have been created with this transform applied. +//@Target(AnnotationTarget.CLASS) +//@Retention(AnnotationRetention.RUNTIME) +//annotation class UnknownTransformAnnotation(val a: Int, val b: Int, val c: Int) + /** - * Utility list of all transforms we support that simplifies our generation code + * Utility list of all transforms we support that simplifies our generation code. * * NOTE: We have to support single instances of the transform annotations as well as the wrapping annotation - * when many instances are repeated + * when many instances are repeated. */ val supportedTransforms = listOf( SupportedTransform( @@ -63,4 +69,8 @@ val supportedTransforms = listOf( TransformTypes.Rename, singleExtract ) + //,SupportedTransform( + // UnknownTransformAnnotation::class.java, + // TransformTypes.UnknownTest, + // singleExtract) ) diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/TansformTypes.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/TansformTypes.kt index 28f5b7e27c..de4f2d8703 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/TansformTypes.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/TansformTypes.kt @@ -14,11 +14,21 @@ import java.io.NotSerializableException * @property build should be a function that takes a transform [Annotation] (currently one of * [CordaSerializationTransformRename] or [CordaSerializationTransformEnumDefaults]) * and constructs an instance of the corresponding [Transform] type + * + * DO NOT REORDER THE CONSTANTS!!! Please append any new entries to the end */ // TODO: it would be awesome to auto build this list by scanning for transform annotations themselves // TODO: annotated with some annotation enum class TransformTypes(val build: (Annotation) -> Transform) : DescribedType { - EnumDefault({ a -> EnumDefaultSchemeTransform((a as CordaSerializationTransformEnumDefault).old, a.new) }) { + /** + * Placeholder entry for future transforms where a node receives a transform we've subsequently + * added and thus the de-serialising node doesn't know about that transform. + */ + Unknown({ UnknownTransform() }) { + override fun getDescriptor(): Any = DESCRIPTOR + override fun getDescribed(): Any = ordinal + }, + EnumDefault({ a -> EnumDefaultSchemaTransform((a as CordaSerializationTransformEnumDefault).old, a.new) }) { override fun getDescriptor(): Any = DESCRIPTOR override fun getDescribed(): Any = ordinal }, @@ -26,6 +36,13 @@ enum class TransformTypes(val build: (Annotation) -> Transform) : DescribedType override fun getDescriptor(): Any = DESCRIPTOR override fun getDescribed(): Any = ordinal }; + // Transform used to test the unknown handler, leave this at as the final constant, uncomment + // when regenerating test cases - if Java had a pre-processor this would be much neater + // + //UnknownTest({ a -> UnknownTestTransform((a as UnknownTransformAnnotation).a, a.b, a.c)}) { + // override fun getDescriptor(): Any = DESCRIPTOR + // override fun getDescribed(): Any = ordinal + //}; companion object : DescribedTypeConstructor { val DESCRIPTOR = AMQPDescriptorRegistry.TRANSFORM_ELEMENT_KEY.amqpDescriptor @@ -42,10 +59,10 @@ enum class TransformTypes(val build: (Annotation) -> Transform) : DescribedType throw NotSerializableException("Unexpected descriptor ${describedType.descriptor}.") } - try { - return values()[describedType.described as Int] + return try { + values()[describedType.described as Int] } catch (e: IndexOutOfBoundsException) { - throw NotSerializableException("Bad ordinal value ${describedType.described}.") + values()[0] } } diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/TransformsSchema.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/TransformsSchema.kt index 54836bc158..2bde766c2c 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/TransformsSchema.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/TransformsSchema.kt @@ -37,15 +37,18 @@ abstract class Transform : DescribedType { * the schema as a list of class name and parameters.Using the class name (list element 0) * create the appropriate class instance * + * For future proofing any unknown transform types are not treated as errors, rather we + * simply create a placeholder object so we can ignore it + * * @param obj: a serialized instance of a described type, should be one of the * descendants of this class */ override fun newInstance(obj: Any?): Transform { val described = Transform.checkDescribed(obj) as List<*> return when (described[0]) { - EnumDefaultSchemeTransform.typeName -> EnumDefaultSchemeTransform.newInstance(described) + EnumDefaultSchemaTransform.typeName -> EnumDefaultSchemaTransform.newInstance(described) RenameSchemaTransform.typeName -> RenameSchemaTransform.newInstance(described) - else -> throw NotSerializableException("Unexpected transform type ${described[0]}") + else -> UnknownTransform() } } @@ -63,27 +66,64 @@ abstract class Transform : DescribedType { abstract val name: String } +/** + * Transform type placeholder that allows for backward compatibility. Should a noce recieve + * a transform type it doesn't recognise, we can will use this as a placeholder + */ +class UnknownTransform : Transform() { + companion object : DescribedTypeConstructor { + val typeName = "UnknownTransform" + + override fun newInstance(obj: Any?) = UnknownTransform() + + override fun getTypeClass(): Class<*> = UnknownTransform::class.java + } + + override fun getDescribed(): Any = emptyList() + override fun params() = "" + + override val name: String get() = typeName +} + +class UnknownTestTransform(val a: Int, val b: Int, val c: Int) : Transform() { + companion object : DescribedTypeConstructor { + val typeName = "UnknownTest" + + override fun newInstance(obj: Any?) : UnknownTestTransform { + val described = obj as List<*> + return UnknownTestTransform(described[1] as Int, described[2] as Int, described[3] as Int) + } + + override fun getTypeClass(): Class<*> = UnknownTransform::class.java + } + + override fun getDescribed(): Any = listOf(name, a, b, c) + override fun params() = "" + + override val name: String get() = typeName +} + /** * Transform to be used on an Enumerated Type whenever a new element is added * * @property old The value the [new] instance should default to when not available * @property new the value (as a String) that has been added */ -class EnumDefaultSchemeTransform(val old: String, val new: String) : Transform() { - companion object : DescribedTypeConstructor { +class EnumDefaultSchemaTransform(val old: String, val new: String) : Transform() { + companion object : DescribedTypeConstructor { /** * Value encoded into the schema that identifies a transform as this type */ val typeName = "EnumDefault" - override fun newInstance(obj: Any?): EnumDefaultSchemeTransform { + override fun newInstance(obj: Any?): EnumDefaultSchemaTransform { val described = obj as List<*> val old = described[1] as? String ?: throw IllegalStateException("Was expecting \"old\" as a String") val new = described[2] as? String ?: throw IllegalStateException("Was expecting \"new\" as a String") - return EnumDefaultSchemeTransform(old, new) + return EnumDefaultSchemaTransform(old, new) } - override fun getTypeClass(): Class<*> = EnumDefaultSchemeTransform::class.java + override fun getTypeClass(): Class<*> = EnumDefaultSchemaTransform::class.java } @Suppress("UNUSED") @@ -93,7 +133,7 @@ class EnumDefaultSchemeTransform(val old: String, val new: String) : Transform() override fun params() = "old=${old.esc()} new=${new.esc()}" override fun equals(other: Any?) = ( - (other is EnumDefaultSchemeTransform && other.new == new && other.old == old) || super.equals(other)) + (other is EnumDefaultSchemaTransform && other.new == new && other.old == old) || super.equals(other)) override fun hashCode() = (17 * new.hashCode()) + old.hashCode() @@ -138,7 +178,6 @@ class RenameSchemaTransform(val from: String, val to: String) : Transform() { override val name: String get() = typeName } - /** * Represents the set of all transforms that can be a applied to all classes represented as part of * an AMQP schema. It forms a part of the AMQP envelope alongside the [Schema] and the serialized bytes diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/EnumEvolvabilityTests.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/EnumEvolvabilityTests.kt index 2e333e15e7..2fffc13b28 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/EnumEvolvabilityTests.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/EnumEvolvabilityTests.kt @@ -1,16 +1,19 @@ package net.corda.nodeapi.internal.serialization.amqp -import net.corda.core.serialization.CordaSerializationTransformEnumDefault -import net.corda.core.serialization.CordaSerializationTransformEnumDefaults -import net.corda.core.serialization.CordaSerializationTransformRename -import net.corda.core.serialization.CordaSerializationTransformRenames +import net.corda.core.serialization.* import org.assertj.core.api.Assertions import org.junit.Test +import java.io.File import java.io.NotSerializableException +import java.net.URI import kotlin.test.assertEquals import kotlin.test.assertTrue class EnumEvolvabilityTests { +// var localPath = "file:///Users/katelynbaker/srcs-ide/corda/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp" + var localPath = "file:///home/katelyn/srcs/corda/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp" + + companion object { val VERBOSE = false } @@ -104,9 +107,9 @@ class EnumEvolvabilityTests { assertEquals(1, schema.size) assertTrue (schema.keys.contains(TransformTypes.EnumDefault)) assertEquals (1, schema[TransformTypes.EnumDefault]!!.size) - assertTrue (schema[TransformTypes.EnumDefault]!![0] is EnumDefaultSchemeTransform) - assertEquals ("D", (schema[TransformTypes.EnumDefault]!![0] as EnumDefaultSchemeTransform).new) - assertEquals ("A", (schema[TransformTypes.EnumDefault]!![0] as EnumDefaultSchemeTransform).old) + assertTrue (schema[TransformTypes.EnumDefault]!![0] is EnumDefaultSchemaTransform) + assertEquals ("D", (schema[TransformTypes.EnumDefault]!![0] as EnumDefaultSchemaTransform).new) + assertEquals ("A", (schema[TransformTypes.EnumDefault]!![0] as EnumDefaultSchemaTransform).old) } @Test @@ -125,12 +128,12 @@ class EnumEvolvabilityTests { assertEquals(1, schema.size) assertTrue (schema.keys.contains(TransformTypes.EnumDefault)) assertEquals (2, schema[TransformTypes.EnumDefault]!!.size) - assertTrue (schema[TransformTypes.EnumDefault]!![0] is EnumDefaultSchemeTransform) - assertEquals ("E", (schema[TransformTypes.EnumDefault]!![0] as EnumDefaultSchemeTransform).new) - assertEquals ("D", (schema[TransformTypes.EnumDefault]!![0] as EnumDefaultSchemeTransform).old) - assertTrue (schema[TransformTypes.EnumDefault]!![1] is EnumDefaultSchemeTransform) - assertEquals ("D", (schema[TransformTypes.EnumDefault]!![1] as EnumDefaultSchemeTransform).new) - assertEquals ("A", (schema[TransformTypes.EnumDefault]!![1] as EnumDefaultSchemeTransform).old) + assertTrue (schema[TransformTypes.EnumDefault]!![0] is EnumDefaultSchemaTransform) + assertEquals ("E", (schema[TransformTypes.EnumDefault]!![0] as EnumDefaultSchemaTransform).new) + assertEquals ("D", (schema[TransformTypes.EnumDefault]!![0] as EnumDefaultSchemaTransform).old) + assertTrue (schema[TransformTypes.EnumDefault]!![1] is EnumDefaultSchemaTransform) + assertEquals ("D", (schema[TransformTypes.EnumDefault]!![1] as EnumDefaultSchemaTransform).new) + assertEquals ("A", (schema[TransformTypes.EnumDefault]!![1] as EnumDefaultSchemaTransform).old) } @Test @@ -157,9 +160,9 @@ class EnumEvolvabilityTests { assertTrue (schema!!.keys.contains(TransformTypes.EnumDefault)) assertEquals (1, schema[TransformTypes.EnumDefault]!!.size) - assertTrue (schema[TransformTypes.EnumDefault]!![0] is EnumDefaultSchemeTransform) - assertEquals ("D", (schema[TransformTypes.EnumDefault]!![0] as EnumDefaultSchemeTransform).new) - assertEquals ("A", (schema[TransformTypes.EnumDefault]!![0] as EnumDefaultSchemeTransform).old) + assertTrue (schema[TransformTypes.EnumDefault]!![0] is EnumDefaultSchemaTransform) + assertEquals ("D", (schema[TransformTypes.EnumDefault]!![0] as EnumDefaultSchemaTransform).new) + assertEquals ("A", (schema[TransformTypes.EnumDefault]!![0] as EnumDefaultSchemaTransform).old) } @Test @@ -183,10 +186,10 @@ class EnumEvolvabilityTests { val enumDefaults = transforms[AnnotatedEnumTwice::class.java.name]!![TransformTypes.EnumDefault]!! - assertEquals("E", (enumDefaults[0] as EnumDefaultSchemeTransform).new) - assertEquals("D", (enumDefaults[0] as EnumDefaultSchemeTransform).old) - assertEquals("D", (enumDefaults[1] as EnumDefaultSchemeTransform).new) - assertEquals("A", (enumDefaults[1] as EnumDefaultSchemeTransform).old) + assertEquals("E", (enumDefaults[0] as EnumDefaultSchemaTransform).new) + assertEquals("D", (enumDefaults[0] as EnumDefaultSchemaTransform).old) + assertEquals("D", (enumDefaults[1] as EnumDefaultSchemaTransform).new) + assertEquals("A", (enumDefaults[1] as EnumDefaultSchemaTransform).old) } @Test @@ -299,8 +302,8 @@ class EnumEvolvabilityTests { assertEquals("X", (serialisedSchema[TransformTypes.Rename]!![0] as RenameSchemaTransform).to) assertEquals(1, serialisedSchema[TransformTypes.EnumDefault]!!.size) - assertEquals("E", (serialisedSchema[TransformTypes.EnumDefault]!![0] as EnumDefaultSchemeTransform).new) - assertEquals("X", (serialisedSchema[TransformTypes.EnumDefault]!![0] as EnumDefaultSchemeTransform).old) + assertEquals("E", (serialisedSchema[TransformTypes.EnumDefault]!![0] as EnumDefaultSchemaTransform).new) + assertEquals("X", (serialisedSchema[TransformTypes.EnumDefault]!![0] as EnumDefaultSchemaTransform).old) } @CordaSerializationTransformEnumDefaults ( @@ -402,6 +405,32 @@ class EnumEvolvabilityTests { assertEquals (sb1.transformsSchema.types[AnnotatedEnumOnce::class.java.name], sb2.transformsSchema.types[AnnotatedEnumOnce::class.java.name]) + } + + // @UnknownTransformAnnotation (10, 20, 30) + enum class WithUnknownTest { + A, B, C, D + } + + data class WrapsUnknown(val unknown: WithUnknownTest) + + // To regenerate the types for this test uncomment the UnknownTransformAnnotation from + // TransformTypes.kt and SupportedTransforms.kt + @Test + fun testUnknownTransform() { + val resource = "EnumEvolvabilityTests.testUnknownTransform" + val sf = testDefaultFactory() + + //File(URI("$localPath/$resource")).writeBytes( + // SerializationOutput(sf).serialize(WrapsUnknown(WithUnknownTest.D)).bytes) + + val path = EvolvabilityTests::class.java.getResource(resource) + val sb1 = File(path.toURI()).readBytes() + + val envelope = DeserializationInput(sf).deserializeAndReturnEnvelope(SerializedBytes(sb1)).envelope + + assertTrue(envelope.transformsSchema.types.containsKey(WithUnknownTest::class.java.name)) + assertTrue(envelope.transformsSchema.types[WithUnknownTest::class.java.name]!!.containsKey(TransformTypes.Unknown)) } } diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.kt index 9c4a949384..f5ab608e44 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.kt @@ -5,36 +5,40 @@ import net.corda.core.serialization.SerializedBytes import org.junit.Test import java.io.File import java.io.NotSerializableException +import java.net.URI import kotlin.test.assertEquals // To regenerate any of the binary test files do the following // +// 0. set localPath accordingly // 1. Uncomment the code where the original form of the class is defined in the test // 2. Comment out the rest of the test // 3. Run the test // 4. Using the printed path copy that file to the resources directory // 5. Comment back out the generation code and uncomment the actual test class EvolvabilityTests { + // When regenerating the test files this needs to be set to the file system location of the resource files + var localPath = "file://////corda/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp" @Test fun simpleOrderSwapSameType() { val sf = testDefaultFactory() - val path = EvolvabilityTests::class.java.getResource("EvolvabilityTests.simpleOrderSwapSameType") - val f = File(path.toURI()) + val resource= "EvolvabilityTests.simpleOrderSwapSameType" val A = 1 val B = 2 // Original version of the class for the serialised version of this class - // // data class C (val a: Int, val b: Int) // val sc = SerializationOutput(sf).serialize(C(A, B)) - // f.writeBytes(sc.bytes) - // println (path) + // File(URI("$localPath/$resource")).writeBytes(sc.bytes) // new version of the class, in this case the order of the parameters has been swapped data class C(val b: Int, val a: Int) + val path = EvolvabilityTests::class.java.getResource(resource) + val f = File(path.toURI()) + val sc2 = f.readBytes() val deserializedC = DeserializationInput(sf).deserialize(SerializedBytes(sc2)) @@ -45,21 +49,20 @@ class EvolvabilityTests { @Test fun simpleOrderSwapDifferentType() { val sf = testDefaultFactory() - val path = EvolvabilityTests::class.java.getResource("EvolvabilityTests.simpleOrderSwapDifferentType") - val f = File(path.toURI()) val A = 1 val B = "two" + val resource = "EvolvabilityTests.simpleOrderSwapDifferentType" // Original version of the class as it was serialised - // // data class C (val a: Int, val b: String) // val sc = SerializationOutput(sf).serialize(C(A, B)) - // f.writeBytes(sc.bytes) - // println (path) + // File(URI("$localPath/$resource")).writeBytes(sc.bytes) // new version of the class, in this case the order of the parameters has been swapped data class C(val b: String, val a: Int) + val path = EvolvabilityTests::class.java.getResource(resource) + val f = File(path.toURI()) val sc2 = f.readBytes() val deserializedC = DeserializationInput(sf).deserialize(SerializedBytes(sc2)) @@ -70,18 +73,18 @@ class EvolvabilityTests { @Test fun addAdditionalParamNotMandatory() { val sf = testDefaultFactory() - val path = EvolvabilityTests::class.java.getResource("EvolvabilityTests.addAdditionalParamNotMandatory") - val f = File(path.toURI()) val A = 1 + val resource = "EvolvabilityTests.addAdditionalParamNotMandatory" // Original version of the class as it was serialised - // // data class C(val a: Int) // val sc = SerializationOutput(sf).serialize(C(A)) - // f.writeBytes(sc.bytes) - // println ("Path = $path") + // File(URI("$localPath/$resource")).writeBytes(sc.bytes) + data class C(val a: Int, val b: Int?) + val path = EvolvabilityTests::class.java.getResource(resource) + val f = File(path.toURI()) val sc2 = f.readBytes() val deserializedC = DeserializationInput(sf).deserialize(SerializedBytes(sc2)) @@ -119,22 +122,21 @@ class EvolvabilityTests { @Test fun removeParameters() { val sf = testDefaultFactory() - val path = EvolvabilityTests::class.java.getResource("EvolvabilityTests.removeParameters") - val f = File(path.toURI()) + val resource = "EvolvabilityTests.removeParameters" val A = 1 val B = "two" val C = "three" val D = 4 // Original version of the class as it was serialised - // // data class CC(val a: Int, val b: String, val c: String, val d: Int) // val scc = SerializationOutput(sf).serialize(CC(A, B, C, D)) - // f.writeBytes(scc.bytes) - // println ("Path = $path") + // File(URI("$localPath/$resource")).writeBytes(scc.bytes) data class CC(val b: String, val d: Int) + val path = EvolvabilityTests::class.java.getResource("EvolvabilityTests.removeParameters") + val f = File(path.toURI()) val sc2 = f.readBytes() val deserializedCC = DeserializationInput(sf).deserialize(SerializedBytes(sc2)) @@ -146,23 +148,23 @@ class EvolvabilityTests { @Test fun addAndRemoveParameters() { val sf = testDefaultFactory() - val path = EvolvabilityTests::class.java.getResource("EvolvabilityTests.addAndRemoveParameters") - val f = File(path.toURI()) val A = 1 val B = "two" val C = "three" val D = 4 val E = null + val resource = "EvolvabilityTests.addAndRemoveParameters" + // Original version of the class as it was serialised - // // data class CC(val a: Int, val b: String, val c: String, val d: Int) // val scc = SerializationOutput(sf).serialize(CC(A, B, C, D)) - // f.writeBytes(scc.bytes) - // println ("Path = $path") + // File(URI("$localPath/$resource")).writeBytes(scc.bytes) data class CC(val a: Int, val e: Boolean?, val d: Int) + val path = EvolvabilityTests::class.java.getResource(resource) + val f = File(path.toURI()) val sc2 = f.readBytes() val deserializedCC = DeserializationInput(sf).deserialize(SerializedBytes(sc2)) @@ -174,16 +176,13 @@ class EvolvabilityTests { @Test fun addMandatoryFieldWithAltConstructor() { val sf = testDefaultFactory() - val path = EvolvabilityTests::class.java.getResource("EvolvabilityTests.addMandatoryFieldWithAltConstructor") - val f = File(path.toURI()) val A = 1 + val resource = "EvolvabilityTests.addMandatoryFieldWithAltConstructor" // Original version of the class as it was serialised - // // data class CC(val a: Int) // val scc = SerializationOutput(sf).serialize(CC(A)) - // f.writeBytes(scc.bytes) - // println ("Path = $path") + // File(URI("$localPath/$resource")).writeBytes(scc.bytes) @Suppress("UNUSED") data class CC(val a: Int, val b: String) { @@ -191,6 +190,8 @@ class EvolvabilityTests { constructor (a: Int) : this(a, "hello") } + val path = EvolvabilityTests::class.java.getResource(resource) + val f = File(path.toURI()) val sc2 = f.readBytes() val deserializedCC = DeserializationInput(sf).deserialize(SerializedBytes(sc2)) @@ -228,19 +229,15 @@ class EvolvabilityTests { @Test fun addMandatoryFieldWithAltReorderedConstructor() { val sf = testDefaultFactory() - val path = EvolvabilityTests::class.java.getResource( - "EvolvabilityTests.addMandatoryFieldWithAltReorderedConstructor") - val f = File(path.toURI()) + val resource = "EvolvabilityTests.addMandatoryFieldWithAltReorderedConstructor" val A = 1 val B = 100 val C = "This is not a banana" // Original version of the class as it was serialised - // // data class CC(val a: Int, val b: Int, val c: String) // val scc = SerializationOutput(sf).serialize(CC(A, B, C)) - // f.writeBytes(scc.bytes) - // println ("Path = $path") + // File(URI("$localPath/$resource")).writeBytes(scc.bytes) @Suppress("UNUSED") data class CC(val a: Int, val b: Int, val c: String, val d: String) { @@ -250,6 +247,8 @@ class EvolvabilityTests { constructor (c: String, a: Int, b: Int) : this(a, b, c, "wibble") } + val path = EvolvabilityTests::class.java.getResource(resource) + val f = File(path.toURI()) val sc2 = f.readBytes() val deserializedCC = DeserializationInput(sf).deserialize(SerializedBytes(sc2)) @@ -262,20 +261,15 @@ class EvolvabilityTests { @Test fun addMandatoryFieldWithAltReorderedConstructorAndRemoval() { val sf = testDefaultFactory() - val path = EvolvabilityTests::class.java.getResource( - "EvolvabilityTests.addMandatoryFieldWithAltReorderedConstructorAndRemoval") - val f = File(path.toURI()) + val resource = "EvolvabilityTests.addMandatoryFieldWithAltReorderedConstructorAndRemoval" val A = 1 @Suppress("UNUSED_VARIABLE") val B = 100 val C = "This is not a banana" // Original version of the class as it was serialised - // // data class CC(val a: Int, val b: Int, val c: String) - // val scc = SerializationOutput(sf).serialize(CC(A, B, C)) - // f.writeBytes(scc.bytes) - // println ("Path = $path") + // File(URI("$localPath/$resource")).writeBytes(SerializationOutput(sf).serialize(CC(A, B, C)).bytes) // b is removed, d is added data class CC(val a: Int, val c: String, val d: String) { @@ -286,6 +280,8 @@ class EvolvabilityTests { constructor (c: String, a: Int) : this(a, c, "wibble") } + val path = EvolvabilityTests::class.java.getResource(resource) + val f = File(path.toURI()) val sc2 = f.readBytes() val deserializedCC = DeserializationInput(sf).deserialize(SerializedBytes(sc2)) @@ -297,9 +293,9 @@ class EvolvabilityTests { @Test fun multiVersion() { val sf = testDefaultFactory() - val path1 = EvolvabilityTests::class.java.getResource("EvolvabilityTests.multiVersion.1") - val path2 = EvolvabilityTests::class.java.getResource("EvolvabilityTests.multiVersion.2") - val path3 = EvolvabilityTests::class.java.getResource("EvolvabilityTests.multiVersion.3") + val resource1 = "EvolvabilityTests.multiVersion.1" + val resource2 = "EvolvabilityTests.multiVersion.2" + val resource3 = "EvolvabilityTests.multiVersion.3" val a = 100 val b = 200 @@ -310,24 +306,15 @@ class EvolvabilityTests { // // Version 1: // data class C (val a: Int, val b: Int) - // - // val scc = SerializationOutput(sf).serialize(C(a, b)) - // File(path1.toURI()).writeBytes(scc.bytes) - // println ("Path = $path1") + // File(URI("$localPath/$resource1")).writeBytes(SerializationOutput(sf).serialize(C(a, b)).bytes) // // Version 2 - add param c // data class C (val c: Int, val b: Int, val a: Int) - // - // val scc = SerializationOutput(sf).serialize(C(c, b, a)) - // File(path2.toURI()).writeBytes(scc.bytes) - // println ("Path = $path2") + // File(URI("$localPath/$resource2")).writeBytes(SerializationOutput(sf).serialize(C(c, b, a)).bytes) // // Version 3 - add param d // data class C (val b: Int, val c: Int, val d: Int, val a: Int) - // - // val scc = SerializationOutput(sf).serialize(C(b, c, d, a)) - // File(path3.toURI()).writeBytes(scc.bytes) - // println ("Path = $path3") + // File(URI("$localPath/$resource3")).writeBytes(SerializationOutput(sf).serialize(C(b, c, d, a)).bytes) @Suppress("UNUSED") data class C(val e: Int, val c: Int, val b: Int, val a: Int, val d: Int) { @@ -341,6 +328,10 @@ class EvolvabilityTests { constructor (a: Int, b: Int, c: Int, d: Int) : this(-1, c, b, a, d) } + val path1 = EvolvabilityTests::class.java.getResource(resource1) + val path2 = EvolvabilityTests::class.java.getResource(resource2) + val path3 = EvolvabilityTests::class.java.getResource(resource3) + val sb1 = File(path1.toURI()).readBytes() val db1 = DeserializationInput(sf).deserialize(SerializedBytes(sb1)) @@ -372,24 +363,21 @@ class EvolvabilityTests { @Test fun changeSubType() { val sf = testDefaultFactory() - val path = EvolvabilityTests::class.java.getResource("EvolvabilityTests.changeSubType") - val f = File(path.toURI()) + val resource = "EvolvabilityTests.changeSubType" val oa = 100 val ia = 200 // Original version of the class as it was serialised - // // data class Inner (val a: Int) // data class Outer (val a: Int, val b: Inner) - // val scc = SerializationOutput(sf).serialize(Outer(oa, Inner (ia))) - // f.writeBytes(scc.bytes) - // println ("Path = $path") + // File(URI("$localPath/$resource")).writeBytes(SerializationOutput(sf).serialize(Outer(oa, Inner (ia))).bytes) // Add a parameter to inner but keep outer unchanged data class Inner(val a: Int, val b: String?) - data class Outer(val a: Int, val b: Inner) + val path = EvolvabilityTests::class.java.getResource(resource) + val f = File(path.toURI()) val sc2 = f.readBytes() val outer = DeserializationInput(sf).deserialize(SerializedBytes(sc2)) @@ -401,9 +389,10 @@ class EvolvabilityTests { @Test fun multiVersionWithRemoval() { val sf = testDefaultFactory() - val path1 = EvolvabilityTests::class.java.getResource("EvolvabilityTests.multiVersionWithRemoval.1") - val path2 = EvolvabilityTests::class.java.getResource("EvolvabilityTests.multiVersionWithRemoval.2") - val path3 = EvolvabilityTests::class.java.getResource("EvolvabilityTests.multiVersionWithRemoval.3") + + val resource1 = "EvolvabilityTests.multiVersionWithRemoval.1" + val resource2 = "EvolvabilityTests.multiVersionWithRemoval.2" + val resource3 = "EvolvabilityTests.multiVersionWithRemoval.3" @Suppress("UNUSED_VARIABLE") val a = 100 @@ -417,24 +406,15 @@ class EvolvabilityTests { // // Version 1: // data class C (val a: Int, val b: Int, val c: Int) + // File(URI("$localPath/$resource1")).writeBytes(SerializationOutput(sf).serialize(C(a, b, c)).bytes) // - // val scc = SerializationOutput(sf).serialize(C(a, b, c)) - // File(path1.toURI()).writeBytes(scc.bytes) - // println ("Path = $path1") - // - // Version 2 - add param c + // Version 2 - remove property a, add property e // data class C (val b: Int, val c: Int, val d: Int, val e: Int) - // - // val scc = SerializationOutput(sf).serialize(C(b, c, d, e)) - // File(path2.toURI()).writeBytes(scc.bytes) - // println ("Path = $path2") + // File(URI("$localPath/$resource2")).writeBytes(SerializationOutput(sf).serialize(C(b, c, d, e)).bytes) // // Version 3 - add param d // data class C (val b: Int, val c: Int, val d: Int, val e: Int, val f: Int) - // - // val scc = SerializationOutput(sf).serialize(C(b, c, d, e, f)) - // File(path3.toURI()).writeBytes(scc.bytes) - // println ("Path = $path3") + // File(URI("$localPath/$resource3")).writeBytes(SerializationOutput(sf).serialize(C(b, c, d, e, f)).bytes) @Suppress("UNUSED") data class C(val b: Int, val c: Int, val d: Int, val e: Int, val f: Int, val g: Int) { @@ -451,6 +431,10 @@ class EvolvabilityTests { constructor (b: Int, c: Int, d: Int, e: Int, f: Int) : this(b, c, d, e, f, -1) } + val path1 = EvolvabilityTests::class.java.getResource(resource1) + val path2 = EvolvabilityTests::class.java.getResource(resource2) + val path3 = EvolvabilityTests::class.java.getResource(resource3) + val sb1 = File(path1.toURI()).readBytes() val db1 = DeserializationInput(sf).deserialize(SerializedBytes(sb1)) diff --git a/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EnumEvolvabilityTests.testUnknownTransform b/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EnumEvolvabilityTests.testUnknownTransform new file mode 100644 index 0000000000000000000000000000000000000000..74c557f8841e8ae8104dd447af0628e58fc06bd7 GIT binary patch literal 785 zcmYe!FG@*dWME)u_zwg}lNcB;FfcIv0tzrQELO@(EzwH`sjxCjc1;g+^m46;$_NiH zb9AmUGS5f}w6#5;$B3dXIV#sJr7FnK&@>>l*f=o4(k#*>vK*+6jcFmHO9(UE9wwkY zjQ@c4fGlME3S=AX6_huOzi7FEK~2IJGDuQb=SEI+3#F)1@Av!pU4wYa2MCA=uHpg1%yJ1@UH&%qUL0rLShCSrW& zaG;P8u8HM93j0F#Qix#-gQ?=Y%#sYK>p<>xb#QQWg4@S-;658otZ{H%$dZ#;TmrWV zJveYhqQe1S7Puz%1MI*kbzI13fF|U$kkJrL$ax{75t@+8LPle_5a+=TCYpHlpaCLP bafdJ;P((y0*8vfhh1^J?AHo$P5+Vlx^;tFt literal 0 HcmV?d00001 diff --git a/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.addAdditionalParamNotMandatory b/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.addAdditionalParamNotMandatory index 8e37059133d3a9853cee8fff322dc0c207a23114..7892b7f89b2195eab0d703c515fd399b6dbf28f7 100644 GIT binary patch delta 115 zcmbQpw1i0}Ilm|+k&%Icq2WIe98F?iyuiS~$O{x;W|(+ZMfw0UV+bQ$lIg%P#si0; r{E4BtEFeu2j~S{%HM1NLU|-0XxR5zBuf%mBqk)5?6I?InK}H4utUWRT delta 70 zcmZ3&G?7U(Ilm|+k&%Icp#g{)jwUfM9{9^NQCdd?%4IrmjPbx>Mu_yp=v)?%_{1}Y KV0tnmqZj~bYY+MW diff --git a/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.addAndRemoveParameters b/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.addAndRemoveParameters index 3943c84020a798e6b7d85324210e8f08669b6fa2..d92fd43a0389b705fb7185aa77bac8f253056354 100644 GIT binary patch delta 188 zcmZo;p3E$roL`iZ$jHFJ(C{A!jwUfMUSMEgtN{uzPn5P&fC@5!1XX~7U|DGp1EOJK zX$}iW&BUjMQBbuk2L#v`GA1r$&de)uUC3zQ;OGQ5oaKNZP%dd9TX9KIW?s6h1Eyp$ S7Rkv4jAFcSLpTpIG5`Q1`AsAM delta 174 zcmbQt+{P@OoL`iZ$jHFJ&;Y~?N0S&BFEB7L<^$PG6Q!*bpn^;wK@}heEGrFSKr~D& z&0ztlnfTN&3Pb}9U^yVbzK}6-A#-M4iR(f}0|!SZxZx}Z1c7o%3)zZGiZb)kT^%qb Mld(unE?^V`02@9gy8r+H diff --git a/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.addMandatoryFieldWithAltConstructor b/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.addMandatoryFieldWithAltConstructor index a7e207ac7570921b04699d400c231783cff4597f..43d786ee3df01a947174bab0dba886a8c53cc59d 100644 GIT binary patch delta 120 zcmbQsw2DbOIlm|+k&%Icq2WIe98F?iyuiS~C=3)}o+zy&0u^LBaE9@~Nk)jGiP6Q} vP;ur1YD^PlRCFB<$U-H7Ca@e3U|-0XxR5zBuf%mBqk)5?6WnypgNzIS%)~P6 delta 107 zcmZ3*G?z&_Ilm|+k&%Icp#g{)jwUfMUSMEgWCOC9CQ9pwKn0l&oMAk0k`bb4VstS# pRGj&M8q-7>6$OU_vQSB&2`mQ$*cUP;E@aNkD{)=OXyD-J1OViM7p1>@foL`iZ$jHFJ(C{A!jwUfMUSMEgtON=$Pn331fC@5!1jT`ZU|Bv81EOJK zeGLmp&BXtPFtscP1lSicCN5;o%qwwS$Y|i;=mb$aIe<|@18N}40YRXm8=iNRX`_j9%N(y0Mckcc>n+a delta 144 zcmbQh+{`SUoL`iZ$jHFJ&;Y~?N0S&BFEB7L<^b7D6Qx}gpn^;wL2)1lEXxOCKr~FO zuVDeHnfTvO0Yn20U^yVbzK}6-A#-M4iR(f}0|!SZh}y{kj1tmtMS?&@$qU(vONuh{ J(p?>ZssO`u9t;2g diff --git a/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.addMandatoryFieldWithAltReorderedConstructorAndRemoval b/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.addMandatoryFieldWithAltReorderedConstructorAndRemoval index 922eecc33539c7e8c483ad07ad2090138c8a0055..95ada2e128e9730af949662bab91f8e08555ff85 100644 GIT binary patch delta 158 zcmbQtyntCcIlm|+k&%Icq2WIe98F?iyuiS~*a;M1o+$0202O2c3917H!Lo8721LWe z`W_aLn#uf(hA_n}2L#v`GA1r$&de)uUC3zQ;OGQVJUM|;LIY|d%K<^4qU42a#U(|V RdFieWa8*Dza2{l2007o~Kn0mVg6cpHSXK_ifM}Rl z-@^h@Gnt>!PyxgMn!s{EfPEoj;zH)kyb{-ij0O&lP7uYD6Bs3=;fe%-ijo(y6_*rc L=B2wj09641z1ba) diff --git a/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.changeSubType b/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.changeSubType index 96e11c7f09fa2c31a82b92521617dbaca74dd69e..636217e01feac82cc33fbd123917d8c845fd05c7 100644 GIT binary patch delta 183 zcmaFC@|8t8Ilm|+k&%Icq2WIe98F?iyuiS~6bBSwo+uru02O2c3Nk(i3W8)A?*SQ1 z5DgQjq_Ke1O#EjEQ_FHdfPEoj;zH)kyb{-ij0O&lP7t+|{Tb7^pa!xYSUGV);A8vw@1R91vh%$e6s4IWw=sbs?jHgQFANXoy@ArrcyhMloKvGR}jH F3;@tFL{0zz delta 142 zcmcb@beu^#Ilm|+k&%Icp#g{)jwUfMUSMEgGyt-hCQ6%$Kn0l&ykR`>k`bb4Vrn!u yRGj&M8q-7>m5J}!Y@i}62L#v`GA1u%&de)uUC3zQ;OGQ58X}j3DL2`WQ49cI2_fMC diff --git a/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.multiVersion.3 b/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.multiVersion.3 index 1e36577ca927ec4b8c0ad0f9d9a5a54794c47338..06a0d6698324959a560fd966846719783fa9199a 100644 GIT binary patch delta 186 zcmeyu)W|HIoL`iZ$jHFJ(C{A!jwUfMUSMEg%mNB9Pn5P+fC@5!1O7%$ui&V!5$ E0EI+Ln*aa+ delta 172 zcmZo<{=y`koL`iZ$jHFJ&;Y~?N0S&BFEB7L#sb+)6Q!*cpn^;wK|vq~EXx66Kr~D& yi)I0-nRwMO0z?B1U^yVbzK}6#A#-M4iR(f}0|!SZxZx1FWIS>ym~xZj7{vg?@g*_< diff --git a/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.multiVersionWithRemoval.1 b/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.multiVersionWithRemoval.1 index 9496b5632a3dc176a8e7ea25eaebc5b58dcea412..70e8a1387a17c02b4071e5ba12fe6e174509a351 100644 GIT binary patch delta 144 zcmcb_be~B&Ilm|+k&%Icq2WIe98F?iyuiS~=m8X9o+xc50u^LB@SX9%7eO>Q8K0?pskz-85sa&r9mG6 delta 131 zcmcc5bcsniIlm|+k&%Icp#g{)jwUfMUSMEgv<9-7CQ6%$Kn0l&d}loHg%P4?Vrm)- sNPOaXLj|Zj%K-uQg^Y;{nKScBTo*DLI5;{%)J|4ll#oJEl#Hwh0LqFUvH$=8 diff --git a/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.multiVersionWithRemoval.2 b/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.multiVersionWithRemoval.2 index 44cd5fb07fa79b1ab257e7ffa18f71049dc2fa9d..3d4e9e897fbc360041e72ce9ab513da7c1d32108 100644 GIT binary patch delta 168 zcmeBRp2{qfoL`iZ$jHFJ(C{A!jwUfMUSMEgtOE)#Gfe!TGSNYj1tc*sRsup-=0NC| yhG2TK9HT@$NC8kM%K-uQg^bAynKScBTo*DLI5;}NO@+v%;E_wkD#v+{kpTd!*GcpM delta 155 zcmbQr+`%l9oL`iZ$jHFJ&;Y~?N0S&BFEB7L76RE!3===7OmvWB0ZB}Zm4MKdIS~4# tA();l$0!j5QUKJ+azKE6A!G7F=FGej*M*D*4vtO?4JdLcc;r%%D@60`#ff@MuX42Xt_ zbvY~`H51<%!ql=H5MW=(n6!{NGq1#TA)|qVqZ35!WP3)5c&LF8MJbqyfG&W@rQ(rG L!z#ylkdXlZfhJcS delta 199 zcmdnSypmZuIlm|+k&%Icp#g{)jwUfMUSMEgoCIVuO_X+4fC@5!1nqzvu&gPF0nsqA zE{6rAX5u?T1rQB1faQPy`$EQ~h0K|GC9Vq@4ICVuAZjPuGfD)ZC`!Rp1UDcRk6aqE F8~_y2EVuvw diff --git a/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.removeParameters b/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.removeParameters index 65d11e24e38b90de2ec4778b916be5447c96fed3..926661d87b79097940bd60201fd8b13576e1b4c0 100644 GIT binary patch delta 190 zcmeyx)XprEoL`iZ$jHFJ(C{A!jwUfMUSMEgEC32HGfaG-GSNX)0V=@+l8^vOfK~8= z7!XYpOA}Z?Y9`(^1k;oG7$p*+nph49vM*#zTF6#hQk0pO?&{#+=mfV4BAJX!QUItk UWg&BBUWw~MMgwGxoCg^h07cwR1^@s6 delta 177 zcmZo?{>3DdoL`iZ$jHFJ&;Y~?N0S&BFEB7LCIi__3=CQ2KM0BMjQ(}9PK2ktRK6irOX pW&w#$Ja4D~qJi=(2L#v`GA1r$&de)uUC3zQ;OGQVJ6VBI901B`7exR7 diff --git a/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.simpleOrderSwapSameType b/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.simpleOrderSwapSameType index b96b0ce60ecd349703faf4b3e207ec9d54d41f87..377e82d50e84774271bdecccc9fd376139dd9ea2 100644 GIT binary patch delta 119 zcmZ3-w3|sLIlm|+k&%Icq2WIe98F?iyuiS~r~(vVW|(+RWugP82vmaUz-`6@Hy9xb uC&s3+fW#*rH-ylCB-EkmSPlrVFJw$w$efv1;<}L0z`@Z8ZaC*bMg{GZF delta 106 zcmdnZw2nz8Ilm|+k&%Icp#g{)jwUfMUSMEg6bG`I7$%-mndrbN0+nDoaGUYK4MvE< oiLq%cAn}RE4I%U&2`Q*LmIDIp3mKCZGH2$MxGrQgaBy@200yQQ=l}o! From a0b9768be7e9f6f1cf8b9b05d4c6b8382654fcb3 Mon Sep 17 00:00:00 2001 From: Alberto Arri <30873160+al-r3@users.noreply.github.com> Date: Fri, 27 Oct 2017 14:30:10 +0100 Subject: [PATCH 78/94] Retire FullNodeConfiguration (#1954) * Retire FullNodeConfiguration * Moved logic and tests for Artermis user names in RPCUserServiceImpl --- .../net/corda/docs/ExampleConfigTest.kt | 5 +- .../main/kotlin/net/corda/node/ArgsParser.kt | 6 +-- .../kotlin/net/corda/node/internal/Node.kt | 16 ++----- .../net/corda/node/internal/NodeStartup.kt | 18 ++++---- .../net/corda/node/services/RPCUserService.kt | 6 +++ .../node/services/config/NodeConfiguration.kt | 33 ++++++++----- .../corda/node/services/RPCUserServiceTest.kt | 20 ++++++++ .../config/FullNodeConfigurationTest.kt | 46 ------------------- .../kotlin/net/corda/irs/IRSDemoTest.kt | 10 ++-- .../kotlin/net/corda/testing/driver/Driver.kt | 15 +++--- .../net/corda/testing/node/NodeBasedTest.kt | 3 +- .../corda/demobench/model/NodeConfigTest.kt | 6 +-- 12 files changed, 78 insertions(+), 106 deletions(-) create mode 100644 node/src/test/kotlin/net/corda/node/services/RPCUserServiceTest.kt delete mode 100644 node/src/test/kotlin/net/corda/node/services/config/FullNodeConfigurationTest.kt diff --git a/docs/source/example-code/src/test/kotlin/net/corda/docs/ExampleConfigTest.kt b/docs/source/example-code/src/test/kotlin/net/corda/docs/ExampleConfigTest.kt index a797479c86..edefef85f7 100644 --- a/docs/source/example-code/src/test/kotlin/net/corda/docs/ExampleConfigTest.kt +++ b/docs/source/example-code/src/test/kotlin/net/corda/docs/ExampleConfigTest.kt @@ -1,8 +1,7 @@ package net.corda.docs import net.corda.node.services.config.ConfigHelper -import net.corda.node.services.config.FullNodeConfiguration -import net.corda.nodeapi.config.parseAs +import net.corda.node.services.config.parseAsNodeConfiguration import net.corda.verifier.Verifier import org.junit.Test import java.nio.file.Path @@ -34,7 +33,7 @@ class ExampleConfigTest { ConfigHelper.loadConfig( baseDirectory = baseDirectory, configFile = it - ).parseAs() + ).parseAsNodeConfiguration() } } diff --git a/node/src/main/kotlin/net/corda/node/ArgsParser.kt b/node/src/main/kotlin/net/corda/node/ArgsParser.kt index 7139055566..3de3f30071 100644 --- a/node/src/main/kotlin/net/corda/node/ArgsParser.kt +++ b/node/src/main/kotlin/net/corda/node/ArgsParser.kt @@ -4,8 +4,7 @@ import joptsimple.OptionParser import joptsimple.util.EnumConverter import net.corda.core.internal.div import net.corda.node.services.config.ConfigHelper -import net.corda.node.services.config.FullNodeConfiguration -import net.corda.nodeapi.config.parseAs +import net.corda.node.services.config.parseAsNodeConfiguration import org.slf4j.event.Level import java.io.PrintStream import java.nio.file.Path @@ -71,6 +70,5 @@ data class CmdLineOptions(val baseDirectory: Path, val sshdServer: Boolean, val justGenerateNodeInfo: Boolean) { fun loadConfig() = ConfigHelper - .loadConfig(baseDirectory, configFile) - .parseAs() + .loadConfig(baseDirectory, configFile).parseAsNodeConfiguration() } diff --git a/node/src/main/kotlin/net/corda/node/internal/Node.kt b/node/src/main/kotlin/net/corda/node/internal/Node.kt index e76f226adc..694e21d11a 100644 --- a/node/src/main/kotlin/net/corda/node/internal/Node.kt +++ b/node/src/main/kotlin/net/corda/node/internal/Node.kt @@ -22,7 +22,6 @@ import net.corda.node.services.RPCUserService import net.corda.node.services.RPCUserServiceImpl import net.corda.node.services.api.NetworkMapCacheInternal import net.corda.node.services.api.SchemaService -import net.corda.node.services.config.FullNodeConfiguration import net.corda.node.services.config.NodeConfiguration import net.corda.node.services.messaging.ArtemisMessagingServer import net.corda.node.services.messaging.ArtemisMessagingServer.Companion.ipDetectRequestProperty @@ -62,7 +61,7 @@ import kotlin.system.exitProcess * * @param configuration This is typically loaded from a TypeSafe HOCON configuration file. */ -open class Node(configuration: FullNodeConfiguration, +open class Node(override val configuration: NodeConfiguration, versionInfo: VersionInfo, val initialiseSerialization: Boolean = true, cordappLoader: CordappLoader = makeCordappLoader(configuration) @@ -84,7 +83,7 @@ open class Node(configuration: FullNodeConfiguration, exitProcess(1) } - private fun createClock(configuration: FullNodeConfiguration): Clock { + private fun createClock(configuration: NodeConfiguration): Clock { return if (configuration.useTestClock) TestClock() else NodeClock() } @@ -99,7 +98,6 @@ open class Node(configuration: FullNodeConfiguration, } override val log: Logger get() = logger - override val configuration get() = super.configuration as FullNodeConfiguration // Necessary to avoid init order NPE. override val networkMapAddress: NetworkMapAddress? get() = configuration.networkMapService?.address?.let(::NetworkMapAddress) override fun makeTransactionVerifierService() = (network as NodeMessagingClient).verifierService @@ -153,14 +151,8 @@ open class Node(configuration: FullNodeConfiguration, override fun makeMessagingService(legalIdentity: PartyAndCertificate): MessagingService { userService = RPCUserServiceImpl(configuration.rpcUsers) - val (serverAddress, advertisedAddress) = with(configuration) { - if (messagingServerAddress != null) { - // External broker - messagingServerAddress to messagingServerAddress - } else { - makeLocalMessageBroker() to getAdvertisedAddress() - } - } + val serverAddress = configuration.messagingServerAddress ?: makeLocalMessageBroker() + val advertisedAddress = configuration.messagingServerAddress ?: getAdvertisedAddress() printBasicNodeInfo("Incoming connection address", advertisedAddress.toString()) diff --git a/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt b/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt index bc95502ee9..16de351f63 100644 --- a/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt +++ b/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt @@ -7,7 +7,7 @@ import net.corda.core.internal.* import net.corda.core.internal.concurrent.thenMatch import net.corda.core.utilities.loggerFor import net.corda.node.* -import net.corda.node.services.config.FullNodeConfiguration +import net.corda.node.services.config.NodeConfiguration import net.corda.node.services.transactions.bftSMaRtSerialFilter import net.corda.node.shell.InteractiveShell import net.corda.node.utilities.registration.HTTPNetworkRegistrationService @@ -84,11 +84,11 @@ open class NodeStartup(val args: Array) { exitProcess(0) } - open protected fun preNetworkRegistration(conf: FullNodeConfiguration) = Unit + open protected fun preNetworkRegistration(conf: NodeConfiguration) = Unit - open protected fun createNode(conf: FullNodeConfiguration, versionInfo: VersionInfo): Node = Node(conf, versionInfo) + open protected fun createNode(conf: NodeConfiguration, versionInfo: VersionInfo): Node = Node(conf, versionInfo) - open protected fun startNode(conf: FullNodeConfiguration, versionInfo: VersionInfo, startTime: Long, cmdlineOptions: CmdLineOptions) { + open protected fun startNode(conf: NodeConfiguration, versionInfo: VersionInfo, startTime: Long, cmdlineOptions: CmdLineOptions) { val node = createNode(conf, versionInfo) if (cmdlineOptions.justGenerateNodeInfo) { // Perform the minimum required start-up logic to be able to write a nodeInfo to disk @@ -118,14 +118,14 @@ open class NodeStartup(val args: Array) { startedNode.internals.run() } - open protected fun logStartupInfo(versionInfo: VersionInfo, cmdlineOptions: CmdLineOptions, conf: FullNodeConfiguration) { + open protected fun logStartupInfo(versionInfo: VersionInfo, cmdlineOptions: CmdLineOptions, conf: NodeConfiguration) { logger.info("Vendor: ${versionInfo.vendor}") logger.info("Release: ${versionInfo.releaseVersion}") logger.info("Platform Version: ${versionInfo.platformVersion}") logger.info("Revision: ${versionInfo.revision}") val info = ManagementFactory.getRuntimeMXBean() logger.info("PID: ${info.name.split("@").firstOrNull()}") // TODO Java 9 has better support for this - logger.info("Main class: ${FullNodeConfiguration::class.java.protectionDomain.codeSource.location.toURI().path}") + logger.info("Main class: ${NodeConfiguration::class.java.protectionDomain.codeSource.location.toURI().path}") logger.info("CommandLine Args: ${info.inputArguments.joinToString(" ")}") logger.info("Application Args: ${args.joinToString(" ")}") logger.info("bootclasspath: ${info.bootClassPath}") @@ -140,7 +140,7 @@ open class NodeStartup(val args: Array) { logger.info("Starting as node on ${conf.p2pAddress}") } - open protected fun maybeRegisterWithNetworkAndExit(cmdlineOptions: CmdLineOptions, conf: FullNodeConfiguration) { + open protected fun maybeRegisterWithNetworkAndExit(cmdlineOptions: CmdLineOptions, conf: NodeConfiguration) { if (!cmdlineOptions.isRegistration) return println() println("******************************************************************") @@ -152,7 +152,7 @@ open class NodeStartup(val args: Array) { exitProcess(0) } - open protected fun loadConfigFile(cmdlineOptions: CmdLineOptions): FullNodeConfiguration { + open protected fun loadConfigFile(cmdlineOptions: CmdLineOptions): NodeConfiguration { try { return cmdlineOptions.loadConfig() } catch (e: ConfigException) { @@ -161,7 +161,7 @@ open class NodeStartup(val args: Array) { } } - open protected fun banJavaSerialisation(conf: FullNodeConfiguration) { + open protected fun banJavaSerialisation(conf: NodeConfiguration) { SerialFilter.install(if (conf.notary?.bftSMaRt != null) ::bftSMaRtSerialFilter else ::defaultSerialFilter) } diff --git a/node/src/main/kotlin/net/corda/node/services/RPCUserService.kt b/node/src/main/kotlin/net/corda/node/services/RPCUserService.kt index 2c4ed57754..58a50dceb7 100644 --- a/node/src/main/kotlin/net/corda/node/services/RPCUserService.kt +++ b/node/src/main/kotlin/net/corda/node/services/RPCUserService.kt @@ -18,6 +18,12 @@ interface RPCUserService { // TODO Or ditch this and consider something like Apache Shiro // TODO Need access to permission checks from inside flows and at other point during audit checking. class RPCUserServiceImpl(override val users: List) : RPCUserService { + init { + users.forEach { + require(it.username.matches("\\w+".toRegex())) { "Username ${it.username} contains invalid characters" } + } + } + override fun getUser(username: String): User? = users.find { it.username == username } } diff --git a/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt b/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt index 6d150a3ab3..6da102ab7f 100644 --- a/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt +++ b/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt @@ -1,5 +1,6 @@ package net.corda.node.services.config +import com.typesafe.config.Config import net.corda.core.identity.CordaX500Name import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.seconds @@ -7,6 +8,7 @@ import net.corda.node.internal.NetworkMapInfo import net.corda.node.services.messaging.CertificateChainCheckPolicy import net.corda.nodeapi.User import net.corda.nodeapi.config.NodeSSLConfiguration +import net.corda.nodeapi.config.parseAs import java.net.URL import java.nio.file.Path import java.util.* @@ -35,6 +37,12 @@ interface NodeConfiguration : NodeSSLConfiguration { val notary: NotaryConfig? val activeMQServer: ActiveMqServerConfiguration val additionalNodeInfoPollingFrequencyMsec: Long + val useHTTPS: Boolean + val p2pAddress: NetworkHostAndPort + val rpcAddress: NetworkHostAndPort? + val messagingServerAddress: NetworkHostAndPort? + val useTestClock: Boolean get() = false + val detectPublicIp: Boolean get() = true } data class NotaryConfig(val validating: Boolean, @@ -68,7 +76,14 @@ data class BridgeConfiguration(val retryIntervalMs: Long, data class ActiveMqServerConfiguration(val bridge: BridgeConfiguration) -data class FullNodeConfiguration( +fun Config.parseAsNodeConfiguration(): NodeConfiguration = this.parseAs() + +/** + * Implementation of [NodeConfiguration] + * This should be private but it can't because its constructor needs to be invoked reflectively by [parseAs] and + * kotlin doesn't allow that. + */ +data class NodeConfigurationImpl( /** This is not retrieved from the config file but rather from a command line argument. */ override val baseDirectory: Path, override val myLegalName: CordaX500Name, @@ -84,17 +99,17 @@ data class FullNodeConfiguration( override val rpcUsers: List, override val verifierType: VerifierType, override val messageRedeliveryDelaySeconds: Int = 30, - val useHTTPS: Boolean, - val p2pAddress: NetworkHostAndPort, - val rpcAddress: NetworkHostAndPort?, + override val useHTTPS: Boolean, + override val p2pAddress: NetworkHostAndPort, + override val rpcAddress: NetworkHostAndPort?, // TODO This field is slightly redundant as p2pAddress is sufficient to hold the address of the node's MQ broker. // Instead this should be a Boolean indicating whether that broker is an internal one started by the node or an external one - val messagingServerAddress: NetworkHostAndPort?, + override val messagingServerAddress: NetworkHostAndPort?, override val notary: NotaryConfig?, override val certificateChainCheckPolicies: List, override val devMode: Boolean = false, - val useTestClock: Boolean = false, - val detectPublicIp: Boolean = true, + override val useTestClock: Boolean = false, + override val detectPublicIp: Boolean = true, override val activeMQServer: ActiveMqServerConfiguration, override val additionalNodeInfoPollingFrequencyMsec: Long = 5.seconds.toMillis() ) : NodeConfiguration { @@ -103,10 +118,6 @@ data class FullNodeConfiguration( init { // This is a sanity feature do not remove. require(!useTestClock || devMode) { "Cannot use test clock outside of dev mode" } - // TODO Move this to ArtemisMessagingServer - rpcUsers.forEach { - require(it.username.matches("\\w+".toRegex())) { "Username ${it.username} contains invalid characters" } - } require(myLegalName.commonName == null) { "Common name must be null: $myLegalName" } require(minimumPlatformVersion >= 1) { "minimumPlatformVersion cannot be less than 1" } } diff --git a/node/src/test/kotlin/net/corda/node/services/RPCUserServiceTest.kt b/node/src/test/kotlin/net/corda/node/services/RPCUserServiceTest.kt new file mode 100644 index 0000000000..88bdae5579 --- /dev/null +++ b/node/src/test/kotlin/net/corda/node/services/RPCUserServiceTest.kt @@ -0,0 +1,20 @@ +package net.corda.node.services + + +import net.corda.nodeapi.User +import org.assertj.core.api.Assertions.assertThatThrownBy +import org.junit.Test + +class RPCUserServiceTest { + + @Test + fun `Artemis special characters not permitted in RPC usernames`() { + assertThatThrownBy { configWithRPCUsername("user.1") }.hasMessageContaining(".") + assertThatThrownBy { configWithRPCUsername("user*1") }.hasMessageContaining("*") + assertThatThrownBy { configWithRPCUsername("user#1") }.hasMessageContaining("#") + } + + private fun configWithRPCUsername(username: String) { + RPCUserServiceImpl(listOf(User(username, "password", setOf()))) + } +} \ No newline at end of file diff --git a/node/src/test/kotlin/net/corda/node/services/config/FullNodeConfigurationTest.kt b/node/src/test/kotlin/net/corda/node/services/config/FullNodeConfigurationTest.kt deleted file mode 100644 index bc40165c06..0000000000 --- a/node/src/test/kotlin/net/corda/node/services/config/FullNodeConfigurationTest.kt +++ /dev/null @@ -1,46 +0,0 @@ -package net.corda.node.services.config - -import net.corda.core.utilities.NetworkHostAndPort -import net.corda.core.utilities.seconds -import net.corda.nodeapi.User -import net.corda.testing.ALICE -import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties -import net.corda.testing.node.MockServices.Companion.makeTestDatabaseProperties -import org.assertj.core.api.Assertions.assertThatThrownBy -import org.junit.Test -import java.net.URL -import java.nio.file.Paths - -class FullNodeConfigurationTest { - @Test - fun `Artemis special characters not permitted in RPC usernames`() { - val testConfiguration = FullNodeConfiguration( - baseDirectory = Paths.get("."), - myLegalName = ALICE.name, - networkMapService = null, - emailAddress = "", - keyStorePassword = "cordacadevpass", - trustStorePassword = "trustpass", - dataSourceProperties = makeTestDataSourceProperties(ALICE.name.organisation), - database = makeTestDatabaseProperties(), - certificateSigningService = URL("http://localhost"), - rpcUsers = emptyList(), - verifierType = VerifierType.InMemory, - useHTTPS = false, - p2pAddress = NetworkHostAndPort("localhost", 0), - rpcAddress = NetworkHostAndPort("localhost", 1), - messagingServerAddress = null, - notary = null, - certificateChainCheckPolicies = emptyList(), - devMode = true, - activeMQServer = ActiveMqServerConfiguration(BridgeConfiguration(0, 0, 0.0)), - additionalNodeInfoPollingFrequencyMsec = 5.seconds.toMillis()) - - fun configWithRPCUsername(username: String) { - testConfiguration.copy(rpcUsers = listOf(User(username, "pass", emptySet()))) - } - assertThatThrownBy { configWithRPCUsername("user.1") }.hasMessageContaining(".") - assertThatThrownBy { configWithRPCUsername("user*1") }.hasMessageContaining("*") - assertThatThrownBy { configWithRPCUsername("user#1") }.hasMessageContaining("#") - } -} diff --git a/samples/irs-demo/src/integration-test/kotlin/net/corda/irs/IRSDemoTest.kt b/samples/irs-demo/src/integration-test/kotlin/net/corda/irs/IRSDemoTest.kt index f5c6feb921..0fa498891f 100644 --- a/samples/irs-demo/src/integration-test/kotlin/net/corda/irs/IRSDemoTest.kt +++ b/samples/irs-demo/src/integration-test/kotlin/net/corda/irs/IRSDemoTest.kt @@ -19,14 +19,10 @@ import net.corda.core.utilities.seconds import net.corda.finance.plugin.registerFinanceJSONMappers import net.corda.irs.contract.InterestRateSwap import net.corda.irs.web.IrsDemoWebApplication -import net.corda.node.services.config.FullNodeConfiguration +import net.corda.node.services.config.NodeConfiguration import net.corda.nodeapi.User import net.corda.test.spring.springDriver -import net.corda.testing.DUMMY_BANK_A -import net.corda.testing.DUMMY_BANK_B -import net.corda.testing.DUMMY_NOTARY -import net.corda.testing.IntegrationTestCategory -import net.corda.testing.chooseIdentity +import net.corda.testing.* import net.corda.testing.http.HttpApi import org.apache.commons.io.IOUtils import org.assertj.core.api.Assertions.assertThat @@ -96,7 +92,7 @@ class IRSDemoTest : IntegrationTestCategory { fun getFloatingLegFixCount(nodeApi: HttpApi) = getTrades(nodeApi)[0].calculation.floatingLegPaymentSchedule.count { it.value.rate.ratioUnit != null } - private fun getFixingDateObservable(config: FullNodeConfiguration): Observable { + private fun getFixingDateObservable(config: NodeConfiguration): Observable { val client = CordaRPCClient(config.rpcAddress!!) val proxy = client.start("user", "password").proxy val vaultUpdates = proxy.vaultTrackBy().updates diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt index 7de6c3fa5e..dd82a08a7a 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/driver/Driver.kt @@ -31,7 +31,6 @@ import net.corda.node.services.network.NetworkMapService import net.corda.node.utilities.ServiceIdentityGenerator import net.corda.nodeapi.NodeInfoFilesCopier import net.corda.nodeapi.User -import net.corda.nodeapi.config.parseAs import net.corda.nodeapi.config.toConfig import net.corda.nodeapi.internal.addShutdownHook import net.corda.testing.* @@ -198,13 +197,13 @@ sealed class NodeHandle { * will be added and that will be used. */ abstract val rpc: CordaRPCOps - abstract val configuration: FullNodeConfiguration + abstract val configuration: NodeConfiguration abstract val webAddress: NetworkHostAndPort data class OutOfProcess( override val nodeInfo: NodeInfo, override val rpc: CordaRPCOps, - override val configuration: FullNodeConfiguration, + override val configuration: NodeConfiguration, override val webAddress: NetworkHostAndPort, val debugPort: Int?, val process: Process, @@ -223,7 +222,7 @@ sealed class NodeHandle { data class InProcess( override val nodeInfo: NodeInfo, override val rpc: CordaRPCOps, - override val configuration: FullNodeConfiguration, + override val configuration: NodeConfiguration, override val webAddress: NetworkHostAndPort, val node: StartedNode, val nodeThread: Thread, @@ -697,7 +696,7 @@ class DriverDSL( _executorService?.shutdownNow() } - private fun establishRpc(config: FullNodeConfiguration, processDeathFuture: CordaFuture): CordaFuture { + private fun establishRpc(config: NodeConfiguration, processDeathFuture: CordaFuture): CordaFuture { val rpcAddress = config.rpcAddress!! val client = CordaRPCClient(rpcAddress) val connectionFuture = poll(executorService, "RPC connection") { @@ -913,7 +912,7 @@ class DriverDSL( } private fun startNodeInternal(config: Config, webAddress: NetworkHostAndPort, startInProcess: Boolean?, maximumHeapSize: String): CordaFuture { - val nodeConfiguration = config.parseAs() + val nodeConfiguration = config.parseAsNodeConfiguration() nodeInfoFilesCopier.addConfig(nodeConfiguration.baseDirectory) val onNodeExit: () -> Unit = { nodeInfoFilesCopier.removeConfig(nodeConfiguration.baseDirectory) @@ -983,7 +982,7 @@ class DriverDSL( private fun startInProcessNode( executorService: ScheduledExecutorService, - nodeConf: FullNodeConfiguration, + nodeConf: NodeConfiguration, config: Config, cordappPackages: List ): CordaFuture, Thread>> { @@ -1002,7 +1001,7 @@ class DriverDSL( private fun startOutOfProcessNode( executorService: ScheduledExecutorService, - nodeConf: FullNodeConfiguration, + nodeConf: NodeConfiguration, config: Config, quasarJarPath: String, debugPort: Int?, diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/NodeBasedTest.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/NodeBasedTest.kt index 36dfb43a45..938c4deb04 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/NodeBasedTest.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/NodeBasedTest.kt @@ -13,7 +13,6 @@ import net.corda.node.internal.cordapp.CordappLoader import net.corda.node.services.config.* import net.corda.node.utilities.ServiceIdentityGenerator import net.corda.nodeapi.User -import net.corda.nodeapi.config.parseAs import net.corda.nodeapi.config.toConfig import net.corda.testing.DUMMY_MAP import net.corda.testing.TestDependencyInjectionBase @@ -190,7 +189,7 @@ abstract class NodeBasedTest(private val cordappPackages: List = emptyLi ) + configOverrides ) - val parsedConfig = config.parseAs() + val parsedConfig = config.parseAsNodeConfiguration() val node = Node( parsedConfig, MOCK_VERSION_INFO.copy(platformVersion = platformVersion), diff --git a/tools/demobench/src/test/kotlin/net/corda/demobench/model/NodeConfigTest.kt b/tools/demobench/src/test/kotlin/net/corda/demobench/model/NodeConfigTest.kt index 0bf02c1ca8..aee32bc49c 100644 --- a/tools/demobench/src/test/kotlin/net/corda/demobench/model/NodeConfigTest.kt +++ b/tools/demobench/src/test/kotlin/net/corda/demobench/model/NodeConfigTest.kt @@ -5,10 +5,8 @@ import com.typesafe.config.ConfigValueFactory import net.corda.core.identity.CordaX500Name import net.corda.core.utilities.NetworkHostAndPort import net.corda.node.internal.NetworkMapInfo -import net.corda.node.internal.cordapp.CordappLoader -import net.corda.node.services.config.FullNodeConfiguration +import net.corda.node.services.config.parseAsNodeConfiguration import net.corda.nodeapi.User -import net.corda.nodeapi.config.parseAs import net.corda.nodeapi.config.toConfig import net.corda.testing.DUMMY_NOTARY import net.corda.webserver.WebServerConfig @@ -43,7 +41,7 @@ class NodeConfigTest { .withValue("baseDirectory", ConfigValueFactory.fromAnyRef(baseDir.toString())) .withFallback(ConfigFactory.parseResources("reference.conf")) .resolve() - val fullConfig = nodeConfig.parseAs() + val fullConfig = nodeConfig.parseAsNodeConfiguration() assertEquals(myLegalName, fullConfig.myLegalName) assertEquals(localPort(40002), fullConfig.rpcAddress) From 5b5a4bd2e4677c9fdfcf36d14e7aae5b10058b27 Mon Sep 17 00:00:00 2001 From: Christian Sailer Date: Fri, 27 Oct 2017 15:13:00 +0100 Subject: [PATCH 79/94] Code rewiew: turn dev mode options into a data class --- .../node/services/config/NodeConfiguration.kt | 10 ++++------ .../statemachine/StateMachineManagerImpl.kt | 3 +-- .../config/FullNodeConfigurationTest.kt | 17 ++++++++--------- 3 files changed, 13 insertions(+), 17 deletions(-) diff --git a/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt b/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt index a47a9bc1d5..353b76eb77 100644 --- a/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt +++ b/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt @@ -11,6 +11,8 @@ import java.net.URL import java.nio.file.Path import java.util.* +data class DevModeOptions(val disableCheckpointChecker: Boolean?) + interface NodeConfiguration : NodeSSLConfiguration { // myLegalName should be only used in the initial network registration, we should use the name from the certificate instead of this. // TODO: Remove this so we don't accidentally use this identity in the code? @@ -28,7 +30,7 @@ interface NodeConfiguration : NodeSSLConfiguration { val database: Properties? val rpcUsers: List val devMode: Boolean - val devModeOptions: Properties? + val devModeOptions: DevModeOptions? val certificateSigningService: URL val certificateChainCheckPolicies: List val verifierType: VerifierType @@ -42,10 +44,6 @@ interface NodeConfiguration : NodeSSLConfiguration { } } -fun NodeConfiguration.isDevModeOptionsFlagSet(flag: String): Boolean { - return this.devModeOptions?.get(flag).toString().toLowerCase() == "true" -} - data class NotaryConfig(val validating: Boolean, val raft: RaftConfig? = null, val bftSMaRt: BFTSMaRtConfiguration? = null, @@ -102,7 +100,7 @@ data class FullNodeConfiguration( override val notary: NotaryConfig?, override val certificateChainCheckPolicies: List, override val devMode: Boolean = false, - override val devModeOptions: Properties? = null, + override val devModeOptions: DevModeOptions? = null, val useTestClock: Boolean = false, val detectPublicIp: Boolean = true, override val activeMQServer: ActiveMqServerConfiguration, diff --git a/node/src/main/kotlin/net/corda/node/services/statemachine/StateMachineManagerImpl.kt b/node/src/main/kotlin/net/corda/node/services/statemachine/StateMachineManagerImpl.kt index 3e05a45e1f..04f9390e91 100644 --- a/node/src/main/kotlin/net/corda/node/services/statemachine/StateMachineManagerImpl.kt +++ b/node/src/main/kotlin/net/corda/node/services/statemachine/StateMachineManagerImpl.kt @@ -32,7 +32,6 @@ import net.corda.node.services.api.Checkpoint import net.corda.node.services.api.CheckpointStorage import net.corda.node.services.api.ServiceHubInternal import net.corda.node.services.config.NodeConfiguration -import net.corda.node.services.config.isDevModeOptionsFlagSet import net.corda.node.services.messaging.ReceivedMessage import net.corda.node.services.messaging.TopicSession import net.corda.node.utilities.* @@ -91,7 +90,7 @@ class StateMachineManagerImpl( private val mutex = ThreadBox(InnerState()) // This thread (only enabled in dev mode) deserialises checkpoints in the background to shake out bugs in checkpoint restore. private val checkpointCheckerThread = if (serviceHub.configuration.devMode - && !serviceHub.configuration.isDevModeOptionsFlagSet(NodeConfiguration.DISABLE_CHECKPOINT_CHECKER)) + && serviceHub.configuration.devModeOptions?.disableCheckpointChecker != true) newNamedSingleThreadExecutor("CheckpointChecker") else null @Volatile private var unrestorableCheckpoints = false diff --git a/node/src/test/kotlin/net/corda/node/services/config/FullNodeConfigurationTest.kt b/node/src/test/kotlin/net/corda/node/services/config/FullNodeConfigurationTest.kt index db1a61b6fb..d4090e2cee 100644 --- a/node/src/test/kotlin/net/corda/node/services/config/FullNodeConfigurationTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/config/FullNodeConfigurationTest.kt @@ -70,10 +70,10 @@ class FullNodeConfigurationTest { activeMQServer = ActiveMqServerConfiguration(BridgeConfiguration(0, 0, 0.0)), additionalNodeInfoPollingFrequencyMsec = 5.seconds.toMillis()) - fun configDebugOptions(devMode: Boolean, debugOptions: Properties?) { + fun configDebugOptions(devMode: Boolean, debugOptions: DevModeOptions?) { testConfiguration.copy(devMode = devMode, devModeOptions = debugOptions) } - val debugOptions = Properties() + val debugOptions = DevModeOptions(null) configDebugOptions(true, debugOptions) configDebugOptions(true,null) assertThatThrownBy{configDebugOptions(false, debugOptions)}.hasMessageMatching( "Cannot use devModeOptions outside of dev mode" ) @@ -105,14 +105,13 @@ class FullNodeConfigurationTest { activeMQServer = ActiveMqServerConfiguration(BridgeConfiguration(0, 0, 0.0)), additionalNodeInfoPollingFrequencyMsec = 5.seconds.toMillis()) - fun configDebugOptions(devMode: Boolean, debugOptions: Properties?) : NodeConfiguration { - return testConfiguration.copy(devMode = devMode, devModeOptions = debugOptions) + fun configDebugOptions(devMode: Boolean, devModeOptions: DevModeOptions?) : NodeConfiguration { + return testConfiguration.copy(devMode = devMode, devModeOptions = devModeOptions) } - val debugOptions = Properties() - assertFalse { configDebugOptions(true,null).isDevModeOptionsFlagSet("foo")} - assertFalse { configDebugOptions(true,debugOptions).isDevModeOptionsFlagSet("foo")} - debugOptions.setProperty("foo", "tRuE") - assert( configDebugOptions(true, debugOptions).isDevModeOptionsFlagSet("foo")) + assertFalse { configDebugOptions(true,null).devModeOptions?.disableCheckpointChecker == true} + assertFalse { configDebugOptions(true,DevModeOptions(null)).devModeOptions?.disableCheckpointChecker == true} + assertFalse { configDebugOptions(true,DevModeOptions(false)).devModeOptions?.disableCheckpointChecker == true} + assert ( configDebugOptions(true,DevModeOptions(true)).devModeOptions?.disableCheckpointChecker == true) } } From cde1587ac70f78f3e8d226247f4da2a7281c4c2c Mon Sep 17 00:00:00 2001 From: Shams Asari Date: Sat, 28 Oct 2017 13:26:06 +0100 Subject: [PATCH 80/94] Cleaned up devModeOptions --- .../BankOfCordaDriverKt___Issue_Web.xml | 1 + .../BankOfCordaDriverKt___Run_Stack.xml | 1 + .../Explorer___demo_nodes.xml | 1 + .../Explorer___demo_nodes__simulation_.xml | 1 + .../node/services/config/NodeConfiguration.kt | 11 +-- .../statemachine/StateMachineManagerImpl.kt | 9 +- .../config/NodeConfigurationImplTest.kt | 99 +++++++------------ .../events/NodeSchedulerServiceTest.kt | 5 +- 8 files changed, 52 insertions(+), 76 deletions(-) diff --git a/.idea/runConfigurations/BankOfCordaDriverKt___Issue_Web.xml b/.idea/runConfigurations/BankOfCordaDriverKt___Issue_Web.xml index 321d3d2d06..b4b35f6ef4 100644 --- a/.idea/runConfigurations/BankOfCordaDriverKt___Issue_Web.xml +++ b/.idea/runConfigurations/BankOfCordaDriverKt___Issue_Web.xml @@ -1,6 +1,7 @@ + Kotlin/ JavaDoc
-Discourse Forums -
Slack
-{% endblock %} +Stack Overflow +
+{% endblock %} \ No newline at end of file diff --git a/docs/source/_templates/layout_for_doc_website.html b/docs/source/_templates/layout_for_doc_website.html index 2272ea7fb4..f0992030c9 100644 --- a/docs/source/_templates/layout_for_doc_website.html +++ b/docs/source/_templates/layout_for_doc_website.html @@ -6,10 +6,10 @@
API reference: Kotlin/ JavaDoc
-Discourse Forums -
Slack
+Stack Overflow +

diff --git a/docs/source/index.rst b/docs/source/index.rst index 75f9733f4e..a655f25c02 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -10,11 +10,15 @@ quick introduction to distributed ledgers and how Corda is different, then watch -Want to see Corda running? Download our demonstration application `DemoBench `_ or follow our :doc:`quickstart guide `. +Want to see Corda running? Download our demonstration application `DemoBench `_ or +follow our :doc:`quickstart guide `. -If you want to start coding on Corda, then familiarise yourself with the :doc:`key concepts `, then read our :doc:`Hello, World! tutorial `. For the background behind Corda, read the non-technical `introductory white paper`_ or for more detail, the `technical white paper`_. +If you want to start coding on Corda, then familiarise yourself with the :doc:`key concepts `, then read +our :doc:`Hello, World! tutorial `. For the background behind Corda, read the non-technical +`introductory white paper`_ or for more detail, the `technical white paper`_. -If you have questions or comments, then get in touch with us either on `Slack `_, `Discourse `_, or write a question on `stackoverflow `_ . +If you have questions or comments, then get in touch on `Slack `_ or write a question on +`Stack Overflow `_ . We look forward to seeing what you can do with Corda! From ecbb4330da9d9cc087abbdb204b2d136618b7d28 Mon Sep 17 00:00:00 2001 From: Katelyn Baker Date: Mon, 30 Oct 2017 11:15:05 +0000 Subject: [PATCH 82/94] CORDA-553 - Review comments --- .../amqp/AMQPDescriptorRegistry.kt | 2 +- .../serialization/amqp/SupportedTransforms.kt | 6 ++--- .../serialization/amqp/TansformTypes.kt | 7 +++--- .../amqp/EnumEvolvabilityTests.kt | 4 +-- .../serialization/amqp/EvolvabilityTests.kt | 23 ++++++------------ ...EnumEvolvabilityTests.testUnknownTransform | Bin 785 -> 785 bytes ...bilityTests.addAdditionalParamNotMandatory | Bin 292 -> 292 bytes .../EvolvabilityTests.addAndRemoveParameters | Bin 403 -> 403 bytes ...yTests.addMandatoryFieldWithAltConstructor | Bin 298 -> 298 bytes ...dMandatoryFieldWithAltReorderedConstructor | Bin 400 -> 400 bytes ...FieldWithAltReorderedConstructorAndRemoval | Bin 416 -> 416 bytes .../amqp/EvolvabilityTests.changeSubType | Bin 629 -> 629 bytes .../amqp/EvolvabilityTests.multiVersion.1 | Bin 307 -> 307 bytes .../amqp/EvolvabilityTests.multiVersion.2 | Bin 340 -> 340 bytes .../amqp/EvolvabilityTests.multiVersion.3 | Bin 385 -> 385 bytes ...volvabilityTests.multiVersionWithRemoval.1 | Bin 351 -> 351 bytes ...volvabilityTests.multiVersionWithRemoval.2 | Bin 405 -> 405 bytes ...volvabilityTests.multiVersionWithRemoval.3 | Bin 438 -> 438 bytes .../amqp/EvolvabilityTests.removeParameters | Bin 391 -> 391 bytes ...vabilityTests.simpleOrderSwapDifferentType | Bin 324 -> 324 bytes .../EvolvabilityTests.simpleOrderSwapSameType | Bin 315 -> 315 bytes 21 files changed, 18 insertions(+), 24 deletions(-) diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/AMQPDescriptorRegistry.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/AMQPDescriptorRegistry.kt index 0c9a06b8fd..78ef9dc52a 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/AMQPDescriptorRegistry.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/AMQPDescriptorRegistry.kt @@ -10,7 +10,7 @@ import org.apache.qpid.proton.amqp.UnsignedLong * Repeated here for brevity: * 50530 - R3 - Mike Hearn - mike&r3.com */ -const val DESCRIPTOR_TOP_32BITS: Long = 0xc562 shl(32 + 16) +const val DESCRIPTOR_TOP_32BITS: Long = 0xc562L shl(32 + 16) /** * AMQP desriptor ID's for our custom types. diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SupportedTransforms.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SupportedTransforms.kt index df3f4f8d45..b785d0e8c6 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SupportedTransforms.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SupportedTransforms.kt @@ -38,9 +38,9 @@ private val singleExtract = { x: Annotation -> listOf(x) } // Transform annotation used to test the handling of transforms the de-serialising node doesn't understand. At // some point test cases will have been created with this transform applied. -//@Target(AnnotationTarget.CLASS) -//@Retention(AnnotationRetention.RUNTIME) -//annotation class UnknownTransformAnnotation(val a: Int, val b: Int, val c: Int) +// @Target(AnnotationTarget.CLASS) +// @Retention(AnnotationRetention.RUNTIME) +// annotation class UnknownTransformAnnotation(val a: Int, val b: Int, val c: Int) /** * Utility list of all transforms we support that simplifies our generation code. diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/TansformTypes.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/TansformTypes.kt index de4f2d8703..5bb6fc05d5 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/TansformTypes.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/TansformTypes.kt @@ -35,14 +35,15 @@ enum class TransformTypes(val build: (Annotation) -> Transform) : DescribedType Rename({ a -> RenameSchemaTransform((a as CordaSerializationTransformRename).from, a.to) }) { override fun getDescriptor(): Any = DESCRIPTOR override fun getDescribed(): Any = ordinal - }; + } // Transform used to test the unknown handler, leave this at as the final constant, uncomment // when regenerating test cases - if Java had a pre-processor this would be much neater // - //UnknownTest({ a -> UnknownTestTransform((a as UnknownTransformAnnotation).a, a.b, a.c)}) { + //,UnknownTest({ a -> UnknownTestTransform((a as UnknownTransformAnnotation).a, a.b, a.c)}) { // override fun getDescriptor(): Any = DESCRIPTOR // override fun getDescribed(): Any = ordinal - //}; + //} + ; companion object : DescribedTypeConstructor { val DESCRIPTOR = AMQPDescriptorRegistry.TRANSFORM_ELEMENT_KEY.amqpDescriptor diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/EnumEvolvabilityTests.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/EnumEvolvabilityTests.kt index 2fffc13b28..6626f558b9 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/EnumEvolvabilityTests.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/EnumEvolvabilityTests.kt @@ -10,7 +10,6 @@ import kotlin.test.assertEquals import kotlin.test.assertTrue class EnumEvolvabilityTests { -// var localPath = "file:///Users/katelynbaker/srcs-ide/corda/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp" var localPath = "file:///home/katelyn/srcs/corda/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp" @@ -408,7 +407,7 @@ class EnumEvolvabilityTests { } - // @UnknownTransformAnnotation (10, 20, 30) + //@UnknownTransformAnnotation (10, 20, 30) enum class WithUnknownTest { A, B, C, D } @@ -417,6 +416,7 @@ class EnumEvolvabilityTests { // To regenerate the types for this test uncomment the UnknownTransformAnnotation from // TransformTypes.kt and SupportedTransforms.kt + // ALSO: remember to re-annotate the enum WithUnkownTest above @Test fun testUnknownTransform() { val resource = "EnumEvolvabilityTests.testUnknownTransform" diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.kt index f5ab608e44..173e7fd87c 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.kt @@ -30,8 +30,7 @@ class EvolvabilityTests { // Original version of the class for the serialised version of this class // data class C (val a: Int, val b: Int) - // val sc = SerializationOutput(sf).serialize(C(A, B)) - // File(URI("$localPath/$resource")).writeBytes(sc.bytes) + // File(URI("$localPath/$resource")).writeBytes(SerializationOutput(sf).serialize(C(A, B)).bytes) // new version of the class, in this case the order of the parameters has been swapped data class C(val b: Int, val a: Int) @@ -55,8 +54,7 @@ class EvolvabilityTests { // Original version of the class as it was serialised // data class C (val a: Int, val b: String) - // val sc = SerializationOutput(sf).serialize(C(A, B)) - // File(URI("$localPath/$resource")).writeBytes(sc.bytes) + // File(URI("$localPath/$resource")).writeBytes(SerializationOutput(sf).serialize(C(A, B)).bytes) // new version of the class, in this case the order of the parameters has been swapped data class C(val b: String, val a: Int) @@ -78,8 +76,7 @@ class EvolvabilityTests { // Original version of the class as it was serialised // data class C(val a: Int) - // val sc = SerializationOutput(sf).serialize(C(A)) - // File(URI("$localPath/$resource")).writeBytes(sc.bytes) + // File(URI("$localPath/$resource")).writeBytes(SerializationOutput(sf).serialize(C(A)).bytes) data class C(val a: Int, val b: Int?) @@ -90,7 +87,7 @@ class EvolvabilityTests { assertEquals(A, deserializedC.a) assertEquals(null, deserializedC.b) - } + } @Test(expected = NotSerializableException::class) fun addAdditionalParam() { @@ -130,8 +127,7 @@ class EvolvabilityTests { // Original version of the class as it was serialised // data class CC(val a: Int, val b: String, val c: String, val d: Int) - // val scc = SerializationOutput(sf).serialize(CC(A, B, C, D)) - // File(URI("$localPath/$resource")).writeBytes(scc.bytes) + // File(URI("$localPath/$resource")).writeBytes(SerializationOutput(sf).serialize(CC(A, B, C, D)).bytes) data class CC(val b: String, val d: Int) @@ -158,8 +154,7 @@ class EvolvabilityTests { // Original version of the class as it was serialised // data class CC(val a: Int, val b: String, val c: String, val d: Int) - // val scc = SerializationOutput(sf).serialize(CC(A, B, C, D)) - // File(URI("$localPath/$resource")).writeBytes(scc.bytes) + // File(URI("$localPath/$resource")).writeBytes(SerializationOutput(sf).serialize(CC(A, B, C, D)).bytes) data class CC(val a: Int, val e: Boolean?, val d: Int) @@ -181,8 +176,7 @@ class EvolvabilityTests { // Original version of the class as it was serialised // data class CC(val a: Int) - // val scc = SerializationOutput(sf).serialize(CC(A)) - // File(URI("$localPath/$resource")).writeBytes(scc.bytes) + // File(URI("$localPath/$resource")).writeBytes(SerializationOutput(sf).serialize(CC(A)).bytes) @Suppress("UNUSED") data class CC(val a: Int, val b: String) { @@ -236,8 +230,7 @@ class EvolvabilityTests { // Original version of the class as it was serialised // data class CC(val a: Int, val b: Int, val c: String) - // val scc = SerializationOutput(sf).serialize(CC(A, B, C)) - // File(URI("$localPath/$resource")).writeBytes(scc.bytes) + // File(URI("$localPath/$resource")).writeBytes(SerializationOutput(sf).serialize(CC(A, B, C)).bytes) @Suppress("UNUSED") data class CC(val a: Int, val b: Int, val c: String, val d: String) { diff --git a/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EnumEvolvabilityTests.testUnknownTransform b/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EnumEvolvabilityTests.testUnknownTransform index 74c557f8841e8ae8104dd447af0628e58fc06bd7..2916621d548edd3e0443ad168e191bc6391e2800 100644 GIT binary patch delta 267 zcmbQpHjzyrIlm|+k&%Icq2Xu}kYt!BkfaG=F)&?VU|{?Q6k`N2z5*FcFjWk!2Y#_G z44Ax-(RJbjer~7+<^yU>6J=B;%Q3RCL4+o2F=oM-vlvrgOco~naHt;k1MEx-866ig z8oIv~Qb XkUKOlJ1@UHFC?|NB!nwOBt#AXV7xyD delta 267 zcmbQpHjzyrIlm|+k&%Icq2WIe98H=ikfaG=F)&?VU|{?Qlwt%iz5*FcFjWk!2Y#_G z44Ax-(RJbjer~7+<^yU>6J=B;%Q3RCL4+o2F=oM-vlvrgOco~naHt;k1MEx-866ig z8oIv~Qb XkUKOlJ1@UHFC?|NB!nwOBt#AXpIUa# diff --git a/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.addAdditionalParamNotMandatory b/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.addAdditionalParamNotMandatory index 7892b7f89b2195eab0d703c515fd399b6dbf28f7..3eb318bb7e9978619d29b517830c28722d805357 100644 GIT binary patch delta 100 zcmZ3&w1i0@Ilm|+k&%Icq2Xu}kYt!BU@it>F)$rC#(3Z`BU~mnn+>YwoW43#4a)%m c_Jxdz3z;+XN?aE*8aOyQ!PGNw9%N(y03zZUQUCw| delta 100 zcmZ3&w1i0@Ilm|+k&%Icq2WIe98H=iU@it>F)$rC#(3Z`BU~mnn+>YwoW43#4a)%m c_Jxdz3z;+XN?aE*8aOyQ!PGNw9%N(y0M?>0IsgCw diff --git a/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.addAndRemoveParameters b/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.addAndRemoveParameters index d92fd43a0389b705fb7185aa77bac8f253056354..52f755150520b7ec758078c3a2522f158cf471ce 100644 GIT binary patch delta 198 zcmbQtJegS_Ilm|+k&%Icq2Xu}kYt!B;Hm^+F)&?VU|>`MiZOy1(jW$0Re2U0RQ+@P zTBv%K0|M*|850*WXXcf-E@U)taCCyHXJ9!X2$V}&$W~lZl$n?A>VPSkj7t(^Y6@0U JIS(>2008G^Fc<&; delta 198 zcmbQtJegS_Ilm|+k&%Icq2WIe98H=i;Hm^+F)&?VU|>`MN-=^M(jW$0Re2U0RQ+@P zTBv%K0|M*|850*WXXcf-E@U)taCCyHXJ9!X2$V}&$W~lZl$n?A>VPSkj7t(^Y6@0U JIS(>2006ncQ}h4; diff --git a/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.addMandatoryFieldWithAltConstructor b/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.addMandatoryFieldWithAltConstructor index 43d786ee3df01a947174bab0dba886a8c53cc59d..f6cb7d74d4c9cabc27c359d6ac9c756992bb8f7f 100644 GIT binary patch delta 111 zcmZ3*w2Da}Ilm|+k&%Icq2Xu}kYt!BU@it>F)$rC!+78%BU~o7kQ*w)d_aw9qKt~J l!vR^iGy}^40rrKAi3^!C^GaM7G8#BII>EFua2{l2001cx8%F>D delta 111 zcmZ3*w2Da}Ilm|+k&%Icq2WIe98H=iU@it>F)$rC!+78%BU~o7kQ*w)d_aw9qKt~J l!vR^iGy}^40rrKAi3^!C^GaM7G8#BII>EFua2{l2004|JGcN!D diff --git a/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.addMandatoryFieldWithAltReorderedConstructor b/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.addMandatoryFieldWithAltReorderedConstructor index b51b42dd55f90f11e5f1313280acb92eb9eeac43..584aef76d73795a61ea36fa961ca83a4b598122d 100644 GIT binary patch delta 152 zcmbQhJb_stIlm|+k&%Icq2Xu}kYt!B;I9N>F)&?VU|{r~Rbv$!MAc+Q wMtwV|29^T?>F)&?VU|{r~Rbv$!MAc+Q wMtwV|29^T?>F)&?VU|>`SiZOy1av%m=Rbv+$MAc+L wMtwV|29^T?>F)&?VU|>`SN-=^Mav%m=Rbv+$MAc+L wMtwV|29^T?>6k`N2?g1H0a8=V%*dVGV zG3vwAvm6j$U&xrakU2B2#C0K~frFzHO#S3Q#uP57v8)GHPFxT;S%8TXYQ$uDCVh3N NBpx#uI1e&1002TADzyLr delta 180 zcmey$@|8s(Ilm|+k&%Icq2WIe98H=ikfH=(F)&?VU|@U>lwt%i?g1H0a8=V%*dVGV zG3vwAvm6j$U&xrakU2B2#C0K~frFzHO#S3Q#uP57v8)GHPFxT;S%8TXYQ$uDCVh3N NBpx#uI1e&1001JpQjGur diff --git a/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.multiVersion.1 b/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.multiVersion.1 index 667eb7472c0b559ac22f4b8f5f6ae2ea1e991017..a7da3eeda3a6c6d33c63be16d2a32e1fbc01661f 100644 GIT binary patch delta 128 zcmdnYw3$gDIlm|+k&%Icq2Xu}kYt!BU?T=$F)$stzIlm|+k&%Icq2Xu}kYt!B;K>GM#f!mM)mbpsYkeqdvI3)6JVYS_%K-uQ ig^bAynKScBTo*DLI5;}NjDyOh;E_wkD#v+{kpTdkJ14~e delta 161 zcmbQrJe64>Ilm|+k&%Icq2WIe98H=i;K>GM#f!mM)mbpsYkeqdvI3)6JVYS_%K-uQ ig^bAynKScBTo*DLI5;}NjDyOh;E_wkD#v+{kpTc#kV~Ne diff --git a/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.multiVersionWithRemoval.3 b/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.multiVersionWithRemoval.3 index 2c3b1ce9eb0bd50812abb242891940aba8d41857..c00a28213633119c60fb277f46a022fe507f7a9c 100644 GIT binary patch delta 207 zcmdnSyp35PIlm|+k&%Icq2Xu}kYt!B;HLy(F)&?VU|_TZiZOy1rXU7fRYMjVRQ(5i zn0l520_+PJlNK^(=9RcEWHfMabb_g$?8qn<4>cB|ECrh~28diL9=SBEa-0Vl831}| BF-`yg delta 207 zcmdnSyp35PIlm|+k&%Icq2WIe98H=i;HLy(F)&?VU|_TZN-=^MrXU7fRYMjVRQ(5i zn0l520_+PJlNK^(=9RcEWHfMabb_g$?8qn<4>cB|ECrh~28diL9=SBEa-0Vl832#` BStbAg diff --git a/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.removeParameters b/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.removeParameters index 926661d87b79097940bd60201fd8b13576e1b4c0..37c419f0765fcc4bba5b63dc15a500b085729edb 100644 GIT binary patch delta 181 zcmZo?Zf6!q&M!(yWMp7qXgHb#BpD_OxGF(d3``do7#JmhVvHaLKZpTWRUXF%RexI_ z%9<>|D3%D-%5p%EeIaAgLbl?PqRhN>R|f}2CzxIah-5M@NdchFl!eThc_pq384Zv% Javo%4002j8Dop?Y delta 181 zcmZo?Zf6!q&M!(yWMp7qX!s8VN0TNBxGF(d3``do7#JmhQj8!5KZpTWRUXF%RexI_ z%9<>|D3%D-%5p%EeIaAgLbl?PqRhN>R|f}2CzxIah-5M@NdchFl!eThc_pq384Zv% Javo%40074PPALEY diff --git a/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.simpleOrderSwapDifferentType b/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.simpleOrderSwapDifferentType index 61da589b24be6143afd29d2feaf3b3ddbc092d65..cc726a13fd0728a971e5e1e3d13ab3428b19690d 100644 GIT binary patch delta 129 zcmX@Ybc9JDIlm|+k&%Icq2Xu}kYt!BU?T=$F)$r?$avr$BU~malMSloqP`1M4a)%m u_Jxdz3z;+XN?aE*8aOyQ!PGOb91sM`B`sttE-A{)OLuiZmgGFh$N&J*ZXx{u delta 129 zcmX@Ybc9JDIlm|+k&%Icq2WIe98H=iU?T=$F)$r?$avr$BU~malMSloqP`1M4a)%m u_Jxdz3z;+XN?aE*8aOyQ!PGOb91sM`B`sttE-A{)OLuiZmgGFh$N&Ia@IB!G diff --git a/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.simpleOrderSwapSameType b/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.simpleOrderSwapSameType index 377e82d50e84774271bdecccc9fd376139dd9ea2..dc9fae7152dcf5d7e8a9b33918cad39be6c2a4ad 100644 GIT binary patch delta 110 zcmdnZw3|sFIlm|+k&%Icq2Xu}kYt!BU?~P+F)$st&3NDjBU~mvg$=6aq&|%GPfQ)E gn&p51`$EQ~h0K|GC9Vq@4ICVuU^*E%4>B?U0AEoYbpQYW delta 110 zcmdnZw3|sFIlm|+k&%Icq2WIe98H=iU?~P+F)$st&3NDjBU~mvg$=6aq&|%GPfQ)E gn&p51`$EQ~h0K|GC9Vq@4ICVuU^*E%4>B?U0BbckSpWb4 From 851918dd76dfa628fa18701cfe832f816fe14822 Mon Sep 17 00:00:00 2001 From: Katelyn Baker Date: Mon, 30 Oct 2017 11:34:54 +0000 Subject: [PATCH 83/94] CORDA-553 - API standard doc update The evolution annotations are being added to core which is being flagged as a breaking API change. Given these are part of the upcoming introduction of AMQP they're not actually breaking anything we've not enabled yet --- .ci/api-current.txt | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/.ci/api-current.txt b/.ci/api-current.txt index e66520b6f2..a1579cb063 100644 --- a/.ci/api-current.txt +++ b/.ci/api-current.txt @@ -2436,6 +2436,20 @@ public interface net.corda.core.serialization.ClassWhitelist ## public @interface net.corda.core.serialization.CordaSerializable ## +public @interface net.corda.core.serialization.CordaSerializationTransformEnumDefault + public abstract String new() + public abstract String old() +## +public @interface net.corda.core.serialization.CordaSerializationTransformEnumDefaults + public abstract net.corda.core.serialization.CordaSerializationTransformEnumDefault[] value() +## +public @interface net.corda.core.serialization.CordaSerializationTransformRename + public abstract String from() + public abstract String to() +## +public @interface net.corda.core.serialization.CordaSerializationTransformRenames + public abstract net.corda.core.serialization.CordaSerializationTransformRename[] value() +## public @interface net.corda.core.serialization.DeprecatedConstructorForDeserialization public abstract int version() ## From 05d6fb91c72bc37d0d70282cff1bbea595ac64fc Mon Sep 17 00:00:00 2001 From: Andrzej Cichocki Date: Mon, 30 Oct 2017 11:41:53 +0000 Subject: [PATCH 84/94] Reinstate Simulation config overrides for notary. (#1947) --- .../net/corda/confidential/SwapIdentitiesFlowTests.kt | 4 ++-- .../src/test/kotlin/net/corda/docs/CustomVaultQueryTest.kt | 2 +- .../kotlin/net/corda/docs/FxTransactionBuildTutorialTest.kt | 2 +- .../net/corda/docs/WorkflowTransactionBuildTutorialTest.kt | 2 +- .../test/kotlin/net/corda/node/internal/CordaServiceTest.kt | 3 +-- .../kotlin/net/corda/node/services/NotaryChangeTests.kt | 5 +++-- .../net/corda/node/services/events/ScheduledFlowTests.kt | 3 +-- .../corda/node/services/transactions/NotaryServiceTests.kt | 2 +- .../services/transactions/ValidatingNotaryServiceTests.kt | 2 +- .../main/kotlin/net/corda/netmap/simulation/Simulation.kt | 2 +- .../kotlin/net/corda/testing/FlowStackSnapshotTest.kt | 2 +- .../src/main/kotlin/net/corda/testing/node/MockNode.kt | 6 ++---- 12 files changed, 16 insertions(+), 19 deletions(-) diff --git a/confidential-identities/src/test/kotlin/net/corda/confidential/SwapIdentitiesFlowTests.kt b/confidential-identities/src/test/kotlin/net/corda/confidential/SwapIdentitiesFlowTests.kt index 27fb5b6791..d323b38b30 100644 --- a/confidential-identities/src/test/kotlin/net/corda/confidential/SwapIdentitiesFlowTests.kt +++ b/confidential-identities/src/test/kotlin/net/corda/confidential/SwapIdentitiesFlowTests.kt @@ -56,7 +56,7 @@ class SwapIdentitiesFlowTests { val mockNet = MockNetwork(threadPerNode = true) // Set up values we'll need - val notaryNode = mockNet.createNotaryNode(DUMMY_NOTARY.name) + val notaryNode = mockNet.createNotaryNode() val aliceNode = mockNet.createPartyNode(ALICE.name) val bobNode = mockNet.createPartyNode(BOB.name) val bob: Party = bobNode.services.myInfo.singleIdentity() @@ -81,7 +81,7 @@ class SwapIdentitiesFlowTests { val mockNet = MockNetwork(threadPerNode = true) // Set up values we'll need - val notaryNode = mockNet.createNotaryNode(DUMMY_NOTARY.name) + val notaryNode = mockNet.createNotaryNode() val aliceNode = mockNet.createPartyNode(ALICE.name) val bobNode = mockNet.createPartyNode(BOB.name) val bob: Party = bobNode.services.myInfo.singleIdentity() diff --git a/docs/source/example-code/src/test/kotlin/net/corda/docs/CustomVaultQueryTest.kt b/docs/source/example-code/src/test/kotlin/net/corda/docs/CustomVaultQueryTest.kt index 9690a5ab41..a4639256f3 100644 --- a/docs/source/example-code/src/test/kotlin/net/corda/docs/CustomVaultQueryTest.kt +++ b/docs/source/example-code/src/test/kotlin/net/corda/docs/CustomVaultQueryTest.kt @@ -35,7 +35,7 @@ class CustomVaultQueryTest { "net.corda.docs" ) ) - mockNet.createNotaryNode(legalName = DUMMY_NOTARY.name) + mockNet.createNotaryNode() nodeA = mockNet.createPartyNode() nodeB = mockNet.createPartyNode() nodeA.internals.registerInitiatedFlow(TopupIssuerFlow.TopupIssuer::class.java) diff --git a/docs/source/example-code/src/test/kotlin/net/corda/docs/FxTransactionBuildTutorialTest.kt b/docs/source/example-code/src/test/kotlin/net/corda/docs/FxTransactionBuildTutorialTest.kt index 7a5193c0b0..5e31ac39ba 100644 --- a/docs/source/example-code/src/test/kotlin/net/corda/docs/FxTransactionBuildTutorialTest.kt +++ b/docs/source/example-code/src/test/kotlin/net/corda/docs/FxTransactionBuildTutorialTest.kt @@ -26,7 +26,7 @@ class FxTransactionBuildTutorialTest { @Before fun setup() { mockNet = MockNetwork(threadPerNode = true, cordappPackages = listOf("net.corda.finance.contracts.asset", CashSchemaV1::class.packageName)) - mockNet.createNotaryNode(legalName = DUMMY_NOTARY.name) + mockNet.createNotaryNode() nodeA = mockNet.createPartyNode() nodeB = mockNet.createPartyNode() nodeB.internals.registerInitiatedFlow(ForeignExchangeRemoteFlow::class.java) diff --git a/docs/source/example-code/src/test/kotlin/net/corda/docs/WorkflowTransactionBuildTutorialTest.kt b/docs/source/example-code/src/test/kotlin/net/corda/docs/WorkflowTransactionBuildTutorialTest.kt index 833f4c140d..9f98082730 100644 --- a/docs/source/example-code/src/test/kotlin/net/corda/docs/WorkflowTransactionBuildTutorialTest.kt +++ b/docs/source/example-code/src/test/kotlin/net/corda/docs/WorkflowTransactionBuildTutorialTest.kt @@ -34,7 +34,7 @@ class WorkflowTransactionBuildTutorialTest { fun setup() { mockNet = MockNetwork(threadPerNode = true, cordappPackages = listOf("net.corda.docs")) // While we don't use the notary, we need there to be one on the network - mockNet.createNotaryNode(legalName = DUMMY_NOTARY.name) + mockNet.createNotaryNode() val aliceNode = mockNet.createPartyNode(ALICE_NAME) val bobNode = mockNet.createPartyNode(BOB_NAME) aliceNode.internals.registerInitiatedFlow(RecordCompletionFlow::class.java) diff --git a/node/src/test/kotlin/net/corda/node/internal/CordaServiceTest.kt b/node/src/test/kotlin/net/corda/node/internal/CordaServiceTest.kt index 6135863388..14242aecb3 100644 --- a/node/src/test/kotlin/net/corda/node/internal/CordaServiceTest.kt +++ b/node/src/test/kotlin/net/corda/node/internal/CordaServiceTest.kt @@ -13,7 +13,6 @@ import net.corda.core.utilities.ProgressTracker import net.corda.finance.DOLLARS import net.corda.finance.flows.CashIssueFlow import net.corda.node.internal.cordapp.DummyRPCFlow -import net.corda.testing.DUMMY_NOTARY import net.corda.testing.node.MockNetwork import org.junit.After import org.junit.Before @@ -82,7 +81,7 @@ class CordaServiceTest { @Before fun start() { mockNet = MockNetwork(threadPerNode = true, cordappPackages = listOf("net.corda.node.internal","net.corda.finance")) - notaryNode = mockNet.createNotaryNode(legalName = DUMMY_NOTARY.name, validating = true) + notaryNode = mockNet.createNotaryNode() nodeA = mockNet.createNode() mockNet.startNodes() } diff --git a/node/src/test/kotlin/net/corda/node/services/NotaryChangeTests.kt b/node/src/test/kotlin/net/corda/node/services/NotaryChangeTests.kt index 1beceb0855..ee21427fdd 100644 --- a/node/src/test/kotlin/net/corda/node/services/NotaryChangeTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/NotaryChangeTests.kt @@ -16,6 +16,7 @@ import net.corda.node.internal.StartedNode import net.corda.testing.* import net.corda.testing.contracts.DummyContract import net.corda.testing.node.MockNetwork +import net.corda.testing.node.MockNodeParameters import org.assertj.core.api.Assertions.assertThatExceptionOfType import org.junit.After import org.junit.Before @@ -37,10 +38,10 @@ class NotaryChangeTests { @Before fun setUp() { mockNet = MockNetwork(cordappPackages = listOf("net.corda.testing.contracts")) - oldNotaryNode = mockNet.createNotaryNode(legalName = DUMMY_NOTARY.name) + oldNotaryNode = mockNet.createNotaryNode(MockNodeParameters(legalName = DUMMY_NOTARY.name)) clientNodeA = mockNet.createNode() clientNodeB = mockNet.createNode() - newNotaryNode = mockNet.createNotaryNode(legalName = DUMMY_NOTARY.name.copy(organisation = "Dummy Notary 2")) + newNotaryNode = mockNet.createNotaryNode(MockNodeParameters(legalName = DUMMY_NOTARY.name.copy(organisation = "Dummy Notary 2"))) mockNet.runNetwork() // Clear network map registration messages oldNotaryParty = newNotaryNode.services.networkMapCache.getNotary(DUMMY_NOTARY_SERVICE_NAME)!! newNotaryParty = newNotaryNode.services.networkMapCache.getNotary(DUMMY_NOTARY_SERVICE_NAME.copy(organisation = "Dummy Notary 2"))!! diff --git a/node/src/test/kotlin/net/corda/node/services/events/ScheduledFlowTests.kt b/node/src/test/kotlin/net/corda/node/services/events/ScheduledFlowTests.kt index 9999b927e0..db4b9dde89 100644 --- a/node/src/test/kotlin/net/corda/node/services/events/ScheduledFlowTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/events/ScheduledFlowTests.kt @@ -16,7 +16,6 @@ import net.corda.core.transactions.TransactionBuilder import net.corda.core.utilities.getOrThrow import net.corda.node.internal.StartedNode import net.corda.node.services.statemachine.StateMachineManager -import net.corda.testing.DUMMY_NOTARY import net.corda.testing.chooseIdentity import net.corda.testing.contracts.DummyContract import net.corda.testing.dummyCommand @@ -95,7 +94,7 @@ class ScheduledFlowTests { @Before fun setup() { mockNet = MockNetwork(threadPerNode = true, cordappPackages = listOf("net.corda.testing.contracts")) - notaryNode = mockNet.createNotaryNode(legalName = DUMMY_NOTARY.name) + notaryNode = mockNet.createNotaryNode() val a = mockNet.createUnstartedNode() val b = mockNet.createUnstartedNode() diff --git a/node/src/test/kotlin/net/corda/node/services/transactions/NotaryServiceTests.kt b/node/src/test/kotlin/net/corda/node/services/transactions/NotaryServiceTests.kt index 2510497167..05bf4ecdca 100644 --- a/node/src/test/kotlin/net/corda/node/services/transactions/NotaryServiceTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/transactions/NotaryServiceTests.kt @@ -37,7 +37,7 @@ class NotaryServiceTests { @Before fun setup() { mockNet = MockNetwork(cordappPackages = listOf("net.corda.testing.contracts")) - val notaryNode = mockNet.createNotaryNode(legalName = DUMMY_NOTARY.name, validating = false) + val notaryNode = mockNet.createNotaryNode(validating = false) aliceServices = mockNet.createNode(MockNodeParameters(legalName = ALICE_NAME)).services mockNet.runNetwork() // Clear network map registration messages notaryServices = notaryNode.services diff --git a/node/src/test/kotlin/net/corda/node/services/transactions/ValidatingNotaryServiceTests.kt b/node/src/test/kotlin/net/corda/node/services/transactions/ValidatingNotaryServiceTests.kt index 1a1c74ea07..4fe3f68a20 100644 --- a/node/src/test/kotlin/net/corda/node/services/transactions/ValidatingNotaryServiceTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/transactions/ValidatingNotaryServiceTests.kt @@ -37,7 +37,7 @@ class ValidatingNotaryServiceTests { @Before fun setup() { mockNet = MockNetwork(cordappPackages = listOf("net.corda.testing.contracts")) - val notaryNode = mockNet.createNotaryNode(legalName = DUMMY_NOTARY.name) + val notaryNode = mockNet.createNotaryNode() val aliceNode = mockNet.createNode(MockNodeParameters(legalName = ALICE_NAME)) mockNet.runNetwork() // Clear network map registration messages notaryServices = notaryNode.services diff --git a/samples/network-visualiser/src/main/kotlin/net/corda/netmap/simulation/Simulation.kt b/samples/network-visualiser/src/main/kotlin/net/corda/netmap/simulation/Simulation.kt index 0533308ec2..d186a74484 100644 --- a/samples/network-visualiser/src/main/kotlin/net/corda/netmap/simulation/Simulation.kt +++ b/samples/network-visualiser/src/main/kotlin/net/corda/netmap/simulation/Simulation.kt @@ -73,7 +73,7 @@ abstract class Simulation(val networkSendManuallyPumped: Boolean, networkSendManuallyPumped = networkSendManuallyPumped, threadPerNode = runAsync, cordappPackages = listOf("net.corda.irs.contract", "net.corda.finance.contract", "net.corda.irs")) - val notary = mockNet.createNotaryNode(DUMMY_NOTARY.name, false) + val notary = mockNet.createNotaryNode(defaultParams.copy(legalName = DUMMY_NOTARY.name), false) // TODO: Regulatory nodes don't actually exist properly, this is a last minute demo request. // So we just fire a message at a node that doesn't know how to handle it, and it'll ignore it. // But that's fine for visualisation purposes. diff --git a/testing/node-driver/src/integration-test/kotlin/net/corda/testing/FlowStackSnapshotTest.kt b/testing/node-driver/src/integration-test/kotlin/net/corda/testing/FlowStackSnapshotTest.kt index ae31e3bbb5..8adba7f34d 100644 --- a/testing/node-driver/src/integration-test/kotlin/net/corda/testing/FlowStackSnapshotTest.kt +++ b/testing/node-driver/src/integration-test/kotlin/net/corda/testing/FlowStackSnapshotTest.kt @@ -290,7 +290,7 @@ class FlowStackSnapshotTest { @Test fun `flowStackSnapshot object is serializable`() { val mockNet = MockNetwork(threadPerNode = true) - mockNet.createNotaryNode(legalName = DUMMY_NOTARY.name) + mockNet.createNotaryNode() val node = mockNet.createPartyNode() node.internals.registerInitiatedFlow(DummyFlow::class.java) node.services.startFlow(FlowStackSnapshotSerializationTestingFlow()).resultFuture.get() diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt index e0bec83c67..52052268bc 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt @@ -341,10 +341,8 @@ class MockNetwork(defaultParameters: MockNetworkParameters = MockNetworkParamete } @JvmOverloads - fun createNotaryNode(legalName: CordaX500Name = DUMMY_NOTARY.name, validating: Boolean = true): StartedNode { - return createNode(MockNodeParameters(legalName = legalName, configOverrides = { - doReturn(NotaryConfig(validating)).whenever(it).notary - })) + fun createNotaryNode(parameters: MockNodeParameters = MockNodeParameters(legalName = DUMMY_NOTARY.name), validating: Boolean = true): StartedNode { + return createNotaryNode(parameters, validating, defaultFactory) } fun createNotaryNode(parameters: MockNodeParameters = MockNodeParameters(legalName = DUMMY_NOTARY.name), From 9176fcb8e6568e71ac8feaa94481c9ab67a02abf Mon Sep 17 00:00:00 2001 From: Alberto Arri <30873160+al-r3@users.noreply.github.com> Date: Mon, 30 Oct 2017 11:45:52 +0000 Subject: [PATCH 85/94] Remove for good network map service node (#1942) * [CORDA-446] Kill network map registration and fix NodeBasedTest --- docs/source/corda-configuration-file.rst | 9 +- .../nodeapi/ArtemisMessagingComponent.kt | 23 +---- .../network/PersistentNetworkMapCacheTest.kt | 91 +++-------------- .../services/messaging/P2PMessagingTest.kt | 6 +- .../services/messaging/P2PSecurityTest.kt | 79 --------------- .../net/corda/node/internal/AbstractNode.kt | 80 +++------------ .../kotlin/net/corda/node/internal/Node.kt | 24 +---- .../net/corda/node/internal/StartedNode.kt | 4 - .../node/services/api/ServiceHubInternal.kt | 24 ----- .../node/services/config/NodeConfiguration.kt | 9 -- .../messaging/ArtemisMessagingServer.kt | 78 ++------------- .../services/messaging/NodeMessagingClient.kt | 16 +-- .../network/PersistentNetworkMapCache.kt | 99 ++----------------- .../config/FullNodeConfigurationTest.kt | 0 .../config/NodeConfigurationImplTest.kt | 1 - .../kotlin/net/corda/testing/NodeTestUtils.kt | 2 - .../corda/testing/node/MockNetworkMapCache.kt | 26 +---- .../kotlin/net/corda/testing/node/MockNode.kt | 9 +- .../net/corda/testing/node/NodeBasedTest.kt | 58 +++-------- .../corda/demobench/model/NodeConfigTest.kt | 2 - 20 files changed, 81 insertions(+), 559 deletions(-) delete mode 100644 node/src/integration-test/kotlin/net/corda/services/messaging/P2PSecurityTest.kt create mode 100644 node/src/test/kotlin/net/corda/node/services/config/FullNodeConfigurationTest.kt diff --git a/docs/source/corda-configuration-file.rst b/docs/source/corda-configuration-file.rst index ce0b3412ad..9bc2aba5e8 100644 --- a/docs/source/corda-configuration-file.rst +++ b/docs/source/corda-configuration-file.rst @@ -118,13 +118,6 @@ path to the node's base directory. Only one of ``raft``, ``bftSMaRt`` or ``custom`` configuration values may be specified. -:networkMapService: If `null`, or missing the node is declaring itself as the NetworkMapService host. Otherwise this is - a config object with the details of the network map service: - - :address: Host and port string of the ArtemisMQ broker hosting the network map node - :legalName: Legal name of the node. This is required as part of the TLS host verification process. The node will - reject the connection to the network map service if it provides a TLS common name which doesn't match with this value. - :minimumPlatformVersion: Used by the node if it's running the network map service to enforce a minimum version requirement on registrations - any node on a Platform Version lower than this value will have their registration rejected. Defaults to 1 if absent. @@ -163,4 +156,4 @@ path to the node's base directory. :jarDirs: An optional list of file system directories containing JARs to include in the classpath when launching via ``corda.jar`` only. Each should be a string. Only the JARs in the directories are added, not the directories themselves. This is useful - for including JDBC drivers and the like. e.g. ``jarDirs = [ 'lib' ]`` \ No newline at end of file + for including JDBC drivers and the like. e.g. ``jarDirs = [ 'lib' ]`` diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/ArtemisMessagingComponent.kt b/node-api/src/main/kotlin/net/corda/nodeapi/ArtemisMessagingComponent.kt index 45f44c899a..672c11aeed 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/ArtemisMessagingComponent.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/ArtemisMessagingComponent.kt @@ -1,7 +1,5 @@ package net.corda.nodeapi -import net.corda.core.identity.CordaX500Name -import net.corda.core.identity.Party import net.corda.core.messaging.MessageRecipientGroup import net.corda.core.messaging.MessageRecipients import net.corda.core.messaging.SingleMessageRecipient @@ -42,11 +40,6 @@ abstract class ArtemisMessagingComponent : SingletonSerializeAsToken() { val hostAndPort: NetworkHostAndPort } - @CordaSerializable - data class NetworkMapAddress(override val hostAndPort: NetworkHostAndPort) : ArtemisPeerAddress { - override val queueName: String get() = NETWORK_MAP_QUEUE - } - /** * This is the class used to implement [SingleMessageRecipient], for now. Note that in future this class * may change or evolve and code that relies upon it being a simple host/port may not function correctly. @@ -60,11 +53,8 @@ abstract class ArtemisMessagingComponent : SingletonSerializeAsToken() { */ @CordaSerializable data class NodeAddress(override val queueName: String, override val hostAndPort: NetworkHostAndPort) : ArtemisPeerAddress { - companion object { - fun asSingleNode(peerIdentity: PublicKey, hostAndPort: NetworkHostAndPort): NodeAddress { - return NodeAddress("$PEERS_PREFIX${peerIdentity.toBase58String()}", hostAndPort) - } - } + constructor(peerIdentity: PublicKey, hostAndPort: NetworkHostAndPort) : + this("$PEERS_PREFIX${peerIdentity.toBase58String()}", hostAndPort) } /** @@ -82,13 +72,4 @@ abstract class ArtemisMessagingComponent : SingletonSerializeAsToken() { /** The config object is used to pass in the passwords for the certificate KeyStore and TrustStore */ abstract val config: SSLConfiguration? - - // Used for bridges creation. - fun getArtemisPeerAddress(party: Party, address: NetworkHostAndPort, netMapName: CordaX500Name? = null): ArtemisPeerAddress { - return if (party.name == netMapName) { - NetworkMapAddress(address) - } else { - NodeAddress.asSingleNode(party.owningKey, address) // It also takes care of services nodes treated as peer nodes - } - } } diff --git a/node/src/integration-test/kotlin/net/corda/node/services/network/PersistentNetworkMapCacheTest.kt b/node/src/integration-test/kotlin/net/corda/node/services/network/PersistentNetworkMapCacheTest.kt index 00671b35a9..6eb921fc4d 100644 --- a/node/src/integration-test/kotlin/net/corda/node/services/network/PersistentNetworkMapCacheTest.kt +++ b/node/src/integration-test/kotlin/net/corda/node/services/network/PersistentNetworkMapCacheTest.kt @@ -16,22 +16,13 @@ import net.corda.testing.node.NodeBasedTest import org.assertj.core.api.Assertions.assertThat import org.junit.Before import org.junit.Test -import java.time.Duration import kotlin.test.assertEquals -import kotlin.test.assertFails -import kotlin.test.assertTrue - -private const val BRIDGE_RETRY_MS: Long = 100 class PersistentNetworkMapCacheTest : NodeBasedTest() { private val partiesList = listOf(DUMMY_NOTARY, ALICE, BOB) private val addressesMap: HashMap = HashMap() private val infos: MutableSet = HashSet() - companion object { - val logger = loggerFor() - } - @Before fun start() { val nodes = startNodesWithPort(partiesList) @@ -45,7 +36,7 @@ class PersistentNetworkMapCacheTest : NodeBasedTest() { @Test fun `get nodes by owning key and by name, no network map service`() { - val alice = startNodesWithPort(listOf(ALICE), noNetworkMap = true)[0] + val alice = startNodesWithPort(listOf(ALICE))[0] val netCache = alice.services.networkMapCache alice.database.transaction { val res = netCache.getNodeByLegalIdentity(alice.info.chooseIdentity()) @@ -57,7 +48,7 @@ class PersistentNetworkMapCacheTest : NodeBasedTest() { @Test fun `get nodes by address no network map service`() { - val alice = startNodesWithPort(listOf(ALICE), noNetworkMap = true)[0] + val alice = startNodesWithPort(listOf(ALICE))[0] val netCache = alice.services.networkMapCache alice.database.transaction { val res = netCache.getNodeByAddress(alice.info.addresses[0]) @@ -67,9 +58,8 @@ class PersistentNetworkMapCacheTest : NodeBasedTest() { @Test fun `restart node with DB map cache and no network map`() { - val alice = startNodesWithPort(listOf(ALICE), noNetworkMap = true)[0] + val alice = startNodesWithPort(listOf(ALICE))[0] val partyNodes = alice.services.networkMapCache.allNodes - assertEquals(NullNetworkMapService, alice.inNodeNetworkMapService) assertEquals(infos.size, partyNodes.size) assertEquals(infos.flatMap { it.legalIdentities }.toSet(), partyNodes.flatMap { it.legalIdentities }.toSet()) } @@ -77,8 +67,7 @@ class PersistentNetworkMapCacheTest : NodeBasedTest() { @Test fun `start 2 nodes without pointing at NetworkMapService and communicate with each other`() { val parties = partiesList.subList(1, partiesList.size) - val nodes = startNodesWithPort(parties, noNetworkMap = true) - assertTrue(nodes.all { it.inNodeNetworkMapService == NullNetworkMapService }) + val nodes = startNodesWithPort(parties) nodes.forEach { val partyNodes = it.services.networkMapCache.allNodes assertEquals(infos.size, partyNodes.size) @@ -90,8 +79,7 @@ class PersistentNetworkMapCacheTest : NodeBasedTest() { @Test fun `start 2 nodes pointing at NetworkMapService but don't start network map node`() { val parties = partiesList.subList(1, partiesList.size) - val nodes = startNodesWithPort(parties, noNetworkMap = false) - assertTrue(nodes.all { it.inNodeNetworkMapService == NullNetworkMapService }) + val nodes = startNodesWithPort(parties) nodes.forEach { val partyNodes = it.services.networkMapCache.allNodes assertEquals(infos.size, partyNodes.size) @@ -103,79 +91,24 @@ class PersistentNetworkMapCacheTest : NodeBasedTest() { @Test fun `start node and network map communicate`() { val parties = partiesList.subList(0, 2) - val nodes = startNodesWithPort(parties, noNetworkMap = false) + val nodes = startNodesWithPort(parties) checkConnectivity(nodes) } @Test - fun `start node without networkMapService and no database - fail`() { - assertFails { startNode(CHARLIE.name, noNetworkMap = true).getOrThrow(2.seconds) } - } - - @Test - fun `new node joins network without network map started`() { - - fun customNodesStart(parties: List): List> = - startNodesWithPort(parties, noNetworkMap = false, customRetryIntervalMs = BRIDGE_RETRY_MS) - - val parties = partiesList.subList(1, partiesList.size) - // Start 2 nodes pointing at network map, but don't start network map service. - val otherNodes = customNodesStart(parties) - otherNodes.forEach { node -> - assertTrue(infos.any { it.legalIdentitiesAndCerts.toSet() == node.info.legalIdentitiesAndCerts.toSet() }) - } - // Start node that is not in databases of other nodes. Point to NMS. Which has't started yet. - val charlie = customNodesStart(listOf(CHARLIE)).single() - otherNodes.forEach { - assertThat(it.services.networkMapCache.allNodes).doesNotContain(charlie.info) - } - // Start Network Map and see that charlie node appears in caches. - val nms = customNodesStart(listOf(DUMMY_NOTARY)).single() - nms.internals.startupComplete.get() - assertTrue(nms.inNodeNetworkMapService != NullNetworkMapService) - assertTrue(infos.any { it.legalIdentities.toSet() == nms.info.legalIdentities.toSet() }) - otherNodes.forEach { - assertTrue(nms.info.chooseIdentity() in it.services.networkMapCache.allNodes.map { it.chooseIdentity() }) - } - charlie.internals.nodeReadyFuture.get() // Finish registration. - - val allTheStartedNodesPopulation = otherNodes.plus(charlie).plus(nms) - - // This is prediction of the longest time it will take to get the cluster into a stable state such that further - // testing can be performed upon it - val maxInstabilityInterval = BRIDGE_RETRY_MS * allTheStartedNodesPopulation.size * 30 - logger.info("Instability interval is set to: $maxInstabilityInterval ms") - - // TODO: Re-visit this sort of re-try for stable cluster once network map redesign is finished. - eventually(Duration.ofMillis(maxInstabilityInterval)) { - logger.info("Checking connectivity") - checkConnectivity(listOf(otherNodes[0], nms)) // Checks connectivity from A to NMS. - logger.info("Loading caches") - val cacheA = otherNodes[0].services.networkMapCache.allNodes - val cacheB = otherNodes[1].services.networkMapCache.allNodes - val cacheC = charlie.services.networkMapCache.allNodes - logger.info("Performing verification") - assertEquals(4, cacheC.size) // Charlie fetched data from NetworkMap - assertThat(cacheB).contains(charlie.info) - assertEquals(cacheA.toSet(), cacheB.toSet()) - assertEquals(cacheA.toSet(), cacheC.toSet()) - } + fun `start node without networkMapService and no database - success`() { + startNode(CHARLIE.name).getOrThrow(2.seconds) } // HELPERS // Helper function to restart nodes with the same host and port. - private fun startNodesWithPort(nodesToStart: List, noNetworkMap: Boolean = false, customRetryIntervalMs: Long? = null): List> { + private fun startNodesWithPort(nodesToStart: List, customRetryIntervalMs: Long? = null): List> { return nodesToStart.map { party -> val configOverrides = (addressesMap[party.name]?.let { mapOf("p2pAddress" to it.toString()) } ?: emptyMap()) + (customRetryIntervalMs?.let { mapOf("activeMQServer.bridge.retryIntervalMs" to it.toString()) } ?: emptyMap()) - if (party == DUMMY_NOTARY) { - startNetworkMapNode(party.name, configOverrides = configOverrides) - } else { - startNode(party.name, - configOverrides = configOverrides, - noNetworkMap = noNetworkMap, - waitForConnection = false).getOrThrow() - } + startNode(party.name, + configOverrides = configOverrides, + waitForConnection = false).getOrThrow() } } diff --git a/node/src/integration-test/kotlin/net/corda/services/messaging/P2PMessagingTest.kt b/node/src/integration-test/kotlin/net/corda/services/messaging/P2PMessagingTest.kt index 5503d5ca19..a371ca1704 100644 --- a/node/src/integration-test/kotlin/net/corda/services/messaging/P2PMessagingTest.kt +++ b/node/src/integration-test/kotlin/net/corda/services/messaging/P2PMessagingTest.kt @@ -45,7 +45,6 @@ class P2PMessagingTest : NodeBasedTest() { startNodes().getOrThrow(timeout = startUpDuration * 3) } - @Ignore @Test fun `communicating with a distributed service which we're part of`() { val distributedService = startNotaryCluster(DISTRIBUTED_SERVICE_NAME, 2).getOrThrow() @@ -85,6 +84,7 @@ class P2PMessagingTest : NodeBasedTest() { } @Test + @Ignore("Fails on Team City due to issues with restaring nodes.") fun `distributed service request retries are persisted across client node restarts`() { val distributedServiceNodes = startNotaryCluster(DISTRIBUTED_SERVICE_NAME, 2).getOrThrow() val alice = startNode(ALICE.name, configOverrides = mapOf("messageRedeliveryDelaySeconds" to 1)).getOrThrow() @@ -110,6 +110,8 @@ class P2PMessagingTest : NodeBasedTest() { // Wait until the first request is received crashingNodes.firstRequestReceived.await(5, TimeUnit.SECONDS) // Stop alice's node after we ensured that the first request was delivered and ignored. + alice.services.networkMapCache.clearNetworkMapCache() + alice.dispose() val numberOfRequestsReceived = crashingNodes.requestsReceived.get() assertThat(numberOfRequestsReceived).isGreaterThanOrEqualTo(1) @@ -117,7 +119,7 @@ class P2PMessagingTest : NodeBasedTest() { crashingNodes.ignoreRequests = false // Restart the node and expect a response - val aliceRestarted = startNode(ALICE.name, configOverrides = mapOf("messageRedeliveryDelaySeconds" to 1)).getOrThrow() + val aliceRestarted = startNode(ALICE.name, waitForConnection = true, configOverrides = mapOf("messageRedeliveryDelaySeconds" to 5)).getOrThrow() val response = aliceRestarted.network.onNext(dummyTopic, sessionId).getOrThrow(5.seconds) assertThat(crashingNodes.requestsReceived.get()).isGreaterThan(numberOfRequestsReceived) diff --git a/node/src/integration-test/kotlin/net/corda/services/messaging/P2PSecurityTest.kt b/node/src/integration-test/kotlin/net/corda/services/messaging/P2PSecurityTest.kt deleted file mode 100644 index 07e396de2b..0000000000 --- a/node/src/integration-test/kotlin/net/corda/services/messaging/P2PSecurityTest.kt +++ /dev/null @@ -1,79 +0,0 @@ -package net.corda.services.messaging - -import com.nhaarman.mockito_kotlin.doReturn -import com.nhaarman.mockito_kotlin.whenever -import net.corda.core.concurrent.CordaFuture -import net.corda.core.crypto.random63BitValue -import net.corda.core.identity.CordaX500Name -import net.corda.core.node.NodeInfo -import net.corda.core.internal.cert -import net.corda.core.utilities.getOrThrow -import net.corda.core.utilities.seconds -import net.corda.node.internal.NetworkMapInfo -import net.corda.node.services.config.ActiveMqServerConfiguration -import net.corda.node.services.config.BridgeConfiguration -import net.corda.node.services.config.configureWithDevSSLCertificate -import net.corda.node.services.messaging.sendRequest -import net.corda.node.services.network.NetworkMapService -import net.corda.node.services.network.NetworkMapService.RegistrationRequest -import net.corda.node.services.network.NodeRegistration -import net.corda.node.utilities.AddOrRemove -import net.corda.testing.* -import net.corda.testing.node.NodeBasedTest -import net.corda.testing.node.SimpleNode -import org.assertj.core.api.Assertions.assertThatExceptionOfType -import org.assertj.core.api.Assertions.assertThatThrownBy -import org.junit.Test -import java.security.cert.X509Certificate -import java.time.Instant -import java.util.concurrent.TimeoutException - -class P2PSecurityTest : NodeBasedTest() { - - @Test - fun `incorrect legal name for the network map service config`() { - val incorrectNetworkMapName = CordaX500Name(organisation = "NetworkMap-${random63BitValue()}", - locality = "London", country = "GB") - val node = startNode(BOB.name, configOverrides = mapOf( - "networkMapService" to mapOf( - "address" to networkMapNode.internals.configuration.p2pAddress.toString(), - "legalName" to incorrectNetworkMapName.toString() - ) - )) - // The connection will be rejected as the legal name doesn't match - assertThatThrownBy { node.getOrThrow() }.hasMessageContaining(incorrectNetworkMapName.toString()) - } - - @Test - fun `register with the network map service using a legal name different from the TLS CN`() { - startSimpleNode(DUMMY_BANK_A.name, DEV_TRUST_ROOT.cert).use { - // Register with the network map using a different legal name - val response = it.registerWithNetworkMap(DUMMY_BANK_B.name) - // We don't expect a response because the network map's host verification will prevent a connection back - // to the attacker as the TLS CN will not match the legal name it has just provided - assertThatExceptionOfType(TimeoutException::class.java).isThrownBy { - response.getOrThrow(2.seconds) - } - } - } - - private fun startSimpleNode(legalName: CordaX500Name, - trustRoot: X509Certificate): SimpleNode { - val config = testNodeConfiguration( - baseDirectory = baseDirectory(legalName), - myLegalName = legalName).also { - doReturn(NetworkMapInfo(networkMapNode.internals.configuration.p2pAddress, networkMapNode.info.chooseIdentity().name)).whenever(it).networkMapService - doReturn(ActiveMqServerConfiguration(BridgeConfiguration(1001, 2, 3.4))).whenever(it).activeMQServer - } - config.configureWithDevSSLCertificate() // This creates the node's TLS cert with the CN as the legal name - return SimpleNode(config, trustRoot = trustRoot).apply { start() } - } - - private fun SimpleNode.registerWithNetworkMap(registrationName: CordaX500Name): CordaFuture { - val legalIdentity = getTestPartyAndCertificate(registrationName, identity.public) - val nodeInfo = NodeInfo(listOf(MOCK_HOST_AND_PORT), listOf(legalIdentity), 1, serial = 1) - val registration = NodeRegistration(nodeInfo, System.currentTimeMillis(), AddOrRemove.ADD, Instant.MAX) - val request = RegistrationRequest(registration.toWire(keyService, identity.public), network.myAddress) - return network.sendRequest(NetworkMapService.REGISTER_TOPIC, request, networkMapNode.network.myAddress) - } -} diff --git a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt index badeb6a9f9..1b49934765 100644 --- a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt +++ b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt @@ -12,8 +12,7 @@ import net.corda.core.identity.CordaX500Name import net.corda.core.identity.Party import net.corda.core.identity.PartyAndCertificate import net.corda.core.internal.* -import net.corda.core.internal.concurrent.doneFuture -import net.corda.core.internal.concurrent.flatMap +import net.corda.core.internal.concurrent.map import net.corda.core.internal.concurrent.openFuture import net.corda.core.messaging.* import net.corda.core.node.* @@ -87,7 +86,6 @@ import net.corda.core.crypto.generateKeyPair as cryptoGenerateKeyPair * Marked as SingletonSerializeAsToken to prevent the invisible reference to AbstractNode in the ServiceHub accidentally * sweeping up the Node into the Kryo checkpoint serialization via any flows holding a reference to ServiceHub. */ -// TODO: Where this node is the initial network map service, currently no networkMapService is provided. // In theory the NodeInfo for the node should be passed in, instead, however currently this is constructed by the // AbstractNode. It should be possible to generate the NodeInfo outside of AbstractNode, so it can be passed in. abstract class AbstractNode(config: NodeConfiguration, @@ -108,7 +106,6 @@ abstract class AbstractNode(config: NodeConfiguration, override val checkpointStorage: CheckpointStorage, override val smm: StateMachineManager, override val attachments: NodeAttachmentService, - override val inNodeNetworkMapService: NetworkMapService, override val network: MessagingService, override val database: CordaPersistence, override val rpcOps: CordaRPCOps, @@ -116,14 +113,8 @@ abstract class AbstractNode(config: NodeConfiguration, internal val schedulerService: NodeSchedulerService) : StartedNode { override val services: StartedNodeServices = object : StartedNodeServices, ServiceHubInternal by services, FlowStarter by flowStarter {} } - // TODO: Persist this, as well as whether the node is registered. - /** - * Sequence number of changes sent to the network map service, when registering/de-registering this node. - */ - var networkMapSeq: Long = 1 protected abstract val log: Logger - protected abstract val networkMapAddress: SingleMessageRecipient? // We will run as much stuff in this single thread as possible to keep the risk of thread safety bugs low during the // low-performance prototyping period. @@ -143,7 +134,6 @@ abstract class AbstractNode(config: NodeConfiguration, protected lateinit var smm: StateMachineManager private lateinit var tokenizableServices: List protected lateinit var attachments: NodeAttachmentService - protected lateinit var inNodeNetworkMapService: NetworkMapService protected lateinit var network: MessagingService protected val runOnStop = ArrayList<() -> Any?>() protected lateinit var database: CordaPersistence @@ -229,10 +219,12 @@ abstract class AbstractNode(config: NodeConfiguration, FlowLogicRefFactoryImpl.classloader = cordappLoader.appClassLoader runOnStop += network::stop - StartedNodeImpl(this, _services, info, checkpointStorage, smm, attachments, inNodeNetworkMapService, network, database, rpcOps, flowStarter, schedulerService) + StartedNodeImpl(this, _services, info, checkpointStorage, smm, attachments, network, database, rpcOps, flowStarter, schedulerService) } // If we successfully loaded network data from database, we set this future to Unit. - _nodeReadyFuture.captureLater(registerWithNetworkMapIfConfigured()) + services.networkMapCache.addNode(info) + _nodeReadyFuture.captureLater(services.networkMapCache.nodeReady.map { Unit }) + return startedImpl.apply { database.transaction { smm.start(tokenizableServices) @@ -489,7 +481,7 @@ abstract class AbstractNode(config: NodeConfiguration, services.auditService, services.monitoringService, networkMapCache, services.schemaService, services.transactionVerifierService, services.validatedTransactions, services.contractUpgradeService, services, cordappProvider, this) - makeNetworkServices(network, networkMapCache, tokenizableServices) + makeNetworkServices(tokenizableServices) return tokenizableServices } @@ -550,16 +542,7 @@ abstract class AbstractNode(config: NodeConfiguration, } } - private fun setupInNodeNetworkMapService(networkMapCache: NetworkMapCacheInternal) { - inNodeNetworkMapService = - if (configuration.networkMapService == null && !configuration.noNetworkMapServiceMode) - makeNetworkMapService(network, networkMapCache) - else - NullNetworkMapService - } - - private fun makeNetworkServices(network: MessagingService, networkMapCache: NetworkMapCacheInternal, tokenizableServices: MutableList) { - setupInNodeNetworkMapService(networkMapCache) + private fun makeNetworkServices(tokenizableServices: MutableList) { configuration.notary?.let { val notaryService = makeCoreNotaryService(it) tokenizableServices.add(notaryService) @@ -570,40 +553,6 @@ abstract class AbstractNode(config: NodeConfiguration, } } - private fun registerWithNetworkMapIfConfigured(): CordaFuture { - services.networkMapCache.addNode(info) - // In the unit test environment, we may sometimes run without any network map service - return if (networkMapAddress == null && inNodeNetworkMapService == NullNetworkMapService) { - services.networkMapCache.runWithoutMapService() - noNetworkMapConfigured() // TODO This method isn't needed as runWithoutMapService sets the Future in the cache - } else { - val netMapRegistration = registerWithNetworkMap() - // We may want to start node immediately with database data and not wait for network map registration (but send it either way). - // So we are ready to go. - if (services.networkMapCache.loadDBSuccess) { - log.info("Node successfully loaded network map data from the database.") - doneFuture(Unit) - } else { - netMapRegistration - } - } - } - - /** - * Register this node with the network map cache, and load network map from a remote service (and register for - * updates) if one has been supplied. - */ - protected open fun registerWithNetworkMap(): CordaFuture { - val address: SingleMessageRecipient = networkMapAddress ?: - network.getAddressOfParty(PartyInfo.SingleNode(services.myInfo.legalIdentitiesAndCerts.first().party, info.addresses)) as SingleMessageRecipient - // Register for updates, even if we're the one running the network map. - return sendNetworkMapRegistration(address).flatMap { (error) -> - check(error == null) { "Unable to register with the network map service: $error" } - // The future returned addMapService will complete on the same executor as sendNetworkMapRegistration, namely the one used by net - services.networkMapCache.addMapService(network, address, true, null) - } - } - private fun sendNetworkMapRegistration(networkMapAddress: SingleMessageRecipient): CordaFuture { // Register this node against the network val instant = platformClock.instant() @@ -616,14 +565,10 @@ abstract class AbstractNode(config: NodeConfiguration, /** Return list of node's addresses. It's overridden in MockNetwork as we don't have real addresses for MockNodes. */ protected abstract fun myAddresses(): List - /** This is overriden by the mock node implementation to enable operation without any network map service */ - protected open fun noNetworkMapConfigured(): CordaFuture { - if (services.networkMapCache.loadDBSuccess || configuration.noNetworkMapServiceMode) { - return doneFuture(Unit) - } else { + open protected fun checkNetworkMapIsInitialized() { + if (!services.networkMapCache.loadDBSuccess) { // TODO: There should be a consistent approach to configuration error exceptions. - throw IllegalStateException("Configuration error: this node isn't being asked to act as the network map, nor " + - "has any other map node been configured.") + throw NetworkMapCacheEmptyException() } } @@ -815,3 +760,8 @@ internal class FlowStarterImpl(private val serverThread: AffinityExecutor, priva return serverThread.fetchFrom { smm.startFlow(logic, flowInitiator, ourIdentity) } } } + +/** + * Thrown when a node is about to start and its network map cache doesn't contain any node. + */ +internal class NetworkMapCacheEmptyException: Exception() \ No newline at end of file diff --git a/node/src/main/kotlin/net/corda/node/internal/Node.kt b/node/src/main/kotlin/net/corda/node/internal/Node.kt index 694e21d11a..992b433081 100644 --- a/node/src/main/kotlin/net/corda/node/internal/Node.kt +++ b/node/src/main/kotlin/net/corda/node/internal/Node.kt @@ -5,8 +5,6 @@ import net.corda.core.CordaException import net.corda.core.concurrent.CordaFuture import net.corda.core.identity.CordaX500Name import net.corda.core.identity.PartyAndCertificate -import net.corda.core.internal.concurrent.doneFuture -import net.corda.core.internal.concurrent.flatMap import net.corda.core.internal.concurrent.openFuture import net.corda.core.internal.concurrent.thenMatch import net.corda.core.internal.uncheckedCast @@ -36,7 +34,6 @@ import net.corda.node.utilities.TestClock import net.corda.nodeapi.ArtemisMessagingComponent import net.corda.nodeapi.ArtemisMessagingComponent.Companion.IP_REQUEST_PREFIX import net.corda.nodeapi.ArtemisMessagingComponent.Companion.PEER_USER -import net.corda.nodeapi.ArtemisMessagingComponent.NetworkMapAddress import net.corda.nodeapi.ArtemisTcpTransport import net.corda.nodeapi.ConnectionDirection import net.corda.nodeapi.internal.ShutdownHook @@ -98,7 +95,6 @@ open class Node(override val configuration: NodeConfiguration, } override val log: Logger get() = logger - override val networkMapAddress: NetworkMapAddress? get() = configuration.networkMapService?.address?.let(::NetworkMapAddress) override fun makeTransactionVerifierService() = (network as NodeMessagingClient).verifierService private val sameVmNodeNumber = sameVmNodeCounter.incrementAndGet() // Under normal (non-test execution) it will always be "1" @@ -156,12 +152,11 @@ open class Node(override val configuration: NodeConfiguration, printBasicNodeInfo("Incoming connection address", advertisedAddress.toString()) - val myIdentityOrNullIfNetworkMapService = if (networkMapAddress != null) legalIdentity.owningKey else null return NodeMessagingClient( configuration, versionInfo, serverAddress, - myIdentityOrNullIfNetworkMapService, + legalIdentity.owningKey, serverThread, database, nodeReadyFuture, @@ -189,16 +184,14 @@ open class Node(override val configuration: NodeConfiguration, /** * Checks whether the specified [host] is a public IP address or hostname. If not, tries to discover the current - * machine's public IP address to be used instead. It first looks through the network interfaces, and if no public IP - * is found, asks the network map service to provide it. + * machine's public IP address to be used instead by looking through the network interfaces. + * TODO this code used to rely on the networkmap node, we might want to look at a different solution. */ private fun tryDetectIfNotPublicHost(host: String): String? { if (!AddressUtils.isPublic(host)) { val foundPublicIP = AddressUtils.tryDetectPublicIP() - if (foundPublicIP == null) { - networkMapAddress?.let { return discoverPublicHost(it.hostAndPort) } - } else { + if (foundPublicIP != null) { log.info("Detected public IP: ${foundPublicIP.hostAddress}. This will be used instead of the provided \"$host\" as the advertised address.") return foundPublicIP.hostAddress } @@ -264,15 +257,6 @@ open class Node(override val configuration: NodeConfiguration, (network as NodeMessagingClient).start(rpcOps, userService) } - /** - * Insert an initial step in the registration process which will throw an exception if a non-recoverable error is - * encountered when trying to connect to the network map node. - */ - override fun registerWithNetworkMap(): CordaFuture { - val networkMapConnection = messageBroker?.networkMapConnectionFuture ?: doneFuture(Unit) - return networkMapConnection.flatMap { super.registerWithNetworkMap() } - } - override fun myAddresses(): List { val address = network.myAddress as ArtemisMessagingComponent.ArtemisPeerAddress return listOf(address.hostAndPort) diff --git a/node/src/main/kotlin/net/corda/node/internal/StartedNode.kt b/node/src/main/kotlin/net/corda/node/internal/StartedNode.kt index 71b80d5aea..fed9c073db 100644 --- a/node/src/main/kotlin/net/corda/node/internal/StartedNode.kt +++ b/node/src/main/kotlin/net/corda/node/internal/StartedNode.kt @@ -7,13 +7,10 @@ import net.corda.core.flows.FlowLogic import net.corda.core.messaging.CordaRPCOps import net.corda.core.node.NodeInfo import net.corda.core.node.StateLoader -import net.corda.core.node.services.CordaService import net.corda.core.node.services.TransactionStorage -import net.corda.core.serialization.SerializeAsToken import net.corda.node.services.api.CheckpointStorage import net.corda.node.services.api.StartedNodeServices import net.corda.node.services.messaging.MessagingService -import net.corda.node.services.network.NetworkMapService import net.corda.node.services.persistence.NodeAttachmentService import net.corda.node.services.statemachine.StateMachineManager import net.corda.node.utilities.CordaPersistence @@ -25,7 +22,6 @@ interface StartedNode { val checkpointStorage: CheckpointStorage val smm: StateMachineManager val attachments: NodeAttachmentService - val inNodeNetworkMapService: NetworkMapService val network: MessagingService val database: CordaPersistence val rpcOps: CordaRPCOps diff --git a/node/src/main/kotlin/net/corda/node/services/api/ServiceHubInternal.kt b/node/src/main/kotlin/net/corda/node/services/api/ServiceHubInternal.kt index 81ad1d57dd..f2d888d54d 100644 --- a/node/src/main/kotlin/net/corda/node/services/api/ServiceHubInternal.kt +++ b/node/src/main/kotlin/net/corda/node/services/api/ServiceHubInternal.kt @@ -11,7 +11,6 @@ import net.corda.core.internal.FlowStateMachine import net.corda.core.internal.VisibleForTesting import net.corda.core.internal.uncheckedCast import net.corda.core.messaging.DataFeed -import net.corda.core.messaging.SingleMessageRecipient import net.corda.core.messaging.StateMachineTransactionMapping import net.corda.core.node.NodeInfo import net.corda.core.node.ServiceHub @@ -33,35 +32,12 @@ import net.corda.node.utilities.CordaPersistence interface NetworkMapCacheInternal : NetworkMapCache, NetworkMapCacheBaseInternal interface NetworkMapCacheBaseInternal : NetworkMapCacheBase { - /** - * Deregister from updates from the given map service. - * @param network the network messaging service. - * @param mapParty the network map service party to fetch current state from. - */ - fun deregisterForUpdates(network: MessagingService, mapParty: Party): CordaFuture - - /** - * Add a network map service; fetches a copy of the latest map from the service and subscribes to any further - * updates. - * @param network the network messaging service. - * @param networkMapAddress the network map service to fetch current state from. - * @param subscribe if the cache should subscribe to updates. - * @param ifChangedSinceVer an optional version number to limit updating the map based on. If the latest map - * version is less than or equal to the given version, no update is fetched. - */ - fun addMapService(network: MessagingService, networkMapAddress: SingleMessageRecipient, - subscribe: Boolean, ifChangedSinceVer: Int? = null): CordaFuture - /** Adds a node to the local cache (generally only used for adding ourselves). */ fun addNode(node: NodeInfo) /** Removes a node from the local cache. */ fun removeNode(node: NodeInfo) - /** For testing where the network map cache is manipulated marks the service as immediately ready. */ - @VisibleForTesting - fun runWithoutMapService() - /** Indicates if loading network map data from database was successful. */ val loadDBSuccess: Boolean } diff --git a/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt b/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt index 43a07000f3..2b09e9dc33 100644 --- a/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt +++ b/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt @@ -4,7 +4,6 @@ import com.typesafe.config.Config import net.corda.core.identity.CordaX500Name import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.seconds -import net.corda.node.internal.NetworkMapInfo import net.corda.node.services.messaging.CertificateChainCheckPolicy import net.corda.nodeapi.User import net.corda.nodeapi.config.NodeSSLConfiguration @@ -19,12 +18,6 @@ interface NodeConfiguration : NodeSSLConfiguration { // myLegalName should be only used in the initial network registration, we should use the name from the certificate instead of this. // TODO: Remove this so we don't accidentally use this identity in the code? val myLegalName: CordaX500Name - /** - * If null then configure the node to run as the netwok map service, otherwise use this to connect to the network map - * service. - */ - val networkMapService: NetworkMapInfo? - val noNetworkMapServiceMode: Boolean val minimumPlatformVersion: Int val emailAddress: String val exportJMXto: String @@ -92,8 +85,6 @@ data class NodeConfigurationImpl( override val dataSourceProperties: Properties, override val database: Properties?, override val certificateSigningService: URL, - override val networkMapService: NetworkMapInfo?, - override val noNetworkMapServiceMode: Boolean = false, override val minimumPlatformVersion: Int = 1, override val rpcUsers: List, override val verifierType: VerifierType, diff --git a/node/src/main/kotlin/net/corda/node/services/messaging/ArtemisMessagingServer.kt b/node/src/main/kotlin/net/corda/node/services/messaging/ArtemisMessagingServer.kt index 52421eb6d1..12951716c9 100644 --- a/node/src/main/kotlin/net/corda/node/services/messaging/ArtemisMessagingServer.kt +++ b/node/src/main/kotlin/net/corda/node/services/messaging/ArtemisMessagingServer.kt @@ -1,21 +1,20 @@ package net.corda.node.services.messaging -import com.google.common.util.concurrent.ListenableFuture import io.netty.handler.ssl.SslHandler -import net.corda.core.concurrent.CordaFuture import net.corda.core.crypto.AddressFormatException import net.corda.core.crypto.newSecureRandom -import net.corda.core.crypto.random63BitValue import net.corda.core.identity.CordaX500Name import net.corda.core.internal.ThreadBox -import net.corda.core.internal.concurrent.openFuture import net.corda.core.internal.div import net.corda.core.internal.noneOrSingle import net.corda.core.internal.uncheckedCast import net.corda.core.node.NodeInfo import net.corda.core.node.services.NetworkMapCache import net.corda.core.node.services.NetworkMapCache.MapChange -import net.corda.core.utilities.* +import net.corda.core.utilities.NetworkHostAndPort +import net.corda.core.utilities.debug +import net.corda.core.utilities.loggerFor +import net.corda.core.utilities.parsePublicKeyBase58 import net.corda.node.internal.Node import net.corda.node.services.RPCUserService import net.corda.node.services.config.NodeConfiguration @@ -37,12 +36,10 @@ import org.apache.activemq.artemis.core.config.Configuration import org.apache.activemq.artemis.core.config.CoreQueueConfiguration import org.apache.activemq.artemis.core.config.impl.ConfigurationImpl import org.apache.activemq.artemis.core.config.impl.SecurityConfiguration -import org.apache.activemq.artemis.core.message.impl.CoreMessage import org.apache.activemq.artemis.core.remoting.impl.netty.* import org.apache.activemq.artemis.core.security.Role import org.apache.activemq.artemis.core.server.ActiveMQServer import org.apache.activemq.artemis.core.server.impl.ActiveMQServerImpl -import org.apache.activemq.artemis.core.server.impl.RoutingContextImpl import org.apache.activemq.artemis.core.settings.impl.AddressFullMessagePolicy import org.apache.activemq.artemis.core.settings.impl.AddressSettings import org.apache.activemq.artemis.spi.core.remoting.* @@ -111,14 +108,7 @@ class ArtemisMessagingServer(override val config: NodeConfiguration, private val mutex = ThreadBox(InnerState()) private lateinit var activeMQServer: ActiveMQServer val serverControl: ActiveMQServerControl get() = activeMQServer.activeMQServerControl - private val _networkMapConnectionFuture = config.networkMapService?.let { openFuture() } - /** - * A [ListenableFuture] which completes when the server successfully connects to the network map node. If a - * non-recoverable error is encountered then the Future will complete with an exception. - */ - val networkMapConnectionFuture: CordaFuture? get() = _networkMapConnectionFuture private var networkChangeHandle: Subscription? = null - private val nodeRunsNetworkMapService = config.networkMapService == null init { config.baseDirectory.requireOnDefaultFileSystem() @@ -132,8 +122,6 @@ class ArtemisMessagingServer(override val config: NodeConfiguration, fun start() = mutex.locked { if (!running) { configureAndStartServer() - // Deploy bridge to the network map service - config.networkMapService?.let { deployBridge(NetworkMapAddress(it.address), setOf(it.legalName)) } networkChangeHandle = networkMapCache.changed.subscribe { updateBridgesOnNetworkChange(it) } running = true } @@ -158,7 +146,6 @@ class ArtemisMessagingServer(override val config: NodeConfiguration, // Some types of queue might need special preparation on our side, like dialling back or preparing // a lazily initialised subsystem. registerPostQueueCreationCallback { deployBridgesFromNewQueue(it.toString()) } - if (nodeRunsNetworkMapService) registerPostQueueCreationCallback { handleIpDetectionRequest(it.toString()) } registerPostQueueDeletionCallback { address, qName -> log.debug { "Queue deleted: $qName for $address" } } } activeMQServer.start() @@ -247,12 +234,6 @@ class ArtemisMessagingServer(override val config: NodeConfiguration, securityRoles[RPCApi.RPC_SERVER_QUEUE_NAME] = setOf(nodeInternalRole, restrictedRole(RPC_ROLE, send = true)) // TODO remove the NODE_USER role once the webserver doesn't need it securityRoles["${RPCApi.RPC_CLIENT_QUEUE_NAME_PREFIX}.$NODE_USER.#"] = setOf(nodeInternalRole) - if (nodeRunsNetworkMapService) { - securityRoles["$IP_REQUEST_PREFIX*"] = setOf( - nodeInternalRole, - restrictedRole(PEER_ROLE, consume = true, createNonDurableQueue = true, deleteNonDurableQueue = true) - ) - } for ((username) in userService.users) { securityRoles["${RPCApi.RPC_CLIENT_QUEUE_NAME_PREFIX}.$username.#"] = setOf( nodeInternalRole, @@ -330,7 +311,7 @@ class ArtemisMessagingServer(override val config: NodeConfiguration, log.debug { "Updating bridges on network map change: ${change.node}" } fun gatherAddresses(node: NodeInfo): Sequence { val address = node.addresses.first() - return node.legalIdentitiesAndCerts.map { getArtemisPeerAddress(it.party, address, config.networkMapService?.legalName) }.asSequence() + return node.legalIdentitiesAndCerts.map { NodeAddress(it.party.owningKey, address) }.asSequence() } fun deployBridges(node: NodeInfo) { @@ -409,47 +390,6 @@ class ArtemisMessagingServer(override val config: NodeConfiguration, private val ArtemisPeerAddress.bridgeName: String get() = getBridgeName(queueName, hostAndPort) private fun getBridgeName(queueName: String, hostAndPort: NetworkHostAndPort): String = "$queueName -> $hostAndPort" - - // This is called on one of Artemis' background threads - internal fun hostVerificationFail(expectedLegalNames: Set, errorMsg: String?) { - log.error(errorMsg) - if (config.networkMapService?.legalName in expectedLegalNames) { - // If the peer that failed host verification was the network map node then we're in big trouble and need to bail! - _networkMapConnectionFuture!!.setException(IOException("${config.networkMapService} failed host verification check")) - } - } - - // This is called on one of Artemis' background threads - internal fun onTcpConnection(peerLegalName: CordaX500Name) { - if (peerLegalName == config.networkMapService?.legalName) { - _networkMapConnectionFuture!!.set(Unit) - } - } - - private fun handleIpDetectionRequest(queueName: String) { - fun getRemoteAddress(requestId: String): String? { - val session = activeMQServer.sessions.first { - it.getMetaData(ipDetectRequestProperty) == requestId - } - return session.remotingConnection.remoteAddress - } - - fun sendResponse(remoteAddress: String?) { - val responseMessage = CoreMessage(random63BitValue(), 0).apply { - putStringProperty(ipDetectResponseProperty, remoteAddress) - } - val routingContext = RoutingContextImpl(null) - val queue = activeMQServer.locateQueue(SimpleString(queueName)) - queue.route(responseMessage, routingContext) - activeMQServer.postOffice.processRoute(responseMessage, routingContext, true) - } - - if (!queueName.startsWith(IP_REQUEST_PREFIX)) return - val requestId = queueName.substringAfter(IP_REQUEST_PREFIX) - val remoteAddress = getRemoteAddress(requestId) - log.debug { "Detected remote address $remoteAddress for request $requestId" } - sendResponse(remoteAddress) - } } class VerifyingNettyConnectorFactory : NettyConnectorFactory() { @@ -473,7 +413,10 @@ private class VerifyingNettyConnector(configuration: MutableMap, scheduledThreadPool: ScheduledExecutorService?, protocolManager: ClientProtocolManager?) : NettyConnector(configuration, handler, listener, closeExecutor, threadPool, scheduledThreadPool, protocolManager) { - private val server = configuration[ArtemisMessagingServer::class.java.name] as ArtemisMessagingServer + companion object { + private val log = loggerFor() + } + private val sslEnabled = ConfigurationHelper.getBooleanProperty(TransportConstants.SSL_ENABLED_PROP_NAME, TransportConstants.DEFAULT_SSL_ENABLED, configuration) override fun createConnection(): Connection? { @@ -504,10 +447,9 @@ private class VerifyingNettyConnector(configuration: MutableMap, "misconfiguration by the remote peer or an SSL man-in-the-middle attack!" } X509Utilities.validateCertificateChain(session.localCertificates.last() as java.security.cert.X509Certificate, *session.peerCertificates) - server.onTcpConnection(peerLegalName) } catch (e: IllegalArgumentException) { connection.close() - server.hostVerificationFail(expectedLegalNames, e.message) + log.error(e.message) return null } } diff --git a/node/src/main/kotlin/net/corda/node/services/messaging/NodeMessagingClient.kt b/node/src/main/kotlin/net/corda/node/services/messaging/NodeMessagingClient.kt index 401709f811..d9b0aa0f13 100644 --- a/node/src/main/kotlin/net/corda/node/services/messaging/NodeMessagingClient.kt +++ b/node/src/main/kotlin/net/corda/node/services/messaging/NodeMessagingClient.kt @@ -48,7 +48,6 @@ import javax.persistence.Column import javax.persistence.Entity import javax.persistence.Id import javax.persistence.Lob -import javax.security.auth.x500.X500Principal // TODO: Stop the wallet explorer and other clients from using this class and get rid of persistentInbox @@ -76,7 +75,7 @@ import javax.security.auth.x500.X500Principal class NodeMessagingClient(override val config: NodeConfiguration, private val versionInfo: VersionInfo, private val serverAddress: NetworkHostAndPort, - private val myIdentity: PublicKey?, + private val myIdentity: PublicKey, private val nodeExecutor: AffinityExecutor.ServiceAffinityExecutor, val database: CordaPersistence, private val networkMapRegistrationFuture: CordaFuture, @@ -172,14 +171,7 @@ class NodeMessagingClient(override val config: NodeConfiguration, /** An executor for sending messages */ private val messagingExecutor = AffinityExecutor.ServiceAffinityExecutor("Messaging", 1) - /** - * Apart from the NetworkMapService this is the only other address accessible to the node outside of lookups against the NetworkMapCache. - */ - override val myAddress: SingleMessageRecipient = if (myIdentity != null) { - NodeAddress.asSingleNode(myIdentity, advertisedAddress) - } else { - NetworkMapAddress(advertisedAddress) - } + override val myAddress: SingleMessageRecipient = NodeAddress(myIdentity, advertisedAddress) private val state = ThreadBox(InnerState()) private val handlers = CopyOnWriteArrayList() @@ -634,9 +626,7 @@ class NodeMessagingClient(override val config: NodeConfiguration, // TODO Rethink PartyInfo idea and merging PeerAddress/ServiceAddress (the only difference is that Service address doesn't hold host and port) override fun getAddressOfParty(partyInfo: PartyInfo): MessageRecipients { return when (partyInfo) { - is PartyInfo.SingleNode -> { - getArtemisPeerAddress(partyInfo.party, partyInfo.addresses.first(), config.networkMapService?.legalName) - } + is PartyInfo.SingleNode -> NodeAddress(partyInfo.party.owningKey, partyInfo.addresses.first()) is PartyInfo.DistributedNode -> ServiceAddress(partyInfo.party.owningKey) } } diff --git a/node/src/main/kotlin/net/corda/node/services/network/PersistentNetworkMapCache.kt b/node/src/main/kotlin/net/corda/node/services/network/PersistentNetworkMapCache.kt index d242f3869f..528fe6a226 100644 --- a/node/src/main/kotlin/net/corda/node/services/network/PersistentNetworkMapCache.kt +++ b/node/src/main/kotlin/net/corda/node/services/network/PersistentNetworkMapCache.kt @@ -1,49 +1,33 @@ package net.corda.node.services.network import net.corda.core.concurrent.CordaFuture -import net.corda.core.crypto.SecureHash import net.corda.core.crypto.toStringShort import net.corda.core.identity.AbstractParty import net.corda.core.identity.CordaX500Name import net.corda.core.identity.Party import net.corda.core.identity.PartyAndCertificate -import net.corda.core.internal.VisibleForTesting import net.corda.core.internal.bufferUntilSubscribed -import net.corda.core.internal.concurrent.map import net.corda.core.internal.concurrent.openFuture import net.corda.core.messaging.DataFeed -import net.corda.core.messaging.SingleMessageRecipient import net.corda.core.node.NodeInfo import net.corda.core.node.services.IdentityService import net.corda.core.node.services.NetworkMapCache.MapChange import net.corda.core.node.services.NotaryService import net.corda.core.node.services.PartyInfo import net.corda.core.schemas.NodeInfoSchemaV1 -import net.corda.core.serialization.SerializationDefaults import net.corda.core.serialization.SingletonSerializeAsToken -import net.corda.core.serialization.deserialize -import net.corda.core.serialization.serialize import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.loggerFor -import net.corda.core.utilities.toBase58String -import net.corda.node.services.api.NetworkCacheException -import net.corda.node.services.api.NetworkMapCacheInternal import net.corda.node.services.api.NetworkMapCacheBaseInternal +import net.corda.node.services.api.NetworkMapCacheInternal import net.corda.node.services.config.NodeConfiguration -import net.corda.node.services.messaging.MessagingService -import net.corda.node.services.messaging.createMessage -import net.corda.node.services.messaging.sendRequest -import net.corda.node.services.network.NetworkMapService.FetchMapResponse -import net.corda.node.services.network.NetworkMapService.SubscribeResponse -import net.corda.node.utilities.* -import net.corda.node.utilities.AddOrRemove +import net.corda.node.utilities.CordaPersistence import net.corda.node.utilities.bufferUntilDatabaseCommit import net.corda.node.utilities.wrapWithDatabaseTransaction import org.hibernate.Session import rx.Observable import rx.subjects.PublishSubject import java.security.PublicKey -import java.security.SignatureException import java.util.* import javax.annotation.concurrent.ThreadSafe import kotlin.collections.HashMap @@ -78,7 +62,6 @@ open class PersistentNetworkMapCache(private val database: CordaPersistence, con val logger = loggerFor() } - private var registeredForPush = false // TODO Small explanation, partyNodes and registeredNodes is left in memory as it was before, because it will be removed in // next PR that gets rid of services. These maps are used only for queries by service. protected val registeredNodes: MutableMap = Collections.synchronizedMap(HashMap()) @@ -88,6 +71,8 @@ open class PersistentNetworkMapCache(private val database: CordaPersistence, con override val changed: Observable = _changed.wrapWithDatabaseTransaction() private val changePublisher: rx.Observer get() = _changed.bufferUntilDatabaseCommit() + // TODO revisit the logic under which nodeReady and loadDBSuccess are set. + // with the NetworkMapService redesign their meaning is not too well defined. private val _registrationFuture = openFuture() override val nodeReady: CordaFuture get() = _registrationFuture private var _loadDBSuccess: Boolean = false @@ -152,38 +137,6 @@ open class PersistentNetworkMapCache(private val database: CordaPersistence, con } } - override fun addMapService(network: MessagingService, networkMapAddress: SingleMessageRecipient, subscribe: Boolean, - ifChangedSinceVer: Int?): CordaFuture { - if (subscribe && !registeredForPush) { - // Add handler to the network, for updates received from the remote network map service. - network.addMessageHandler(NetworkMapService.PUSH_TOPIC) { message, _ -> - try { - val req = message.data.deserialize() - val ackMessage = network.createMessage(NetworkMapService.PUSH_ACK_TOPIC, - data = NetworkMapService.UpdateAcknowledge(req.mapVersion, network.myAddress).serialize().bytes) - network.send(ackMessage, req.replyTo) - processUpdatePush(req) - } catch (e: NodeMapException) { - logger.warn("Failure during node map update due to bad update: ${e.javaClass.name}") - } catch (e: Exception) { - logger.error("Exception processing update from network map service", e) - } - } - registeredForPush = true - } - - // Fetch the network map and register for updates at the same time - val req = NetworkMapService.FetchMapRequest(subscribe, ifChangedSinceVer, network.myAddress) - val future = network.sendRequest(NetworkMapService.FETCH_TOPIC, req, networkMapAddress).map { (nodes) -> - // We may not receive any nodes back, if the map hasn't changed since the version specified - nodes?.forEach { processRegistration(it) } - Unit - } - _registrationFuture.captureLater(future.map { null }) - - return future - } - override fun addNode(node: NodeInfo) { logger.info("Adding node with info: $node") synchronized(_changed) { @@ -210,6 +163,8 @@ open class PersistentNetworkMapCache(private val database: CordaPersistence, con logger.info("Previous node was identical to incoming one - doing nothing") } } + _loadDBSuccess = true // This is used in AbstractNode to indicate that node is ready. + _registrationFuture.set(null) logger.info("Done adding node with info: $node") } @@ -225,49 +180,11 @@ open class PersistentNetworkMapCache(private val database: CordaPersistence, con logger.info("Done removing node with info: $node") } - /** - * Unsubscribes from updates from the given map service. - * @param mapParty the network map service party to listen to updates from. - */ - override fun deregisterForUpdates(network: MessagingService, mapParty: Party): CordaFuture { - // Fetch the network map and register for updates at the same time - val req = NetworkMapService.SubscribeRequest(false, network.myAddress) - // `network.getAddressOfParty(partyInfo)` is a work-around for MockNetwork and InMemoryMessaging to get rid of SingleMessageRecipient in NodeInfo. - val address = getPartyInfo(mapParty)?.let { network.getAddressOfParty(it) } ?: - throw IllegalArgumentException("Can't deregister for updates, don't know the party: $mapParty") - val future = network.sendRequest(NetworkMapService.SUBSCRIPTION_TOPIC, req, address).map { - if (it.confirmed) Unit else throw NetworkCacheException.DeregistrationFailed() - } - _registrationFuture.captureLater(future.map { null }) - return future - } - - fun processUpdatePush(req: NetworkMapService.Update) { - try { - val reg = req.wireReg.verified() - processRegistration(reg) - } catch (e: SignatureException) { - throw NodeMapException.InvalidSignature() - } - } - override val allNodes: List get() = database.transaction { getAllInfos(session).map { it.toNodeInfo() } } - private fun processRegistration(reg: NodeRegistration) { - when (reg.type) { - AddOrRemove.ADD -> addNode(reg.node) - AddOrRemove.REMOVE -> removeNode(reg.node) - } - } - - @VisibleForTesting - override fun runWithoutMapService() { - _registrationFuture.set(null) - } - // Changes related to NetworkMap redesign // TODO It will be properly merged into network map cache after services removal. @@ -288,14 +205,10 @@ open class PersistentNetworkMapCache(private val database: CordaPersistence, con logger.info("Loaded node info: $nodeInfo") val node = nodeInfo.toNodeInfo() addNode(node) - _loadDBSuccess = true // This is used in AbstractNode to indicate that node is ready. } catch (e: Exception) { logger.warn("Exception parsing network map from the database.", e) } } - if (loadDBSuccess) { - _registrationFuture.set(null) // Useful only if we don't have NetworkMapService configured so StateMachineManager can start. - } } private fun updateInfoDB(nodeInfo: NodeInfo) { diff --git a/node/src/test/kotlin/net/corda/node/services/config/FullNodeConfigurationTest.kt b/node/src/test/kotlin/net/corda/node/services/config/FullNodeConfigurationTest.kt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/node/src/test/kotlin/net/corda/node/services/config/NodeConfigurationImplTest.kt b/node/src/test/kotlin/net/corda/node/services/config/NodeConfigurationImplTest.kt index 0bdf58e6f8..292c87ac8b 100644 --- a/node/src/test/kotlin/net/corda/node/services/config/NodeConfigurationImplTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/config/NodeConfigurationImplTest.kt @@ -36,7 +36,6 @@ class NodeConfigurationImplTest { private val testConfiguration = NodeConfigurationImpl( baseDirectory = Paths.get("."), myLegalName = ALICE.name, - networkMapService = null, emailAddress = "", keyStorePassword = "cordacadevpass", trustStorePassword = "trustpass", diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/NodeTestUtils.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/NodeTestUtils.kt index f781935fe6..0f698b2f94 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/NodeTestUtils.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/NodeTestUtils.kt @@ -59,7 +59,6 @@ fun testNodeConfiguration( myLegalName: CordaX500Name): NodeConfiguration { abstract class MockableNodeConfiguration : NodeConfiguration // Otherwise Mockito is defeated by val getters. return rigorousMock().also { - doReturn(true).whenever(it).noNetworkMapServiceMode doReturn(baseDirectory).whenever(it).baseDirectory doReturn(myLegalName).whenever(it).myLegalName doReturn(1).whenever(it).minimumPlatformVersion @@ -77,7 +76,6 @@ fun testNodeConfiguration( doReturn(VerifierType.InMemory).whenever(it).verifierType doReturn(5).whenever(it).messageRedeliveryDelaySeconds doReturn(0L).whenever(it).additionalNodeInfoPollingFrequencyMsec - doReturn(null).whenever(it).networkMapService doReturn(null).whenever(it).devModeOptions doCallRealMethod().whenever(it).certificatesDirectory doCallRealMethod().whenever(it).trustStoreFile diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNetworkMapCache.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNetworkMapCache.kt index 889f9cfd3b..71ee228f1b 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNetworkMapCache.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNetworkMapCache.kt @@ -1,9 +1,9 @@ package net.corda.testing.node -import co.paralleluniverse.common.util.VisibleForTesting +import net.corda.core.concurrent.CordaFuture import net.corda.core.crypto.entropyToKeyPair import net.corda.core.identity.CordaX500Name -import net.corda.core.identity.Party +import net.corda.core.internal.concurrent.doneFuture import net.corda.core.node.NodeInfo import net.corda.core.node.services.NetworkMapCache import net.corda.core.utilities.NetworkHostAndPort @@ -27,32 +27,12 @@ class MockNetworkMapCache(database: CordaPersistence, configuration: NodeConfigu } override val changed: Observable = PublishSubject.create() + override val nodeReady: CordaFuture get() = doneFuture(null) init { val mockNodeA = NodeInfo(listOf(BANK_C_ADDR), listOf(BANK_C), 1, serial = 1L) val mockNodeB = NodeInfo(listOf(BANK_D_ADDR), listOf(BANK_D), 1, serial = 1L) partyNodes.add(mockNodeA) partyNodes.add(mockNodeB) - runWithoutMapService() - } - - /** - * Directly add a registration to the internal cache. DOES NOT fire the change listeners, as it's - * not a change being received. - */ - @VisibleForTesting - fun addRegistration(node: NodeInfo) { - val previousIndex = partyNodes.indexOfFirst { it.legalIdentitiesAndCerts == node.legalIdentitiesAndCerts } - if (previousIndex != -1) partyNodes[previousIndex] = node - else partyNodes.add(node) - } - - /** - * Directly remove a registration from the internal cache. DOES NOT fire the change listeners, as it's - * not a change being received. - */ - @VisibleForTesting - fun deleteRegistration(legalIdentity: Party): Boolean { - return partyNodes.removeIf { legalIdentity.owningKey in it.legalIdentitiesAndCerts.map { it.owningKey } } } } diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt index 52052268bc..cac8607e85 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/MockNode.kt @@ -8,7 +8,6 @@ import net.corda.core.crypto.entropyToKeyPair import net.corda.core.crypto.random63BitValue import net.corda.core.identity.CordaX500Name import net.corda.core.identity.PartyAndCertificate -import net.corda.core.internal.concurrent.doneFuture import net.corda.core.internal.createDirectories import net.corda.core.internal.createDirectory import net.corda.core.internal.uncheckedCast @@ -182,7 +181,6 @@ class MockNetwork(defaultParameters: MockNetworkParameters = MockNetworkParamete CordappLoader.createDefaultWithTestPackages(args.config, args.network.cordappPackages), args.network.busyLatch) { val mockNet = args.network - override val networkMapAddress = null val id = args.id internal val notaryIdentity = args.notaryIdentity val entropyRoot = args.entropyRoot @@ -235,8 +233,11 @@ class MockNetwork(defaultParameters: MockNetworkParameters = MockNetworkParamete return entropyToKeyPair(counter) } - // It's OK to not have a network map service in the mock network. - override fun noNetworkMapConfigured() = doneFuture(Unit) + /** + * MockNetwork will ensure nodes are connected to each other. The nodes themselves + * won't be able to tell if that happened already or not. + */ + override fun checkNetworkMapIsInitialized() = Unit override fun makeTransactionVerifierService() = InMemoryTransactionVerifierService(1) diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/NodeBasedTest.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/NodeBasedTest.kt index 938c4deb04..d91bb3316e 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/NodeBasedTest.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/NodeBasedTest.kt @@ -5,6 +5,7 @@ import net.corda.core.identity.CordaX500Name import net.corda.core.internal.concurrent.* import net.corda.core.internal.createDirectories import net.corda.core.internal.div +import net.corda.core.node.NodeInfo import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.getOrThrow import net.corda.node.internal.Node @@ -14,7 +15,6 @@ import net.corda.node.services.config.* import net.corda.node.utilities.ServiceIdentityGenerator import net.corda.nodeapi.User import net.corda.nodeapi.config.toConfig -import net.corda.testing.DUMMY_MAP import net.corda.testing.TestDependencyInjectionBase import net.corda.testing.driver.addressMustNotBeBoundFuture import net.corda.testing.getFreeLocalPorts @@ -41,9 +41,7 @@ abstract class NodeBasedTest(private val cordappPackages: List = emptyLi val tempFolder = TemporaryFolder() private val nodes = mutableListOf>() - private var _networkMapNode: StartedNode? = null - - val networkMapNode: StartedNode get() = _networkMapNode ?: startNetworkMapNode() + private val nodeInfos = mutableListOf() init { System.setProperty("consoleLogLevel", Level.DEBUG.name().toLowerCase()) @@ -65,7 +63,6 @@ abstract class NodeBasedTest(private val cordappPackages: List = emptyLi ) }.filterNotNull() nodes.clear() - _networkMapNode = null portNotBoundChecks.transpose().getOrThrow() } @@ -76,49 +73,17 @@ abstract class NodeBasedTest(private val cordappPackages: List = emptyLi nodes.forEach { it.services.networkMapCache.clearNetworkMapCache() } } - /** - * You can use this method to start the network map node in a more customised manner. Otherwise it - * will automatically be started with the default parameters. - */ - fun startNetworkMapNode(legalName: CordaX500Name = DUMMY_MAP.name, - platformVersion: Int = 1, - rpcUsers: List = emptyList(), - configOverrides: Map = emptyMap()): StartedNode { - check(_networkMapNode == null || _networkMapNode!!.info.legalIdentitiesAndCerts.first().name == legalName) - return startNodeInternal(legalName, platformVersion, rpcUsers, configOverrides).apply { - _networkMapNode = this - } - } - @JvmOverloads fun startNode(legalName: CordaX500Name, platformVersion: Int = 1, rpcUsers: List = emptyList(), configOverrides: Map = emptyMap(), - noNetworkMap: Boolean = false, waitForConnection: Boolean = true): CordaFuture> { - val networkMapConf = if (noNetworkMap) { - // Nonexistent network map service address. - mapOf( - "networkMapService" to mapOf( - "address" to "localhost:10000", - "legalName" to networkMapNode.info.legalIdentitiesAndCerts.first().name.toString() - ) - ) - } else { - mapOf( - "networkMapService" to mapOf( - "address" to networkMapNode.internals.configuration.p2pAddress.toString(), - "legalName" to networkMapNode.info.legalIdentitiesAndCerts.first().name.toString() - ) - ) - } val node = startNodeInternal( legalName, platformVersion, rpcUsers, - networkMapConf + configOverrides, - noNetworkMap) + configOverrides) return if (waitForConnection) node.internals.nodeReadyFuture.map { node } else doneFuture(node) } @@ -169,11 +134,19 @@ abstract class NodeBasedTest(private val cordappPackages: List = emptyLi protected fun baseDirectory(legalName: CordaX500Name) = tempFolder.root.toPath() / legalName.organisation.replace(WHITESPACE, "") + private fun ensureAllNetworkMapCachesHaveAllNodeInfos() { + val runningNodes = nodes.filter { it.internals.started != null } + val runningNodesInfo = runningNodes.map { it.info } + for (node in runningNodes) + for (nodeInfo in runningNodesInfo) { + node.services.networkMapCache.addNode(nodeInfo) + } + } + private fun startNodeInternal(legalName: CordaX500Name, platformVersion: Int, rpcUsers: List, - configOverrides: Map, - noNetworkMap: Boolean = false): StartedNode { + configOverrides: Map): StartedNode { val baseDirectory = baseDirectory(legalName).createDirectories() val localPort = getFreeLocalPorts("localhost", 2) val p2pAddress = configOverrides["p2pAddress"] ?: localPort[0].toString() @@ -184,8 +157,7 @@ abstract class NodeBasedTest(private val cordappPackages: List = emptyLi "myLegalName" to legalName.toString(), "p2pAddress" to p2pAddress, "rpcAddress" to localPort[1].toString(), - "rpcUsers" to rpcUsers.map { it.toMap() }, - "noNetworkMap" to noNetworkMap + "rpcUsers" to rpcUsers.map { it.toMap() } ) + configOverrides ) @@ -196,9 +168,11 @@ abstract class NodeBasedTest(private val cordappPackages: List = emptyLi initialiseSerialization = false, cordappLoader = CordappLoader.createDefaultWithTestPackages(parsedConfig, cordappPackages)).start() nodes += node + ensureAllNetworkMapCachesHaveAllNodeInfos() thread(name = legalName.organisation) { node.internals.run() } + return node } } diff --git a/tools/demobench/src/test/kotlin/net/corda/demobench/model/NodeConfigTest.kt b/tools/demobench/src/test/kotlin/net/corda/demobench/model/NodeConfigTest.kt index aee32bc49c..92ed477be8 100644 --- a/tools/demobench/src/test/kotlin/net/corda/demobench/model/NodeConfigTest.kt +++ b/tools/demobench/src/test/kotlin/net/corda/demobench/model/NodeConfigTest.kt @@ -4,7 +4,6 @@ import com.typesafe.config.ConfigFactory import com.typesafe.config.ConfigValueFactory import net.corda.core.identity.CordaX500Name import net.corda.core.utilities.NetworkHostAndPort -import net.corda.node.internal.NetworkMapInfo import net.corda.node.services.config.parseAsNodeConfiguration import net.corda.nodeapi.User import net.corda.nodeapi.config.toConfig @@ -47,7 +46,6 @@ class NodeConfigTest { assertEquals(localPort(40002), fullConfig.rpcAddress) assertEquals(localPort(10001), fullConfig.p2pAddress) assertEquals(listOf(user("jenny")), fullConfig.rpcUsers) - assertEquals(NetworkMapInfo(localPort(12345), DUMMY_NOTARY.name), fullConfig.networkMapService) assertThat(fullConfig.dataSourceProperties["dataSource.url"] as String).contains("AUTO_SERVER_PORT=30001") assertTrue(fullConfig.useTestClock) assertFalse(fullConfig.detectPublicIp) From 02a5ddb7dff568e4d17dab357abe1d1843db36f3 Mon Sep 17 00:00:00 2001 From: Maksymilian Pawlak <120831+m4ksio@users.noreply.github.com> Date: Mon, 30 Oct 2017 16:51:38 +0000 Subject: [PATCH 86/94] Remove Bower dependencies from repo and download them on demand (#1959) Bower dependencies installed by Gradle as a part of deployment --- .idea/compiler.xml | 1 - samples/irs-demo/web/.gitignore | 1 + samples/irs-demo/web/build.gradle | 24 + .../web/src/main/resources/static/bower.json | 4 +- .../web/src/main/resources/static/index.html | 2 +- .../angular-fcsa-number/.bower.json | 43 - .../angular-fcsa-number/CHANGLOG.md | 56 - .../angular-fcsa-number/LICENSE | 201 - .../angular-fcsa-number/README.md | 129 - .../angular-fcsa-number/bower.json | 34 - .../angular-fcsa-number/e2e/fcsaNumber.e2e.js | 29 - .../angular-fcsa-number/e2e/protractor.config | 19 - .../angular-fcsa-number/e2e/web/app.js | 6 - .../e2e/web/public/angular.js | 22873 ---------- .../e2e/web/public/fcsaNumber.js | 195 - .../e2e/web/public/index.html | 16 - .../angular-fcsa-number/src/fcsaNumber.js | 195 - .../angular-fcsa-number/src/fcsaNumber.min.js | 2 - .../angular-route/.bower.json | 20 - .../bower_components/angular-route/LICENSE.md | 21 - .../bower_components/angular-route/README.md | 68 - .../angular-route/angular-route.js | 1069 - .../angular-route/angular-route.min.js | 16 - .../angular-route/angular-route.min.js.map | 8 - .../bower_components/angular-route/bower.json | 10 - .../bower_components/angular-route/index.js | 2 - .../angular-route/package.json | 33 - .../js/bower_components/angular/.bower.json | 18 - .../js/bower_components/angular/LICENSE.md | 21 - .../js/bower_components/angular/README.md | 64 - .../bower_components/angular/angular-csp.css | 21 - .../js/bower_components/angular/angular.js | 31768 ------------- .../bower_components/angular/angular.min.js | 318 - .../angular/angular.min.js.gzip | Bin 56905 -> 0 bytes .../angular/angular.min.js.map | 8 - .../js/bower_components/angular/bower.json | 9 - .../js/bower_components/angular/index.js | 2 - .../js/bower_components/angular/package.json | 25 - .../jquery.maskedinput/.bower.json | 39 - .../jquery.maskedinput/CONTRIBUTING.md | 127 - .../jquery.maskedinput/LICENSE | 22 - .../jquery.maskedinput/README.md | 95 - .../jquery.maskedinput/bower.json | 30 - .../jquery.maskedinput/demo/datepicker.html | 22 - .../jquery.maskedinput/demo/index.html | 49 - .../dist/jquery.maskedinput.js | 182 - .../dist/jquery.maskedinput.min.js | 7 - .../jquery.maskedinput/gruntfile.js | 62 - .../jquery.maskedinput.nuspec | 21 - .../jquery.maskedinput/package.json | 16 - .../src/jquery.maskedinput.js | 436 - .../js/bower_components/jquery/.bower.json | 25 - .../js/bower_components/jquery/AUTHORS.txt | 295 - .../js/bower_components/jquery/LICENSE.txt | 36 - .../js/bower_components/jquery/README.md | 65 - .../js/bower_components/jquery/bower.json | 14 - .../js/bower_components/jquery/dist/core.js | 482 - .../js/bower_components/jquery/dist/jquery.js | 10220 ----- .../jquery/dist/jquery.min.js | 4 - .../jquery/dist/jquery.min.map | 1 - .../jquery/dist/jquery.slim.js | 8107 ---- .../jquery/dist/jquery.slim.min.js | 4 - .../jquery/dist/jquery.slim.min.map | 1 - .../jquery/external/sizzle/LICENSE.txt | 36 - .../jquery/external/sizzle/dist/sizzle.js | 2272 - .../jquery/external/sizzle/dist/sizzle.min.js | 3 - .../external/sizzle/dist/sizzle.min.map | 1 - .../jquery/src/.eslintrc.json | 18 - .../js/bower_components/jquery/src/ajax.js | 855 - .../bower_components/jquery/src/ajax/jsonp.js | 102 - .../bower_components/jquery/src/ajax/load.js | 76 - .../jquery/src/ajax/parseXML.js | 30 - .../jquery/src/ajax/script.js | 77 - .../jquery/src/ajax/var/location.js | 5 - .../jquery/src/ajax/var/nonce.js | 7 - .../jquery/src/ajax/var/rquery.js | 5 - .../bower_components/jquery/src/ajax/xhr.js | 169 - .../bower_components/jquery/src/attributes.js | 13 - .../jquery/src/attributes/attr.js | 140 - .../jquery/src/attributes/classes.js | 174 - .../jquery/src/attributes/prop.js | 143 - .../jquery/src/attributes/support.js | 33 - .../jquery/src/attributes/val.js | 188 - .../bower_components/jquery/src/callbacks.js | 234 - .../js/bower_components/jquery/src/core.js | 482 - .../jquery/src/core/DOMEval.js | 16 - .../jquery/src/core/access.js | 70 - .../bower_components/jquery/src/core/init.js | 127 - .../jquery/src/core/parseHTML.js | 65 - .../jquery/src/core/ready-no-deferred.js | 105 - .../bower_components/jquery/src/core/ready.js | 95 - .../jquery/src/core/readyException.js | 13 - .../jquery/src/core/stripAndCollapse.js | 14 - .../jquery/src/core/support.js | 20 - .../jquery/src/core/var/rsingleTag.js | 6 - .../js/bower_components/jquery/src/css.js | 426 - .../jquery/src/css/addGetHookIf.js | 26 - .../jquery/src/css/adjustCSS.js | 71 - .../bower_components/jquery/src/css/curCSS.js | 59 - .../jquery/src/css/hiddenVisibleSelectors.js | 15 - .../jquery/src/css/showHide.js | 105 - .../jquery/src/css/support.js | 89 - .../jquery/src/css/var/cssExpand.js | 5 - .../jquery/src/css/var/getStyles.js | 17 - .../jquery/src/css/var/isHiddenWithinTree.js | 34 - .../jquery/src/css/var/rmargin.js | 5 - .../jquery/src/css/var/rnumnonpx.js | 7 - .../jquery/src/css/var/swap.js | 26 - .../js/bower_components/jquery/src/data.js | 179 - .../bower_components/jquery/src/data/Data.js | 161 - .../jquery/src/data/var/acceptData.js | 19 - .../jquery/src/data/var/dataPriv.js | 7 - .../jquery/src/data/var/dataUser.js | 7 - .../bower_components/jquery/src/deferred.js | 389 - .../jquery/src/deferred/exceptionHook.js | 21 - .../bower_components/jquery/src/deprecated.js | 30 - .../bower_components/jquery/src/dimensions.js | 56 - .../js/bower_components/jquery/src/effects.js | 693 - .../jquery/src/effects/Tween.js | 123 - .../jquery/src/effects/animatedSelector.js | 15 - .../js/bower_components/jquery/src/event.js | 745 - .../bower_components/jquery/src/event/ajax.js | 22 - .../jquery/src/event/alias.js | 29 - .../jquery/src/event/focusin.js | 55 - .../jquery/src/event/support.js | 11 - .../jquery/src/event/trigger.js | 185 - .../jquery/src/exports/amd.js | 26 - .../jquery/src/exports/global.js | 34 - .../js/bower_components/jquery/src/jquery.js | 41 - .../jquery/src/manipulation.js | 486 - .../jquery/src/manipulation/_evalUrl.js | 23 - .../jquery/src/manipulation/buildFragment.js | 104 - .../jquery/src/manipulation/getAll.js | 31 - .../jquery/src/manipulation/setGlobalEval.js | 22 - .../jquery/src/manipulation/support.js | 35 - .../src/manipulation/var/rcheckableType.js | 5 - .../src/manipulation/var/rscriptType.js | 5 - .../jquery/src/manipulation/var/rtagName.js | 5 - .../jquery/src/manipulation/wrapMap.js | 29 - .../js/bower_components/jquery/src/offset.js | 232 - .../js/bower_components/jquery/src/queue.js | 145 - .../jquery/src/queue/delay.js | 24 - .../jquery/src/selector-native.js | 237 - .../jquery/src/selector-sizzle.js | 19 - .../bower_components/jquery/src/selector.js | 3 - .../bower_components/jquery/src/serialize.js | 130 - .../bower_components/jquery/src/traversing.js | 178 - .../jquery/src/traversing/findFilter.js | 106 - .../jquery/src/traversing/var/dir.js | 22 - .../src/traversing/var/rneedsContext.js | 8 - .../jquery/src/traversing/var/siblings.js | 17 - .../jquery/src/var/ObjectFunctionString.js | 7 - .../js/bower_components/jquery/src/var/arr.js | 5 - .../jquery/src/var/class2type.js | 6 - .../bower_components/jquery/src/var/concat.js | 7 - .../jquery/src/var/document.js | 5 - .../jquery/src/var/documentElement.js | 7 - .../jquery/src/var/fnToString.js | 7 - .../jquery/src/var/getProto.js | 5 - .../bower_components/jquery/src/var/hasOwn.js | 7 - .../jquery/src/var/indexOf.js | 7 - .../bower_components/jquery/src/var/pnum.js | 5 - .../bower_components/jquery/src/var/push.js | 7 - .../jquery/src/var/rcssNum.js | 9 - .../jquery/src/var/rnothtmlwhite.js | 8 - .../bower_components/jquery/src/var/slice.js | 7 - .../jquery/src/var/support.js | 6 - .../jquery/src/var/toString.js | 7 - .../js/bower_components/jquery/src/wrap.js | 77 - .../js/bower_components/lodash/.bower.json | 14 - .../js/bower_components/lodash/.editorconfig | 12 - .../js/bower_components/lodash/.gitattributes | 1 - .../lodash/.github/CONTRIBUTING.md | 78 - .../js/bower_components/lodash/.gitignore | 4 - .../static/js/bower_components/lodash/.jscsrc | 97 - .../lodash/.markdown-doctest-setup.js | 11 - .../js/bower_components/lodash/.travis.yml | 98 - .../js/bower_components/lodash/CHANGELOG | 1 - .../static/js/bower_components/lodash/LICENSE | 47 - .../js/bower_components/lodash/README.md | 80 - .../lodash/dist/lodash.core.js | 3831 -- .../lodash/dist/lodash.core.min.js | 29 - .../bower_components/lodash/dist/lodash.fp.js | 1006 - .../lodash/dist/lodash.fp.min.js | 20 - .../js/bower_components/lodash/dist/lodash.js | 16982 ------- .../lodash/dist/lodash.min.js | 134 - .../lodash/dist/mapping.fp.js | 429 - .../js/bower_components/lodash/doc/README.md | 10996 ----- .../lodash/fp/_baseConvert.js | 535 - .../lodash/fp/_convertBrowser.js | 18 - .../js/bower_components/lodash/fp/_mapping.js | 367 - .../bower_components/lodash/fp/placeholder.js | 6 - .../lodash/lib/common/file.js | 71 - .../lodash/lib/common/mapping.js | 9 - .../lodash/lib/common/minify.js | 39 - .../lodash/lib/common/uglify.options.js | 22 - .../lodash/lib/common/util.js | 40 - .../lodash/lib/fp/build-dist.js | 56 - .../lodash/lib/fp/build-doc.js | 78 - .../lodash/lib/fp/build-modules.js | 150 - .../lodash/lib/fp/template/doc/wiki.jst | 226 - .../lib/fp/template/modules/_falseOptions.jst | 7 - .../lodash/lib/fp/template/modules/_util.jst | 15 - .../lodash/lib/fp/template/modules/alias.jst | 1 - .../lib/fp/template/modules/category.jst | 2 - .../lib/fp/template/modules/convert.jst | 18 - .../lodash/lib/fp/template/modules/fp.jst | 2 - .../lodash/lib/fp/template/modules/module.jst | 5 - .../lodash/lib/fp/template/modules/thru.jst | 5 - .../lodash/lib/main/build-dist.js | 31 - .../lodash/lib/main/build-doc.js | 83 - .../lodash/lib/main/build-modules.js | 34 - .../lodash/lib/main/build-site.js | 215 - .../js/bower_components/lodash/lodash.js | 16982 ------- .../js/bower_components/lodash/package.json | 63 - .../lodash/perf/asset/perf-ui.js | 112 - .../bower_components/lodash/perf/index.html | 80 - .../js/bower_components/lodash/perf/perf.js | 1978 - .../lodash/test/asset/test-ui.js | 155 - .../lodash/test/asset/worker.js | 17 - .../lodash/test/backbone.html | 170 - .../js/bower_components/lodash/test/fp.html | 41 - .../bower_components/lodash/test/index.html | 341 - .../js/bower_components/lodash/test/remove.js | 27 - .../bower_components/lodash/test/saucelabs.js | 913 - .../bower_components/lodash/test/test-fp.js | 2319 - .../js/bower_components/lodash/test/test.js | 26655 ----------- .../lodash/test/underscore.html | 466 - .../lodash/vendor/backbone/LICENSE | 22 - .../lodash/vendor/backbone/backbone.js | 1946 - .../lodash/vendor/backbone/test/collection.js | 2023 - .../lodash/vendor/backbone/test/events.js | 706 - .../lodash/vendor/backbone/test/model.js | 1448 - .../lodash/vendor/backbone/test/noconflict.js | 13 - .../lodash/vendor/backbone/test/router.js | 1081 - .../vendor/backbone/test/setup/dom-setup.js | 4 - .../vendor/backbone/test/setup/environment.js | 45 - .../lodash/vendor/backbone/test/sync.js | 239 - .../lodash/vendor/backbone/test/view.js | 516 - .../lodash/vendor/firebug-lite/license.txt | 30 - .../vendor/firebug-lite/skin/xp/blank.gif | Bin 43 -> 0 bytes .../vendor/firebug-lite/skin/xp/buttonBg.png | Bin 167 -> 0 bytes .../firebug-lite/skin/xp/buttonBgHover.png | Bin 171 -> 0 bytes .../vendor/firebug-lite/skin/xp/debugger.css | 331 - .../vendor/firebug-lite/skin/xp/detach.png | Bin 655 -> 0 bytes .../firebug-lite/skin/xp/detachHover.png | Bin 586 -> 0 bytes .../vendor/firebug-lite/skin/xp/disable.gif | Bin 340 -> 0 bytes .../vendor/firebug-lite/skin/xp/disable.png | Bin 543 -> 0 bytes .../firebug-lite/skin/xp/disableHover.gif | Bin 344 -> 0 bytes .../firebug-lite/skin/xp/disableHover.png | Bin 512 -> 0 bytes .../vendor/firebug-lite/skin/xp/down.png | Bin 637 -> 0 bytes .../firebug-lite/skin/xp/downActive.png | Bin 543 -> 0 bytes .../vendor/firebug-lite/skin/xp/downHover.png | Bin 526 -> 0 bytes .../firebug-lite/skin/xp/errorIcon-sm.png | Bin 447 -> 0 bytes .../vendor/firebug-lite/skin/xp/errorIcon.gif | Bin 365 -> 0 bytes .../vendor/firebug-lite/skin/xp/errorIcon.png | Bin 457 -> 0 bytes .../firebug-lite/skin/xp/firebug-1.3a2.css | 817 - .../firebug-lite/skin/xp/firebug.IE6.css | 20 - .../vendor/firebug-lite/skin/xp/firebug.css | 3147 -- .../vendor/firebug-lite/skin/xp/firebug.html | 215 - .../vendor/firebug-lite/skin/xp/firebug.png | Bin 1167 -> 0 bytes .../vendor/firebug-lite/skin/xp/group.gif | Bin 158 -> 0 bytes .../vendor/firebug-lite/skin/xp/html.css | 272 - .../vendor/firebug-lite/skin/xp/infoIcon.gif | Bin 359 -> 0 bytes .../vendor/firebug-lite/skin/xp/infoIcon.png | Bin 524 -> 0 bytes .../firebug-lite/skin/xp/loading_16.gif | Bin 1553 -> 0 bytes .../vendor/firebug-lite/skin/xp/min.png | Bin 552 -> 0 bytes .../vendor/firebug-lite/skin/xp/minHover.png | Bin 485 -> 0 bytes .../vendor/firebug-lite/skin/xp/off.png | Bin 742 -> 0 bytes .../vendor/firebug-lite/skin/xp/offHover.png | Bin 680 -> 0 bytes .../skin/xp/pixel_transparent.gif | Bin 43 -> 0 bytes .../firebug-lite/skin/xp/roundCorner.svg | 6 - .../vendor/firebug-lite/skin/xp/search.gif | Bin 550 -> 0 bytes .../vendor/firebug-lite/skin/xp/search.png | Bin 685 -> 0 bytes .../vendor/firebug-lite/skin/xp/shadow.gif | Bin 4364 -> 0 bytes .../vendor/firebug-lite/skin/xp/shadow2.gif | Bin 3093 -> 0 bytes .../firebug-lite/skin/xp/shadowAlpha.png | Bin 3403 -> 0 bytes .../vendor/firebug-lite/skin/xp/sprite.png | Bin 40027 -> 0 bytes .../firebug-lite/skin/xp/tabHoverLeft.png | Bin 438 -> 0 bytes .../firebug-lite/skin/xp/tabHoverMid.png | Bin 261 -> 0 bytes .../firebug-lite/skin/xp/tabHoverRight.png | Bin 436 -> 0 bytes .../vendor/firebug-lite/skin/xp/tabLeft.png | Bin 449 -> 0 bytes .../firebug-lite/skin/xp/tabMenuCheckbox.png | Bin 220 -> 0 bytes .../firebug-lite/skin/xp/tabMenuPin.png | Bin 207 -> 0 bytes .../firebug-lite/skin/xp/tabMenuRadio.png | Bin 192 -> 0 bytes .../firebug-lite/skin/xp/tabMenuTarget.png | Bin 142 -> 0 bytes .../skin/xp/tabMenuTargetHover.png | Bin 148 -> 0 bytes .../vendor/firebug-lite/skin/xp/tabMid.png | Bin 262 -> 0 bytes .../vendor/firebug-lite/skin/xp/tabRight.png | Bin 448 -> 0 bytes .../skin/xp/textEditorBorders.gif | Bin 117 -> 0 bytes .../skin/xp/textEditorBorders.png | Bin 3144 -> 0 bytes .../skin/xp/textEditorCorners.gif | Bin 1821 -> 0 bytes .../skin/xp/textEditorCorners.png | Bin 3960 -> 0 bytes .../firebug-lite/skin/xp/titlebarMid.png | Bin 273 -> 0 bytes .../firebug-lite/skin/xp/toolbarMid.png | Bin 242 -> 0 bytes .../firebug-lite/skin/xp/tree_close.gif | Bin 300 -> 0 bytes .../vendor/firebug-lite/skin/xp/tree_open.gif | Bin 202 -> 0 bytes .../firebug-lite/skin/xp/twistyClosed.png | Bin 334 -> 0 bytes .../firebug-lite/skin/xp/twistyOpen.png | Bin 309 -> 0 bytes .../lodash/vendor/firebug-lite/skin/xp/up.png | Bin 619 -> 0 bytes .../vendor/firebug-lite/skin/xp/upActive.png | Bin 551 -> 0 bytes .../vendor/firebug-lite/skin/xp/upHover.png | Bin 526 -> 0 bytes .../firebug-lite/skin/xp/warningIcon.gif | Bin 357 -> 0 bytes .../firebug-lite/skin/xp/warningIcon.png | Bin 516 -> 0 bytes .../firebug-lite/src/firebug-lite-debug.js | 31176 ------------- .../lodash/vendor/json-js/json2.js | 519 - .../lodash/vendor/underscore/LICENSE | 23 - .../lodash/vendor/underscore/test/arrays.js | 563 - .../lodash/vendor/underscore/test/chaining.js | 99 - .../vendor/underscore/test/collections.js | 896 - .../vendor/underscore/test/cross-document.js | 141 - .../vendor/underscore/test/functions.js | 765 - .../lodash/vendor/underscore/test/objects.js | 1105 - .../lodash/vendor/underscore/test/utility.js | 421 - .../vendor/underscore/underscore-min.js | 6 - .../lodash/vendor/underscore/underscore.js | 1626 - .../js/bower_components/requirejs/.bower.json | 26 - .../js/bower_components/requirejs/LICENSE | 45 - .../js/bower_components/requirejs/README.md | 4 - .../js/bower_components/requirejs/bower.json | 17 - .../js/bower_components/requirejs/require.js | 2142 - .../js/bower_components/semantic/.bower.json | 37 - .../bower_components/semantic/.csscomb.json | 322 - .../js/bower_components/semantic/.csslintrc | 18 - .../js/bower_components/semantic/.gitignore | 36 - .../js/bower_components/semantic/.jshintrc | 38 - .../bower_components/semantic/CONTRIBUTING.md | 58 - .../js/bower_components/semantic/LICENSE.md | 7 - .../js/bower_components/semantic/README.md | 116 - .../semantic/RELEASE-NOTES.md | 2524 -- .../js/bower_components/semantic/bower.json | 27 - .../bower_components/semantic/composer.json | 24 - .../semantic/dist/components/accordion.css | 256 - .../semantic/dist/components/accordion.js | 610 - .../dist/components/accordion.min.css | 9 - .../semantic/dist/components/accordion.min.js | 10 - .../semantic/dist/components/ad.css | 276 - .../semantic/dist/components/ad.min.css | 10 - .../semantic/dist/components/api.js | 1167 - .../semantic/dist/components/api.min.js | 10 - .../semantic/dist/components/breadcrumb.css | 124 - .../dist/components/breadcrumb.min.css | 9 - .../semantic/dist/components/button.css | 3470 -- .../semantic/dist/components/button.min.css | 9 - .../semantic/dist/components/card.css | 975 - .../semantic/dist/components/card.min.css | 9 - .../semantic/dist/components/checkbox.css | 627 - .../semantic/dist/components/checkbox.js | 831 - .../semantic/dist/components/checkbox.min.css | 9 - .../semantic/dist/components/checkbox.min.js | 10 - .../semantic/dist/components/colorize.js | 274 - .../semantic/dist/components/colorize.min.js | 11 - .../semantic/dist/components/comment.css | 270 - .../semantic/dist/components/comment.min.css | 9 - .../semantic/dist/components/container.css | 148 - .../dist/components/container.min.css | 9 - .../semantic/dist/components/dimmer.css | 199 - .../semantic/dist/components/dimmer.js | 708 - .../semantic/dist/components/dimmer.min.css | 9 - .../semantic/dist/components/dimmer.min.js | 10 - .../semantic/dist/components/divider.css | 262 - .../semantic/dist/components/divider.min.css | 9 - .../semantic/dist/components/dropdown.css | 1425 - .../semantic/dist/components/dropdown.js | 3741 -- .../semantic/dist/components/dropdown.min.css | 9 - .../semantic/dist/components/dropdown.min.js | 11 - .../semantic/dist/components/embed.css | 167 - .../semantic/dist/components/embed.js | 696 - .../semantic/dist/components/embed.min.css | 9 - .../semantic/dist/components/embed.min.js | 10 - .../semantic/dist/components/feed.css | 300 - .../semantic/dist/components/feed.min.css | 9 - .../semantic/dist/components/flag.css | 1031 - .../semantic/dist/components/flag.min.css | 9 - .../semantic/dist/components/form.css | 1079 - .../semantic/dist/components/form.js | 1558 - .../semantic/dist/components/form.min.css | 9 - .../semantic/dist/components/form.min.js | 10 - .../semantic/dist/components/grid.css | 2032 - .../semantic/dist/components/grid.min.css | 9 - .../semantic/dist/components/header.css | 721 - .../semantic/dist/components/header.min.css | 9 - .../semantic/dist/components/icon.css | 3149 -- .../semantic/dist/components/icon.min.css | 9 - .../semantic/dist/components/image.css | 306 - .../semantic/dist/components/image.min.css | 9 - .../semantic/dist/components/input.css | 517 - .../semantic/dist/components/input.min.css | 9 - .../semantic/dist/components/item.css | 481 - .../semantic/dist/components/item.min.css | 9 - .../semantic/dist/components/label.css | 1314 - .../semantic/dist/components/label.min.css | 9 - .../semantic/dist/components/list.css | 951 - .../semantic/dist/components/list.min.css | 9 - .../semantic/dist/components/loader.css | 349 - .../semantic/dist/components/loader.min.css | 9 - .../semantic/dist/components/menu.css | 2022 - .../semantic/dist/components/menu.min.css | 1 - .../semantic/dist/components/message.css | 482 - .../semantic/dist/components/message.min.css | 9 - .../semantic/dist/components/modal.css | 509 - .../semantic/dist/components/modal.js | 913 - .../semantic/dist/components/modal.min.css | 9 - .../semantic/dist/components/modal.min.js | 10 - .../semantic/dist/components/nag.css | 147 - .../semantic/dist/components/nag.js | 507 - .../semantic/dist/components/nag.min.css | 9 - .../semantic/dist/components/nag.min.js | 10 - .../semantic/dist/components/popup.css | 764 - .../semantic/dist/components/popup.js | 1475 - .../semantic/dist/components/popup.min.css | 9 - .../semantic/dist/components/popup.min.js | 10 - .../semantic/dist/components/progress.css | 516 - .../semantic/dist/components/progress.js | 910 - .../semantic/dist/components/progress.min.css | 9 - .../semantic/dist/components/progress.min.js | 10 - .../semantic/dist/components/rail.css | 152 - .../semantic/dist/components/rail.min.css | 9 - .../semantic/dist/components/rating.css | 265 - .../semantic/dist/components/rating.js | 508 - .../semantic/dist/components/rating.min.css | 9 - .../semantic/dist/components/rating.min.js | 10 - .../semantic/dist/components/reset.css | 424 - .../semantic/dist/components/reset.min.css | 9 - .../semantic/dist/components/reveal.css | 307 - .../semantic/dist/components/reveal.min.css | 9 - .../semantic/dist/components/search.css | 409 - .../semantic/dist/components/search.js | 1398 - .../semantic/dist/components/search.min.css | 9 - .../semantic/dist/components/search.min.js | 10 - .../semantic/dist/components/segment.css | 799 - .../semantic/dist/components/segment.min.css | 9 - .../semantic/dist/components/shape.css | 157 - .../semantic/dist/components/shape.js | 921 - .../semantic/dist/components/shape.min.css | 9 - .../semantic/dist/components/shape.min.js | 10 - .../semantic/dist/components/sidebar.css | 644 - .../semantic/dist/components/sidebar.js | 1036 - .../semantic/dist/components/sidebar.min.css | 9 - .../semantic/dist/components/sidebar.min.js | 10 - .../semantic/dist/components/site.css | 160 - .../semantic/dist/components/site.js | 487 - .../semantic/dist/components/site.min.css | 9 - .../semantic/dist/components/site.min.js | 10 - .../semantic/dist/components/state.js | 708 - .../semantic/dist/components/state.min.js | 10 - .../semantic/dist/components/statistic.css | 583 - .../dist/components/statistic.min.css | 9 - .../semantic/dist/components/step.css | 646 - .../semantic/dist/components/step.min.css | 9 - .../semantic/dist/components/sticky.css | 78 - .../semantic/dist/components/sticky.js | 936 - .../semantic/dist/components/sticky.min.css | 9 - .../semantic/dist/components/sticky.min.js | 10 - .../semantic/dist/components/tab.css | 91 - .../semantic/dist/components/tab.js | 928 - .../semantic/dist/components/tab.min.css | 9 - .../semantic/dist/components/tab.min.js | 10 - .../semantic/dist/components/table.css | 1108 - .../semantic/dist/components/table.min.css | 9 - .../semantic/dist/components/transition.css | 1981 - .../semantic/dist/components/transition.js | 1089 - .../dist/components/transition.min.css | 9 - .../dist/components/transition.min.js | 10 - .../semantic/dist/components/video.css | 125 - .../semantic/dist/components/video.js | 532 - .../semantic/dist/components/video.min.css | 10 - .../semantic/dist/components/video.min.js | 11 - .../semantic/dist/components/visibility.js | 1283 - .../dist/components/visibility.min.js | 10 - .../semantic/dist/components/visit.js | 517 - .../semantic/dist/components/visit.min.js | 11 - .../semantic/dist/semantic.css | 36882 ---------------- .../semantic/dist/semantic.js | 22440 ---------- .../semantic/dist/semantic.min.css | 11 - .../semantic/dist/semantic.min.js | 19 - .../dist/themes/basic/assets/fonts/icons.eot | Bin 40166 -> 0 bytes .../dist/themes/basic/assets/fonts/icons.svg | 450 - .../dist/themes/basic/assets/fonts/icons.ttf | Bin 39924 -> 0 bytes .../dist/themes/basic/assets/fonts/icons.woff | Bin 24676 -> 0 bytes .../themes/default/assets/fonts/icons.eot | Bin 76518 -> 0 bytes .../themes/default/assets/fonts/icons.otf | Bin 93888 -> 0 bytes .../themes/default/assets/fonts/icons.svg | 685 - .../themes/default/assets/fonts/icons.ttf | Bin 152796 -> 0 bytes .../themes/default/assets/fonts/icons.woff | Bin 90412 -> 0 bytes .../themes/default/assets/fonts/icons.woff2 | Bin 71896 -> 0 bytes .../themes/default/assets/images/flags.png | Bin 28123 -> 0 bytes .../github/assets/fonts/octicons-local.ttf | Bin 53604 -> 0 bytes .../themes/github/assets/fonts/octicons.svg | 200 - .../themes/github/assets/fonts/octicons.ttf | Bin 31740 -> 0 bytes .../themes/github/assets/fonts/octicons.woff | Bin 17772 -> 0 bytes .../themes/material/assets/fonts/icons.eot | Bin 143258 -> 0 bytes .../themes/material/assets/fonts/icons.svg | 2373 - .../themes/material/assets/fonts/icons.ttf | Bin 128180 -> 0 bytes .../themes/material/assets/fonts/icons.woff | Bin 57620 -> 0 bytes .../examples/assets/images/avatar/nan.jpg | Bin 23839 -> 0 bytes .../examples/assets/images/avatar/tom.jpg | Bin 25265 -> 0 bytes .../semantic/examples/assets/images/bg.jpg | Bin 111239 -> 0 bytes .../semantic/examples/assets/images/logo.png | Bin 731 -> 0 bytes .../images/wireframe/centered-paragraph.png | Bin 1988 -> 0 bytes .../assets/images/wireframe/image-square.png | Bin 6227 -> 0 bytes .../assets/images/wireframe/image-text.png | Bin 9835 -> 0 bytes .../assets/images/wireframe/image.png | Bin 7175 -> 0 bytes .../images/wireframe/media-paragraph-alt.png | Bin 2393 -> 0 bytes .../images/wireframe/media-paragraph.png | Bin 3465 -> 0 bytes .../assets/images/wireframe/paragraph.png | Bin 2476 -> 0 bytes .../images/wireframe/short-paragraph.png | Bin 1281 -> 0 bytes .../assets/images/wireframe/square-image.png | Bin 1787 -> 0 bytes .../assets/images/wireframe/text-image.png | Bin 1924 -> 0 bytes .../assets/images/wireframe/white-image.png | Bin 5951 -> 0 bytes .../examples/assets/library/iframe-content.js | 707 - .../examples/assets/library/iframe.js | 642 - .../examples/assets/library/jquery.min.js | 4 - .../semantic/examples/assets/show-examples.js | 15 - .../semantic/examples/attached.html | 381 - .../semantic/examples/bootstrap.html | 453 - .../semantic/examples/components/button.html | 222 - .../semantic/examples/components/card.html | 313 - .../semantic/examples/components/input.html | 159 - .../semantic/examples/components/menu.html | 183 - .../semantic/examples/components/site.html | 74 - .../semantic/examples/components/table.html | 471 - .../semantic/examples/fixed.html | 137 - .../semantic/examples/grid.html | 408 - .../semantic/examples/homepage.html | 296 - .../semantic/examples/login.html | 125 - .../semantic/examples/responsive.html | 689 - .../semantic/examples/sticky.html | 230 - .../semantic/examples/theming.html | 88 - .../js/bower_components/semantic/gulpfile.js | 84 - .../bower_components/semantic/karma.conf.js | 85 - .../js/bower_components/semantic/logo.png | Bin 7348 -> 0 bytes .../js/bower_components/semantic/package.json | 98 - .../semantic/semantic.json.example | 24 - .../bower_components/semantic/src/README.md | 127 - .../_site/collections/breadcrumb.overrides | 3 - .../_site/collections/breadcrumb.variables | 3 - .../src/_site/collections/form.overrides | 3 - .../src/_site/collections/form.variables | 3 - .../src/_site/collections/grid.overrides | 3 - .../src/_site/collections/grid.variables | 3 - .../src/_site/collections/menu.overrides | 3 - .../src/_site/collections/menu.variables | 3 - .../src/_site/collections/message.overrides | 3 - .../src/_site/collections/message.variables | 3 - .../src/_site/collections/table.overrides | 3 - .../src/_site/collections/table.variables | 3 - .../src/_site/elements/button.overrides | 3 - .../src/_site/elements/button.variables | 3 - .../src/_site/elements/container.overrides | 3 - .../src/_site/elements/container.variables | 3 - .../src/_site/elements/divider.overrides | 3 - .../src/_site/elements/divider.variables | 3 - .../src/_site/elements/flag.overrides | 3 - .../src/_site/elements/flag.variables | 3 - .../src/_site/elements/header.overrides | 3 - .../src/_site/elements/header.variables | 3 - .../src/_site/elements/icon.overrides | 3 - .../src/_site/elements/icon.variables | 3 - .../src/_site/elements/image.overrides | 3 - .../src/_site/elements/image.variables | 3 - .../src/_site/elements/input.overrides | 3 - .../src/_site/elements/input.variables | 3 - .../src/_site/elements/label.overrides | 3 - .../src/_site/elements/label.variables | 3 - .../src/_site/elements/list.overrides | 3 - .../src/_site/elements/list.variables | 3 - .../src/_site/elements/loader.overrides | 3 - .../src/_site/elements/loader.variables | 3 - .../src/_site/elements/rail.overrides | 3 - .../src/_site/elements/rail.variables | 3 - .../src/_site/elements/reveal.overrides | 3 - .../src/_site/elements/reveal.variables | 3 - .../src/_site/elements/segment.overrides | 3 - .../src/_site/elements/segment.variables | 3 - .../src/_site/elements/step.overrides | 3 - .../src/_site/elements/step.variables | 3 - .../src/_site/globals/reset.overrides | 3 - .../src/_site/globals/reset.variables | 3 - .../semantic/src/_site/globals/site.overrides | 3 - .../semantic/src/_site/globals/site.variables | 3 - .../src/_site/modules/accordion.overrides | 3 - .../src/_site/modules/accordion.variables | 3 - .../src/_site/modules/chatroom.overrides | 3 - .../src/_site/modules/chatroom.variables | 3 - .../src/_site/modules/checkbox.overrides | 3 - .../src/_site/modules/checkbox.variables | 3 - .../src/_site/modules/dimmer.overrides | 3 - .../src/_site/modules/dimmer.variables | 3 - .../src/_site/modules/dropdown.overrides | 3 - .../src/_site/modules/dropdown.variables | 3 - .../src/_site/modules/embed.overrides | 3 - .../src/_site/modules/modal.overrides | 3 - .../src/_site/modules/modal.variables | 3 - .../semantic/src/_site/modules/nag.overrides | 3 - .../semantic/src/_site/modules/nag.variables | 3 - .../src/_site/modules/popup.overrides | 3 - .../src/_site/modules/popup.variables | 3 - .../src/_site/modules/progress.overrides | 3 - .../src/_site/modules/progress.variables | 3 - .../src/_site/modules/rating.overrides | 3 - .../src/_site/modules/rating.variables | 3 - .../src/_site/modules/search.overrides | 3 - .../src/_site/modules/search.variables | 3 - .../src/_site/modules/shape.overrides | 3 - .../src/_site/modules/shape.variables | 3 - .../src/_site/modules/sidebar.overrides | 3 - .../src/_site/modules/sidebar.variables | 3 - .../src/_site/modules/sticky.overrides | 3 - .../src/_site/modules/sticky.variables | 3 - .../semantic/src/_site/modules/tab.overrides | 3 - .../semantic/src/_site/modules/tab.variables | 3 - .../src/_site/modules/transition.overrides | 3 - .../src/_site/modules/transition.variables | 3 - .../semantic/src/_site/views/ad.overrides | 3 - .../semantic/src/_site/views/ad.variables | 3 - .../semantic/src/_site/views/card.overrides | 3 - .../semantic/src/_site/views/card.variables | 3 - .../src/_site/views/comment.overrides | 3 - .../src/_site/views/comment.variables | 3 - .../semantic/src/_site/views/feed.overrides | 3 - .../semantic/src/_site/views/feed.variables | 3 - .../semantic/src/_site/views/item.overrides | 3 - .../semantic/src/_site/views/item.variables | 3 - .../src/_site/views/statistic.overrides | 3 - .../src/_site/views/statistic.variables | 3 - .../semantic/src/definitions/behaviors/api.js | 1167 - .../src/definitions/behaviors/colorize.js | 280 - .../src/definitions/behaviors/form.js | 1558 - .../src/definitions/behaviors/state.js | 708 - .../src/definitions/behaviors/visibility.js | 1283 - .../src/definitions/behaviors/visit.js | 525 - .../definitions/collections/breadcrumb.less | 122 - .../src/definitions/collections/form.less | 1053 - .../src/definitions/collections/grid.less | 1920 - .../src/definitions/collections/menu.less | 1965 - .../src/definitions/collections/message.less | 478 - .../src/definitions/collections/table.less | 1114 - .../src/definitions/elements/button.less | 3427 -- .../src/definitions/elements/container.less | 143 - .../src/definitions/elements/divider.less | 255 - .../src/definitions/elements/flag.less | 52 - .../src/definitions/elements/header.less | 708 - .../src/definitions/elements/icon.less | 477 - .../src/definitions/elements/image.less | 324 - .../src/definitions/elements/input.less | 507 - .../src/definitions/elements/label.less | 1257 - .../src/definitions/elements/list.less | 951 - .../src/definitions/elements/loader.less | 332 - .../src/definitions/elements/rail.less | 154 - .../src/definitions/elements/reveal.less | 265 - .../src/definitions/elements/segment.less | 768 - .../src/definitions/elements/step.less | 562 - .../src/definitions/globals/reset.less | 40 - .../semantic/src/definitions/globals/site.js | 487 - .../src/definitions/globals/site.less | 166 - .../src/definitions/modules/accordion.js | 610 - .../src/definitions/modules/accordion.less | 219 - .../src/definitions/modules/checkbox.js | 831 - .../src/definitions/modules/checkbox.less | 595 - .../src/definitions/modules/dimmer.js | 708 - .../src/definitions/modules/dimmer.less | 189 - .../src/definitions/modules/dropdown.js | 3741 -- .../src/definitions/modules/dropdown.less | 1352 - .../semantic/src/definitions/modules/embed.js | 696 - .../src/definitions/modules/embed.less | 163 - .../semantic/src/definitions/modules/modal.js | 913 - .../src/definitions/modules/modal.less | 484 - .../semantic/src/definitions/modules/nag.js | 507 - .../semantic/src/definitions/modules/nag.less | 158 - .../semantic/src/definitions/modules/popup.js | 1475 - .../src/definitions/modules/popup.less | 712 - .../src/definitions/modules/progress.js | 910 - .../src/definitions/modules/progress.less | 503 - .../src/definitions/modules/rating.js | 508 - .../src/definitions/modules/rating.less | 191 - .../src/definitions/modules/search.js | 1398 - .../src/definitions/modules/search.less | 417 - .../semantic/src/definitions/modules/shape.js | 921 - .../src/definitions/modules/shape.less | 150 - .../src/definitions/modules/sidebar.js | 1036 - .../src/definitions/modules/sidebar.less | 552 - .../src/definitions/modules/sticky.js | 936 - .../src/definitions/modules/sticky.less | 74 - .../semantic/src/definitions/modules/tab.js | 928 - .../semantic/src/definitions/modules/tab.less | 94 - .../src/definitions/modules/transition.js | 1089 - .../src/definitions/modules/transition.less | 78 - .../semantic/src/definitions/views/ad.less | 268 - .../semantic/src/definitions/views/card.less | 1059 - .../src/definitions/views/comment.less | 269 - .../semantic/src/definitions/views/feed.less | 278 - .../semantic/src/definitions/views/item.less | 464 - .../src/definitions/views/statistic.less | 557 - .../semantic/src/semantic.less | 66 - .../semantic/src/theme.config.example | 92 - .../bower_components/semantic/src/theme.less | 61 - .../themes/amazon/elements/button.overrides | 46 - .../themes/amazon/elements/button.variables | 58 - .../src/themes/amazon/globals/site.variables | 43 - .../src/themes/basic/assets/fonts/icons.eot | Bin 40166 -> 0 bytes .../src/themes/basic/assets/fonts/icons.svg | 450 - .../src/themes/basic/assets/fonts/icons.ttf | Bin 39924 -> 0 bytes .../src/themes/basic/assets/fonts/icons.woff | Bin 24676 -> 0 bytes .../themes/basic/collections/table.overrides | 4 - .../themes/basic/collections/table.variables | 11 - .../themes/basic/elements/button.overrides | 4 - .../themes/basic/elements/button.variables | 44 - .../src/themes/basic/elements/icon.overrides | 189 - .../src/themes/basic/elements/icon.variables | 12 - .../src/themes/basic/elements/step.overrides | 10 - .../src/themes/basic/elements/step.variables | 18 - .../src/themes/basic/globals/reset.overrides | 5 - .../src/themes/basic/globals/reset.variables | 3 - .../themes/basic/modules/progress.overrides | 3 - .../themes/basic/modules/progress.variables | 15 - .../src/themes/basic/views/card.overrides | 4 - .../src/themes/basic/views/card.variables | 35 - .../themes/bookish/elements/header.overrides | 15 - .../themes/bookish/elements/header.variables | 37 - .../bootstrap3/elements/button.variables | 63 - .../themes/chubby/collections/form.overrides | 16 - .../themes/chubby/collections/form.variables | 9 - .../themes/chubby/collections/menu.variables | 40 - .../themes/chubby/elements/button.overrides | 21 - .../themes/chubby/elements/button.variables | 57 - .../themes/chubby/elements/header.overrides | 5 - .../themes/chubby/elements/header.variables | 21 - .../themes/chubby/modules/accordion.overrides | 7 - .../themes/chubby/modules/accordion.variables | 15 - .../src/themes/chubby/views/comment.overrides | 12 - .../src/themes/chubby/views/comment.variables | 46 - .../classic/collections/table.overrides | 3 - .../classic/collections/table.variables | 14 - .../themes/classic/elements/button.overrides | 3 - .../themes/classic/elements/button.variables | 96 - .../themes/classic/elements/header.overrides | 3 - .../themes/classic/elements/header.variables | 12 - .../themes/classic/modules/progress.overrides | 3 - .../themes/classic/modules/progress.variables | 9 - .../src/themes/classic/views/card.overrides | 98 - .../src/themes/classic/views/card.variables | 22 - .../themes/colored/modules/checkbox.variables | 29 - .../src/themes/default/assets/fonts/icons.eot | Bin 76518 -> 0 bytes .../src/themes/default/assets/fonts/icons.svg | 685 - .../src/themes/default/assets/fonts/icons.ttf | Bin 152796 -> 0 bytes .../themes/default/assets/fonts/icons.woff | Bin 90412 -> 0 bytes .../themes/default/assets/fonts/icons.woff2 | Bin 71896 -> 0 bytes .../themes/default/assets/images/flags.png | Bin 28123 -> 0 bytes .../default/collections/breadcrumb.overrides | 3 - .../default/collections/breadcrumb.variables | 33 - .../themes/default/collections/form.overrides | 3 - .../themes/default/collections/form.variables | 195 - .../themes/default/collections/grid.overrides | 4 - .../themes/default/collections/grid.variables | 103 - .../themes/default/collections/menu.overrides | 3 - .../themes/default/collections/menu.variables | 458 - .../default/collections/message.overrides | 3 - .../default/collections/message.variables | 158 - .../default/collections/table.variables | 246 - .../themes/default/elements/button.overrides | 3 - .../themes/default/elements/button.variables | 358 - .../default/elements/container.overrides | 3 - .../default/elements/container.variables | 58 - .../themes/default/elements/divider.overrides | 18 - .../themes/default/elements/divider.variables | 53 - .../themes/default/elements/flag.overrides | 986 - .../themes/default/elements/flag.variables | 13 - .../themes/default/elements/header.overrides | 4 - .../themes/default/elements/header.variables | 151 - .../themes/default/elements/icon.overrides | 941 - .../themes/default/elements/icon.variables | 62 - .../themes/default/elements/image.overrides | 3 - .../themes/default/elements/image.variables | 44 - .../themes/default/elements/input.overrides | 3 - .../themes/default/elements/input.variables | 101 - .../themes/default/elements/label.overrides | 3 - .../themes/default/elements/label.variables | 254 - .../themes/default/elements/list.overrides | 3 - .../themes/default/elements/list.variables | 224 - .../themes/default/elements/loader.overrides | 3 - .../themes/default/elements/loader.variables | 72 - .../themes/default/elements/rail.overrides | 3 - .../themes/default/elements/rail.variables | 34 - .../themes/default/elements/reveal.overrides | 3 - .../themes/default/elements/reveal.variables | 17 - .../themes/default/elements/segment.overrides | 3 - .../themes/default/elements/segment.variables | 139 - .../themes/default/elements/step.overrides | 16 - .../themes/default/elements/step.variables | 130 - .../themes/default/globals/reset.overrides | 429 - .../themes/default/globals/reset.variables | 3 - .../src/themes/default/globals/site.overrides | 3 - .../src/themes/default/globals/site.variables | 936 - .../default/modules/accordion.overrides | 28 - .../default/modules/accordion.variables | 100 - .../themes/default/modules/chatroom.overrides | 3 - .../themes/default/modules/chatroom.variables | 3 - .../themes/default/modules/checkbox.overrides | 36 - .../themes/default/modules/checkbox.variables | 189 - .../themes/default/modules/dimmer.overrides | 3 - .../themes/default/modules/dimmer.variables | 61 - .../themes/default/modules/dropdown.overrides | 62 - .../themes/default/modules/dropdown.variables | 373 - .../themes/default/modules/embed.overrides | 3 - .../themes/default/modules/embed.variables | 53 - .../themes/default/modules/modal.overrides | 3 - .../themes/default/modules/modal.variables | 151 - .../src/themes/default/modules/nag.overrides | 3 - .../src/themes/default/modules/nag.variables | 74 - .../themes/default/modules/popup.overrides | 3 - .../themes/default/modules/popup.variables | 137 - .../themes/default/modules/progress.overrides | 3 - .../themes/default/modules/progress.variables | 113 - .../themes/default/modules/rating.overrides | 68 - .../themes/default/modules/rating.variables | 103 - .../themes/default/modules/search.overrides | 3 - .../themes/default/modules/search.variables | 158 - .../themes/default/modules/shape.overrides | 3 - .../themes/default/modules/shape.variables | 40 - .../themes/default/modules/sidebar.overrides | 3 - .../themes/default/modules/sidebar.variables | 45 - .../themes/default/modules/sticky.overrides | 3 - .../themes/default/modules/sticky.variables | 7 - .../src/themes/default/modules/tab.overrides | 3 - .../src/themes/default/modules/tab.variables | 11 - .../default/modules/transition.overrides | 910 - .../default/modules/transition.variables | 10 - .../src/themes/default/views/ad.overrides | 3 - .../src/themes/default/views/ad.variables | 13 - .../src/themes/default/views/card.overrides | 3 - .../src/themes/default/views/card.variables | 220 - .../themes/default/views/comment.overrides | 3 - .../themes/default/views/comment.variables | 104 - .../src/themes/default/views/feed.overrides | 3 - .../src/themes/default/views/feed.variables | 141 - .../src/themes/default/views/item.overrides | 3 - .../src/themes/default/views/item.variables | 154 - .../themes/default/views/statistic.overrides | 3 - .../themes/default/views/statistic.variables | 98 - .../src/themes/duo/elements/loader.overrides | 3 - .../src/themes/duo/elements/loader.variables | 6 - .../fixed-width/collections/grid.overrides | 3 - .../fixed-width/collections/grid.variables | 23 - .../fixed-width/modules/modal.overrides | 3 - .../fixed-width/modules/modal.variables | 37 - .../themes/flat/collections/form.overrides | 28 - .../themes/flat/collections/form.variables | 73 - .../src/themes/flat/globals/site.overrides | 3 - .../src/themes/flat/globals/site.variables | 107 - .../github/assets/fonts/octicons-local.ttf | Bin 53604 -> 0 bytes .../themes/github/assets/fonts/octicons.svg | 200 - .../themes/github/assets/fonts/octicons.ttf | Bin 31740 -> 0 bytes .../themes/github/assets/fonts/octicons.woff | Bin 17772 -> 0 bytes .../github/collections/breadcrumb.variables | 11 - .../themes/github/collections/form.overrides | 16 - .../themes/github/collections/form.variables | 40 - .../themes/github/collections/grid.variables | 2 - .../themes/github/collections/menu.overrides | 7 - .../themes/github/collections/menu.variables | 61 - .../github/collections/message.overrides | 11 - .../github/collections/message.variables | 29 - .../themes/github/collections/table.variables | 8 - .../themes/github/elements/button.overrides | 4 - .../themes/github/elements/button.variables | 77 - .../themes/github/elements/header.variables | 9 - .../src/themes/github/elements/icon.overrides | 208 - .../src/themes/github/elements/icon.variables | 13 - .../themes/github/elements/image.variables | 5 - .../themes/github/elements/input.overrides | 32 - .../themes/github/elements/input.variables | 16 - .../themes/github/elements/label.overrides | 9 - .../themes/github/elements/label.variables | 4 - .../themes/github/elements/segment.overrides | 3 - .../themes/github/elements/segment.variables | 40 - .../src/themes/github/elements/step.overrides | 26 - .../src/themes/github/elements/step.variables | 26 - .../src/themes/github/globals/site.variables | 47 - .../themes/github/modules/dropdown.overrides | 53 - .../themes/github/modules/dropdown.variables | 35 - .../src/themes/github/modules/popup.variables | 12 - .../gmail/collections/message.variables | 15 - .../src/themes/instagram/views/card.overrides | 12 - .../src/themes/instagram/views/card.variables | 23 - .../themes/material/assets/fonts/icons.eot | Bin 143258 -> 0 bytes .../themes/material/assets/fonts/icons.svg | 2373 - .../themes/material/assets/fonts/icons.ttf | Bin 128180 -> 0 bytes .../themes/material/assets/fonts/icons.woff | Bin 57620 -> 0 bytes .../material/collections/menu.overrides | 1 - .../material/collections/menu.variables | 10 - .../themes/material/elements/button.overrides | 15 - .../themes/material/elements/button.variables | 97 - .../themes/material/elements/header.overrides | 15 - .../themes/material/elements/header.variables | 21 - .../themes/material/elements/icon.overrides | 934 - .../themes/material/elements/icon.variables | 11 - .../themes/material/globals/site.variables | 128 - .../material/modules/dropdown.overrides | 5 - .../material/modules/dropdown.variables | 20 - .../themes/material/modules/modal.overrides | 6 - .../themes/material/modules/modal.variables | 15 - .../themes/pulsar/elements/loader.overrides | 70 - .../themes/pulsar/elements/loader.variables | 7 - .../themes/raised/elements/button.overrides | 3 - .../themes/raised/elements/button.variables | 27 - .../themes/resetcss/globals/reset.overrides | 52 - .../themes/resetcss/globals/reset.variables | 3 - .../themes/round/elements/button.variables | 138 - .../src/themes/rtl/globals/site.overrides | 6 - .../src/themes/rtl/globals/site.variables | 14 - .../themes/striped/modules/progress.overrides | 29 - .../themes/striped/modules/progress.variables | 3 - .../src/themes/timeline/views/feed.overrides | 27 - .../src/themes/timeline/views/feed.variables | 36 - .../themes/twitter/elements/button.overrides | 13 - .../themes/twitter/elements/button.variables | 44 - .../bower_components/semantic/tasks/README.md | 17 - .../semantic/tasks/admin/components/create.js | 332 - .../semantic/tasks/admin/components/init.js | 170 - .../semantic/tasks/admin/components/update.js | 184 - .../tasks/admin/distributions/create.js | 219 - .../tasks/admin/distributions/init.js | 169 - .../tasks/admin/distributions/update.js | 179 - .../semantic/tasks/admin/publish.js | 25 - .../semantic/tasks/admin/register.js | 55 - .../semantic/tasks/admin/release.js | 29 - .../bower_components/semantic/tasks/build.js | 50 - .../semantic/tasks/check-install.js | 28 - .../bower_components/semantic/tasks/clean.js | 14 - .../semantic/tasks/collections/README.md | 16 - .../semantic/tasks/collections/admin.js | 49 - .../semantic/tasks/collections/build.js | 19 - .../semantic/tasks/collections/internal.js | 227 - .../semantic/tasks/collections/rtl.js | 16 - .../semantic/tasks/config/admin/github.js | 37 - .../tasks/config/admin/oauth.example.js | 11 - .../semantic/tasks/config/admin/release.js | 111 - .../tasks/config/admin/templates/README.md | 32 - .../tasks/config/admin/templates/bower.json | 29 - .../admin/templates/component-package.js | 14 - .../config/admin/templates/composer.json | 20 - .../config/admin/templates/css-package.js | 34 - .../config/admin/templates/less-package.js | 21 - .../tasks/config/admin/templates/package.json | 17 - .../semantic/tasks/config/defaults.js | 117 - .../semantic/tasks/config/docs.js | 32 - .../semantic/tasks/config/npm/gulpfile.js | 72 - .../semantic/tasks/config/project/config.js | 141 - .../semantic/tasks/config/project/install.js | 763 - .../semantic/tasks/config/project/release.js | 65 - .../semantic/tasks/config/tasks.js | 166 - .../semantic/tasks/config/user.js | 58 - .../semantic/tasks/docs/build.js | 189 - .../semantic/tasks/docs/metadata.js | 138 - .../semantic/tasks/docs/serve.js | 255 - .../semantic/tasks/install.js | 457 - .../semantic/tasks/rtl/build.js | 132 - .../semantic/tasks/rtl/watch.js | 221 - .../semantic/tasks/version.js | 11 - .../bower_components/semantic/tasks/watch.js | 231 - .../resources/static/js/require-config.js | 6 +- 961 files changed, 31 insertions(+), 429498 deletions(-) create mode 100644 samples/irs-demo/web/.gitignore delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/angular-fcsa-number/.bower.json delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/angular-fcsa-number/CHANGLOG.md delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/angular-fcsa-number/LICENSE delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/angular-fcsa-number/README.md delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/angular-fcsa-number/bower.json delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/angular-fcsa-number/e2e/fcsaNumber.e2e.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/angular-fcsa-number/e2e/protractor.config delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/angular-fcsa-number/e2e/web/app.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/angular-fcsa-number/e2e/web/public/angular.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/angular-fcsa-number/e2e/web/public/fcsaNumber.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/angular-fcsa-number/e2e/web/public/index.html delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/angular-fcsa-number/src/fcsaNumber.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/angular-fcsa-number/src/fcsaNumber.min.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/angular-route/.bower.json delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/angular-route/LICENSE.md delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/angular-route/README.md delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/angular-route/angular-route.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/angular-route/angular-route.min.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/angular-route/angular-route.min.js.map delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/angular-route/bower.json delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/angular-route/index.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/angular-route/package.json delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/angular/.bower.json delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/angular/LICENSE.md delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/angular/README.md delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/angular/angular-csp.css delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/angular/angular.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/angular/angular.min.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/angular/angular.min.js.gzip delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/angular/angular.min.js.map delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/angular/bower.json delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/angular/index.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/angular/package.json delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery.maskedinput/.bower.json delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery.maskedinput/CONTRIBUTING.md delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery.maskedinput/LICENSE delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery.maskedinput/README.md delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery.maskedinput/bower.json delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery.maskedinput/demo/datepicker.html delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery.maskedinput/demo/index.html delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery.maskedinput/dist/jquery.maskedinput.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery.maskedinput/dist/jquery.maskedinput.min.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery.maskedinput/gruntfile.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery.maskedinput/jquery.maskedinput.nuspec delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery.maskedinput/package.json delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery.maskedinput/src/jquery.maskedinput.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/.bower.json delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/AUTHORS.txt delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/LICENSE.txt delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/README.md delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/bower.json delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/dist/core.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/dist/jquery.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/dist/jquery.min.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/dist/jquery.min.map delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/dist/jquery.slim.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/dist/jquery.slim.min.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/dist/jquery.slim.min.map delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/external/sizzle/LICENSE.txt delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/external/sizzle/dist/sizzle.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/external/sizzle/dist/sizzle.min.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/external/sizzle/dist/sizzle.min.map delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/.eslintrc.json delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/ajax.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/ajax/jsonp.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/ajax/load.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/ajax/parseXML.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/ajax/script.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/ajax/var/location.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/ajax/var/nonce.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/ajax/var/rquery.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/ajax/xhr.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/attributes.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/attributes/attr.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/attributes/classes.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/attributes/prop.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/attributes/support.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/attributes/val.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/callbacks.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/core.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/core/DOMEval.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/core/access.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/core/init.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/core/parseHTML.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/core/ready-no-deferred.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/core/ready.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/core/readyException.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/core/stripAndCollapse.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/core/support.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/core/var/rsingleTag.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/css.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/css/addGetHookIf.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/css/adjustCSS.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/css/curCSS.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/css/hiddenVisibleSelectors.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/css/showHide.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/css/support.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/css/var/cssExpand.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/css/var/getStyles.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/css/var/isHiddenWithinTree.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/css/var/rmargin.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/css/var/rnumnonpx.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/css/var/swap.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/data.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/data/Data.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/data/var/acceptData.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/data/var/dataPriv.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/data/var/dataUser.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/deferred.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/deferred/exceptionHook.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/deprecated.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/dimensions.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/effects.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/effects/Tween.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/effects/animatedSelector.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/event.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/event/ajax.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/event/alias.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/event/focusin.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/event/support.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/event/trigger.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/exports/amd.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/exports/global.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/jquery.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/manipulation.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/manipulation/_evalUrl.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/manipulation/buildFragment.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/manipulation/getAll.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/manipulation/setGlobalEval.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/manipulation/support.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/manipulation/var/rcheckableType.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/manipulation/var/rscriptType.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/manipulation/var/rtagName.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/manipulation/wrapMap.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/offset.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/queue.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/queue/delay.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/selector-native.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/selector-sizzle.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/selector.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/serialize.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/traversing.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/traversing/findFilter.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/traversing/var/dir.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/traversing/var/rneedsContext.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/traversing/var/siblings.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/var/ObjectFunctionString.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/var/arr.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/var/class2type.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/var/concat.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/var/document.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/var/documentElement.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/var/fnToString.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/var/getProto.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/var/hasOwn.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/var/indexOf.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/var/pnum.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/var/push.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/var/rcssNum.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/var/rnothtmlwhite.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/var/slice.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/var/support.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/var/toString.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/jquery/src/wrap.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/.bower.json delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/.editorconfig delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/.gitattributes delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/.github/CONTRIBUTING.md delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/.gitignore delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/.jscsrc delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/.markdown-doctest-setup.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/.travis.yml delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/CHANGELOG delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/LICENSE delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/README.md delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/dist/lodash.core.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/dist/lodash.core.min.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/dist/lodash.fp.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/dist/lodash.fp.min.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/dist/lodash.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/dist/lodash.min.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/dist/mapping.fp.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/doc/README.md delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/fp/_baseConvert.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/fp/_convertBrowser.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/fp/_mapping.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/fp/placeholder.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/lib/common/file.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/lib/common/mapping.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/lib/common/minify.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/lib/common/uglify.options.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/lib/common/util.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/lib/fp/build-dist.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/lib/fp/build-doc.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/lib/fp/build-modules.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/lib/fp/template/doc/wiki.jst delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/lib/fp/template/modules/_falseOptions.jst delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/lib/fp/template/modules/_util.jst delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/lib/fp/template/modules/alias.jst delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/lib/fp/template/modules/category.jst delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/lib/fp/template/modules/convert.jst delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/lib/fp/template/modules/fp.jst delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/lib/fp/template/modules/module.jst delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/lib/fp/template/modules/thru.jst delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/lib/main/build-dist.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/lib/main/build-doc.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/lib/main/build-modules.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/lib/main/build-site.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/lodash.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/package.json delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/perf/asset/perf-ui.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/perf/index.html delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/perf/perf.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/test/asset/test-ui.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/test/asset/worker.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/test/backbone.html delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/test/fp.html delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/test/index.html delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/test/remove.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/test/saucelabs.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/test/test-fp.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/test/test.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/test/underscore.html delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/backbone/LICENSE delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/backbone/backbone.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/backbone/test/collection.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/backbone/test/events.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/backbone/test/model.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/backbone/test/noconflict.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/backbone/test/router.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/backbone/test/setup/dom-setup.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/backbone/test/setup/environment.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/backbone/test/sync.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/backbone/test/view.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/license.txt delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/blank.gif delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/buttonBg.png delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/buttonBgHover.png delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/debugger.css delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/detach.png delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/detachHover.png delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/disable.gif delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/disable.png delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/disableHover.gif delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/disableHover.png delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/down.png delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/downActive.png delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/downHover.png delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/errorIcon-sm.png delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/errorIcon.gif delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/errorIcon.png delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/firebug-1.3a2.css delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/firebug.IE6.css delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/firebug.css delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/firebug.html delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/firebug.png delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/group.gif delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/html.css delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/infoIcon.gif delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/infoIcon.png delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/loading_16.gif delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/min.png delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/minHover.png delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/off.png delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/offHover.png delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/pixel_transparent.gif delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/roundCorner.svg delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/search.gif delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/search.png delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/shadow.gif delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/shadow2.gif delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/shadowAlpha.png delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/sprite.png delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/tabHoverLeft.png delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/tabHoverMid.png delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/tabHoverRight.png delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/tabLeft.png delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/tabMenuCheckbox.png delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/tabMenuPin.png delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/tabMenuRadio.png delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/tabMenuTarget.png delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/tabMenuTargetHover.png delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/tabMid.png delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/tabRight.png delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/textEditorBorders.gif delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/textEditorBorders.png delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/textEditorCorners.gif delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/textEditorCorners.png delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/titlebarMid.png delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/toolbarMid.png delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/tree_close.gif delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/tree_open.gif delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/twistyClosed.png delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/twistyOpen.png delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/up.png delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/upActive.png delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/upHover.png delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/warningIcon.gif delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/skin/xp/warningIcon.png delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/firebug-lite/src/firebug-lite-debug.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/json-js/json2.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/underscore/LICENSE delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/underscore/test/arrays.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/underscore/test/chaining.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/underscore/test/collections.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/underscore/test/cross-document.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/underscore/test/functions.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/underscore/test/objects.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/underscore/test/utility.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/underscore/underscore-min.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/lodash/vendor/underscore/underscore.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/requirejs/.bower.json delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/requirejs/LICENSE delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/requirejs/README.md delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/requirejs/bower.json delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/requirejs/require.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/.bower.json delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/.csscomb.json delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/.csslintrc delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/.gitignore delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/.jshintrc delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/CONTRIBUTING.md delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/LICENSE.md delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/README.md delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/RELEASE-NOTES.md delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/bower.json delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/composer.json delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/accordion.css delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/accordion.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/accordion.min.css delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/accordion.min.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/ad.css delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/ad.min.css delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/api.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/api.min.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/breadcrumb.css delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/breadcrumb.min.css delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/button.css delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/button.min.css delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/card.css delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/card.min.css delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/checkbox.css delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/checkbox.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/checkbox.min.css delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/checkbox.min.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/colorize.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/colorize.min.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/comment.css delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/comment.min.css delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/container.css delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/container.min.css delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/dimmer.css delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/dimmer.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/dimmer.min.css delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/dimmer.min.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/divider.css delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/divider.min.css delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/dropdown.css delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/dropdown.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/dropdown.min.css delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/dropdown.min.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/embed.css delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/embed.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/embed.min.css delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/embed.min.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/feed.css delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/feed.min.css delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/flag.css delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/flag.min.css delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/form.css delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/form.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/form.min.css delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/form.min.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/grid.css delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/grid.min.css delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/header.css delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/header.min.css delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/icon.css delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/icon.min.css delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/image.css delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/image.min.css delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/input.css delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/input.min.css delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/item.css delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/item.min.css delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/label.css delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/label.min.css delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/list.css delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/list.min.css delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/loader.css delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/loader.min.css delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/menu.css delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/menu.min.css delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/message.css delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/message.min.css delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/modal.css delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/modal.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/modal.min.css delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/modal.min.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/nag.css delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/nag.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/nag.min.css delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/nag.min.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/popup.css delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/popup.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/popup.min.css delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/popup.min.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/progress.css delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/progress.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/progress.min.css delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/progress.min.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/rail.css delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/rail.min.css delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/rating.css delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/rating.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/rating.min.css delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/rating.min.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/reset.css delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/reset.min.css delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/reveal.css delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/reveal.min.css delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/search.css delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/search.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/search.min.css delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/search.min.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/segment.css delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/segment.min.css delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/shape.css delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/shape.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/shape.min.css delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/shape.min.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/sidebar.css delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/sidebar.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/sidebar.min.css delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/sidebar.min.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/site.css delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/site.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/site.min.css delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/site.min.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/state.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/state.min.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/statistic.css delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/statistic.min.css delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/step.css delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/step.min.css delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/sticky.css delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/sticky.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/sticky.min.css delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/sticky.min.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/tab.css delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/tab.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/tab.min.css delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/tab.min.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/table.css delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/table.min.css delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/transition.css delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/transition.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/transition.min.css delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/transition.min.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/video.css delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/video.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/video.min.css delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/video.min.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/visibility.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/visibility.min.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/visit.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/components/visit.min.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/semantic.css delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/semantic.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/semantic.min.css delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/semantic.min.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/themes/basic/assets/fonts/icons.eot delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/themes/basic/assets/fonts/icons.svg delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/themes/basic/assets/fonts/icons.ttf delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/themes/basic/assets/fonts/icons.woff delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/themes/default/assets/fonts/icons.eot delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/themes/default/assets/fonts/icons.otf delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/themes/default/assets/fonts/icons.svg delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/themes/default/assets/fonts/icons.ttf delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/themes/default/assets/fonts/icons.woff delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/themes/default/assets/fonts/icons.woff2 delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/themes/default/assets/images/flags.png delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/themes/github/assets/fonts/octicons-local.ttf delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/themes/github/assets/fonts/octicons.svg delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/themes/github/assets/fonts/octicons.ttf delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/themes/github/assets/fonts/octicons.woff delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/themes/material/assets/fonts/icons.eot delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/themes/material/assets/fonts/icons.svg delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/themes/material/assets/fonts/icons.ttf delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/dist/themes/material/assets/fonts/icons.woff delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/examples/assets/images/avatar/nan.jpg delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/examples/assets/images/avatar/tom.jpg delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/examples/assets/images/bg.jpg delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/examples/assets/images/logo.png delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/examples/assets/images/wireframe/centered-paragraph.png delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/examples/assets/images/wireframe/image-square.png delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/examples/assets/images/wireframe/image-text.png delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/examples/assets/images/wireframe/image.png delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/examples/assets/images/wireframe/media-paragraph-alt.png delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/examples/assets/images/wireframe/media-paragraph.png delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/examples/assets/images/wireframe/paragraph.png delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/examples/assets/images/wireframe/short-paragraph.png delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/examples/assets/images/wireframe/square-image.png delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/examples/assets/images/wireframe/text-image.png delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/examples/assets/images/wireframe/white-image.png delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/examples/assets/library/iframe-content.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/examples/assets/library/iframe.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/examples/assets/library/jquery.min.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/examples/assets/show-examples.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/examples/attached.html delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/examples/bootstrap.html delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/examples/components/button.html delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/examples/components/card.html delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/examples/components/input.html delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/examples/components/menu.html delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/examples/components/site.html delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/examples/components/table.html delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/examples/fixed.html delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/examples/grid.html delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/examples/homepage.html delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/examples/login.html delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/examples/responsive.html delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/examples/sticky.html delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/examples/theming.html delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/gulpfile.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/karma.conf.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/logo.png delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/package.json delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/semantic.json.example delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/README.md delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/collections/breadcrumb.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/collections/breadcrumb.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/collections/form.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/collections/form.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/collections/grid.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/collections/grid.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/collections/menu.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/collections/menu.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/collections/message.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/collections/message.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/collections/table.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/collections/table.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/elements/button.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/elements/button.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/elements/container.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/elements/container.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/elements/divider.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/elements/divider.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/elements/flag.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/elements/flag.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/elements/header.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/elements/header.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/elements/icon.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/elements/icon.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/elements/image.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/elements/image.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/elements/input.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/elements/input.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/elements/label.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/elements/label.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/elements/list.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/elements/list.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/elements/loader.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/elements/loader.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/elements/rail.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/elements/rail.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/elements/reveal.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/elements/reveal.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/elements/segment.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/elements/segment.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/elements/step.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/elements/step.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/globals/reset.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/globals/reset.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/globals/site.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/globals/site.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/modules/accordion.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/modules/accordion.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/modules/chatroom.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/modules/chatroom.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/modules/checkbox.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/modules/checkbox.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/modules/dimmer.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/modules/dimmer.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/modules/dropdown.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/modules/dropdown.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/modules/embed.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/modules/modal.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/modules/modal.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/modules/nag.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/modules/nag.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/modules/popup.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/modules/popup.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/modules/progress.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/modules/progress.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/modules/rating.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/modules/rating.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/modules/search.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/modules/search.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/modules/shape.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/modules/shape.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/modules/sidebar.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/modules/sidebar.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/modules/sticky.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/modules/sticky.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/modules/tab.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/modules/tab.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/modules/transition.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/modules/transition.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/views/ad.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/views/ad.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/views/card.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/views/card.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/views/comment.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/views/comment.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/views/feed.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/views/feed.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/views/item.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/views/item.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/views/statistic.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/_site/views/statistic.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/behaviors/api.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/behaviors/colorize.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/behaviors/form.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/behaviors/state.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/behaviors/visibility.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/behaviors/visit.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/collections/breadcrumb.less delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/collections/form.less delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/collections/grid.less delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/collections/menu.less delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/collections/message.less delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/collections/table.less delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/elements/button.less delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/elements/container.less delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/elements/divider.less delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/elements/flag.less delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/elements/header.less delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/elements/icon.less delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/elements/image.less delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/elements/input.less delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/elements/label.less delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/elements/list.less delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/elements/loader.less delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/elements/rail.less delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/elements/reveal.less delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/elements/segment.less delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/elements/step.less delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/globals/reset.less delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/globals/site.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/globals/site.less delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/modules/accordion.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/modules/accordion.less delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/modules/checkbox.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/modules/checkbox.less delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/modules/dimmer.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/modules/dimmer.less delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/modules/dropdown.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/modules/dropdown.less delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/modules/embed.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/modules/embed.less delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/modules/modal.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/modules/modal.less delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/modules/nag.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/modules/nag.less delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/modules/popup.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/modules/popup.less delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/modules/progress.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/modules/progress.less delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/modules/rating.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/modules/rating.less delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/modules/search.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/modules/search.less delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/modules/shape.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/modules/shape.less delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/modules/sidebar.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/modules/sidebar.less delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/modules/sticky.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/modules/sticky.less delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/modules/tab.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/modules/tab.less delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/modules/transition.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/modules/transition.less delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/views/ad.less delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/views/card.less delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/views/comment.less delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/views/feed.less delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/views/item.less delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/definitions/views/statistic.less delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/semantic.less delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/theme.config.example delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/theme.less delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/amazon/elements/button.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/amazon/elements/button.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/amazon/globals/site.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/basic/assets/fonts/icons.eot delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/basic/assets/fonts/icons.svg delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/basic/assets/fonts/icons.ttf delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/basic/assets/fonts/icons.woff delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/basic/collections/table.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/basic/collections/table.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/basic/elements/button.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/basic/elements/button.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/basic/elements/icon.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/basic/elements/icon.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/basic/elements/step.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/basic/elements/step.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/basic/globals/reset.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/basic/globals/reset.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/basic/modules/progress.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/basic/modules/progress.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/basic/views/card.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/basic/views/card.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/bookish/elements/header.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/bookish/elements/header.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/bootstrap3/elements/button.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/chubby/collections/form.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/chubby/collections/form.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/chubby/collections/menu.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/chubby/elements/button.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/chubby/elements/button.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/chubby/elements/header.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/chubby/elements/header.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/chubby/modules/accordion.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/chubby/modules/accordion.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/chubby/views/comment.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/chubby/views/comment.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/classic/collections/table.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/classic/collections/table.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/classic/elements/button.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/classic/elements/button.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/classic/elements/header.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/classic/elements/header.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/classic/modules/progress.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/classic/modules/progress.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/classic/views/card.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/classic/views/card.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/colored/modules/checkbox.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/assets/fonts/icons.eot delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/assets/fonts/icons.svg delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/assets/fonts/icons.ttf delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/assets/fonts/icons.woff delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/assets/fonts/icons.woff2 delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/assets/images/flags.png delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/collections/breadcrumb.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/collections/breadcrumb.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/collections/form.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/collections/form.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/collections/grid.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/collections/grid.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/collections/menu.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/collections/menu.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/collections/message.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/collections/message.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/collections/table.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/elements/button.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/elements/button.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/elements/container.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/elements/container.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/elements/divider.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/elements/divider.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/elements/flag.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/elements/flag.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/elements/header.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/elements/header.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/elements/icon.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/elements/icon.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/elements/image.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/elements/image.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/elements/input.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/elements/input.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/elements/label.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/elements/label.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/elements/list.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/elements/list.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/elements/loader.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/elements/loader.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/elements/rail.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/elements/rail.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/elements/reveal.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/elements/reveal.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/elements/segment.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/elements/segment.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/elements/step.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/elements/step.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/globals/reset.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/globals/reset.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/globals/site.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/globals/site.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/modules/accordion.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/modules/accordion.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/modules/chatroom.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/modules/chatroom.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/modules/checkbox.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/modules/checkbox.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/modules/dimmer.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/modules/dimmer.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/modules/dropdown.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/modules/dropdown.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/modules/embed.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/modules/embed.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/modules/modal.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/modules/modal.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/modules/nag.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/modules/nag.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/modules/popup.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/modules/popup.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/modules/progress.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/modules/progress.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/modules/rating.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/modules/rating.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/modules/search.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/modules/search.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/modules/shape.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/modules/shape.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/modules/sidebar.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/modules/sidebar.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/modules/sticky.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/modules/sticky.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/modules/tab.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/modules/tab.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/modules/transition.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/modules/transition.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/views/ad.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/views/ad.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/views/card.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/views/card.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/views/comment.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/views/comment.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/views/feed.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/views/feed.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/views/item.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/views/item.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/views/statistic.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/default/views/statistic.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/duo/elements/loader.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/duo/elements/loader.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/fixed-width/collections/grid.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/fixed-width/collections/grid.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/fixed-width/modules/modal.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/fixed-width/modules/modal.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/flat/collections/form.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/flat/collections/form.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/flat/globals/site.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/flat/globals/site.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/github/assets/fonts/octicons-local.ttf delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/github/assets/fonts/octicons.svg delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/github/assets/fonts/octicons.ttf delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/github/assets/fonts/octicons.woff delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/github/collections/breadcrumb.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/github/collections/form.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/github/collections/form.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/github/collections/grid.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/github/collections/menu.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/github/collections/menu.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/github/collections/message.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/github/collections/message.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/github/collections/table.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/github/elements/button.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/github/elements/button.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/github/elements/header.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/github/elements/icon.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/github/elements/icon.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/github/elements/image.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/github/elements/input.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/github/elements/input.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/github/elements/label.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/github/elements/label.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/github/elements/segment.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/github/elements/segment.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/github/elements/step.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/github/elements/step.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/github/globals/site.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/github/modules/dropdown.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/github/modules/dropdown.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/github/modules/popup.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/gmail/collections/message.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/instagram/views/card.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/instagram/views/card.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/material/assets/fonts/icons.eot delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/material/assets/fonts/icons.svg delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/material/assets/fonts/icons.ttf delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/material/assets/fonts/icons.woff delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/material/collections/menu.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/material/collections/menu.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/material/elements/button.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/material/elements/button.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/material/elements/header.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/material/elements/header.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/material/elements/icon.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/material/elements/icon.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/material/globals/site.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/material/modules/dropdown.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/material/modules/dropdown.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/material/modules/modal.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/material/modules/modal.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/pulsar/elements/loader.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/pulsar/elements/loader.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/raised/elements/button.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/raised/elements/button.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/resetcss/globals/reset.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/resetcss/globals/reset.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/round/elements/button.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/rtl/globals/site.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/rtl/globals/site.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/striped/modules/progress.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/striped/modules/progress.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/timeline/views/feed.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/timeline/views/feed.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/twitter/elements/button.overrides delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/src/themes/twitter/elements/button.variables delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/README.md delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/admin/components/create.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/admin/components/init.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/admin/components/update.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/admin/distributions/create.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/admin/distributions/init.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/admin/distributions/update.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/admin/publish.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/admin/register.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/admin/release.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/build.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/check-install.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/clean.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/collections/README.md delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/collections/admin.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/collections/build.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/collections/internal.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/collections/rtl.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/config/admin/github.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/config/admin/oauth.example.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/config/admin/release.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/config/admin/templates/README.md delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/config/admin/templates/bower.json delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/config/admin/templates/component-package.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/config/admin/templates/composer.json delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/config/admin/templates/css-package.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/config/admin/templates/less-package.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/config/admin/templates/package.json delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/config/defaults.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/config/docs.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/config/npm/gulpfile.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/config/project/config.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/config/project/install.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/config/project/release.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/config/tasks.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/config/user.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/docs/build.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/docs/metadata.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/docs/serve.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/install.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/rtl/build.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/rtl/watch.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/version.js delete mode 100644 samples/irs-demo/web/src/main/resources/static/js/bower_components/semantic/tasks/watch.js diff --git a/.idea/compiler.xml b/.idea/compiler.xml index 9197de4b88..ae994c7ef9 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -42,7 +42,6 @@ - diff --git a/samples/irs-demo/web/.gitignore b/samples/irs-demo/web/.gitignore new file mode 100644 index 0000000000..de17ef45ab --- /dev/null +++ b/samples/irs-demo/web/.gitignore @@ -0,0 +1 @@ +src/main/resources/static/js/bower_components \ No newline at end of file diff --git a/samples/irs-demo/web/build.gradle b/samples/irs-demo/web/build.gradle index f2093facb1..78450b877d 100644 --- a/samples/irs-demo/web/build.gradle +++ b/samples/irs-demo/web/build.gradle @@ -8,6 +8,29 @@ buildscript { } } +plugins { + id 'com.craigburke.client-dependencies' version '1.4.0' +} + +clientDependencies { + registry 'realBower', type:'bower', url:'https://registry.bower.io' + realBower { + "angular"("1.5.8") + "jquery"("^3.0.0") + "angular-route"("1.5.8") + "lodash"("^4.13.1") + "angular-fcsa-number"("^1.5.3") + "jquery.maskedinput"("^1.4.1") + "requirejs"("^2.2.0") + "semantic-ui"("^2.2.2", into: "semantic") + } + + // put the JS dependencies into src directory so it can easily be referenced + // from HTML files in webapp frontend, useful for testing/development + // Note that this dir is added to .gitignore + installDir = 'src/main/resources/static/js/bower_components' +} + // Spring Boot plugin adds a numerous hardcoded dependencies in the version much lower then Corda expects // causing the problems in runtime. Those can be changed by manipulating above properties // See https://github.com/spring-gradle-plugins/dependency-management-plugin/blob/master/README.md#changing-the-value-of-a-version-property @@ -39,6 +62,7 @@ dependencies { jar { from sourceSets.test.output + dependsOn clientInstall } task deployWebapps(type: Copy, dependsOn: ['jar', 'bootRepackage']) { diff --git a/samples/irs-demo/web/src/main/resources/static/bower.json b/samples/irs-demo/web/src/main/resources/static/bower.json index c9946e1b00..9e5c6b3b49 100644 --- a/samples/irs-demo/web/src/main/resources/static/bower.json +++ b/samples/irs-demo/web/src/main/resources/static/bower.json @@ -13,9 +13,9 @@ "tests" ], "dependencies": { - "angular": "^1.5.6", + "angular": "1.5.8", "jquery": "^3.0.0", - "angular-route": "^1.5.7", + "angular-route": "1.5.8", "lodash": "^4.13.1", "angular-fcsa-number": "^1.5.3", "jquery.maskedinput": "^1.4.1", diff --git a/samples/irs-demo/web/src/main/resources/static/index.html b/samples/irs-demo/web/src/main/resources/static/index.html index 3419d41c4b..cb848044c9 100644 --- a/samples/irs-demo/web/src/main/resources/static/index.html +++ b/samples/irs-demo/web/src/main/resources/static/index.html @@ -9,7 +9,7 @@ IRS Demo Viewer - + diff --git a/samples/irs-demo/web/src/main/resources/static/js/bower_components/angular-fcsa-number/.bower.json b/samples/irs-demo/web/src/main/resources/static/js/bower_components/angular-fcsa-number/.bower.json deleted file mode 100644 index f1bb58cee2..0000000000 --- a/samples/irs-demo/web/src/main/resources/static/js/bower_components/angular-fcsa-number/.bower.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "name": "angular-fcsa-number", - "version": "1.5.3", - "authors": [ - "Paul Yoder " - ], - "description": "An Angular directive that validates numbers and adds commas for the thousands separator.", - "main": "src/fcsaNumber.js", - "keywords": [ - "angular", - "directive", - "format", - "number", - "validate" - ], - "license": "Apache V2", - "homepage": "https://github.com/FCSAmericaDev/angular-fcsa-number", - "ignore": [ - "**/.*", - "**/*.coffee", - "node_modules", - "bower_components", - "test", - "Gruntfile.coffee", - "karma.conf.js", - "package.json" - ], - "dependencies": { - "angular": "~1.x" - }, - "devDependencies": { - "angular-mocks": "~1.x" - }, - "_release": "1.5.3", - "_resolution": { - "type": "version", - "tag": "v1.5.3", - "commit": "3472da66d408c9fed412f954397ea9a860e56dc6" - }, - "_source": "https://github.com/FCSAmericaDev/angular-fcsa-number.git", - "_target": "^1.5.3", - "_originalSource": "angular-fcsa-number" -} \ No newline at end of file diff --git a/samples/irs-demo/web/src/main/resources/static/js/bower_components/angular-fcsa-number/CHANGLOG.md b/samples/irs-demo/web/src/main/resources/static/js/bower_components/angular-fcsa-number/CHANGLOG.md deleted file mode 100644 index da61f5337f..0000000000 --- a/samples/irs-demo/web/src/main/resources/static/js/bower_components/angular-fcsa-number/CHANGLOG.md +++ /dev/null @@ -1,56 +0,0 @@ -# Changelog - -### 1.5.3 - -* Bug Fixes - * Fixed a bug where `-1.2` was being formatted as `-1-1.2` - -### 1.5.2 - -* Bug Fixes - * `preventInvalidInput` is correctly working again - -### 1.5.1 - -* Bug Fixes - * Fixed issue where overriding a global option on a specific input element would override that - option for all input elements - -### 1.5.0 - -* Enhancements - * Can set default options with `fcsaNumberConfigProvider.setDefaultOptions()` - -### 1.4.1 - -* Bug Fixes - * `prepend` and `append` values are removed on focus - -### 1.4 - -* Enhancements - * Added `prepend` option - * Added `append` option - -### 1.3 - -* Enhancements - * Not adding commas to the decimal portion of the number - * Allowing commas to be entered by the user. Thanks @jbulat - * A hyphen only value is marked invalid - -### 1.2.1 - -* Refactorings - * using `angular.element` instead of `$` and therefore no longer requiring jQuery to be used - -### 1.2 - -* Enhancements - * added 'preventInvalidInput' option - -### 1.1 - -* Enhancements - * renamed `decimals` option to `maxDecimals` - * renamed `digits` option to `maxDigits` diff --git a/samples/irs-demo/web/src/main/resources/static/js/bower_components/angular-fcsa-number/LICENSE b/samples/irs-demo/web/src/main/resources/static/js/bower_components/angular-fcsa-number/LICENSE deleted file mode 100644 index ad410e1130..0000000000 --- a/samples/irs-demo/web/src/main/resources/static/js/bower_components/angular-fcsa-number/LICENSE +++ /dev/null @@ -1,201 +0,0 @@ -Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "{}" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright {yyyy} {name of copyright owner} - - 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. \ No newline at end of file diff --git a/samples/irs-demo/web/src/main/resources/static/js/bower_components/angular-fcsa-number/README.md b/samples/irs-demo/web/src/main/resources/static/js/bower_components/angular-fcsa-number/README.md deleted file mode 100644 index 68826fc6ed..0000000000 --- a/samples/irs-demo/web/src/main/resources/static/js/bower_components/angular-fcsa-number/README.md +++ /dev/null @@ -1,129 +0,0 @@ -# FCSA Number - -An Angular directive that validates numbers and adds commas for the thousands separator. - -So when the user enters `1000` into a textbox and tabs out, the value will be formatted to include the thousands separator and display: `1,000` - -If an invalid number is entered, the textbox will be invalid with the `fcsaNumber` error. - -## Installation - -#### With Bower - - bower install angular-fcsa-number - -Then reference `angular-fcsa-number/src/fcsaNumber.js` in your project. - -#### Manually - -Copy `src/fcsaNumber.js` into your project and reference it. - -## Quick Start - -Add the `fcsa-number` module as a dependency to you Angular app. - - angular.module('yourApp', ['fcsa-number']); - -Add the `fcsa-number` attribute to textboxes you want to have validated and formatted with thousands separators. - - - -When an invalid number is entered by the user, the form and control will become invalid and the 'fcsa-number-invalid' class will be added to the text box. - -## Options - -Without any options passed to it, fcsa-number will validate that the input is a valid number and will also add commas for the thousand separators. - - -#### max - -Validates the number is not above the max number. - - fcsa-number="{ max: 100 }" - - * Valid: 100 - * Invalid: 101 - -#### min - -Validates the number is not below the min number. - - fcsa-number="{ min: -5 }" - - * Valid: -5 - * Invalid: -6 - -#### maxDecimals - -Validates the number does not have more than the specified number of decimals. - - fcsa-number="{ maxDecimals: 2 }" - - * Valid: 1.23 - * Invalid: 1.234 - -#### maxDigits - -Validates the number does not have more than the specified number of digits. - - fcsa-number="{ maxDigits: 2 }" - - * Valid: 76 - * Invalid: 123 - -#### preventInvalidInput - -By default users are allowed to enter invalid characters, and then the textbox is marked invalid. -If you want to prevent users from entering invalid characters altogether, then use the `preventInvalidInput` option. -If the user presses the 'a' key, the directive will catch it and prevent 'a' from being shown in the textbox. - - fcsa-number="{ preventInvalidInput: true }" - -#### prepend - -Prepends the specified text before the number. - - fcsa-number="{ prepend: '$' }" - -#### append - -Appends the specified text after the number. - - fcsa-number="{ append: '%' }" - -## Default Options - -It's possible to set the options globally too. You do this by calling `fcsaNumberConfigProvider.setDefaultOptions()` -inside a config function in your app. - -Here's a code example that sets the default options: - -```javascript -var app = angular.module('yourApp', ['fcsa-number']); -app.config(['fcsaNumberConfigProvider', function(fcsaNumberConfigProvider) { - fcsaNumberConfigProvider.setDefaultOptions({ - max: 100, - min: 0 - }); -}); -``` - -The default options can be overridden locally by passing in an options object: `fcsa-number="{ max: 10 }"` - -## Developing - -Grunt is used to compile the CoffeeScript files and run the tests. To get started run the following commands on the command line: - - // installs the required node modules - npm install - - // installs the required bower components - bower install - -Run the following command to automatically compile and run the unit and end to end tests whenever you make a change to a file. - - grunt karma:unit:start express:dev watch - -To just run the protractor tests, you can run the following command. - - grunt express:dev e2e diff --git a/samples/irs-demo/web/src/main/resources/static/js/bower_components/angular-fcsa-number/bower.json b/samples/irs-demo/web/src/main/resources/static/js/bower_components/angular-fcsa-number/bower.json deleted file mode 100644 index 63bb1d25e1..0000000000 --- a/samples/irs-demo/web/src/main/resources/static/js/bower_components/angular-fcsa-number/bower.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "name": "angular-fcsa-number", - "version": "1.5.3", - "authors": [ - "Paul Yoder " - ], - "description": "An Angular directive that validates numbers and adds commas for the thousands separator.", - "main": "src/fcsaNumber.js", - "keywords": [ - "angular", - "directive", - "format", - "number", - "validate" - ], - "license": "Apache V2", - "homepage": "https://github.com/FCSAmericaDev/angular-fcsa-number", - "ignore": [ - "**/.*", - "**/*.coffee", - "node_modules", - "bower_components", - "test", - "Gruntfile.coffee", - "karma.conf.js", - "package.json" - ], - "dependencies": { - "angular": "~1.x" - }, - "devDependencies": { - "angular-mocks": "~1.x" - } -} diff --git a/samples/irs-demo/web/src/main/resources/static/js/bower_components/angular-fcsa-number/e2e/fcsaNumber.e2e.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/angular-fcsa-number/e2e/fcsaNumber.e2e.js deleted file mode 100644 index f804b30bfc..0000000000 --- a/samples/irs-demo/web/src/main/resources/static/js/bower_components/angular-fcsa-number/e2e/fcsaNumber.e2e.js +++ /dev/null @@ -1,29 +0,0 @@ -(function() { - describe('fcsaNumber', function() { - describe('on blur', function() { - it('adds the thousand separator', function() { - var input; - input = element(By.model('amount')); - input.clear(); - input.sendKeys('1000\t'); - return expect(input.getAttribute('value')).toBe('1,000'); - }); - return it('does not add commas to the decimal portion', function() { - var input; - input = element(By.model('amount')); - input.clear(); - input.sendKeys('1234.5678\t'); - return expect(input.getAttribute('value')).toBe('1,234.5678'); - }); - }); - return it('removes the thousand separators on focus', function() { - var input; - input = element(By.model('amount')); - input.clear(); - input.sendKeys('1000\t'); - input.click(); - return expect(input.getAttribute('value')).toBe('1000'); - }); - }); - -}).call(this); diff --git a/samples/irs-demo/web/src/main/resources/static/js/bower_components/angular-fcsa-number/e2e/protractor.config b/samples/irs-demo/web/src/main/resources/static/js/bower_components/angular-fcsa-number/e2e/protractor.config deleted file mode 100644 index 147954edb1..0000000000 --- a/samples/irs-demo/web/src/main/resources/static/js/bower_components/angular-fcsa-number/e2e/protractor.config +++ /dev/null @@ -1,19 +0,0 @@ -exports.config = { - specs: ['fcsaNumber.e2e.js'], - - baseUrl: 'http://localhost:3000', - onPrepare: function () { - browser.driver.manage().window().maximize(); - browser.get(''); - }, - - capabilities: { - 'browserName': 'chrome' - }, - - // Options to be passed to Jasmine-node. - jasmineNodeOpts: { - showColors: true, - defaultTimeoutInterval: 30000 - } -}; diff --git a/samples/irs-demo/web/src/main/resources/static/js/bower_components/angular-fcsa-number/e2e/web/app.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/angular-fcsa-number/e2e/web/app.js deleted file mode 100644 index d02ceab30d..0000000000 --- a/samples/irs-demo/web/src/main/resources/static/js/bower_components/angular-fcsa-number/e2e/web/app.js +++ /dev/null @@ -1,6 +0,0 @@ -var express = require('express'); -var app = express(); - -app.use('/', express.static(__dirname + '/public')); - -app.listen(3000); diff --git a/samples/irs-demo/web/src/main/resources/static/js/bower_components/angular-fcsa-number/e2e/web/public/angular.js b/samples/irs-demo/web/src/main/resources/static/js/bower_components/angular-fcsa-number/e2e/web/public/angular.js deleted file mode 100644 index e9d011eda6..0000000000 --- a/samples/irs-demo/web/src/main/resources/static/js/bower_components/angular-fcsa-number/e2e/web/public/angular.js +++ /dev/null @@ -1,22873 +0,0 @@ -/** - * @license AngularJS v1.3.0-build.2849+sha.ea820b5 - * (c) 2010-2014 Google, Inc. http://angularjs.org - * License: MIT - */ -(function(window, document, undefined) {'use strict'; - -/** - * @description - * - * This object provides a utility for producing rich Error messages within - * Angular. It can be called as follows: - * - * var exampleMinErr = minErr('example'); - * throw exampleMinErr('one', 'This {0} is {1}', foo, bar); - * - * The above creates an instance of minErr in the example namespace. The - * resulting error will have a namespaced error code of example.one. The - * resulting error will replace {0} with the value of foo, and {1} with the - * value of bar. The object is not restricted in the number of arguments it can - * take. - * - * If fewer arguments are specified than necessary for interpolation, the extra - * interpolation markers will be preserved in the final string. - * - * Since data will be parsed statically during a build step, some restrictions - * are applied with respect to how minErr instances are created and called. - * Instances should have names of the form namespaceMinErr for a minErr created - * using minErr('namespace') . Error codes, namespaces and template strings - * should all be static strings, not variables or general expressions. - * - * @param {string} module The namespace to use for the new minErr instance. - * @returns {function(code:string, template:string, ...templateArgs): Error} minErr instance - */ - -function minErr(module) { - return function () { - var code = arguments[0], - prefix = '[' + (module ? module + ':' : '') + code + '] ', - template = arguments[1], - templateArgs = arguments, - stringify = function (obj) { - if (typeof obj === 'function') { - return obj.toString().replace(/ \{[\s\S]*$/, ''); - } else if (typeof obj === 'undefined') { - return 'undefined'; - } else if (typeof obj !== 'string') { - return JSON.stringify(obj); - } - return obj; - }, - message, i; - - message = prefix + template.replace(/\{\d+\}/g, function (match) { - var index = +match.slice(1, -1), arg; - - if (index + 2 < templateArgs.length) { - arg = templateArgs[index + 2]; - if (typeof arg === 'function') { - return arg.toString().replace(/ ?\{[\s\S]*$/, ''); - } else if (typeof arg === 'undefined') { - return 'undefined'; - } else if (typeof arg !== 'string') { - return toJson(arg); - } - return arg; - } - return match; - }); - - message = message + '\nhttp://errors.angularjs.org/1.3.0-build.2849+sha.ea820b5/' + - (module ? module + '/' : '') + code; - for (i = 2; i < arguments.length; i++) { - message = message + (i == 2 ? '?' : '&') + 'p' + (i-2) + '=' + - encodeURIComponent(stringify(arguments[i])); - } - - return new Error(message); - }; -} - -/* We need to tell jshint what variables are being exported */ -/* global - -angular, - -msie, - -jqLite, - -jQuery, - -slice, - -push, - -toString, - -ngMinErr, - -angularModule, - -nodeName_, - -uid, - -REGEX_STRING_REGEXP, - - -lowercase, - -uppercase, - -manualLowercase, - -manualUppercase, - -nodeName_, - -isArrayLike, - -forEach, - -sortedKeys, - -forEachSorted, - -reverseParams, - -nextUid, - -setHashKey, - -extend, - -int, - -inherit, - -noop, - -identity, - -valueFn, - -isUndefined, - -isDefined, - -isObject, - -isString, - -isNumber, - -isDate, - -isArray, - -isFunction, - -isRegExp, - -isWindow, - -isScope, - -isFile, - -isBlob, - -isBoolean, - -trim, - -isElement, - -makeMap, - -map, - -size, - -includes, - -indexOf, - -arrayRemove, - -isLeafNode, - -copy, - -shallowCopy, - -equals, - -csp, - -concat, - -sliceArgs, - -bind, - -toJsonReplacer, - -toJson, - -fromJson, - -toBoolean, - -startingTag, - -tryDecodeURIComponent, - -parseKeyValue, - -toKeyValue, - -encodeUriSegment, - -encodeUriQuery, - -angularInit, - -bootstrap, - -snake_case, - -bindJQuery, - -assertArg, - -assertArgFn, - -assertNotHasOwnProperty, - -getter, - -getBlockElements, - -hasOwnProperty, - -*/ - -//////////////////////////////////// - -/** - * @ngdoc module - * @name ng - * @module ng - * @description - * - * # ng (core module) - * The ng module is loaded by default when an AngularJS application is started. The module itself - * contains the essential components for an AngularJS application to function. The table below - * lists a high level breakdown of each of the services/factories, filters, directives and testing - * components available within this core module. - * - *

- */ - -var REGEX_STRING_REGEXP = /^\/(.+)\/([a-z]*)$/; - -/** - * @ngdoc function - * @name angular.lowercase - * @module ng - * @kind function - * - * @description Converts the specified string to lowercase. - * @param {string} string String to be converted to lowercase. - * @returns {string} Lowercased string. - */ -var lowercase = function(string){return isString(string) ? string.toLowerCase() : string;}; -var hasOwnProperty = Object.prototype.hasOwnProperty; - -/** - * @ngdoc function - * @name angular.uppercase - * @module ng - * @kind function - * - * @description Converts the specified string to uppercase. - * @param {string} string String to be converted to uppercase. - * @returns {string} Uppercased string. - */ -var uppercase = function(string){return isString(string) ? string.toUpperCase() : string;}; - - -var manualLowercase = function(s) { - /* jshint bitwise: false */ - return isString(s) - ? s.replace(/[A-Z]/g, function(ch) {return String.fromCharCode(ch.charCodeAt(0) | 32);}) - : s; -}; -var manualUppercase = function(s) { - /* jshint bitwise: false */ - return isString(s) - ? s.replace(/[a-z]/g, function(ch) {return String.fromCharCode(ch.charCodeAt(0) & ~32);}) - : s; -}; - - -// String#toLowerCase and String#toUpperCase don't produce correct results in browsers with Turkish -// locale, for this reason we need to detect this case and redefine lowercase/uppercase methods -// with correct but slower alternatives. -if ('i' !== 'I'.toLowerCase()) { - lowercase = manualLowercase; - uppercase = manualUppercase; -} - - -var /** holds major version number for IE or NaN for real browsers */ - msie, - jqLite, // delay binding since jQuery could be loaded after us. - jQuery, // delay binding - slice = [].slice, - push = [].push, - toString = Object.prototype.toString, - ngMinErr = minErr('ng'), - - /** @name angular */ - angular = window.angular || (window.angular = {}), - angularModule, - nodeName_, - uid = 0; - -/** - * IE 11 changed the format of the UserAgent string. - * See http://msdn.microsoft.com/en-us/library/ms537503.aspx - */ -msie = int((/msie (\d+)/.exec(lowercase(navigator.userAgent)) || [])[1]); -if (isNaN(msie)) { - msie = int((/trident\/.*; rv:(\d+)/.exec(lowercase(navigator.userAgent)) || [])[1]); -} - - -/** - * @private - * @param {*} obj - * @return {boolean} Returns true if `obj` is an array or array-like object (NodeList, Arguments, - * String ...) - */ -function isArrayLike(obj) { - if (obj == null || isWindow(obj)) { - return false; - } - - var length = obj.length; - - if (obj.nodeType === 1 && length) { - return true; - } - - return isString(obj) || isArray(obj) || length === 0 || - typeof length === 'number' && length > 0 && (length - 1) in obj; -} - -/** - * @ngdoc function - * @name angular.forEach - * @module ng - * @kind function - * - * @description - * Invokes the `iterator` function once for each item in `obj` collection, which can be either an - * object or an array. The `iterator` function is invoked with `iterator(value, key)`, where `value` - * is the value of an object property or an array element and `key` is the object property key or - * array element index. Specifying a `context` for the function is optional. - * - * It is worth noting that `.forEach` does not iterate over inherited properties because it filters - * using the `hasOwnProperty` method. - * - ```js - var values = {name: 'misko', gender: 'male'}; - var log = []; - angular.forEach(values, function(value, key) { - this.push(key + ': ' + value); - }, log); - expect(log).toEqual(['name: misko', 'gender: male']); - ``` - * - * @param {Object|Array} obj Object to iterate over. - * @param {Function} iterator Iterator function. - * @param {Object=} context Object to become context (`this`) for the iterator function. - * @returns {Object|Array} Reference to `obj`. - */ - -function forEach(obj, iterator, context) { - var key, length; - if (obj) { - if (isFunction(obj)) { - for (key in obj) { - // Need to check if hasOwnProperty exists, - // as on IE8 the result of querySelectorAll is an object without a hasOwnProperty function - if (key != 'prototype' && key != 'length' && key != 'name' && (!obj.hasOwnProperty || obj.hasOwnProperty(key))) { - iterator.call(context, obj[key], key); - } - } - } else if (obj.forEach && obj.forEach !== forEach) { - obj.forEach(iterator, context); - } else if (isArrayLike(obj)) { - for (key = 0, length = obj.length; key < length; key++) { - iterator.call(context, obj[key], key); - } - } else { - for (key in obj) { - if (obj.hasOwnProperty(key)) { - iterator.call(context, obj[key], key); - } - } - } - } - return obj; -} - -function sortedKeys(obj) { - var keys = []; - for (var key in obj) { - if (obj.hasOwnProperty(key)) { - keys.push(key); - } - } - return keys.sort(); -} - -function forEachSorted(obj, iterator, context) { - var keys = sortedKeys(obj); - for ( var i = 0; i < keys.length; i++) { - iterator.call(context, obj[keys[i]], keys[i]); - } - return keys; -} - - -/** - * when using forEach the params are value, key, but it is often useful to have key, value. - * @param {function(string, *)} iteratorFn - * @returns {function(*, string)} - */ -function reverseParams(iteratorFn) { - return function(value, key) { iteratorFn(key, value); }; -} - -/** - * A consistent way of creating unique IDs in angular. - * - * Using simple numbers allows us to generate 28.6 million unique ids per second for 10 years before - * we hit number precision issues in JavaScript. - * - * Math.pow(2,53) / 60 / 60 / 24 / 365 / 10 = 28.6M - * - * @returns {number} an unique alpha-numeric string - */ -function nextUid() { - return ++uid; -} - - -/** - * Set or clear the hashkey for an object. - * @param obj object - * @param h the hashkey (!truthy to delete the hashkey) - */ -function setHashKey(obj, h) { - if (h) { - obj.$$hashKey = h; - } - else { - delete obj.$$hashKey; - } -} - -/** - * @ngdoc function - * @name angular.extend - * @module ng - * @kind function - * - * @description - * Extends the destination object `dst` by copying all of the properties from the `src` object(s) - * to `dst`. You can specify multiple `src` objects. - * - * @param {Object} dst Destination object. - * @param {...Object} src Source object(s). - * @returns {Object} Reference to `dst`. - */ -function extend(dst) { - var h = dst.$$hashKey; - forEach(arguments, function(obj) { - if (obj !== dst) { - forEach(obj, function(value, key) { - dst[key] = value; - }); - } - }); - - setHashKey(dst,h); - return dst; -} - -function int(str) { - return parseInt(str, 10); -} - - -function inherit(parent, extra) { - return extend(new (extend(function() {}, {prototype:parent}))(), extra); -} - -/** - * @ngdoc function - * @name angular.noop - * @module ng - * @kind function - * - * @description - * A function that performs no operations. This function can be useful when writing code in the - * functional style. - ```js - function foo(callback) { - var result = calculateResult(); - (callback || angular.noop)(result); - } - ``` - */ -function noop() {} -noop.$inject = []; - - -/** - * @ngdoc function - * @name angular.identity - * @module ng - * @kind function - * - * @description - * A function that returns its first argument. This function is useful when writing code in the - * functional style. - * - ```js - function transformer(transformationFn, value) { - return (transformationFn || angular.identity)(value); - }; - ``` - */ -function identity($) {return $;} -identity.$inject = []; - - -function valueFn(value) {return function() {return value;};} - -/** - * @ngdoc function - * @name angular.isUndefined - * @module ng - * @kind function - * - * @description - * Determines if a reference is undefined. - * - * @param {*} value Reference to check. - * @returns {boolean} True if `value` is undefined. - */ -function isUndefined(value){return typeof value === 'undefined';} - - -/** - * @ngdoc function - * @name angular.isDefined - * @module ng - * @kind function - * - * @description - * Determines if a reference is defined. - * - * @param {*} value Reference to check. - * @returns {boolean} True if `value` is defined. - */ -function isDefined(value){return typeof value !== 'undefined';} - - -/** - * @ngdoc function - * @name angular.isObject - * @module ng - * @kind function - * - * @description - * Determines if a reference is an `Object`. Unlike `typeof` in JavaScript, `null`s are not - * considered to be objects. Note that JavaScript arrays are objects. - * - * @param {*} value Reference to check. - * @returns {boolean} True if `value` is an `Object` but not `null`. - */ -function isObject(value){return value != null && typeof value === 'object';} - - -/** - * @ngdoc function - * @name angular.isString - * @module ng - * @kind function - * - * @description - * Determines if a reference is a `String`. - * - * @param {*} value Reference to check. - * @returns {boolean} True if `value` is a `String`. - */ -function isString(value){return typeof value === 'string';} - - -/** - * @ngdoc function - * @name angular.isNumber - * @module ng - * @kind function - * - * @description - * Determines if a reference is a `Number`. - * - * @param {*} value Reference to check. - * @returns {boolean} True if `value` is a `Number`. - */ -function isNumber(value){return typeof value === 'number';} - - -/** - * @ngdoc function - * @name angular.isDate - * @module ng - * @kind function - * - * @description - * Determines if a value is a date. - * - * @param {*} value Reference to check. - * @returns {boolean} True if `value` is a `Date`. - */ -function isDate(value) { - return toString.call(value) === '[object Date]'; -} - - -/** - * @ngdoc function - * @name angular.isArray - * @module ng - * @kind function - * - * @description - * Determines if a reference is an `Array`. - * - * @param {*} value Reference to check. - * @returns {boolean} True if `value` is an `Array`. - */ -var isArray = (function() { - if (!isFunction(Array.isArray)) { - return function(value) { - return toString.call(value) === '[object Array]'; - }; - } - return Array.isArray; -})(); - -/** - * @ngdoc function - * @name angular.isFunction - * @module ng - * @kind function - * - * @description - * Determines if a reference is a `Function`. - * - * @param {*} value Reference to check. - * @returns {boolean} True if `value` is a `Function`. - */ -function isFunction(value){return typeof value === 'function';} - - -/** - * Determines if a value is a regular expression object. - * - * @private - * @param {*} value Reference to check. - * @returns {boolean} True if `value` is a `RegExp`. - */ -function isRegExp(value) { - return toString.call(value) === '[object RegExp]'; -} - - -/** - * Checks if `obj` is a window object. - * - * @private - * @param {*} obj Object to check - * @returns {boolean} True if `obj` is a window obj. - */ -function isWindow(obj) { - return obj && obj.window === obj; -} - - -function isScope(obj) { - return obj && obj.$evalAsync && obj.$watch; -} - - -function isFile(obj) { - return toString.call(obj) === '[object File]'; -} - - -function isBlob(obj) { - return toString.call(obj) === '[object Blob]'; -} - - -function isBoolean(value) { - return typeof value === 'boolean'; -} - - -var trim = (function() { - // native trim is way faster: http://jsperf.com/angular-trim-test - // but IE doesn't have it... :-( - // TODO: we should move this into IE/ES5 polyfill - if (!String.prototype.trim) { - return function(value) { - return isString(value) ? value.replace(/^\s\s*/, '').replace(/\s\s*$/, '') : value; - }; - } - return function(value) { - return isString(value) ? value.trim() : value; - }; -})(); - - -/** - * @ngdoc function - * @name angular.isElement - * @module ng - * @kind function - * - * @description - * Determines if a reference is a DOM element (or wrapped jQuery element). - * - * @param {*} value Reference to check. - * @returns {boolean} True if `value` is a DOM element (or wrapped jQuery element). - */ -function isElement(node) { - return !!(node && - (node.nodeName // we are a direct element - || (node.prop && node.attr && node.find))); // we have an on and find method part of jQuery API -} - -/** - * @param str 'key1,key2,...' - * @returns {object} in the form of {key1:true, key2:true, ...} - */ -function makeMap(str) { - var obj = {}, items = str.split(","), i; - for ( i = 0; i < items.length; i++ ) - obj[ items[i] ] = true; - return obj; -} - - -if (msie < 9) { - nodeName_ = function(element) { - element = element.nodeName ? element : element[0]; - return lowercase( - (element.scopeName && element.scopeName != 'HTML') - ? element.scopeName + ':' + element.nodeName : element.nodeName - ); - }; -} else { - nodeName_ = function(element) { - return lowercase(element.nodeName ? element.nodeName : element[0].nodeName); - }; -} - - -function map(obj, iterator, context) { - var results = []; - forEach(obj, function(value, index, list) { - results.push(iterator.call(context, value, index, list)); - }); - return results; -} - - -/** - * @description - * Determines the number of elements in an array, the number of properties an object has, or - * the length of a string. - * - * Note: This function is used to augment the Object type in Angular expressions. See - * {@link angular.Object} for more information about Angular arrays. - * - * @param {Object|Array|string} obj Object, array, or string to inspect. - * @param {boolean} [ownPropsOnly=false] Count only "own" properties in an object - * @returns {number} The size of `obj` or `0` if `obj` is neither an object nor an array. - */ -function size(obj, ownPropsOnly) { - var count = 0, key; - - if (isArray(obj) || isString(obj)) { - return obj.length; - } else if (isObject(obj)) { - for (key in obj) - if (!ownPropsOnly || obj.hasOwnProperty(key)) - count++; - } - - return count; -} - - -function includes(array, obj) { - return indexOf(array, obj) != -1; -} - -function indexOf(array, obj) { - if (array.indexOf) return array.indexOf(obj); - - for (var i = 0; i < array.length; i++) { - if (obj === array[i]) return i; - } - return -1; -} - -function arrayRemove(array, value) { - var index = indexOf(array, value); - if (index >=0) - array.splice(index, 1); - return value; -} - -function isLeafNode (node) { - if (node) { - switch (nodeName_(node)) { - case "option": - case "pre": - case "title": - return true; - } - } - return false; -} - -/** - * @ngdoc function - * @name angular.copy - * @module ng - * @kind function - * - * @description - * Creates a deep copy of `source`, which should be an object or an array. - * - * * If no destination is supplied, a copy of the object or array is created. - * * If a destination is provided, all of its elements (for array) or properties (for objects) - * are deleted and then all elements/properties from the source are copied to it. - * * If `source` is not an object or array (inc. `null` and `undefined`), `source` is returned. - * * If `source` is identical to 'destination' an exception will be thrown. - * - * @param {*} source The source that will be used to make a copy. - * Can be any type, including primitives, `null`, and `undefined`. - * @param {(Object|Array)=} destination Destination into which the source is copied. If - * provided, must be of the same type as `source`. - * @returns {*} The copy or updated `destination`, if `destination` was specified. - * - * @example - - -
-
- Name:
- E-mail:
- Gender: male - female
- - -
-
form = {{user | json}}
-
master = {{master | json}}
-
- - -
-
- */ -function copy(source, destination, stackSource, stackDest) { - if (isWindow(source) || isScope(source)) { - throw ngMinErr('cpws', - "Can't copy! Making copies of Window or Scope instances is not supported."); - } - - if (!destination) { - destination = source; - if (source) { - if (isArray(source)) { - destination = copy(source, [], stackSource, stackDest); - } else if (isDate(source)) { - destination = new Date(source.getTime()); - } else if (isRegExp(source)) { - destination = new RegExp(source.source); - } else if (isObject(source)) { - destination = copy(source, {}, stackSource, stackDest); - } - } - } else { - if (source === destination) throw ngMinErr('cpi', - "Can't copy! Source and destination are identical."); - - stackSource = stackSource || []; - stackDest = stackDest || []; - - if (isObject(source)) { - var index = indexOf(stackSource, source); - if (index !== -1) return stackDest[index]; - - stackSource.push(source); - stackDest.push(destination); - } - - var result; - if (isArray(source)) { - destination.length = 0; - for ( var i = 0; i < source.length; i++) { - result = copy(source[i], null, stackSource, stackDest); - if (isObject(source[i])) { - stackSource.push(source[i]); - stackDest.push(result); - } - destination.push(result); - } - } else { - var h = destination.$$hashKey; - forEach(destination, function(value, key) { - delete destination[key]; - }); - for ( var key in source) { - result = copy(source[key], null, stackSource, stackDest); - if (isObject(source[key])) { - stackSource.push(source[key]); - stackDest.push(result); - } - destination[key] = result; - } - setHashKey(destination,h); - } - - } - return destination; -} - -/** - * Creates a shallow copy of an object, an array or a primitive - */ -function shallowCopy(src, dst) { - var i = 0; - if (isArray(src)) { - dst = dst || []; - - for (; i < src.length; i++) { - dst[i] = src[i]; - } - } else if (isObject(src)) { - dst = dst || {}; - - var keys = Object.keys(src); - - for (var l = keys.length; i < l; i++) { - var key = keys[i]; - - if (!(key.charAt(0) === '$' && key.charAt(1) === '$')) { - dst[key] = src[key]; - } - } - } - - return dst || src; -} - - -/** - * @ngdoc function - * @name angular.equals - * @module ng - * @kind function - * - * @description - * Determines if two objects or two values are equivalent. Supports value types, regular - * expressions, arrays and objects. - * - * Two objects or values are considered equivalent if at least one of the following is true: - * - * * Both objects or values pass `===` comparison. - * * Both objects or values are of the same type and all of their properties are equal by - * comparing them with `angular.equals`. - * * Both values are NaN. (In JavaScript, NaN == NaN => false. But we consider two NaN as equal) - * * Both values represent the same regular expression (In JavaScript, - * /abc/ == /abc/ => false. But we consider two regular expressions as equal when their textual - * representation matches). - * - * During a property comparison, properties of `function` type and properties with names - * that begin with `$` are ignored. - * - * Scope and DOMWindow objects are being compared only by identify (`===`). - * - * @param {*} o1 Object or value to compare. - * @param {*} o2 Object or value to compare. - * @returns {boolean} True if arguments are equal. - */ -function equals(o1, o2) { - if (o1 === o2) return true; - if (o1 === null || o2 === null) return false; - if (o1 !== o1 && o2 !== o2) return true; // NaN === NaN - var t1 = typeof o1, t2 = typeof o2, length, key, keySet; - if (t1 == t2) { - if (t1 == 'object') { - if (isArray(o1)) { - if (!isArray(o2)) return false; - if ((length = o1.length) == o2.length) { - for(key=0; key 2 ? sliceArgs(arguments, 2) : []; - if (isFunction(fn) && !(fn instanceof RegExp)) { - return curryArgs.length - ? function() { - return arguments.length - ? fn.apply(self, curryArgs.concat(slice.call(arguments, 0))) - : fn.apply(self, curryArgs); - } - : function() { - return arguments.length - ? fn.apply(self, arguments) - : fn.call(self); - }; - } else { - // in IE, native methods are not functions so they cannot be bound (note: they don't need to be) - return fn; - } -} - - -function toJsonReplacer(key, value) { - var val = value; - - if (typeof key === 'string' && key.charAt(0) === '$' && key.charAt(1) === '$') { - val = undefined; - } else if (isWindow(value)) { - val = '$WINDOW'; - } else if (value && document === value) { - val = '$DOCUMENT'; - } else if (isScope(value)) { - val = '$SCOPE'; - } - - return val; -} - - -/** - * @ngdoc function - * @name angular.toJson - * @module ng - * @kind function - * - * @description - * Serializes input into a JSON-formatted string. Properties with leading $$ characters will be - * stripped since angular uses this notation internally. - * - * @param {Object|Array|Date|string|number} obj Input to be serialized into JSON. - * @param {boolean=} pretty If set to true, the JSON output will contain newlines and whitespace. - * @returns {string|undefined} JSON-ified string representing `obj`. - */ -function toJson(obj, pretty) { - if (typeof obj === 'undefined') return undefined; - return JSON.stringify(obj, toJsonReplacer, pretty ? ' ' : null); -} - - -/** - * @ngdoc function - * @name angular.fromJson - * @module ng - * @kind function - * - * @description - * Deserializes a JSON string. - * - * @param {string} json JSON string to deserialize. - * @returns {Object|Array|string|number} Deserialized thingy. - */ -function fromJson(json) { - return isString(json) - ? JSON.parse(json) - : json; -} - - -function toBoolean(value) { - if (typeof value === 'function') { - value = true; - } else if (value && value.length !== 0) { - var v = lowercase("" + value); - value = !(v == 'f' || v == '0' || v == 'false' || v == 'no' || v == 'n' || v == '[]'); - } else { - value = false; - } - return value; -} - -/** - * @returns {string} Returns the string representation of the element. - */ -function startingTag(element) { - element = jqLite(element).clone(); - try { - // turns out IE does not let you set .html() on elements which - // are not allowed to have children. So we just ignore it. - element.empty(); - } catch(e) {} - // As Per DOM Standards - var TEXT_NODE = 3; - var elemHtml = jqLite('
').append(element).html(); - try { - return element[0].nodeType === TEXT_NODE ? lowercase(elemHtml) : - elemHtml. - match(/^(<[^>]+>)/)[1]. - replace(/^<([\w\-]+)/, function(match, nodeName) { return '<' + lowercase(nodeName); }); - } catch(e) { - return lowercase(elemHtml); - } - -} - - -///////////////////////////////////////////////// - -/** - * Tries to decode the URI component without throwing an exception. - * - * @private - * @param str value potential URI component to check. - * @returns {boolean} True if `value` can be decoded - * with the decodeURIComponent function. - */ -function tryDecodeURIComponent(value) { - try { - return decodeURIComponent(value); - } catch(e) { - // Ignore any invalid uri component - } -} - - -/** - * Parses an escaped url query string into key-value pairs. - * @returns {Object.} - */ -function parseKeyValue(/**string*/keyValue) { - var obj = {}, key_value, key; - forEach((keyValue || "").split('&'), function(keyValue) { - if ( keyValue ) { - key_value = keyValue.split('='); - key = tryDecodeURIComponent(key_value[0]); - if ( isDefined(key) ) { - var val = isDefined(key_value[1]) ? tryDecodeURIComponent(key_value[1]) : true; - if (!obj[key]) { - obj[key] = val; - } else if(isArray(obj[key])) { - obj[key].push(val); - } else { - obj[key] = [obj[key],val]; - } - } - } - }); - return obj; -} - -function toKeyValue(obj) { - var parts = []; - forEach(obj, function(value, key) { - if (isArray(value)) { - forEach(value, function(arrayValue) { - parts.push(encodeUriQuery(key, true) + - (arrayValue === true ? '' : '=' + encodeUriQuery(arrayValue, true))); - }); - } else { - parts.push(encodeUriQuery(key, true) + - (value === true ? '' : '=' + encodeUriQuery(value, true))); - } - }); - return parts.length ? parts.join('&') : ''; -} - - -/** - * We need our custom method because encodeURIComponent is too aggressive and doesn't follow - * http://www.ietf.org/rfc/rfc3986.txt with regards to the character set (pchar) allowed in path - * segments: - * segment = *pchar - * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" - * pct-encoded = "%" HEXDIG HEXDIG - * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" - * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" - * / "*" / "+" / "," / ";" / "=" - */ -function encodeUriSegment(val) { - return encodeUriQuery(val, true). - replace(/%26/gi, '&'). - replace(/%3D/gi, '='). - replace(/%2B/gi, '+'); -} - - -/** - * This method is intended for encoding *key* or *value* parts of query component. We need a custom - * method because encodeURIComponent is too aggressive and encodes stuff that doesn't have to be - * encoded per http://tools.ietf.org/html/rfc3986: - * query = *( pchar / "/" / "?" ) - * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" - * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" - * pct-encoded = "%" HEXDIG HEXDIG - * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" - * / "*" / "+" / "," / ";" / "=" - */ -function encodeUriQuery(val, pctEncodeSpaces) { - return encodeURIComponent(val). - replace(/%40/gi, '@'). - replace(/%3A/gi, ':'). - replace(/%24/g, '$'). - replace(/%2C/gi, ','). - replace(/%20/g, (pctEncodeSpaces ? '%20' : '+')); -} - -var ngAttrPrefixes = ['ng-', 'data-ng-', 'ng:', 'x-ng-']; - -function getNgAttribute(element, ngAttr) { - var attr, i, ii = ngAttrPrefixes.length, j, jj; - element = jqLite(element); - for (i=0; i` or `` tags. - * - * Only one AngularJS application can be auto-bootstrapped per HTML document. The first `ngApp` - * found in the document will be used to define the root element to auto-bootstrap as an - * application. To run multiple applications in an HTML document you must manually bootstrap them using - * {@link angular.bootstrap} instead. AngularJS applications cannot be nested within each other. - * - * You can specify an **AngularJS module** to be used as the root module for the application. This - * module will be loaded into the {@link auto.$injector} when the application is bootstrapped and - * should contain the application code needed or have dependencies on other modules that will - * contain the code. See {@link angular.module} for more information. - * - * In the example below if the `ngApp` directive were not placed on the `html` element then the - * document would not be compiled, the `AppController` would not be instantiated and the `{{ a+b }}` - * would not be resolved to `3`. - * - * `ngApp` is the easiest, and most common, way to bootstrap an application. - * - - -
- I can add: {{a}} + {{b}} = {{ a+b }} -
-
- - angular.module('ngAppDemo', []).controller('ngAppDemoController', function($scope) { - $scope.a = 1; - $scope.b = 2; - }); - -
- * - * Using `ngStrictDi`, you would see something like this: - * - - -
-
- I can add: {{a}} + {{b}} = {{ a+b }} - -

This renders because the controller does not fail to - instantiate, by using explicit annotation style (see - script.js for details) -

-
- -
- Name:
- Hello, {{name}}! - -

This renders because the controller does not fail to - instantiate, by using explicit annotation style - (see script.js for details) -

-
- -
- I can add: {{a}} + {{b}} = {{ a+b }} - -

The controller could not be instantiated, due to relying - on automatic function annotations (which are disabled in - strict mode). As such, the content of this section is not - interpolated, and there should be an error in your web console. -

-
-
-
- - angular.module('ngAppStrictDemo', []) - // BadController will fail to instantiate, due to relying on automatic function annotation, - // rather than an explicit annotation - .controller('BadController', function($scope) { - $scope.a = 1; - $scope.b = 2; - }) - // Unlike BadController, GoodController1 and GoodController2 will not fail to be instantiated, - // due to using explicit annotations using the array style and $inject property, respectively. - .controller('GoodController1', ['$scope', function($scope) { - $scope.a = 1; - $scope.b = 2; - }]) - .controller('GoodController2', GoodController2); - function GoodController2($scope) { - $scope.name = "World"; - } - GoodController2.$inject = ['$scope']; - - - div[ng-controller] { - margin-bottom: 1em; - -webkit-border-radius: 4px; - border-radius: 4px; - border: 1px solid; - padding: .5em; - } - div[ng-controller^=Good] { - border-color: #d6e9c6; - background-color: #dff0d8; - color: #3c763d; - } - div[ng-controller^=Bad] { - border-color: #ebccd1; - background-color: #f2dede; - color: #a94442; - margin-bottom: 0; - } - -
- */ -function angularInit(element, bootstrap) { - var elements = [element], - appElement, - module, - config = {}, - names = ['ng:app', 'ng-app', 'x-ng-app', 'data-ng-app'], - options = { - 'boolean': ['strict-di'] - }, - NG_APP_CLASS_REGEXP = /\sng[:\-]app(:\s*([\w\d_]+);?)?\s/; - - function append(element) { - element && elements.push(element); - } - - forEach(names, function(name) { - names[name] = true; - append(document.getElementById(name)); - name = name.replace(':', '\\:'); - if (element.querySelectorAll) { - forEach(element.querySelectorAll('.' + name), append); - forEach(element.querySelectorAll('.' + name + '\\:'), append); - forEach(element.querySelectorAll('[' + name + ']'), append); - } - }); - - forEach(elements, function(element) { - if (!appElement) { - var className = ' ' + element.className + ' '; - var match = NG_APP_CLASS_REGEXP.exec(className); - if (match) { - appElement = element; - module = (match[2] || '').replace(/\s+/g, ','); - } else { - forEach(element.attributes, function(attr) { - if (!appElement && names[attr.name]) { - appElement = element; - module = attr.value; - } - }); - } - } - }); - if (appElement) { - config.strictDi = getNgAttribute(appElement, "strict-di") !== null; - bootstrap(appElement, module ? [module] : [], config); - } -} - -/** - * @ngdoc function - * @name angular.bootstrap - * @module ng - * @description - * Use this function to manually start up angular application. - * - * See: {@link guide/bootstrap Bootstrap} - * - * Note that Protractor based end-to-end tests cannot use this function to bootstrap manually. - * They must use {@link ng.directive:ngApp ngApp}. - * - * Angular will detect if it has been loaded into the browser more than once and only allow the - * first loaded script to be bootstrapped and will report a warning to the browser console for - * each of the subsequent scripts. This prevents strange results in applications, where otherwise - * multiple instances of Angular try to work on the DOM. - * - * ```html - * - * - * - *
- * {{greeting}} - *
- * - * - * - * - * - * ``` - * - * @param {DOMElement} element DOM element which is the root of angular application. - * @param {Array=} modules an array of modules to load into the application. - * Each item in the array should be the name of a predefined module or a (DI annotated) - * function that will be invoked by the injector as a run block. - * See: {@link angular.module modules} - * @param {Object=} config an object for defining configuration options for the application. The - * following keys are supported: - * - * - `strictDi`: disable automatic function annotation for the application. This is meant to - * assist in finding bugs which break minified code. - * - * @returns {auto.$injector} Returns the newly created injector for this app. - */ -function bootstrap(element, modules, config) { - if (!isObject(config)) config = {}; - var defaultConfig = { - strictDi: false - }; - config = extend(defaultConfig, config); - var doBootstrap = function() { - element = jqLite(element); - - if (element.injector()) { - var tag = (element[0] === document) ? 'document' : startingTag(element); - throw ngMinErr('btstrpd', "App Already Bootstrapped with this Element '{0}'", tag); - } - - modules = modules || []; - modules.unshift(['$provide', function($provide) { - $provide.value('$rootElement', element); - }]); - modules.unshift('ng'); - var injector = createInjector(modules, config.strictDi); - injector.invoke(['$rootScope', '$rootElement', '$compile', '$injector', - function(scope, element, compile, injector) { - scope.$apply(function() { - element.data('$injector', injector); - compile(element)(scope); - }); - }] - ); - return injector; - }; - - var NG_DEFER_BOOTSTRAP = /^NG_DEFER_BOOTSTRAP!/; - - if (window && !NG_DEFER_BOOTSTRAP.test(window.name)) { - return doBootstrap(); - } - - window.name = window.name.replace(NG_DEFER_BOOTSTRAP, ''); - angular.resumeBootstrap = function(extraModules) { - forEach(extraModules, function(module) { - modules.push(module); - }); - doBootstrap(); - }; -} - -var SNAKE_CASE_REGEXP = /[A-Z]/g; -function snake_case(name, separator) { - separator = separator || '_'; - return name.replace(SNAKE_CASE_REGEXP, function(letter, pos) { - return (pos ? separator : '') + letter.toLowerCase(); - }); -} - -function bindJQuery() { - var originalCleanData; - // bind to jQuery if present; - jQuery = window.jQuery; - // Use jQuery if it exists with proper functionality, otherwise default to us. - // Angular 1.2+ requires jQuery 1.7.1+ for on()/off() support. - if (jQuery && jQuery.fn.on) { - jqLite = jQuery; - extend(jQuery.fn, { - scope: JQLitePrototype.scope, - isolateScope: JQLitePrototype.isolateScope, - controller: JQLitePrototype.controller, - injector: JQLitePrototype.injector, - inheritedData: JQLitePrototype.inheritedData - }); - - originalCleanData = jQuery.cleanData; - // Prevent double-proxying. - originalCleanData = originalCleanData.$$original || originalCleanData; - - // All nodes removed from the DOM via various jQuery APIs like .remove() - // are passed through jQuery.cleanData. Monkey-patch this method to fire - // the $destroy event on all removed nodes. - jQuery.cleanData = function(elems) { - for (var i = 0, elem; (elem = elems[i]) != null; i++) { - jQuery(elem).triggerHandler('$destroy'); - } - originalCleanData(elems); - }; - jQuery.cleanData.$$original = originalCleanData; - } else { - jqLite = JQLite; - } - - angular.element = jqLite; -} - -/** - * throw error if the argument is falsy. - */ -function assertArg(arg, name, reason) { - if (!arg) { - throw ngMinErr('areq', "Argument '{0}' is {1}", (name || '?'), (reason || "required")); - } - return arg; -} - -function assertArgFn(arg, name, acceptArrayAnnotation) { - if (acceptArrayAnnotation && isArray(arg)) { - arg = arg[arg.length - 1]; - } - - assertArg(isFunction(arg), name, 'not a function, got ' + - (arg && typeof arg == 'object' ? arg.constructor.name || 'Object' : typeof arg)); - return arg; -} - -/** - * throw error if the name given is hasOwnProperty - * @param {String} name the name to test - * @param {String} context the context in which the name is used, such as module or directive - */ -function assertNotHasOwnProperty(name, context) { - if (name === 'hasOwnProperty') { - throw ngMinErr('badname', "hasOwnProperty is not a valid {0} name", context); - } -} - -/** - * Return the value accessible from the object by path. Any undefined traversals are ignored - * @param {Object} obj starting object - * @param {String} path path to traverse - * @param {boolean} [bindFnToScope=true] - * @returns {Object} value as accessible by path - */ -//TODO(misko): this function needs to be removed -function getter(obj, path, bindFnToScope) { - if (!path) return obj; - var keys = path.split('.'); - var key; - var lastInstance = obj; - var len = keys.length; - - for (var i = 0; i < len; i++) { - key = keys[i]; - if (obj) { - obj = (lastInstance = obj)[key]; - } - } - if (!bindFnToScope && isFunction(obj)) { - return bind(lastInstance, obj); - } - return obj; -} - -/** - * Return the DOM siblings between the first and last node in the given array. - * @param {Array} array like object - * @returns {DOMElement} object containing the elements - */ -function getBlockElements(nodes) { - var startNode = nodes[0], - endNode = nodes[nodes.length - 1]; - if (startNode === endNode) { - return jqLite(startNode); - } - - var element = startNode; - var elements = [element]; - - do { - element = element.nextSibling; - if (!element) break; - elements.push(element); - } while (element !== endNode); - - return jqLite(elements); -} - -/** - * @ngdoc type - * @name angular.Module - * @module ng - * @description - * - * Interface for configuring angular {@link angular.module modules}. - */ - -function setupModuleLoader(window) { - - var $injectorMinErr = minErr('$injector'); - var ngMinErr = minErr('ng'); - - function ensure(obj, name, factory) { - return obj[name] || (obj[name] = factory()); - } - - var angular = ensure(window, 'angular', Object); - - // We need to expose `angular.$$minErr` to modules such as `ngResource` that reference it during bootstrap - angular.$$minErr = angular.$$minErr || minErr; - - return ensure(angular, 'module', function() { - /** @type {Object.} */ - var modules = {}; - - /** - * @ngdoc function - * @name angular.module - * @module ng - * @description - * - * The `angular.module` is a global place for creating, registering and retrieving Angular - * modules. - * All modules (angular core or 3rd party) that should be available to an application must be - * registered using this mechanism. - * - * When passed two or more arguments, a new module is created. If passed only one argument, an - * existing module (the name passed as the first argument to `module`) is retrieved. - * - * - * # Module - * - * A module is a collection of services, directives, controllers, filters, and configuration information. - * `angular.module` is used to configure the {@link auto.$injector $injector}. - * - * ```js - * // Create a new module - * var myModule = angular.module('myModule', []); - * - * // register a new service - * myModule.value('appName', 'MyCoolApp'); - * - * // configure existing services inside initialization blocks. - * myModule.config(['$locationProvider', function($locationProvider) { - * // Configure existing providers - * $locationProvider.hashPrefix('!'); - * }]); - * ``` - * - * Then you can create an injector and load your modules like this: - * - * ```js - * var injector = angular.injector(['ng', 'myModule']) - * ``` - * - * However it's more likely that you'll just use - * {@link ng.directive:ngApp ngApp} or - * {@link angular.bootstrap} to simplify this process for you. - * - * @param {!string} name The name of the module to create or retrieve. - * @param {!Array.=} requires If specified then new module is being created. If - * unspecified then the module is being retrieved for further configuration. - * @param {Function=} configFn Optional configuration function for the module. Same as - * {@link angular.Module#config Module#config()}. - * @returns {module} new module with the {@link angular.Module} api. - */ - return function module(name, requires, configFn) { - var assertNotHasOwnProperty = function(name, context) { - if (name === 'hasOwnProperty') { - throw ngMinErr('badname', 'hasOwnProperty is not a valid {0} name', context); - } - }; - - assertNotHasOwnProperty(name, 'module'); - if (requires && modules.hasOwnProperty(name)) { - modules[name] = null; - } - return ensure(modules, name, function() { - if (!requires) { - throw $injectorMinErr('nomod', "Module '{0}' is not available! You either misspelled " + - "the module name or forgot to load it. If registering a module ensure that you " + - "specify the dependencies as the second argument.", name); - } - - /** @type {!Array.>} */ - var invokeQueue = []; - - /** @type {!Array.} */ - var configBlocks = []; - - /** @type {!Array.} */ - var runBlocks = []; - - var config = invokeLater('$injector', 'invoke', 'push', configBlocks); - - /** @type {angular.Module} */ - var moduleInstance = { - // Private state - _invokeQueue: invokeQueue, - _configBlocks: configBlocks, - _runBlocks: runBlocks, - - /** - * @ngdoc property - * @name angular.Module#requires - * @module ng - * @returns {Array.} List of module names which must be loaded before this module. - * @description - * Holds the list of modules which the injector will load before the current module is - * loaded. - */ - requires: requires, - - /** - * @ngdoc property - * @name angular.Module#name - * @module ng - * @returns {string} Name of the module. - * @description - */ - name: name, - - - /** - * @ngdoc method - * @name angular.Module#provider - * @module ng - * @param {string} name service name - * @param {Function} providerType Construction function for creating new instance of the - * service. - * @description - * See {@link auto.$provide#provider $provide.provider()}. - */ - provider: invokeLater('$provide', 'provider'), - - /** - * @ngdoc method - * @name angular.Module#factory - * @module ng - * @param {string} name service name - * @param {Function} providerFunction Function for creating new instance of the service. - * @description - * See {@link auto.$provide#factory $provide.factory()}. - */ - factory: invokeLater('$provide', 'factory'), - - /** - * @ngdoc method - * @name angular.Module#service - * @module ng - * @param {string} name service name - * @param {Function} constructor A constructor function that will be instantiated. - * @description - * See {@link auto.$provide#service $provide.service()}. - */ - service: invokeLater('$provide', 'service'), - - /** - * @ngdoc method - * @name angular.Module#value - * @module ng - * @param {string} name service name - * @param {*} object Service instance object. - * @description - * See {@link auto.$provide#value $provide.value()}. - */ - value: invokeLater('$provide', 'value'), - - /** - * @ngdoc method - * @name angular.Module#constant - * @module ng - * @param {string} name constant name - * @param {*} object Constant value. - * @description - * Because the constant are fixed, they get applied before other provide methods. - * See {@link auto.$provide#constant $provide.constant()}. - */ - constant: invokeLater('$provide', 'constant', 'unshift'), - - /** - * @ngdoc method - * @name angular.Module#animation - * @module ng - * @param {string} name animation name - * @param {Function} animationFactory Factory function for creating new instance of an - * animation. - * @description - * - * **NOTE**: animations take effect only if the **ngAnimate** module is loaded. - * - * - * Defines an animation hook that can be later used with - * {@link ngAnimate.$animate $animate} service and directives that use this service. - * - * ```js - * module.animation('.animation-name', function($inject1, $inject2) { - * return { - * eventName : function(element, done) { - * //code to run the animation - * //once complete, then run done() - * return function cancellationFunction(element) { - * //code to cancel the animation - * } - * } - * } - * }) - * ``` - * - * See {@link ngAnimate.$animateProvider#register $animateProvider.register()} and - * {@link ngAnimate ngAnimate module} for more information. - */ - animation: invokeLater('$animateProvider', 'register'), - - /** - * @ngdoc method - * @name angular.Module#filter - * @module ng - * @param {string} name Filter name. - * @param {Function} filterFactory Factory function for creating new instance of filter. - * @description - * See {@link ng.$filterProvider#register $filterProvider.register()}. - */ - filter: invokeLater('$filterProvider', 'register'), - - /** - * @ngdoc method - * @name angular.Module#controller - * @module ng - * @param {string|Object} name Controller name, or an object map of controllers where the - * keys are the names and the values are the constructors. - * @param {Function} constructor Controller constructor function. - * @description - * See {@link ng.$controllerProvider#register $controllerProvider.register()}. - */ - controller: invokeLater('$controllerProvider', 'register'), - - /** - * @ngdoc method - * @name angular.Module#directive - * @module ng - * @param {string|Object} name Directive name, or an object map of directives where the - * keys are the names and the values are the factories. - * @param {Function} directiveFactory Factory function for creating new instance of - * directives. - * @description - * See {@link ng.$compileProvider#directive $compileProvider.directive()}. - */ - directive: invokeLater('$compileProvider', 'directive'), - - /** - * @ngdoc method - * @name angular.Module#config - * @module ng - * @param {Function} configFn Execute this function on module load. Useful for service - * configuration. - * @description - * Use this method to register work which needs to be performed on module loading. - * For more about how to configure services, see - * {@link providers#providers_provider-recipe Provider Recipe}. - */ - config: config, - - /** - * @ngdoc method - * @name angular.Module#run - * @module ng - * @param {Function} initializationFn Execute this function after injector creation. - * Useful for application initialization. - * @description - * Use this method to register work which should be performed when the injector is done - * loading all modules. - */ - run: function(block) { - runBlocks.push(block); - return this; - } - }; - - if (configFn) { - config(configFn); - } - - return moduleInstance; - - /** - * @param {string} provider - * @param {string} method - * @param {String=} insertMethod - * @returns {angular.Module} - */ - function invokeLater(provider, method, insertMethod, queue) { - if (!queue) queue = invokeQueue; - return function() { - queue[insertMethod || 'push']([provider, method, arguments]); - return moduleInstance; - }; - } - }); - }; - }); - -} - -/* global - angularModule: true, - version: true, - - $LocaleProvider, - $CompileProvider, - - htmlAnchorDirective, - inputDirective, - inputDirective, - formDirective, - scriptDirective, - selectDirective, - styleDirective, - optionDirective, - ngBindDirective, - ngBindHtmlDirective, - ngBindTemplateDirective, - ngClassDirective, - ngClassEvenDirective, - ngClassOddDirective, - ngCspDirective, - ngCloakDirective, - ngControllerDirective, - ngFormDirective, - ngHideDirective, - ngIfDirective, - ngIncludeDirective, - ngIncludeFillContentDirective, - ngInitDirective, - ngNonBindableDirective, - ngPluralizeDirective, - ngRepeatDirective, - ngShowDirective, - ngStyleDirective, - ngSwitchDirective, - ngSwitchWhenDirective, - ngSwitchDefaultDirective, - ngOptionsDirective, - ngTranscludeDirective, - ngModelDirective, - ngListDirective, - ngChangeDirective, - patternDirective, - patternDirective, - requiredDirective, - requiredDirective, - minlengthDirective, - minlengthDirective, - maxlengthDirective, - maxlengthDirective, - ngValueDirective, - ngModelOptionsDirective, - ngAttributeAliasDirectives, - ngEventDirectives, - - $AnchorScrollProvider, - $AnimateProvider, - $BrowserProvider, - $CacheFactoryProvider, - $ControllerProvider, - $DocumentProvider, - $ExceptionHandlerProvider, - $FilterProvider, - $InterpolateProvider, - $IntervalProvider, - $HttpProvider, - $HttpBackendProvider, - $LocationProvider, - $LogProvider, - $ParseProvider, - $RootScopeProvider, - $QProvider, - $$SanitizeUriProvider, - $SceProvider, - $SceDelegateProvider, - $SnifferProvider, - $TemplateCacheProvider, - $TimeoutProvider, - $$RAFProvider, - $$AsyncCallbackProvider, - $WindowProvider -*/ - - -/** - * @ngdoc object - * @name angular.version - * @module ng - * @description - * An object that contains information about the current AngularJS version. This object has the - * following properties: - * - * - `full` – `{string}` – Full version string, such as "0.9.18". - * - `major` – `{number}` – Major version number, such as "0". - * - `minor` – `{number}` – Minor version number, such as "9". - * - `dot` – `{number}` – Dot version number, such as "18". - * - `codeName` – `{string}` – Code name of the release, such as "jiggling-armfat". - */ -var version = { - full: '1.3.0-build.2849+sha.ea820b5', // all of these placeholder strings will be replaced by grunt's - major: 1, // package task - minor: 3, - dot: 0, - codeName: 'snapshot' -}; - - -function publishExternalAPI(angular){ - extend(angular, { - 'bootstrap': bootstrap, - 'copy': copy, - 'extend': extend, - 'equals': equals, - 'element': jqLite, - 'forEach': forEach, - 'injector': createInjector, - 'noop':noop, - 'bind':bind, - 'toJson': toJson, - 'fromJson': fromJson, - 'identity':identity, - 'isUndefined': isUndefined, - 'isDefined': isDefined, - 'isString': isString, - 'isFunction': isFunction, - 'isObject': isObject, - 'isNumber': isNumber, - 'isElement': isElement, - 'isArray': isArray, - 'version': version, - 'isDate': isDate, - 'lowercase': lowercase, - 'uppercase': uppercase, - 'callbacks': {counter: 0}, - '$$minErr': minErr, - '$$csp': csp - }); - - angularModule = setupModuleLoader(window); - try { - angularModule('ngLocale'); - } catch (e) { - angularModule('ngLocale', []).provider('$locale', $LocaleProvider); - } - - angularModule('ng', ['ngLocale'], ['$provide', - function ngModule($provide) { - // $$sanitizeUriProvider needs to be before $compileProvider as it is used by it. - $provide.provider({ - $$sanitizeUri: $$SanitizeUriProvider - }); - $provide.provider('$compile', $CompileProvider). - directive({ - a: htmlAnchorDirective, - input: inputDirective, - textarea: inputDirective, - form: formDirective, - script: scriptDirective, - select: selectDirective, - style: styleDirective, - option: optionDirective, - ngBind: ngBindDirective, - ngBindHtml: ngBindHtmlDirective, - ngBindTemplate: ngBindTemplateDirective, - ngClass: ngClassDirective, - ngClassEven: ngClassEvenDirective, - ngClassOdd: ngClassOddDirective, - ngCloak: ngCloakDirective, - ngController: ngControllerDirective, - ngForm: ngFormDirective, - ngHide: ngHideDirective, - ngIf: ngIfDirective, - ngInclude: ngIncludeDirective, - ngInit: ngInitDirective, - ngNonBindable: ngNonBindableDirective, - ngPluralize: ngPluralizeDirective, - ngRepeat: ngRepeatDirective, - ngShow: ngShowDirective, - ngStyle: ngStyleDirective, - ngSwitch: ngSwitchDirective, - ngSwitchWhen: ngSwitchWhenDirective, - ngSwitchDefault: ngSwitchDefaultDirective, - ngOptions: ngOptionsDirective, - ngTransclude: ngTranscludeDirective, - ngModel: ngModelDirective, - ngList: ngListDirective, - ngChange: ngChangeDirective, - pattern: patternDirective, - ngPattern: patternDirective, - required: requiredDirective, - ngRequired: requiredDirective, - minlength: minlengthDirective, - ngMinlength: minlengthDirective, - maxlength: maxlengthDirective, - ngMaxlength: maxlengthDirective, - ngValue: ngValueDirective, - ngModelOptions: ngModelOptionsDirective - }). - directive({ - ngInclude: ngIncludeFillContentDirective - }). - directive(ngAttributeAliasDirectives). - directive(ngEventDirectives); - $provide.provider({ - $anchorScroll: $AnchorScrollProvider, - $animate: $AnimateProvider, - $browser: $BrowserProvider, - $cacheFactory: $CacheFactoryProvider, - $controller: $ControllerProvider, - $document: $DocumentProvider, - $exceptionHandler: $ExceptionHandlerProvider, - $filter: $FilterProvider, - $interpolate: $InterpolateProvider, - $interval: $IntervalProvider, - $http: $HttpProvider, - $httpBackend: $HttpBackendProvider, - $location: $LocationProvider, - $log: $LogProvider, - $parse: $ParseProvider, - $rootScope: $RootScopeProvider, - $q: $QProvider, - $sce: $SceProvider, - $sceDelegate: $SceDelegateProvider, - $sniffer: $SnifferProvider, - $templateCache: $TemplateCacheProvider, - $timeout: $TimeoutProvider, - $window: $WindowProvider, - $$rAF: $$RAFProvider, - $$asyncCallback : $$AsyncCallbackProvider - }); - } - ]); -} - -/* global - - -JQLitePrototype, - -addEventListenerFn, - -removeEventListenerFn, - -BOOLEAN_ATTR, - -ALIASED_ATTR -*/ - -////////////////////////////////// -//JQLite -////////////////////////////////// - -/** - * @ngdoc function - * @name angular.element - * @module ng - * @kind function - * - * @description - * Wraps a raw DOM element or HTML string as a [jQuery](http://jquery.com) element. - * - * If jQuery is available, `angular.element` is an alias for the - * [jQuery](http://api.jquery.com/jQuery/) function. If jQuery is not available, `angular.element` - * delegates to Angular's built-in subset of jQuery, called "jQuery lite" or "jqLite." - * - *
jqLite is a tiny, API-compatible subset of jQuery that allows - * Angular to manipulate the DOM in a cross-browser compatible way. **jqLite** implements only the most - * commonly needed functionality with the goal of having a very small footprint.
- * - * To use jQuery, simply load it before `DOMContentLoaded` event fired. - * - *
**Note:** all element references in Angular are always wrapped with jQuery or - * jqLite; they are never raw DOM references.
- * - * ## Angular's jqLite - * jqLite provides only the following jQuery methods: - * - * - [`addClass()`](http://api.jquery.com/addClass/) - * - [`after()`](http://api.jquery.com/after/) - * - [`append()`](http://api.jquery.com/append/) - * - [`attr()`](http://api.jquery.com/attr/) - * - [`bind()`](http://api.jquery.com/bind/) - Does not support namespaces, selectors or eventData - * - [`children()`](http://api.jquery.com/children/) - Does not support selectors - * - [`clone()`](http://api.jquery.com/clone/) - * - [`contents()`](http://api.jquery.com/contents/) - * - [`css()`](http://api.jquery.com/css/) - * - [`data()`](http://api.jquery.com/data/) - * - [`empty()`](http://api.jquery.com/empty/) - * - [`eq()`](http://api.jquery.com/eq/) - * - [`find()`](http://api.jquery.com/find/) - Limited to lookups by tag name - * - [`hasClass()`](http://api.jquery.com/hasClass/) - * - [`html()`](http://api.jquery.com/html/) - * - [`next()`](http://api.jquery.com/next/) - Does not support selectors - * - [`on()`](http://api.jquery.com/on/) - Does not support namespaces, selectors or eventData - * - [`off()`](http://api.jquery.com/off/) - Does not support namespaces or selectors - * - [`one()`](http://api.jquery.com/one/) - Does not support namespaces or selectors - * - [`parent()`](http://api.jquery.com/parent/) - Does not support selectors - * - [`prepend()`](http://api.jquery.com/prepend/) - * - [`prop()`](http://api.jquery.com/prop/) - * - [`ready()`](http://api.jquery.com/ready/) - * - [`remove()`](http://api.jquery.com/remove/) - * - [`removeAttr()`](http://api.jquery.com/removeAttr/) - * - [`removeClass()`](http://api.jquery.com/removeClass/) - * - [`removeData()`](http://api.jquery.com/removeData/) - * - [`replaceWith()`](http://api.jquery.com/replaceWith/) - * - [`text()`](http://api.jquery.com/text/) - * - [`toggleClass()`](http://api.jquery.com/toggleClass/) - * - [`triggerHandler()`](http://api.jquery.com/triggerHandler/) - Passes a dummy event object to handlers. - * - [`unbind()`](http://api.jquery.com/unbind/) - Does not support namespaces - * - [`val()`](http://api.jquery.com/val/) - * - [`wrap()`](http://api.jquery.com/wrap/) - * - * ## jQuery/jqLite Extras - * Angular also provides the following additional methods and events to both jQuery and jqLite: - * - * ### Events - * - `$destroy` - AngularJS intercepts all jqLite/jQuery's DOM destruction apis and fires this event - * on all DOM nodes being removed. This can be used to clean up any 3rd party bindings to the DOM - * element before it is removed. - * - * ### Methods - * - `controller(name)` - retrieves the controller of the current element or its parent. By default - * retrieves controller associated with the `ngController` directive. If `name` is provided as - * camelCase directive name, then the controller for this directive will be retrieved (e.g. - * `'ngModel'`). - * - `injector()` - retrieves the injector of the current element or its parent. - * - `scope()` - retrieves the {@link ng.$rootScope.Scope scope} of the current - * element or its parent. - * - `isolateScope()` - retrieves an isolate {@link ng.$rootScope.Scope scope} if one is attached directly to the - * current element. This getter should be used only on elements that contain a directive which starts a new isolate - * scope. Calling `scope()` on this element always returns the original non-isolate scope. - * - `inheritedData()` - same as `data()`, but walks up the DOM until a value is found or the top - * parent element is reached. - * - * @param {string|DOMElement} element HTML string or DOMElement to be wrapped into jQuery. - * @returns {Object} jQuery object. - */ - -JQLite.expando = 'ng339'; - -var jqCache = JQLite.cache = {}, - jqId = 1, - addEventListenerFn = (window.document.addEventListener - ? function(element, type, fn) {element.addEventListener(type, fn, false);} - : function(element, type, fn) {element.attachEvent('on' + type, fn);}), - removeEventListenerFn = (window.document.removeEventListener - ? function(element, type, fn) {element.removeEventListener(type, fn, false); } - : function(element, type, fn) {element.detachEvent('on' + type, fn); }); - -/* - * !!! This is an undocumented "private" function !!! - */ -var jqData = JQLite._data = function(node) { - //jQuery always returns an object on cache miss - return this.cache[node[this.expando]] || {}; -}; - -function jqNextId() { return ++jqId; } - - -var SPECIAL_CHARS_REGEXP = /([\:\-\_]+(.))/g; -var MOZ_HACK_REGEXP = /^moz([A-Z])/; -var jqLiteMinErr = minErr('jqLite'); - -/** - * Converts snake_case to camelCase. - * Also there is special case for Moz prefix starting with upper case letter. - * @param name Name to normalize - */ -function camelCase(name) { - return name. - replace(SPECIAL_CHARS_REGEXP, function(_, separator, letter, offset) { - return offset ? letter.toUpperCase() : letter; - }). - replace(MOZ_HACK_REGEXP, 'Moz$1'); -} - -var SINGLE_TAG_REGEXP = /^<(\w+)\s*\/?>(?:<\/\1>|)$/; -var HTML_REGEXP = /<|&#?\w+;/; -var TAG_NAME_REGEXP = /<([\w:]+)/; -var XHTML_TAG_REGEXP = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi; - -var wrapMap = { - 'option': [1, ''], - - 'thead': [1, '', '
'], - 'col': [2, '', '
'], - 'tr': [2, '', '
'], - 'td': [3, '', '
'], - '_default': [0, "", ""] -}; - -wrapMap.optgroup = wrapMap.option; -wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; -wrapMap.th = wrapMap.td; - -function jqLiteIsTextNode(html) { - return !HTML_REGEXP.test(html); -} - -function jqLiteBuildFragment(html, context) { - var elem, tmp, tag, wrap, - fragment = context.createDocumentFragment(), - nodes = [], i; - - if (jqLiteIsTextNode(html)) { - // Convert non-html into a text node - nodes.push(context.createTextNode(html)); - } else { - // Convert html into DOM nodes - tmp = tmp || fragment.appendChild(context.createElement("div")); - tag = (TAG_NAME_REGEXP.exec(html) || ["", ""])[1].toLowerCase(); - wrap = wrapMap[tag] || wrapMap._default; - tmp.innerHTML = wrap[1] + html.replace(XHTML_TAG_REGEXP, "<$1>") + wrap[2]; - - // Descend through wrappers to the right content - i = wrap[0]; - while (i--) { - tmp = tmp.lastChild; - } - - nodes = concat(nodes, tmp.childNodes); - - tmp = fragment.firstChild; - tmp.textContent = ""; - } - - // Remove wrapper from fragment - fragment.textContent = ""; - fragment.innerHTML = ""; // Clear inner HTML - forEach(nodes, function(node) { - fragment.appendChild(node); - }); - - return fragment; -} - -function jqLiteParseHTML(html, context) { - context = context || document; - var parsed; - - if ((parsed = SINGLE_TAG_REGEXP.exec(html))) { - return [context.createElement(parsed[1])]; - } - - if ((parsed = jqLiteBuildFragment(html, context))) { - return parsed.childNodes; - } - - return []; -} - -///////////////////////////////////////////// -function JQLite(element) { - if (element instanceof JQLite) { - return element; - } - if (isString(element)) { - element = trim(element); - } - if (!(this instanceof JQLite)) { - if (isString(element) && element.charAt(0) != '<') { - throw jqLiteMinErr('nosel', 'Looking up elements via selectors is not supported by jqLite! See: http://docs.angularjs.org/api/angular.element'); - } - return new JQLite(element); - } - - if (isString(element)) { - jqLiteAddNodes(this, jqLiteParseHTML(element)); - } else { - jqLiteAddNodes(this, element); - } -} - -function jqLiteClone(element) { - return element.cloneNode(true); -} - -function jqLiteDealoc(element){ - jqLiteRemoveData(element); - var childElement; - for ( var i = 0, children = element.children, l = (children && children.length) || 0; i < l; i++) { - childElement = children[i]; - jqLiteDealoc(childElement); - } -} - -function jqLiteOff(element, type, fn, unsupported) { - if (isDefined(unsupported)) throw jqLiteMinErr('offargs', 'jqLite#off() does not support the `selector` argument'); - - var events = jqLiteExpandoStore(element, 'events'), - handle = jqLiteExpandoStore(element, 'handle'); - - if (!handle) return; //no listeners registered - - if (isUndefined(type)) { - forEach(events, function(eventHandler, type) { - removeEventListenerFn(element, type, eventHandler); - delete events[type]; - }); - } else { - forEach(type.split(' '), function(type) { - if (isUndefined(fn)) { - removeEventListenerFn(element, type, events[type]); - delete events[type]; - } else { - arrayRemove(events[type] || [], fn); - } - }); - } -} - -function jqLiteRemoveData(element, name) { - var expandoId = element.ng339, - expandoStore = jqCache[expandoId]; - - if (expandoStore) { - if (name) { - delete jqCache[expandoId].data[name]; - return; - } - - if (expandoStore.handle) { - expandoStore.events.$destroy && expandoStore.handle({}, '$destroy'); - jqLiteOff(element); - } - delete jqCache[expandoId]; - element.ng339 = undefined; // don't delete DOM expandos. IE and Chrome don't like it - } -} - -function jqLiteExpandoStore(element, key, value) { - var expandoId = element.ng339, - expandoStore = jqCache[expandoId || -1]; - - if (isDefined(value)) { - if (!expandoStore) { - element.ng339 = expandoId = jqNextId(); - expandoStore = jqCache[expandoId] = {}; - } - expandoStore[key] = value; - } else { - return expandoStore && expandoStore[key]; - } -} - -function jqLiteData(element, key, value) { - var data = jqLiteExpandoStore(element, 'data'), - isSetter = isDefined(value), - keyDefined = !isSetter && isDefined(key), - isSimpleGetter = keyDefined && !isObject(key); - - if (!data && !isSimpleGetter) { - jqLiteExpandoStore(element, 'data', data = {}); - } - - if (isSetter) { - // set data only on Elements and Documents - if (element.nodeType === 1 || element.nodeType === 9) { - data[key] = value; - } - } else { - if (keyDefined) { - if (isSimpleGetter) { - // don't create data in this case. - return data && data[key]; - } else { - extend(data, key); - } - } else { - return data; - } - } -} - -function jqLiteHasClass(element, selector) { - if (!element.getAttribute) return false; - return ((" " + (element.getAttribute('class') || '') + " ").replace(/[\n\t]/g, " "). - indexOf( " " + selector + " " ) > -1); -} - -function jqLiteRemoveClass(element, cssClasses) { - if (cssClasses && element.setAttribute) { - forEach(cssClasses.split(' '), function(cssClass) { - element.setAttribute('class', trim( - (" " + (element.getAttribute('class') || '') + " ") - .replace(/[\n\t]/g, " ") - .replace(" " + trim(cssClass) + " ", " ")) - ); - }); - } -} - -function jqLiteAddClass(element, cssClasses) { - if (cssClasses && element.setAttribute) { - var existingClasses = (' ' + (element.getAttribute('class') || '') + ' ') - .replace(/[\n\t]/g, " "); - - forEach(cssClasses.split(' '), function(cssClass) { - cssClass = trim(cssClass); - if (existingClasses.indexOf(' ' + cssClass + ' ') === -1) { - existingClasses += cssClass + ' '; - } - }); - - element.setAttribute('class', trim(existingClasses)); - } -} - - -function jqLiteAddNodes(root, elements) { - // THIS CODE IS VERY HOT. Don't make changes without benchmarking. - - if (elements) { - - // if a Node (the most common case) - if (elements.nodeType) { - root[root.length++] = elements; - } else { - var length = elements.length; - - // if an Array or NodeList and not a Window - if (typeof length === 'number' && elements.window !== elements) { - if (length) { - push.apply(root, elements); - } - } else { - root[root.length++] = elements; - } - } - } -} - - -function jqLiteController(element, name) { - return jqLiteInheritedData(element, '$' + (name || 'ngController' ) + 'Controller'); -} - -function jqLiteInheritedData(element, name, value) { - element = jqLite(element); - - // if element is the document object work with the html element instead - // this makes $(document).scope() possible - if(element[0].nodeType == 9) { - element = element.find('html'); - } - var names = isArray(name) ? name : [name]; - - while (element.length) { - var node = element[0]; - for (var i = 0, ii = names.length; i < ii; i++) { - if ((value = element.data(names[i])) !== undefined) return value; - } - - // If dealing with a document fragment node with a host element, and no parent, use the host - // element as the parent. This enables directives within a Shadow DOM or polyfilled Shadow DOM - // to lookup parent controllers. - element = jqLite(node.parentNode || (node.nodeType === 11 && node.host)); - } -} - -function jqLiteEmpty(element) { - for (var i = 0, childNodes = element.childNodes; i < childNodes.length; i++) { - jqLiteDealoc(childNodes[i]); - } - while (element.firstChild) { - element.removeChild(element.firstChild); - } -} - -////////////////////////////////////////// -// Functions which are declared directly. -////////////////////////////////////////// -var JQLitePrototype = JQLite.prototype = { - ready: function(fn) { - var fired = false; - - function trigger() { - if (fired) return; - fired = true; - fn(); - } - - // check if document already is loaded - if (document.readyState === 'complete'){ - setTimeout(trigger); - } else { - this.on('DOMContentLoaded', trigger); // works for modern browsers and IE9 - // we can not use jqLite since we are not done loading and jQuery could be loaded later. - // jshint -W064 - JQLite(window).on('load', trigger); // fallback to window.onload for others - // jshint +W064 - } - }, - toString: function() { - var value = []; - forEach(this, function(e){ value.push('' + e);}); - return '[' + value.join(', ') + ']'; - }, - - eq: function(index) { - return (index >= 0) ? jqLite(this[index]) : jqLite(this[this.length + index]); - }, - - length: 0, - push: push, - sort: [].sort, - splice: [].splice -}; - -////////////////////////////////////////// -// Functions iterating getter/setters. -// these functions return self on setter and -// value on get. -////////////////////////////////////////// -var BOOLEAN_ATTR = {}; -forEach('multiple,selected,checked,disabled,readOnly,required,open'.split(','), function(value) { - BOOLEAN_ATTR[lowercase(value)] = value; -}); -var BOOLEAN_ELEMENTS = {}; -forEach('input,select,option,textarea,button,form,details'.split(','), function(value) { - BOOLEAN_ELEMENTS[value] = true; -}); -var ALIASED_ATTR = { - 'ngMinlength' : 'minlength', - 'ngMaxlength' : 'maxlength', - 'ngPattern' : 'pattern' -}; - -function getBooleanAttrName(element, name) { - // check dom last since we will most likely fail on name - var booleanAttr = BOOLEAN_ATTR[name.toLowerCase()]; - - // booleanAttr is here twice to minimize DOM access - return booleanAttr && BOOLEAN_ELEMENTS[nodeName_(element)] && booleanAttr; -} - -function getAliasedAttrName(element, name) { - var nodeName = element.nodeName; - return (nodeName === 'INPUT' || nodeName === 'TEXTAREA') && ALIASED_ATTR[name]; -} - -forEach({ - data: jqLiteData, - inheritedData: jqLiteInheritedData, - - scope: function(element) { - // Can't use jqLiteData here directly so we stay compatible with jQuery! - return jqLite(element).data('$scope') || jqLiteInheritedData(element.parentNode || element, ['$isolateScope', '$scope']); - }, - - isolateScope: function(element) { - // Can't use jqLiteData here directly so we stay compatible with jQuery! - return jqLite(element).data('$isolateScope') || jqLite(element).data('$isolateScopeNoTemplate'); - }, - - controller: jqLiteController, - - injector: function(element) { - return jqLiteInheritedData(element, '$injector'); - }, - - removeAttr: function(element,name) { - element.removeAttribute(name); - }, - - hasClass: jqLiteHasClass, - - css: function(element, name, value) { - name = camelCase(name); - - if (isDefined(value)) { - element.style[name] = value; - } else { - var val; - - if (msie <= 8) { - // this is some IE specific weirdness that jQuery 1.6.4 does not sure why - val = element.currentStyle && element.currentStyle[name]; - if (val === '') val = 'auto'; - } - - val = val || element.style[name]; - - if (msie <= 8) { - // jquery weirdness :-/ - val = (val === '') ? undefined : val; - } - - return val; - } - }, - - attr: function(element, name, value){ - var lowercasedName = lowercase(name); - if (BOOLEAN_ATTR[lowercasedName]) { - if (isDefined(value)) { - if (!!value) { - element[name] = true; - element.setAttribute(name, lowercasedName); - } else { - element[name] = false; - element.removeAttribute(lowercasedName); - } - } else { - return (element[name] || - (element.attributes.getNamedItem(name)|| noop).specified) - ? lowercasedName - : undefined; - } - } else if (isDefined(value)) { - element.setAttribute(name, value); - } else if (element.getAttribute) { - // the extra argument "2" is to get the right thing for a.href in IE, see jQuery code - // some elements (e.g. Document) don't have get attribute, so return undefined - var ret = element.getAttribute(name, 2); - // normalize non-existing attributes to undefined (as jQuery) - return ret === null ? undefined : ret; - } - }, - - prop: function(element, name, value) { - if (isDefined(value)) { - element[name] = value; - } else { - return element[name]; - } - }, - - text: (function() { - getText.$dv = ''; - return getText; - - function getText(element, value) { - if (isUndefined(value)) { - var nodeType = element.nodeType; - return (nodeType === 1 || nodeType === 3) ? element.textContent : ''; - } - element.textContent = value; - } - })(), - - val: function(element, value) { - if (isUndefined(value)) { - if (element.multiple && nodeName_(element) === 'select') { - var result = []; - forEach(element.options, function (option) { - if (option.selected) { - result.push(option.value || option.text); - } - }); - return result.length === 0 ? null : result; - } - return element.value; - } - element.value = value; - }, - - html: function(element, value) { - if (isUndefined(value)) { - return element.innerHTML; - } - for (var i = 0, childNodes = element.childNodes; i < childNodes.length; i++) { - jqLiteDealoc(childNodes[i]); - } - element.innerHTML = value; - }, - - empty: jqLiteEmpty -}, function(fn, name){ - /** - * Properties: writes return selection, reads return first value - */ - JQLite.prototype[name] = function(arg1, arg2) { - var i, key; - var nodeCount = this.length; - - // jqLiteHasClass has only two arguments, but is a getter-only fn, so we need to special-case it - // in a way that survives minification. - // jqLiteEmpty takes no arguments but is a setter. - if (fn !== jqLiteEmpty && - (((fn.length == 2 && (fn !== jqLiteHasClass && fn !== jqLiteController)) ? arg1 : arg2) === undefined)) { - if (isObject(arg1)) { - - // we are a write, but the object properties are the key/values - for (i = 0; i < nodeCount; i++) { - if (fn === jqLiteData) { - // data() takes the whole object in jQuery - fn(this[i], arg1); - } else { - for (key in arg1) { - fn(this[i], key, arg1[key]); - } - } - } - // return self for chaining - return this; - } else { - // we are a read, so read the first child. - // TODO: do we still need this? - var value = fn.$dv; - // Only if we have $dv do we iterate over all, otherwise it is just the first element. - var jj = (value === undefined) ? Math.min(nodeCount, 1) : nodeCount; - for (var j = 0; j < jj; j++) { - var nodeValue = fn(this[j], arg1, arg2); - value = value ? value + nodeValue : nodeValue; - } - return value; - } - } else { - // we are a write, so apply to all children - for (i = 0; i < nodeCount; i++) { - fn(this[i], arg1, arg2); - } - // return self for chaining - return this; - } - }; -}); - -function createEventHandler(element, events) { - var eventHandler = function (event, type) { - if (!event.preventDefault) { - event.preventDefault = function() { - event.returnValue = false; //ie - }; - } - - if (!event.stopPropagation) { - event.stopPropagation = function() { - event.cancelBubble = true; //ie - }; - } - - if (!event.target) { - event.target = event.srcElement || document; - } - - if (isUndefined(event.defaultPrevented)) { - var prevent = event.preventDefault; - event.preventDefault = function() { - event.defaultPrevented = true; - prevent.call(event); - }; - event.defaultPrevented = false; - } - - event.isDefaultPrevented = function() { - return event.defaultPrevented || event.returnValue === false; - }; - - // Copy event handlers in case event handlers array is modified during execution. - var eventHandlersCopy = shallowCopy(events[type || event.type] || []); - - forEach(eventHandlersCopy, function(fn) { - fn.call(element, event); - }); - - // Remove monkey-patched methods (IE), - // as they would cause memory leaks in IE8. - if (msie <= 8) { - // IE7/8 does not allow to delete property on native object - event.preventDefault = null; - event.stopPropagation = null; - event.isDefaultPrevented = null; - } else { - // It shouldn't affect normal browsers (native methods are defined on prototype). - delete event.preventDefault; - delete event.stopPropagation; - delete event.isDefaultPrevented; - } - }; - eventHandler.elem = element; - return eventHandler; -} - -////////////////////////////////////////// -// Functions iterating traversal. -// These functions chain results into a single -// selector. -////////////////////////////////////////// -forEach({ - removeData: jqLiteRemoveData, - - dealoc: jqLiteDealoc, - - on: function onFn(element, type, fn, unsupported){ - if (isDefined(unsupported)) throw jqLiteMinErr('onargs', 'jqLite#on() does not support the `selector` or `eventData` parameters'); - - var events = jqLiteExpandoStore(element, 'events'), - handle = jqLiteExpandoStore(element, 'handle'); - - if (!events) jqLiteExpandoStore(element, 'events', events = {}); - if (!handle) jqLiteExpandoStore(element, 'handle', handle = createEventHandler(element, events)); - - forEach(type.split(' '), function(type){ - var eventFns = events[type]; - - if (!eventFns) { - if (type == 'mouseenter' || type == 'mouseleave') { - var contains = document.body.contains || document.body.compareDocumentPosition ? - function( a, b ) { - // jshint bitwise: false - var adown = a.nodeType === 9 ? a.documentElement : a, - bup = b && b.parentNode; - return a === bup || !!( bup && bup.nodeType === 1 && ( - adown.contains ? - adown.contains( bup ) : - a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 - )); - } : - function( a, b ) { - if ( b ) { - while ( (b = b.parentNode) ) { - if ( b === a ) { - return true; - } - } - } - return false; - }; - - events[type] = []; - - // Refer to jQuery's implementation of mouseenter & mouseleave - // Read about mouseenter and mouseleave: - // http://www.quirksmode.org/js/events_mouse.html#link8 - var eventmap = { mouseleave : "mouseout", mouseenter : "mouseover"}; - - onFn(element, eventmap[type], function(event) { - var target = this, related = event.relatedTarget; - // For mousenter/leave call the handler if related is outside the target. - // NB: No relatedTarget if the mouse left/entered the browser window - if ( !related || (related !== target && !contains(target, related)) ){ - handle(event, type); - } - }); - - } else { - addEventListenerFn(element, type, handle); - events[type] = []; - } - eventFns = events[type]; - } - eventFns.push(fn); - }); - }, - - off: jqLiteOff, - - one: function(element, type, fn) { - element = jqLite(element); - - //add the listener twice so that when it is called - //you can remove the original function and still be - //able to call element.off(ev, fn) normally - element.on(type, function onFn() { - element.off(type, fn); - element.off(type, onFn); - }); - element.on(type, fn); - }, - - replaceWith: function(element, replaceNode) { - var index, parent = element.parentNode; - jqLiteDealoc(element); - forEach(new JQLite(replaceNode), function(node){ - if (index) { - parent.insertBefore(node, index.nextSibling); - } else { - parent.replaceChild(node, element); - } - index = node; - }); - }, - - children: function(element) { - var children = []; - forEach(element.childNodes, function(element){ - if (element.nodeType === 1) - children.push(element); - }); - return children; - }, - - contents: function(element) { - return element.contentDocument || element.childNodes || []; - }, - - append: function(element, node) { - forEach(new JQLite(node), function(child){ - if (element.nodeType === 1 || element.nodeType === 11) { - element.appendChild(child); - } - }); - }, - - prepend: function(element, node) { - if (element.nodeType === 1) { - var index = element.firstChild; - forEach(new JQLite(node), function(child){ - element.insertBefore(child, index); - }); - } - }, - - wrap: function(element, wrapNode) { - wrapNode = jqLite(wrapNode)[0]; - var parent = element.parentNode; - if (parent) { - parent.replaceChild(wrapNode, element); - } - wrapNode.appendChild(element); - }, - - remove: function(element) { - jqLiteDealoc(element); - var parent = element.parentNode; - if (parent) parent.removeChild(element); - }, - - after: function(element, newElement) { - var index = element, parent = element.parentNode; - forEach(new JQLite(newElement), function(node){ - parent.insertBefore(node, index.nextSibling); - index = node; - }); - }, - - addClass: jqLiteAddClass, - removeClass: jqLiteRemoveClass, - - toggleClass: function(element, selector, condition) { - if (selector) { - forEach(selector.split(' '), function(className){ - var classCondition = condition; - if (isUndefined(classCondition)) { - classCondition = !jqLiteHasClass(element, className); - } - (classCondition ? jqLiteAddClass : jqLiteRemoveClass)(element, className); - }); - } - }, - - parent: function(element) { - var parent = element.parentNode; - return parent && parent.nodeType !== 11 ? parent : null; - }, - - next: function(element) { - if (element.nextElementSibling) { - return element.nextElementSibling; - } - - // IE8 doesn't have nextElementSibling - var elm = element.nextSibling; - while (elm != null && elm.nodeType !== 1) { - elm = elm.nextSibling; - } - return elm; - }, - - find: function(element, selector) { - if (element.getElementsByTagName) { - return element.getElementsByTagName(selector); - } else { - return []; - } - }, - - clone: jqLiteClone, - - triggerHandler: function(element, eventName, eventData) { - var eventFns = (jqLiteExpandoStore(element, 'events') || {})[eventName]; - - eventData = eventData || []; - - var event = [{ - preventDefault: noop, - stopPropagation: noop - }]; - - forEach(eventFns, function(fn) { - fn.apply(element, event.concat(eventData)); - }); - } -}, function(fn, name){ - /** - * chaining functions - */ - JQLite.prototype[name] = function(arg1, arg2, arg3) { - var value; - for(var i=0; i < this.length; i++) { - if (isUndefined(value)) { - value = fn(this[i], arg1, arg2, arg3); - if (isDefined(value)) { - // any function which returns a value needs to be wrapped - value = jqLite(value); - } - } else { - jqLiteAddNodes(value, fn(this[i], arg1, arg2, arg3)); - } - } - return isDefined(value) ? value : this; - }; - - // bind legacy bind/unbind to on/off - JQLite.prototype.bind = JQLite.prototype.on; - JQLite.prototype.unbind = JQLite.prototype.off; -}); - -/** - * Computes a hash of an 'obj'. - * Hash of a: - * string is string - * number is number as string - * object is either result of calling $$hashKey function on the object or uniquely generated id, - * that is also assigned to the $$hashKey property of the object. - * - * @param obj - * @returns {string} hash string such that the same input will have the same hash string. - * The resulting string key is in 'type:hashKey' format. - */ -function hashKey(obj, nextUidFn) { - var objType = typeof obj, - key; - - if (objType == 'function' || (objType == 'object' && obj !== null)) { - if (typeof (key = obj.$$hashKey) == 'function') { - // must invoke on object to keep the right this - key = obj.$$hashKey(); - } else if (key === undefined) { - key = obj.$$hashKey = (nextUidFn || nextUid)(); - } - } else { - key = obj; - } - - return objType + ':' + key; -} - -/** - * HashMap which can use objects as keys - */ -function HashMap(array, isolatedUid) { - if (isolatedUid) { - var uid = 0; - this.nextUid = function() { - return ++uid; - }; - } - forEach(array, this.put, this); -} -HashMap.prototype = { - /** - * Store key value pair - * @param key key to store can be any type - * @param value value to store can be any type - */ - put: function(key, value) { - this[hashKey(key, this.nextUid)] = value; - }, - - /** - * @param key - * @returns {Object} the value for the key - */ - get: function(key) { - return this[hashKey(key, this.nextUid)]; - }, - - /** - * Remove the key/value pair - * @param key - */ - remove: function(key) { - var value = this[key = hashKey(key, this.nextUid)]; - delete this[key]; - return value; - } -}; - -/** - * @ngdoc function - * @module ng - * @name angular.injector - * @kind function - * - * @description - * Creates an injector function that can be used for retrieving services as well as for - * dependency injection (see {@link guide/di dependency injection}). - * - - * @param {Array.} modules A list of module functions or their aliases. See - * {@link angular.module}. The `ng` module must be explicitly added. - * @returns {function()} Injector function. See {@link auto.$injector $injector}. - * - * @example - * Typical usage - * ```js - * // create an injector - * var $injector = angular.injector(['ng']); - * - * // use the injector to kick off your application - * // use the type inference to auto inject arguments, or use implicit injection - * $injector.invoke(function($rootScope, $compile, $document){ - * $compile($document)($rootScope); - * $rootScope.$digest(); - * }); - * ``` - * - * Sometimes you want to get access to the injector of a currently running Angular app - * from outside Angular. Perhaps, you want to inject and compile some markup after the - * application has been bootstrapped. You can do this using the extra `injector()` added - * to JQuery/jqLite elements. See {@link angular.element}. - * - * *This is fairly rare but could be the case if a third party library is injecting the - * markup.* - * - * In the following example a new block of HTML containing a `ng-controller` - * directive is added to the end of the document body by JQuery. We then compile and link - * it into the current AngularJS scope. - * - * ```js - * var $div = $('
{{content.label}}
'); - * $(document.body).append($div); - * - * angular.element(document).injector().invoke(function($compile) { - * var scope = angular.element($div).scope(); - * $compile($div)(scope); - * }); - * ``` - */ - - -/** - * @ngdoc module - * @name auto - * @description - * - * Implicit module which gets automatically added to each {@link auto.$injector $injector}. - */ - -var FN_ARGS = /^function\s*[^\(]*\(\s*([^\)]*)\)/m; -var FN_ARG_SPLIT = /,/; -var FN_ARG = /^\s*(_?)(\S+?)\1\s*$/; -var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg; -var $injectorMinErr = minErr('$injector'); - -function anonFn(fn) { - // For anonymous functions, showing at the very least the function signature can help in - // debugging. - var fnText = fn.toString().replace(STRIP_COMMENTS, ''), - args = fnText.match(FN_ARGS); - if (args) { - return 'function(' + (args[1] || '').replace(/[\s\r\n]+/, ' ') + ')'; - } - return 'fn'; -} - -function annotate(fn, strictDi, name) { - var $inject, - fnText, - argDecl, - last; - - if (typeof fn == 'function') { - if (!($inject = fn.$inject)) { - $inject = []; - if (fn.length) { - if (strictDi) { - if (!isString(name) || !name) { - name = fn.name || anonFn(fn); - } - throw $injectorMinErr('strictdi', - '{0} is not using explicit annotation and cannot be invoked in strict mode', name); - } - fnText = fn.toString().replace(STRIP_COMMENTS, ''); - argDecl = fnText.match(FN_ARGS); - forEach(argDecl[1].split(FN_ARG_SPLIT), function(arg){ - arg.replace(FN_ARG, function(all, underscore, name){ - $inject.push(name); - }); - }); - } - fn.$inject = $inject; - } - } else if (isArray(fn)) { - last = fn.length - 1; - assertArgFn(fn[last], 'fn'); - $inject = fn.slice(0, last); - } else { - assertArgFn(fn, 'fn', true); - } - return $inject; -} - -/////////////////////////////////////// - -/** - * @ngdoc service - * @name $injector - * @kind function - * - * @description - * - * `$injector` is used to retrieve object instances as defined by - * {@link auto.$provide provider}, instantiate types, invoke methods, - * and load modules. - * - * The following always holds true: - * - * ```js - * var $injector = angular.injector(); - * expect($injector.get('$injector')).toBe($injector); - * expect($injector.invoke(function($injector){ - * return $injector; - * }).toBe($injector); - * ``` - * - * # Injection Function Annotation - * - * JavaScript does not have annotations, and annotations are needed for dependency injection. The - * following are all valid ways of annotating function with injection arguments and are equivalent. - * - * ```js - * // inferred (only works if code not minified/obfuscated) - * $injector.invoke(function(serviceA){}); - * - * // annotated - * function explicit(serviceA) {}; - * explicit.$inject = ['serviceA']; - * $injector.invoke(explicit); - * - * // inline - * $injector.invoke(['serviceA', function(serviceA){}]); - * ``` - * - * ## Inference - * - * In JavaScript calling `toString()` on a function returns the function definition. The definition - * can then be parsed and the function arguments can be extracted. *NOTE:* This does not work with - * minification, and obfuscation tools since these tools change the argument names. - * - * ## `$inject` Annotation - * By adding an `$inject` property onto a function the injection parameters can be specified. - * - * ## Inline - * As an array of injection names, where the last item in the array is the function to call. - */ - -/** - * @ngdoc method - * @name $injector#get - * - * @description - * Return an instance of the service. - * - * @param {string} name The name of the instance to retrieve. - * @return {*} The instance. - */ - -/** - * @ngdoc method - * @name $injector#invoke - * - * @description - * Invoke the method and supply the method arguments from the `$injector`. - * - * @param {!Function} fn The function to invoke. Function parameters are injected according to the - * {@link guide/di $inject Annotation} rules. - * @param {Object=} self The `this` for the invoked method. - * @param {Object=} locals Optional object. If preset then any argument names are read from this - * object first, before the `$injector` is consulted. - * @returns {*} the value returned by the invoked `fn` function. - */ - -/** - * @ngdoc method - * @name $injector#has - * - * @description - * Allows the user to query if the particular service exists. - * - * @param {string} Name of the service to query. - * @returns {boolean} returns true if injector has given service. - */ - -/** - * @ngdoc method - * @name $injector#instantiate - * @description - * Create a new instance of JS type. The method takes a constructor function, invokes the new - * operator, and supplies all of the arguments to the constructor function as specified by the - * constructor annotation. - * - * @param {Function} Type Annotated constructor function. - * @param {Object=} locals Optional object. If preset then any argument names are read from this - * object first, before the `$injector` is consulted. - * @returns {Object} new instance of `Type`. - */ - -/** - * @ngdoc method - * @name $injector#annotate - * - * @description - * Returns an array of service names which the function is requesting for injection. This API is - * used by the injector to determine which services need to be injected into the function when the - * function is invoked. There are three ways in which the function can be annotated with the needed - * dependencies. - * - * # Argument names - * - * The simplest form is to extract the dependencies from the arguments of the function. This is done - * by converting the function into a string using `toString()` method and extracting the argument - * names. - * ```js - * // Given - * function MyController($scope, $route) { - * // ... - * } - * - * // Then - * expect(injector.annotate(MyController)).toEqual(['$scope', '$route']); - * ``` - * - * This method does not work with code minification / obfuscation. For this reason the following - * annotation strategies are supported. - * - * # The `$inject` property - * - * If a function has an `$inject` property and its value is an array of strings, then the strings - * represent names of services to be injected into the function. - * ```js - * // Given - * var MyController = function(obfuscatedScope, obfuscatedRoute) { - * // ... - * } - * // Define function dependencies - * MyController['$inject'] = ['$scope', '$route']; - * - * // Then - * expect(injector.annotate(MyController)).toEqual(['$scope', '$route']); - * ``` - * - * # The array notation - * - * It is often desirable to inline Injected functions and that's when setting the `$inject` property - * is very inconvenient. In these situations using the array notation to specify the dependencies in - * a way that survives minification is a better choice: - * - * ```js - * // We wish to write this (not minification / obfuscation safe) - * injector.invoke(function($compile, $rootScope) { - * // ... - * }); - * - * // We are forced to write break inlining - * var tmpFn = function(obfuscatedCompile, obfuscatedRootScope) { - * // ... - * }; - * tmpFn.$inject = ['$compile', '$rootScope']; - * injector.invoke(tmpFn); - * - * // To better support inline function the inline annotation is supported - * injector.invoke(['$compile', '$rootScope', function(obfCompile, obfRootScope) { - * // ... - * }]); - * - * // Therefore - * expect(injector.annotate( - * ['$compile', '$rootScope', function(obfus_$compile, obfus_$rootScope) {}]) - * ).toEqual(['$compile', '$rootScope']); - * ``` - * - * @param {Function|Array.} fn Function for which dependent service names need to - * be retrieved as described above. - * - * @returns {Array.} The names of the services which the function requires. - */ - - - - -/** - * @ngdoc service - * @name $provide - * - * @description - * - * The {@link auto.$provide $provide} service has a number of methods for registering components - * with the {@link auto.$injector $injector}. Many of these functions are also exposed on - * {@link angular.Module}. - * - * An Angular **service** is a singleton object created by a **service factory**. These **service - * factories** are functions which, in turn, are created by a **service provider**. - * The **service providers** are constructor functions. When instantiated they must contain a - * property called `$get`, which holds the **service factory** function. - * - * When you request a service, the {@link auto.$injector $injector} is responsible for finding the - * correct **service provider**, instantiating it and then calling its `$get` **service factory** - * function to get the instance of the **service**. - * - * Often services have no configuration options and there is no need to add methods to the service - * provider. The provider will be no more than a constructor function with a `$get` property. For - * these cases the {@link auto.$provide $provide} service has additional helper methods to register - * services without specifying a provider. - * - * * {@link auto.$provide#provider provider(provider)} - registers a **service provider** with the - * {@link auto.$injector $injector} - * * {@link auto.$provide#constant constant(obj)} - registers a value/object that can be accessed by - * providers and services. - * * {@link auto.$provide#value value(obj)} - registers a value/object that can only be accessed by - * services, not providers. - * * {@link auto.$provide#factory factory(fn)} - registers a service **factory function**, `fn`, - * that will be wrapped in a **service provider** object, whose `$get` property will contain the - * given factory function. - * * {@link auto.$provide#service service(class)} - registers a **constructor function**, `class` - * that will be wrapped in a **service provider** object, whose `$get` property will instantiate - * a new object using the given constructor function. - * - * See the individual methods for more information and examples. - */ - -/** - * @ngdoc method - * @name $provide#provider - * @description - * - * Register a **provider function** with the {@link auto.$injector $injector}. Provider functions - * are constructor functions, whose instances are responsible for "providing" a factory for a - * service. - * - * Service provider names start with the name of the service they provide followed by `Provider`. - * For example, the {@link ng.$log $log} service has a provider called - * {@link ng.$logProvider $logProvider}. - * - * Service provider objects can have additional methods which allow configuration of the provider - * and its service. Importantly, you can configure what kind of service is created by the `$get` - * method, or how that service will act. For example, the {@link ng.$logProvider $logProvider} has a - * method {@link ng.$logProvider#debugEnabled debugEnabled} - * which lets you specify whether the {@link ng.$log $log} service will log debug messages to the - * console or not. - * - * @param {string} name The name of the instance. NOTE: the provider will be available under `name + - 'Provider'` key. - * @param {(Object|function())} provider If the provider is: - * - * - `Object`: then it should have a `$get` method. The `$get` method will be invoked using - * {@link auto.$injector#invoke $injector.invoke()} when an instance needs to be created. - * - `Constructor`: a new instance of the provider will be created using - * {@link auto.$injector#instantiate $injector.instantiate()}, then treated as `object`. - * - * @returns {Object} registered provider instance - - * @example - * - * The following example shows how to create a simple event tracking service and register it using - * {@link auto.$provide#provider $provide.provider()}. - * - * ```js - * // Define the eventTracker provider - * function EventTrackerProvider() { - * var trackingUrl = '/track'; - * - * // A provider method for configuring where the tracked events should been saved - * this.setTrackingUrl = function(url) { - * trackingUrl = url; - * }; - * - * // The service factory function - * this.$get = ['$http', function($http) { - * var trackedEvents = {}; - * return { - * // Call this to track an event - * event: function(event) { - * var count = trackedEvents[event] || 0; - * count += 1; - * trackedEvents[event] = count; - * return count; - * }, - * // Call this to save the tracked events to the trackingUrl - * save: function() { - * $http.post(trackingUrl, trackedEvents); - * } - * }; - * }]; - * } - * - * describe('eventTracker', function() { - * var postSpy; - * - * beforeEach(module(function($provide) { - * // Register the eventTracker provider - * $provide.provider('eventTracker', EventTrackerProvider); - * })); - * - * beforeEach(module(function(eventTrackerProvider) { - * // Configure eventTracker provider - * eventTrackerProvider.setTrackingUrl('/custom-track'); - * })); - * - * it('tracks events', inject(function(eventTracker) { - * expect(eventTracker.event('login')).toEqual(1); - * expect(eventTracker.event('login')).toEqual(2); - * })); - * - * it('saves to the tracking url', inject(function(eventTracker, $http) { - * postSpy = spyOn($http, 'post'); - * eventTracker.event('login'); - * eventTracker.save(); - * expect(postSpy).toHaveBeenCalled(); - * expect(postSpy.mostRecentCall.args[0]).not.toEqual('/track'); - * expect(postSpy.mostRecentCall.args[0]).toEqual('/custom-track'); - * expect(postSpy.mostRecentCall.args[1]).toEqual({ 'login': 1 }); - * })); - * }); - * ``` - */ - -/** - * @ngdoc method - * @name $provide#factory - * @description - * - * Register a **service factory**, which will be called to return the service instance. - * This is short for registering a service where its provider consists of only a `$get` property, - * which is the given service factory function. - * You should use {@link auto.$provide#factory $provide.factory(getFn)} if you do not need to - * configure your service in a provider. - * - * @param {string} name The name of the instance. - * @param {function()} $getFn The $getFn for the instance creation. Internally this is a short hand - * for `$provide.provider(name, {$get: $getFn})`. - * @returns {Object} registered provider instance - * - * @example - * Here is an example of registering a service - * ```js - * $provide.factory('ping', ['$http', function($http) { - * return function ping() { - * return $http.send('/ping'); - * }; - * }]); - * ``` - * You would then inject and use this service like this: - * ```js - * someModule.controller('Ctrl', ['ping', function(ping) { - * ping(); - * }]); - * ``` - */ - - -/** - * @ngdoc method - * @name $provide#service - * @description - * - * Register a **service constructor**, which will be invoked with `new` to create the service - * instance. - * This is short for registering a service where its provider's `$get` property is the service - * constructor function that will be used to instantiate the service instance. - * - * You should use {@link auto.$provide#service $provide.service(class)} if you define your service - * as a type/class. - * - * @param {string} name The name of the instance. - * @param {Function} constructor A class (constructor function) that will be instantiated. - * @returns {Object} registered provider instance - * - * @example - * Here is an example of registering a service using - * {@link auto.$provide#service $provide.service(class)}. - * ```js - * var Ping = function($http) { - * this.$http = $http; - * }; - * - * Ping.$inject = ['$http']; - * - * Ping.prototype.send = function() { - * return this.$http.get('/ping'); - * }; - * $provide.service('ping', Ping); - * ``` - * You would then inject and use this service like this: - * ```js - * someModule.controller('Ctrl', ['ping', function(ping) { - * ping.send(); - * }]); - * ``` - */ - - -/** - * @ngdoc method - * @name $provide#value - * @description - * - * Register a **value service** with the {@link auto.$injector $injector}, such as a string, a - * number, an array, an object or a function. This is short for registering a service where its - * provider's `$get` property is a factory function that takes no arguments and returns the **value - * service**. - * - * Value services are similar to constant services, except that they cannot be injected into a - * module configuration function (see {@link angular.Module#config}) but they can be overridden by - * an Angular - * {@link auto.$provide#decorator decorator}. - * - * @param {string} name The name of the instance. - * @param {*} value The value. - * @returns {Object} registered provider instance - * - * @example - * Here are some examples of creating value services. - * ```js - * $provide.value('ADMIN_USER', 'admin'); - * - * $provide.value('RoleLookup', { admin: 0, writer: 1, reader: 2 }); - * - * $provide.value('halfOf', function(value) { - * return value / 2; - * }); - * ``` - */ - - -/** - * @ngdoc method - * @name $provide#constant - * @description - * - * Register a **constant service**, such as a string, a number, an array, an object or a function, - * with the {@link auto.$injector $injector}. Unlike {@link auto.$provide#value value} it can be - * injected into a module configuration function (see {@link angular.Module#config}) and it cannot - * be overridden by an Angular {@link auto.$provide#decorator decorator}. - * - * @param {string} name The name of the constant. - * @param {*} value The constant value. - * @returns {Object} registered instance - * - * @example - * Here a some examples of creating constants: - * ```js - * $provide.constant('SHARD_HEIGHT', 306); - * - * $provide.constant('MY_COLOURS', ['red', 'blue', 'grey']); - * - * $provide.constant('double', function(value) { - * return value * 2; - * }); - * ``` - */ - - -/** - * @ngdoc method - * @name $provide#decorator - * @description - * - * Register a **service decorator** with the {@link auto.$injector $injector}. A service decorator - * intercepts the creation of a service, allowing it to override or modify the behaviour of the - * service. The object returned by the decorator may be the original service, or a new service - * object which replaces or wraps and delegates to the original service. - * - * @param {string} name The name of the service to decorate. - * @param {function()} decorator This function will be invoked when the service needs to be - * instantiated and should return the decorated service instance. The function is called using - * the {@link auto.$injector#invoke injector.invoke} method and is therefore fully injectable. - * Local injection arguments: - * - * * `$delegate` - The original service instance, which can be monkey patched, configured, - * decorated or delegated to. - * - * @example - * Here we decorate the {@link ng.$log $log} service to convert warnings to errors by intercepting - * calls to {@link ng.$log#error $log.warn()}. - * ```js - * $provide.decorator('$log', ['$delegate', function($delegate) { - * $delegate.warn = $delegate.error; - * return $delegate; - * }]); - * ``` - */ - - -function createInjector(modulesToLoad, strictDi) { - strictDi = (strictDi === true); - var INSTANTIATING = {}, - providerSuffix = 'Provider', - path = [], - loadedModules = new HashMap([], true), - providerCache = { - $provide: { - provider: supportObject(provider), - factory: supportObject(factory), - service: supportObject(service), - value: supportObject(value), - constant: supportObject(constant), - decorator: decorator - } - }, - providerInjector = (providerCache.$injector = - createInternalInjector(providerCache, function() { - throw $injectorMinErr('unpr', "Unknown provider: {0}", path.join(' <- ')); - }, strictDi)), - instanceCache = {}, - instanceInjector = (instanceCache.$injector = - createInternalInjector(instanceCache, function(servicename) { - var provider = providerInjector.get(servicename + providerSuffix); - return instanceInjector.invoke(provider.$get, provider, undefined, servicename); - }, strictDi)); - - - forEach(loadModules(modulesToLoad), function(fn) { instanceInjector.invoke(fn || noop); }); - - return instanceInjector; - - //////////////////////////////////// - // $provider - //////////////////////////////////// - - function supportObject(delegate) { - return function(key, value) { - if (isObject(key)) { - forEach(key, reverseParams(delegate)); - } else { - return delegate(key, value); - } - }; - } - - function provider(name, provider_) { - assertNotHasOwnProperty(name, 'service'); - if (isFunction(provider_) || isArray(provider_)) { - provider_ = providerInjector.instantiate(provider_); - } - if (!provider_.$get) { - throw $injectorMinErr('pget', "Provider '{0}' must define $get factory method.", name); - } - return providerCache[name + providerSuffix] = provider_; - } - - function factory(name, factoryFn) { return provider(name, { $get: factoryFn }); } - - function service(name, constructor) { - return factory(name, ['$injector', function($injector) { - return $injector.instantiate(constructor); - }]); - } - - function value(name, val) { return factory(name, valueFn(val)); } - - function constant(name, value) { - assertNotHasOwnProperty(name, 'constant'); - providerCache[name] = value; - instanceCache[name] = value; - } - - function decorator(serviceName, decorFn) { - var origProvider = providerInjector.get(serviceName + providerSuffix), - orig$get = origProvider.$get; - - origProvider.$get = function() { - var origInstance = instanceInjector.invoke(orig$get, origProvider); - return instanceInjector.invoke(decorFn, null, {$delegate: origInstance}); - }; - } - - //////////////////////////////////// - // Module Loading - //////////////////////////////////// - function loadModules(modulesToLoad){ - var runBlocks = [], moduleFn, invokeQueue; - forEach(modulesToLoad, function(module) { - if (loadedModules.get(module)) return; - loadedModules.put(module, true); - - function runInvokeQueue(queue) { - var i, ii; - for(i = 0, ii = queue.length; i < ii; i++) { - var invokeArgs = queue[i], - provider = providerInjector.get(invokeArgs[0]); - - provider[invokeArgs[1]].apply(provider, invokeArgs[2]); - } - } - - try { - if (isString(module)) { - moduleFn = angularModule(module); - runBlocks = runBlocks.concat(loadModules(moduleFn.requires)).concat(moduleFn._runBlocks); - runInvokeQueue(moduleFn._invokeQueue); - runInvokeQueue(moduleFn._configBlocks); - } else if (isFunction(module)) { - runBlocks.push(providerInjector.invoke(module)); - } else if (isArray(module)) { - runBlocks.push(providerInjector.invoke(module)); - } else { - assertArgFn(module, 'module'); - } - } catch (e) { - if (isArray(module)) { - module = module[module.length - 1]; - } - if (e.message && e.stack && e.stack.indexOf(e.message) == -1) { - // Safari & FF's stack traces don't contain error.message content - // unlike those of Chrome and IE - // So if stack doesn't contain message, we create a new string that contains both. - // Since error.stack is read-only in Safari, I'm overriding e and not e.stack here. - /* jshint -W022 */ - e = e.message + '\n' + e.stack; - } - throw $injectorMinErr('modulerr', "Failed to instantiate module {0} due to:\n{1}", - module, e.stack || e.message || e); - } - }); - return runBlocks; - } - - //////////////////////////////////// - // internal Injector - //////////////////////////////////// - - function createInternalInjector(cache, factory) { - - function getService(serviceName) { - if (cache.hasOwnProperty(serviceName)) { - if (cache[serviceName] === INSTANTIATING) { - throw $injectorMinErr('cdep', 'Circular dependency found: {0}', - serviceName + ' <- ' + path.join(' <- ')); - } - return cache[serviceName]; - } else { - try { - path.unshift(serviceName); - cache[serviceName] = INSTANTIATING; - return cache[serviceName] = factory(serviceName); - } catch (err) { - if (cache[serviceName] === INSTANTIATING) { - delete cache[serviceName]; - } - throw err; - } finally { - path.shift(); - } - } - } - - function invoke(fn, self, locals, serviceName){ - if (typeof locals === 'string') { - serviceName = locals; - locals = null; - } - - var args = [], - $inject = annotate(fn, strictDi, serviceName), - length, i, - key; - - for(i = 0, length = $inject.length; i < length; i++) { - key = $inject[i]; - if (typeof key !== 'string') { - throw $injectorMinErr('itkn', - 'Incorrect injection token! Expected service name as string, got {0}', key); - } - args.push( - locals && locals.hasOwnProperty(key) - ? locals[key] - : getService(key) - ); - } - if (!fn.$inject) { - // this means that we must be an array. - fn = fn[length]; - } - - // http://jsperf.com/angularjs-invoke-apply-vs-switch - // #5388 - return fn.apply(self, args); - } - - function instantiate(Type, locals, serviceName) { - var Constructor = function() {}, - instance, returnedValue; - - // Check if Type is annotated and use just the given function at n-1 as parameter - // e.g. someModule.factory('greeter', ['$window', function(renamed$window) {}]); - Constructor.prototype = (isArray(Type) ? Type[Type.length - 1] : Type).prototype; - instance = new Constructor(); - returnedValue = invoke(Type, instance, locals, serviceName); - - return isObject(returnedValue) || isFunction(returnedValue) ? returnedValue : instance; - } - - return { - invoke: invoke, - instantiate: instantiate, - get: getService, - annotate: annotate, - has: function(name) { - return providerCache.hasOwnProperty(name + providerSuffix) || cache.hasOwnProperty(name); - } - }; - } -} - -createInjector.$$annotate = annotate; - -/** - * @ngdoc service - * @name $anchorScroll - * @kind function - * @requires $window - * @requires $location - * @requires $rootScope - * - * @description - * When called, it checks current value of `$location.hash()` and scrolls to the related element, - * according to rules specified in - * [Html5 spec](http://dev.w3.org/html5/spec/Overview.html#the-indicated-part-of-the-document). - * - * It also watches the `$location.hash()` and scrolls whenever it changes to match any anchor. - * This can be disabled by calling `$anchorScrollProvider.disableAutoScrolling()`. - * - * @example - - -
- Go to bottom - You're at the bottom! -
-
- - function ScrollCtrl($scope, $location, $anchorScroll) { - $scope.gotoBottom = function (){ - // set the location.hash to the id of - // the element you wish to scroll to. - $location.hash('bottom'); - - // call $anchorScroll() - $anchorScroll(); - }; - } - - - #scrollArea { - height: 350px; - overflow: auto; - } - - #bottom { - display: block; - margin-top: 2000px; - } - -
- */ -function $AnchorScrollProvider() { - - var autoScrollingEnabled = true; - - this.disableAutoScrolling = function() { - autoScrollingEnabled = false; - }; - - this.$get = ['$window', '$location', '$rootScope', function($window, $location, $rootScope) { - var document = $window.document; - - // helper function to get first anchor from a NodeList - // can't use filter.filter, as it accepts only instances of Array - // and IE can't convert NodeList to an array using [].slice - // TODO(vojta): use filter if we change it to accept lists as well - function getFirstAnchor(list) { - var result = null; - forEach(list, function(element) { - if (!result && nodeName_(element) === 'a') result = element; - }); - return result; - } - - function scroll() { - var hash = $location.hash(), elm; - - // empty hash, scroll to the top of the page - if (!hash) $window.scrollTo(0, 0); - - // element with given id - else if ((elm = document.getElementById(hash))) elm.scrollIntoView(); - - // first anchor with given name :-D - else if ((elm = getFirstAnchor(document.getElementsByName(hash)))) elm.scrollIntoView(); - - // no element and hash == 'top', scroll to the top of the page - else if (hash === 'top') $window.scrollTo(0, 0); - } - - // does not scroll when user clicks on anchor link that is currently on - // (no url change, no $location.hash() change), browser native does scroll - if (autoScrollingEnabled) { - $rootScope.$watch(function autoScrollWatch() {return $location.hash();}, - function autoScrollWatchAction() { - $rootScope.$evalAsync(scroll); - }); - } - - return scroll; - }]; -} - -var $animateMinErr = minErr('$animate'); - -/** - * @ngdoc provider - * @name $animateProvider - * - * @description - * Default implementation of $animate that doesn't perform any animations, instead just - * synchronously performs DOM - * updates and calls done() callbacks. - * - * In order to enable animations the ngAnimate module has to be loaded. - * - * To see the functional implementation check out src/ngAnimate/animate.js - */ -var $AnimateProvider = ['$provide', function($provide) { - - - this.$$selectors = {}; - - - /** - * @ngdoc method - * @name $animateProvider#register - * - * @description - * Registers a new injectable animation factory function. The factory function produces the - * animation object which contains callback functions for each event that is expected to be - * animated. - * - * * `eventFn`: `function(Element, doneFunction)` The element to animate, the `doneFunction` - * must be called once the element animation is complete. If a function is returned then the - * animation service will use this function to cancel the animation whenever a cancel event is - * triggered. - * - * - * ```js - * return { - * eventFn : function(element, done) { - * //code to run the animation - * //once complete, then run done() - * return function cancellationFunction() { - * //code to cancel the animation - * } - * } - * } - * ``` - * - * @param {string} name The name of the animation. - * @param {Function} factory The factory function that will be executed to return the animation - * object. - */ - this.register = function(name, factory) { - var key = name + '-animation'; - if (name && name.charAt(0) != '.') throw $animateMinErr('notcsel', - "Expecting class selector starting with '.' got '{0}'.", name); - this.$$selectors[name.substr(1)] = key; - $provide.factory(key, factory); - }; - - /** - * @ngdoc method - * @name $animateProvider#classNameFilter - * - * @description - * Sets and/or returns the CSS class regular expression that is checked when performing - * an animation. Upon bootstrap the classNameFilter value is not set at all and will - * therefore enable $animate to attempt to perform an animation on any element. - * When setting the classNameFilter value, animations will only be performed on elements - * that successfully match the filter expression. This in turn can boost performance - * for low-powered devices as well as applications containing a lot of structural operations. - * @param {RegExp=} expression The className expression which will be checked against all animations - * @return {RegExp} The current CSS className expression value. If null then there is no expression value - */ - this.classNameFilter = function(expression) { - if(arguments.length === 1) { - this.$$classNameFilter = (expression instanceof RegExp) ? expression : null; - } - return this.$$classNameFilter; - }; - - this.$get = ['$timeout', '$$asyncCallback', function($timeout, $$asyncCallback) { - - function async(fn) { - fn && $$asyncCallback(fn); - } - - /** - * - * @ngdoc service - * @name $animate - * @description The $animate service provides rudimentary DOM manipulation functions to - * insert, remove and move elements within the DOM, as well as adding and removing classes. - * This service is the core service used by the ngAnimate $animator service which provides - * high-level animation hooks for CSS and JavaScript. - * - * $animate is available in the AngularJS core, however, the ngAnimate module must be included - * to enable full out animation support. Otherwise, $animate will only perform simple DOM - * manipulation operations. - * - * To learn more about enabling animation support, click here to visit the {@link ngAnimate - * ngAnimate module page} as well as the {@link ngAnimate.$animate ngAnimate $animate service - * page}. - */ - return { - - /** - * - * @ngdoc method - * @name $animate#enter - * @kind function - * @description Inserts the element into the DOM either after the `after` element or - * as the first child within the `parent` element. Once complete, the done() callback - * will be fired (if provided). - * @param {DOMElement} element the element which will be inserted into the DOM - * @param {DOMElement} parent the parent element which will append the element as - * a child (if the after element is not present) - * @param {DOMElement} after the sibling element which will append the element - * after itself - * @param {Function=} done callback function that will be called after the element has been - * inserted into the DOM - */ - enter : function(element, parent, after, done) { - after - ? after.after(element) - : parent.prepend(element); - async(done); - }, - - /** - * - * @ngdoc method - * @name $animate#leave - * @kind function - * @description Removes the element from the DOM. Once complete, the done() callback will be - * fired (if provided). - * @param {DOMElement} element the element which will be removed from the DOM - * @param {Function=} done callback function that will be called after the element has been - * removed from the DOM - */ - leave : function(element, done) { - element.remove(); - async(done); - }, - - /** - * - * @ngdoc method - * @name $animate#move - * @kind function - * @description Moves the position of the provided element within the DOM to be placed - * either after the `after` element or inside of the `parent` element. Once complete, the - * done() callback will be fired (if provided). - * - * @param {DOMElement} element the element which will be moved around within the - * DOM - * @param {DOMElement} parent the parent element where the element will be - * inserted into (if the after element is not present) - * @param {DOMElement} after the sibling element where the element will be - * positioned next to - * @param {Function=} done the callback function (if provided) that will be fired after the - * element has been moved to its new position - */ - move : function(element, parent, after, done) { - // Do not remove element before insert. Removing will cause data associated with the - // element to be dropped. Insert will implicitly do the remove. - this.enter(element, parent, after, done); - }, - - /** - * - * @ngdoc method - * @name $animate#addClass - * @kind function - * @description Adds the provided className CSS class value to the provided element. Once - * complete, the done() callback will be fired (if provided). - * @param {DOMElement} element the element which will have the className value - * added to it - * @param {string} className the CSS class which will be added to the element - * @param {Function=} done the callback function (if provided) that will be fired after the - * className value has been added to the element - */ - addClass : function(element, className, done) { - className = isString(className) ? - className : - isArray(className) ? className.join(' ') : ''; - forEach(element, function (element) { - jqLiteAddClass(element, className); - }); - async(done); - }, - - /** - * - * @ngdoc method - * @name $animate#removeClass - * @kind function - * @description Removes the provided className CSS class value from the provided element. - * Once complete, the done() callback will be fired (if provided). - * @param {DOMElement} element the element which will have the className value - * removed from it - * @param {string} className the CSS class which will be removed from the element - * @param {Function=} done the callback function (if provided) that will be fired after the - * className value has been removed from the element - */ - removeClass : function(element, className, done) { - className = isString(className) ? - className : - isArray(className) ? className.join(' ') : ''; - forEach(element, function (element) { - jqLiteRemoveClass(element, className); - }); - async(done); - }, - - /** - * - * @ngdoc method - * @name $animate#setClass - * @kind function - * @description Adds and/or removes the given CSS classes to and from the element. - * Once complete, the done() callback will be fired (if provided). - * @param {DOMElement} element the element which will have its CSS classes changed - * removed from it - * @param {string} add the CSS classes which will be added to the element - * @param {string} remove the CSS class which will be removed from the element - * @param {Function=} done the callback function (if provided) that will be fired after the - * CSS classes have been set on the element - */ - setClass : function(element, add, remove, done) { - forEach(element, function (element) { - jqLiteAddClass(element, add); - jqLiteRemoveClass(element, remove); - }); - async(done); - }, - - enabled : noop - }; - }]; -}]; - -function $$AsyncCallbackProvider(){ - this.$get = ['$$rAF', '$timeout', function($$rAF, $timeout) { - return $$rAF.supported - ? function(fn) { return $$rAF(fn); } - : function(fn) { - return $timeout(fn, 0, false); - }; - }]; -} - -/** - * ! This is a private undocumented service ! - * - * @name $browser - * @requires $log - * @description - * This object has two goals: - * - * - hide all the global state in the browser caused by the window object - * - abstract away all the browser specific features and inconsistencies - * - * For tests we provide {@link ngMock.$browser mock implementation} of the `$browser` - * service, which can be used for convenient testing of the application without the interaction with - * the real browser apis. - */ -/** - * @param {object} window The global window object. - * @param {object} document jQuery wrapped document. - * @param {function()} XHR XMLHttpRequest constructor. - * @param {object} $log console.log or an object with the same interface. - * @param {object} $sniffer $sniffer service - */ -function Browser(window, document, $log, $sniffer) { - var self = this, - rawDocument = document[0], - location = window.location, - history = window.history, - setTimeout = window.setTimeout, - clearTimeout = window.clearTimeout, - pendingDeferIds = {}; - - self.isMock = false; - - var outstandingRequestCount = 0; - var outstandingRequestCallbacks = []; - - // TODO(vojta): remove this temporary api - self.$$completeOutstandingRequest = completeOutstandingRequest; - self.$$incOutstandingRequestCount = function() { outstandingRequestCount++; }; - - /** - * Executes the `fn` function(supports currying) and decrements the `outstandingRequestCallbacks` - * counter. If the counter reaches 0, all the `outstandingRequestCallbacks` are executed. - */ - function completeOutstandingRequest(fn) { - try { - fn.apply(null, sliceArgs(arguments, 1)); - } finally { - outstandingRequestCount--; - if (outstandingRequestCount === 0) { - while(outstandingRequestCallbacks.length) { - try { - outstandingRequestCallbacks.pop()(); - } catch (e) { - $log.error(e); - } - } - } - } - } - - /** - * @private - * Note: this method is used only by scenario runner - * TODO(vojta): prefix this method with $$ ? - * @param {function()} callback Function that will be called when no outstanding request - */ - self.notifyWhenNoOutstandingRequests = function(callback) { - // force browser to execute all pollFns - this is needed so that cookies and other pollers fire - // at some deterministic time in respect to the test runner's actions. Leaving things up to the - // regular poller would result in flaky tests. - forEach(pollFns, function(pollFn){ pollFn(); }); - - if (outstandingRequestCount === 0) { - callback(); - } else { - outstandingRequestCallbacks.push(callback); - } - }; - - ////////////////////////////////////////////////////////////// - // Poll Watcher API - ////////////////////////////////////////////////////////////// - var pollFns = [], - pollTimeout; - - /** - * @name $browser#addPollFn - * - * @param {function()} fn Poll function to add - * - * @description - * Adds a function to the list of functions that poller periodically executes, - * and starts polling if not started yet. - * - * @returns {function()} the added function - */ - self.addPollFn = function(fn) { - if (isUndefined(pollTimeout)) startPoller(100, setTimeout); - pollFns.push(fn); - return fn; - }; - - /** - * @param {number} interval How often should browser call poll functions (ms) - * @param {function()} setTimeout Reference to a real or fake `setTimeout` function. - * - * @description - * Configures the poller to run in the specified intervals, using the specified - * setTimeout fn and kicks it off. - */ - function startPoller(interval, setTimeout) { - (function check() { - forEach(pollFns, function(pollFn){ pollFn(); }); - pollTimeout = setTimeout(check, interval); - })(); - } - - ////////////////////////////////////////////////////////////// - // URL API - ////////////////////////////////////////////////////////////// - - var lastBrowserUrl = location.href, - baseElement = document.find('base'), - newLocation = null; - - /** - * @name $browser#url - * - * @description - * GETTER: - * Without any argument, this method just returns current value of location.href. - * - * SETTER: - * With at least one argument, this method sets url to new value. - * If html5 history api supported, pushState/replaceState is used, otherwise - * location.href/location.replace is used. - * Returns its own instance to allow chaining - * - * NOTE: this api is intended for use only by the $location service. Please use the - * {@link ng.$location $location service} to change url. - * - * @param {string} url New url (when used as setter) - * @param {boolean=} replace Should new url replace current history record ? - */ - self.url = function(url, replace) { - // Android Browser BFCache causes location, history reference to become stale. - if (location !== window.location) location = window.location; - if (history !== window.history) history = window.history; - - // setter - if (url) { - if (lastBrowserUrl == url) return; - lastBrowserUrl = url; - if ($sniffer.history) { - if (replace) history.replaceState(null, '', url); - else { - history.pushState(null, '', url); - // Crazy Opera Bug: http://my.opera.com/community/forums/topic.dml?id=1185462 - baseElement.attr('href', baseElement.attr('href')); - } - } else { - newLocation = url; - if (replace) { - location.replace(url); - } else { - location.href = url; - } - } - return self; - // getter - } else { - // - newLocation is a workaround for an IE7-9 issue with location.replace and location.href - // methods not updating location.href synchronously. - // - the replacement is a workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=407172 - return newLocation || location.href.replace(/%27/g,"'"); - } - }; - - var urlChangeListeners = [], - urlChangeInit = false; - - function fireUrlChange() { - newLocation = null; - if (lastBrowserUrl == self.url()) return; - - lastBrowserUrl = self.url(); - forEach(urlChangeListeners, function(listener) { - listener(self.url()); - }); - } - - /** - * @name $browser#onUrlChange - * - * @description - * Register callback function that will be called, when url changes. - * - * It's only called when the url is changed from outside of angular: - * - user types different url into address bar - * - user clicks on history (forward/back) button - * - user clicks on a link - * - * It's not called when url is changed by $browser.url() method - * - * The listener gets called with new url as parameter. - * - * NOTE: this api is intended for use only by the $location service. Please use the - * {@link ng.$location $location service} to monitor url changes in angular apps. - * - * @param {function(string)} listener Listener function to be called when url changes. - * @return {function(string)} Returns the registered listener fn - handy if the fn is anonymous. - */ - self.onUrlChange = function(callback) { - // TODO(vojta): refactor to use node's syntax for events - if (!urlChangeInit) { - // We listen on both (hashchange/popstate) when available, as some browsers (e.g. Opera) - // don't fire popstate when user change the address bar and don't fire hashchange when url - // changed by push/replaceState - - // html5 history api - popstate event - if ($sniffer.history) jqLite(window).on('popstate', fireUrlChange); - // hashchange event - if ($sniffer.hashchange) jqLite(window).on('hashchange', fireUrlChange); - // polling - else self.addPollFn(fireUrlChange); - - urlChangeInit = true; - } - - urlChangeListeners.push(callback); - return callback; - }; - - ////////////////////////////////////////////////////////////// - // Misc API - ////////////////////////////////////////////////////////////// - - /** - * @name $browser#baseHref - * - * @description - * Returns current - * (always relative - without domain) - * - * @returns {string} The current base href - */ - self.baseHref = function() { - var href = baseElement.attr('href'); - return href ? href.replace(/^(https?\:)?\/\/[^\/]*/, '') : ''; - }; - - ////////////////////////////////////////////////////////////// - // Cookies API - ////////////////////////////////////////////////////////////// - var lastCookies = {}; - var lastCookieString = ''; - var cookiePath = self.baseHref(); - - /** - * @name $browser#cookies - * - * @param {string=} name Cookie name - * @param {string=} value Cookie value - * - * @description - * The cookies method provides a 'private' low level access to browser cookies. - * It is not meant to be used directly, use the $cookie service instead. - * - * The return values vary depending on the arguments that the method was called with as follows: - * - * - cookies() -> hash of all cookies, this is NOT a copy of the internal state, so do not modify - * it - * - cookies(name, value) -> set name to value, if value is undefined delete the cookie - * - cookies(name) -> the same as (name, undefined) == DELETES (no one calls it right now that - * way) - * - * @returns {Object} Hash of all cookies (if called without any parameter) - */ - self.cookies = function(name, value) { - /* global escape: false, unescape: false */ - var cookieLength, cookieArray, cookie, i, index; - - if (name) { - if (value === undefined) { - rawDocument.cookie = escape(name) + "=;path=" + cookiePath + - ";expires=Thu, 01 Jan 1970 00:00:00 GMT"; - } else { - if (isString(value)) { - cookieLength = (rawDocument.cookie = escape(name) + '=' + escape(value) + - ';path=' + cookiePath).length + 1; - - // per http://www.ietf.org/rfc/rfc2109.txt browser must allow at minimum: - // - 300 cookies - // - 20 cookies per unique domain - // - 4096 bytes per cookie - if (cookieLength > 4096) { - $log.warn("Cookie '"+ name + - "' possibly not set or overflowed because it was too large ("+ - cookieLength + " > 4096 bytes)!"); - } - } - } - } else { - if (rawDocument.cookie !== lastCookieString) { - lastCookieString = rawDocument.cookie; - cookieArray = lastCookieString.split("; "); - lastCookies = {}; - - for (i = 0; i < cookieArray.length; i++) { - cookie = cookieArray[i]; - index = cookie.indexOf('='); - if (index > 0) { //ignore nameless cookies - name = unescape(cookie.substring(0, index)); - // the first value that is seen for a cookie is the most - // specific one. values for the same cookie name that - // follow are for less specific paths. - if (lastCookies[name] === undefined) { - lastCookies[name] = unescape(cookie.substring(index + 1)); - } - } - } - } - return lastCookies; - } - }; - - - /** - * @name $browser#defer - * @param {function()} fn A function, who's execution should be deferred. - * @param {number=} [delay=0] of milliseconds to defer the function execution. - * @returns {*} DeferId that can be used to cancel the task via `$browser.defer.cancel()`. - * - * @description - * Executes a fn asynchronously via `setTimeout(fn, delay)`. - * - * Unlike when calling `setTimeout` directly, in test this function is mocked and instead of using - * `setTimeout` in tests, the fns are queued in an array, which can be programmatically flushed - * via `$browser.defer.flush()`. - * - */ - self.defer = function(fn, delay) { - var timeoutId; - outstandingRequestCount++; - timeoutId = setTimeout(function() { - delete pendingDeferIds[timeoutId]; - completeOutstandingRequest(fn); - }, delay || 0); - pendingDeferIds[timeoutId] = true; - return timeoutId; - }; - - - /** - * @name $browser#defer.cancel - * - * @description - * Cancels a deferred task identified with `deferId`. - * - * @param {*} deferId Token returned by the `$browser.defer` function. - * @returns {boolean} Returns `true` if the task hasn't executed yet and was successfully - * canceled. - */ - self.defer.cancel = function(deferId) { - if (pendingDeferIds[deferId]) { - delete pendingDeferIds[deferId]; - clearTimeout(deferId); - completeOutstandingRequest(noop); - return true; - } - return false; - }; - -} - -function $BrowserProvider(){ - this.$get = ['$window', '$log', '$sniffer', '$document', - function( $window, $log, $sniffer, $document){ - return new Browser($window, $document, $log, $sniffer); - }]; -} - -/** - * @ngdoc service - * @name $cacheFactory - * - * @description - * Factory that constructs {@link $cacheFactory.Cache Cache} objects and gives access to - * them. - * - * ```js - * - * var cache = $cacheFactory('cacheId'); - * expect($cacheFactory.get('cacheId')).toBe(cache); - * expect($cacheFactory.get('noSuchCacheId')).not.toBeDefined(); - * - * cache.put("key", "value"); - * cache.put("another key", "another value"); - * - * // We've specified no options on creation - * expect(cache.info()).toEqual({id: 'cacheId', size: 2}); - * - * ``` - * - * - * @param {string} cacheId Name or id of the newly created cache. - * @param {object=} options Options object that specifies the cache behavior. Properties: - * - * - `{number=}` `capacity` — turns the cache into LRU cache. - * - * @returns {object} Newly created cache object with the following set of methods: - * - * - `{object}` `info()` — Returns id, size, and options of cache. - * - `{{*}}` `put({string} key, {*} value)` — Puts a new key-value pair into the cache and returns - * it. - * - `{{*}}` `get({string} key)` — Returns cached value for `key` or undefined for cache miss. - * - `{void}` `remove({string} key)` — Removes a key-value pair from the cache. - * - `{void}` `removeAll()` — Removes all cached values. - * - `{void}` `destroy()` — Removes references to this cache from $cacheFactory. - * - * @example - - -
- - - - -

Cached Values

-
- - : - -
- -

Cache Info

-
- - : - -
-
-
- - angular.module('cacheExampleApp', []). - controller('CacheController', ['$scope', '$cacheFactory', function($scope, $cacheFactory) { - $scope.keys = []; - $scope.cache = $cacheFactory('cacheId'); - $scope.put = function(key, value) { - $scope.cache.put(key, value); - $scope.keys.push(key); - }; - }]); - - - p { - margin: 10px 0 3px; - } - -
- */ -function $CacheFactoryProvider() { - - this.$get = function() { - var caches = {}; - - function cacheFactory(cacheId, options) { - if (cacheId in caches) { - throw minErr('$cacheFactory')('iid', "CacheId '{0}' is already taken!", cacheId); - } - - var size = 0, - stats = extend({}, options, {id: cacheId}), - data = {}, - capacity = (options && options.capacity) || Number.MAX_VALUE, - lruHash = {}, - freshEnd = null, - staleEnd = null; - - /** - * @ngdoc type - * @name $cacheFactory.Cache - * - * @description - * A cache object used to store and retrieve data, primarily used by - * {@link $http $http} and the {@link ng.directive:script script} directive to cache - * templates and other data. - * - * ```js - * angular.module('superCache') - * .factory('superCache', ['$cacheFactory', function($cacheFactory) { - * return $cacheFactory('super-cache'); - * }]); - * ``` - * - * Example test: - * - * ```js - * it('should behave like a cache', inject(function(superCache) { - * superCache.put('key', 'value'); - * superCache.put('another key', 'another value'); - * - * expect(superCache.info()).toEqual({ - * id: 'super-cache', - * size: 2 - * }); - * - * superCache.remove('another key'); - * expect(superCache.get('another key')).toBeUndefined(); - * - * superCache.removeAll(); - * expect(superCache.info()).toEqual({ - * id: 'super-cache', - * size: 0 - * }); - * })); - * ``` - */ - return caches[cacheId] = { - - /** - * @ngdoc method - * @name $cacheFactory.Cache#put - * @kind function - * - * @description - * Inserts a named entry into the {@link $cacheFactory.Cache Cache} object to be - * retrieved later, and incrementing the size of the cache if the key was not already - * present in the cache. If behaving like an LRU cache, it will also remove stale - * entries from the set. - * - * It will not insert undefined values into the cache. - * - * @param {string} key the key under which the cached data is stored. - * @param {*} value the value to store alongside the key. If it is undefined, the key - * will not be stored. - * @returns {*} the value stored. - */ - put: function(key, value) { - if (capacity < Number.MAX_VALUE) { - var lruEntry = lruHash[key] || (lruHash[key] = {key: key}); - - refresh(lruEntry); - } - - if (isUndefined(value)) return; - if (!(key in data)) size++; - data[key] = value; - - if (size > capacity) { - this.remove(staleEnd.key); - } - - return value; - }, - - /** - * @ngdoc method - * @name $cacheFactory.Cache#get - * @kind function - * - * @description - * Retrieves named data stored in the {@link $cacheFactory.Cache Cache} object. - * - * @param {string} key the key of the data to be retrieved - * @returns {*} the value stored. - */ - get: function(key) { - if (capacity < Number.MAX_VALUE) { - var lruEntry = lruHash[key]; - - if (!lruEntry) return; - - refresh(lruEntry); - } - - return data[key]; - }, - - - /** - * @ngdoc method - * @name $cacheFactory.Cache#remove - * @kind function - * - * @description - * Removes an entry from the {@link $cacheFactory.Cache Cache} object. - * - * @param {string} key the key of the entry to be removed - */ - remove: function(key) { - if (capacity < Number.MAX_VALUE) { - var lruEntry = lruHash[key]; - - if (!lruEntry) return; - - if (lruEntry == freshEnd) freshEnd = lruEntry.p; - if (lruEntry == staleEnd) staleEnd = lruEntry.n; - link(lruEntry.n,lruEntry.p); - - delete lruHash[key]; - } - - delete data[key]; - size--; - }, - - - /** - * @ngdoc method - * @name $cacheFactory.Cache#removeAll - * @kind function - * - * @description - * Clears the cache object of any entries. - */ - removeAll: function() { - data = {}; - size = 0; - lruHash = {}; - freshEnd = staleEnd = null; - }, - - - /** - * @ngdoc method - * @name $cacheFactory.Cache#destroy - * @kind function - * - * @description - * Destroys the {@link $cacheFactory.Cache Cache} object entirely, - * removing it from the {@link $cacheFactory $cacheFactory} set. - */ - destroy: function() { - data = null; - stats = null; - lruHash = null; - delete caches[cacheId]; - }, - - - /** - * @ngdoc method - * @name $cacheFactory.Cache#info - * @kind function - * - * @description - * Retrieve information regarding a particular {@link $cacheFactory.Cache Cache}. - * - * @returns {object} an object with the following properties: - *
    - *
  • **id**: the id of the cache instance
  • - *
  • **size**: the number of entries kept in the cache instance
  • - *
  • **...**: any additional properties from the options object when creating the - * cache.
  • - *
- */ - info: function() { - return extend({}, stats, {size: size}); - } - }; - - - /** - * makes the `entry` the freshEnd of the LRU linked list - */ - function refresh(entry) { - if (entry != freshEnd) { - if (!staleEnd) { - staleEnd = entry; - } else if (staleEnd == entry) { - staleEnd = entry.n; - } - - link(entry.n, entry.p); - link(entry, freshEnd); - freshEnd = entry; - freshEnd.n = null; - } - } - - - /** - * bidirectionally links two entries of the LRU linked list - */ - function link(nextEntry, prevEntry) { - if (nextEntry != prevEntry) { - if (nextEntry) nextEntry.p = prevEntry; //p stands for previous, 'prev' didn't minify - if (prevEntry) prevEntry.n = nextEntry; //n stands for next, 'next' didn't minify - } - } - } - - - /** - * @ngdoc method - * @name $cacheFactory#info - * - * @description - * Get information about all the caches that have been created - * - * @returns {Object} - key-value map of `cacheId` to the result of calling `cache#info` - */ - cacheFactory.info = function() { - var info = {}; - forEach(caches, function(cache, cacheId) { - info[cacheId] = cache.info(); - }); - return info; - }; - - - /** - * @ngdoc method - * @name $cacheFactory#get - * - * @description - * Get access to a cache object by the `cacheId` used when it was created. - * - * @param {string} cacheId Name or id of a cache to access. - * @returns {object} Cache object identified by the cacheId or undefined if no such cache. - */ - cacheFactory.get = function(cacheId) { - return caches[cacheId]; - }; - - - return cacheFactory; - }; -} - -/** - * @ngdoc service - * @name $templateCache - * - * @description - * The first time a template is used, it is loaded in the template cache for quick retrieval. You - * can load templates directly into the cache in a `script` tag, or by consuming the - * `$templateCache` service directly. - * - * Adding via the `script` tag: - * - * ```html - * - * ``` - * - * **Note:** the `script` tag containing the template does not need to be included in the `head` of - * the document, but it must be below the `ng-app` definition. - * - * Adding via the $templateCache service: - * - * ```js - * var myApp = angular.module('myApp', []); - * myApp.run(function($templateCache) { - * $templateCache.put('templateId.html', 'This is the content of the template'); - * }); - * ``` - * - * To retrieve the template later, simply use it in your HTML: - * ```html - *
- * ``` - * - * or get it via Javascript: - * ```js - * $templateCache.get('templateId.html') - * ``` - * - * See {@link ng.$cacheFactory $cacheFactory}. - * - */ -function $TemplateCacheProvider() { - this.$get = ['$cacheFactory', function($cacheFactory) { - return $cacheFactory('templates'); - }]; -} - -/* ! VARIABLE/FUNCTION NAMING CONVENTIONS THAT APPLY TO THIS FILE! - * - * DOM-related variables: - * - * - "node" - DOM Node - * - "element" - DOM Element or Node - * - "$node" or "$element" - jqLite-wrapped node or element - * - * - * Compiler related stuff: - * - * - "linkFn" - linking fn of a single directive - * - "nodeLinkFn" - function that aggregates all linking fns for a particular node - * - "childLinkFn" - function that aggregates all linking fns for child nodes of a particular node - * - "compositeLinkFn" - function that aggregates all linking fns for a compilation root (nodeList) - */ - - -/** - * @ngdoc service - * @name $compile - * @kind function - * - * @description - * Compiles an HTML string or DOM into a template and produces a template function, which - * can then be used to link {@link ng.$rootScope.Scope `scope`} and the template together. - * - * The compilation is a process of walking the DOM tree and matching DOM elements to - * {@link ng.$compileProvider#directive directives}. - * - *
- * **Note:** This document is an in-depth reference of all directive options. - * For a gentle introduction to directives with examples of common use cases, - * see the {@link guide/directive directive guide}. - *
- * - * ## Comprehensive Directive API - * - * There are many different options for a directive. - * - * The difference resides in the return value of the factory function. - * You can either return a "Directive Definition Object" (see below) that defines the directive properties, - * or just the `postLink` function (all other properties will have the default values). - * - *
- * **Best Practice:** It's recommended to use the "directive definition object" form. - *
- * - * Here's an example directive declared with a Directive Definition Object: - * - * ```js - * var myModule = angular.module(...); - * - * myModule.directive('directiveName', function factory(injectables) { - * var directiveDefinitionObject = { - * priority: 0, - * template: '
', // or // function(tElement, tAttrs) { ... }, - * // or - * // templateUrl: 'directive.html', // or // function(tElement, tAttrs) { ... }, - * transclude: false, - * restrict: 'A', - * scope: false, - * controller: function($scope, $element, $attrs, $transclude, otherInjectables) { ... }, - * controllerAs: 'stringAlias', - * require: 'siblingDirectiveName', // or // ['^parentDirectiveName', '?optionalDirectiveName', '?^optionalParent'], - * compile: function compile(tElement, tAttrs, transclude) { - * return { - * pre: function preLink(scope, iElement, iAttrs, controller) { ... }, - * post: function postLink(scope, iElement, iAttrs, controller) { ... } - * } - * // or - * // return function postLink( ... ) { ... } - * }, - * // or - * // link: { - * // pre: function preLink(scope, iElement, iAttrs, controller) { ... }, - * // post: function postLink(scope, iElement, iAttrs, controller) { ... } - * // } - * // or - * // link: function postLink( ... ) { ... } - * }; - * return directiveDefinitionObject; - * }); - * ``` - * - *
- * **Note:** Any unspecified options will use the default value. You can see the default values below. - *
- * - * Therefore the above can be simplified as: - * - * ```js - * var myModule = angular.module(...); - * - * myModule.directive('directiveName', function factory(injectables) { - * var directiveDefinitionObject = { - * link: function postLink(scope, iElement, iAttrs) { ... } - * }; - * return directiveDefinitionObject; - * // or - * // return function postLink(scope, iElement, iAttrs) { ... } - * }); - * ``` - * - * - * - * ### Directive Definition Object - * - * The directive definition object provides instructions to the {@link ng.$compile - * compiler}. The attributes are: - * - * #### `priority` - * When there are multiple directives defined on a single DOM element, sometimes it - * is necessary to specify the order in which the directives are applied. The `priority` is used - * to sort the directives before their `compile` functions get called. Priority is defined as a - * number. Directives with greater numerical `priority` are compiled first. Pre-link functions - * are also run in priority order, but post-link functions are run in reverse order. The order - * of directives with the same priority is undefined. The default priority is `0`. - * - * #### `terminal` - * If set to true then the current `priority` will be the last set of directives - * which will execute (any directives at the current priority will still execute - * as the order of execution on same `priority` is undefined). - * - * #### `scope` - * **If set to `true`,** then a new scope will be created for this directive. If multiple directives on the - * same element request a new scope, only one new scope is created. The new scope rule does not - * apply for the root of the template since the root of the template always gets a new scope. - * - * **If set to `{}` (object hash),** then a new "isolate" scope is created. The 'isolate' scope differs from - * normal scope in that it does not prototypically inherit from the parent scope. This is useful - * when creating reusable components, which should not accidentally read or modify data in the - * parent scope. - * - * The 'isolate' scope takes an object hash which defines a set of local scope properties - * derived from the parent scope. These local properties are useful for aliasing values for - * templates. Locals definition is a hash of local scope property to its source: - * - * * `@` or `@attr` - bind a local scope property to the value of DOM attribute. The result is - * always a string since DOM attributes are strings. If no `attr` name is specified then the - * attribute name is assumed to be the same as the local name. - * Given `` and widget definition - * of `scope: { localName:'@myAttr' }`, then widget scope property `localName` will reflect - * the interpolated value of `hello {{name}}`. As the `name` attribute changes so will the - * `localName` property on the widget scope. The `name` is read from the parent scope (not - * component scope). - * - * * `=` or `=attr` - set up bi-directional binding between a local scope property and the - * parent scope property of name defined via the value of the `attr` attribute. If no `attr` - * name is specified then the attribute name is assumed to be the same as the local name. - * Given `` and widget definition of - * `scope: { localModel:'=myAttr' }`, then widget scope property `localModel` will reflect the - * value of `parentModel` on the parent scope. Any changes to `parentModel` will be reflected - * in `localModel` and any changes in `localModel` will reflect in `parentModel`. If the parent - * scope property doesn't exist, it will throw a NON_ASSIGNABLE_MODEL_EXPRESSION exception. You - * can avoid this behavior using `=?` or `=?attr` in order to flag the property as optional. - * - * * `&` or `&attr` - provides a way to execute an expression in the context of the parent scope. - * If no `attr` name is specified then the attribute name is assumed to be the same as the - * local name. Given `` and widget definition of - * `scope: { localFn:'&myAttr' }`, then isolate scope property `localFn` will point to - * a function wrapper for the `count = count + value` expression. Often it's desirable to - * pass data from the isolated scope via an expression and to the parent scope, this can be - * done by passing a map of local variable names and values into the expression wrapper fn. - * For example, if the expression is `increment(amount)` then we can specify the amount value - * by calling the `localFn` as `localFn({amount: 22})`. - * - * - * - * #### `controller` - * Controller constructor function. The controller is instantiated before the - * pre-linking phase and it is shared with other directives (see - * `require` attribute). This allows the directives to communicate with each other and augment - * each other's behavior. The controller is injectable (and supports bracket notation) with the following locals: - * - * * `$scope` - Current scope associated with the element - * * `$element` - Current element - * * `$attrs` - Current attributes object for the element - * * `$transclude` - A transclude linking function pre-bound to the correct transclusion scope. - * The scope can be overridden by an optional first argument. - * `function([scope], cloneLinkingFn)`. - * - * - * #### `require` - * Require another directive and inject its controller as the fourth argument to the linking function. The - * `require` takes a string name (or array of strings) of the directive(s) to pass in. If an array is used, the - * injected argument will be an array in corresponding order. If no such directive can be - * found, or if the directive does not have a controller, then an error is raised. The name can be prefixed with: - * - * * (no prefix) - Locate the required controller on the current element. Throw an error if not found. - * * `?` - Attempt to locate the required controller or pass `null` to the `link` fn if not found. - * * `^` - Locate the required controller by searching the element's parents. Throw an error if not found. - * * `?^` - Attempt to locate the required controller by searching the element's parents or pass `null` to the - * `link` fn if not found. - * - * - * #### `controllerAs` - * Controller alias at the directive scope. An alias for the controller so it - * can be referenced at the directive template. The directive needs to define a scope for this - * configuration to be used. Useful in the case when directive is used as component. - * - * - * #### `restrict` - * String of subset of `EACM` which restricts the directive to a specific directive - * declaration style. If omitted, the default (attributes only) is used. - * - * * `E` - Element name: `` - * * `A` - Attribute (default): `
` - * * `C` - Class: `
` - * * `M` - Comment: `` - * - * - * #### `type` - * String representing the document type used by the markup. This is useful for templates where the root - * node is non-HTML content (such as SVG or MathML). The default value is "html". - * - * * `html` - All root template nodes are HTML, and don't need to be wrapped. Root nodes may also be - * top-level elements such as `` or ``. - * * `svg` - The template contains only SVG content, and must be wrapped in an `` node prior to - * processing. - * * `math` - The template contains only MathML content, and must be wrapped in an `` node prior to - * processing. - * - * If no `type` is specified, then the type is considered to be html. - * - * #### `template` - * replace the current element with the contents of the HTML. The replacement process - * migrates all of the attributes / classes from the old element to the new one. See the - * {@link guide/directive#creating-custom-directives_creating-directives_template-expanding-directive - * Directives Guide} for an example. - * - * You can specify `template` as a string representing the template or as a function which takes - * two arguments `tElement` and `tAttrs` (described in the `compile` function api below) and - * returns a string value representing the template. - * - * - * #### `templateUrl` - * Same as `template` but the template is loaded from the specified URL. Because - * the template loading is asynchronous the compilation/linking is suspended until the template - * is loaded. - * - * You can specify `templateUrl` as a string representing the URL or as a function which takes two - * arguments `tElement` and `tAttrs` (described in the `compile` function api below) and returns - * a string value representing the url. In either case, the template URL is passed through {@link - * api/ng.$sce#getTrustedResourceUrl $sce.getTrustedResourceUrl}. - * - * - * #### `replace` ([*DEPRECATED*!], will be removed in next major release) - * specify where the template should be inserted. Defaults to `false`. - * - * * `true` - the template will replace the current element. - * * `false` - the template will replace the contents of the current element. - * - * - * #### `transclude` - * compile the content of the element and make it available to the directive. - * Typically used with {@link ng.directive:ngTransclude - * ngTransclude}. The advantage of transclusion is that the linking function receives a - * transclusion function which is pre-bound to the correct scope. In a typical setup the widget - * creates an `isolate` scope, but the transclusion is not a child, but a sibling of the `isolate` - * scope. This makes it possible for the widget to have private state, and the transclusion to - * be bound to the parent (pre-`isolate`) scope. - * - * * `true` - transclude the content of the directive. - * * `'element'` - transclude the whole element including any directives defined at lower priority. - * - * - * #### `compile` - * - * ```js - * function compile(tElement, tAttrs, transclude) { ... } - * ``` - * - * The compile function deals with transforming the template DOM. Since most directives do not do - * template transformation, it is not used often. The compile function takes the following arguments: - * - * * `tElement` - template element - The element where the directive has been declared. It is - * safe to do template transformation on the element and child elements only. - * - * * `tAttrs` - template attributes - Normalized list of attributes declared on this element shared - * between all directive compile functions. - * - * * `transclude` - [*DEPRECATED*!] A transclude linking function: `function(scope, cloneLinkingFn)` - * - *
- * **Note:** The template instance and the link instance may be different objects if the template has - * been cloned. For this reason it is **not** safe to do anything other than DOM transformations that - * apply to all cloned DOM nodes within the compile function. Specifically, DOM listener registration - * should be done in a linking function rather than in a compile function. - *
- - *
- * **Note:** The compile function cannot handle directives that recursively use themselves in their - * own templates or compile functions. Compiling these directives results in an infinite loop and a - * stack overflow errors. - * - * This can be avoided by manually using $compile in the postLink function to imperatively compile - * a directive's template instead of relying on automatic template compilation via `template` or - * `templateUrl` declaration or manual compilation inside the compile function. - *
- * - *
- * **Note:** The `transclude` function that is passed to the compile function is deprecated, as it - * e.g. does not know about the right outer scope. Please use the transclude function that is passed - * to the link function instead. - *
- - * A compile function can have a return value which can be either a function or an object. - * - * * returning a (post-link) function - is equivalent to registering the linking function via the - * `link` property of the config object when the compile function is empty. - * - * * returning an object with function(s) registered via `pre` and `post` properties - allows you to - * control when a linking function should be called during the linking phase. See info about - * pre-linking and post-linking functions below. - * - * - * #### `link` - * This property is used only if the `compile` property is not defined. - * - * ```js - * function link(scope, iElement, iAttrs, controller, transcludeFn) { ... } - * ``` - * - * The link function is responsible for registering DOM listeners as well as updating the DOM. It is - * executed after the template has been cloned. This is where most of the directive logic will be - * put. - * - * * `scope` - {@link ng.$rootScope.Scope Scope} - The scope to be used by the - * directive for registering {@link ng.$rootScope.Scope#$watch watches}. - * - * * `iElement` - instance element - The element where the directive is to be used. It is safe to - * manipulate the children of the element only in `postLink` function since the children have - * already been linked. - * - * * `iAttrs` - instance attributes - Normalized list of attributes declared on this element shared - * between all directive linking functions. - * - * * `controller` - a controller instance - A controller instance if at least one directive on the - * element defines a controller. The controller is shared among all the directives, which allows - * the directives to use the controllers as a communication channel. - * - * * `transcludeFn` - A transclude linking function pre-bound to the correct transclusion scope. - * The scope can be overridden by an optional first argument. This is the same as the `$transclude` - * parameter of directive controllers. - * `function([scope], cloneLinkingFn)`. - * - * - * #### Pre-linking function - * - * Executed before the child elements are linked. Not safe to do DOM transformation since the - * compiler linking function will fail to locate the correct elements for linking. - * - * #### Post-linking function - * - * Executed after the child elements are linked. It is safe to do DOM transformation in the post-linking function. - * - * - * ### Attributes - * - * The {@link ng.$compile.directive.Attributes Attributes} object - passed as a parameter in the - * `link()` or `compile()` functions. It has a variety of uses. - * - * accessing *Normalized attribute names:* - * Directives like 'ngBind' can be expressed in many ways: 'ng:bind', `data-ng-bind`, or 'x-ng-bind'. - * the attributes object allows for normalized access to - * the attributes. - * - * * *Directive inter-communication:* All directives share the same instance of the attributes - * object which allows the directives to use the attributes object as inter directive - * communication. - * - * * *Supports interpolation:* Interpolation attributes are assigned to the attribute object - * allowing other directives to read the interpolated value. - * - * * *Observing interpolated attributes:* Use `$observe` to observe the value changes of attributes - * that contain interpolation (e.g. `src="{{bar}}"`). Not only is this very efficient but it's also - * the only way to easily get the actual value because during the linking phase the interpolation - * hasn't been evaluated yet and so the value is at this time set to `undefined`. - * - * ```js - * function linkingFn(scope, elm, attrs, ctrl) { - * // get the attribute value - * console.log(attrs.ngModel); - * - * // change the attribute - * attrs.$set('ngModel', 'new value'); - * - * // observe changes to interpolated attribute - * attrs.$observe('ngModel', function(value) { - * console.log('ngModel has changed value to ' + value); - * }); - * } - * ``` - * - * Below is an example using `$compileProvider`. - * - *
- * **Note**: Typically directives are registered with `module.directive`. The example below is - * to illustrate how `$compile` works. - *
- * - - - -
-
-
-
-
-
- - it('should auto compile', function() { - var textarea = $('textarea'); - var output = $('div[compile]'); - // The initial state reads 'Hello Angular'. - expect(output.getText()).toBe('Hello Angular'); - textarea.clear(); - textarea.sendKeys('{{name}}!'); - expect(output.getText()).toBe('Angular!'); - }); - -
- - * - * - * @param {string|DOMElement} element Element or HTML string to compile into a template function. - * @param {function(angular.Scope, cloneAttachFn=)} transclude function available to directives. - * @param {number} maxPriority only apply directives lower than given priority (Only effects the - * root element(s), not their children) - * @returns {function(scope, cloneAttachFn=)} a link function which is used to bind template - * (a DOM element/tree) to a scope. Where: - * - * * `scope` - A {@link ng.$rootScope.Scope Scope} to bind to. - * * `cloneAttachFn` - If `cloneAttachFn` is provided, then the link function will clone the - * `template` and call the `cloneAttachFn` function allowing the caller to attach the - * cloned elements to the DOM document at the appropriate place. The `cloneAttachFn` is - * called as:
`cloneAttachFn(clonedElement, scope)` where: - * - * * `clonedElement` - is a clone of the original `element` passed into the compiler. - * * `scope` - is the current scope with which the linking function is working with. - * - * Calling the linking function returns the element of the template. It is either the original - * element passed in, or the clone of the element if the `cloneAttachFn` is provided. - * - * After linking the view is not updated until after a call to $digest which typically is done by - * Angular automatically. - * - * If you need access to the bound view, there are two ways to do it: - * - * - If you are not asking the linking function to clone the template, create the DOM element(s) - * before you send them to the compiler and keep this reference around. - * ```js - * var element = $compile('

{{total}}

')(scope); - * ``` - * - * - if on the other hand, you need the element to be cloned, the view reference from the original - * example would not point to the clone, but rather to the original template that was cloned. In - * this case, you can access the clone via the cloneAttachFn: - * ```js - * var templateElement = angular.element('

{{total}}

'), - * scope = ....; - * - * var clonedElement = $compile(templateElement)(scope, function(clonedElement, scope) { - * //attach the clone to DOM document at the right place - * }); - * - * //now we have reference to the cloned DOM via `clonedElement` - * ``` - * - * - * For information on how the compiler works, see the - * {@link guide/compiler Angular HTML Compiler} section of the Developer Guide. - */ - -var $compileMinErr = minErr('$compile'); - -/** - * @ngdoc provider - * @name $compileProvider - * @kind function - * - * @description - */ -$CompileProvider.$inject = ['$provide', '$$sanitizeUriProvider']; -function $CompileProvider($provide, $$sanitizeUriProvider) { - var hasDirectives = {}, - Suffix = 'Directive', - COMMENT_DIRECTIVE_REGEXP = /^\s*directive\:\s*([\d\w_\-]+)\s+(.*)$/, - CLASS_DIRECTIVE_REGEXP = /(([\d\w_\-]+)(?:\:([^;]+))?;?)/, - ALL_OR_NOTHING_ATTRS = makeMap('ngSrc,ngSrcset,src,srcset'); - - // Ref: http://developers.whatwg.org/webappapis.html#event-handler-idl-attributes - // The assumption is that future DOM event attribute names will begin with - // 'on' and be composed of only English letters. - var EVENT_HANDLER_ATTR_REGEXP = /^(on[a-z]+|formaction)$/; - - /** - * @ngdoc method - * @name $compileProvider#directive - * @kind function - * - * @description - * Register a new directive with the compiler. - * - * @param {string|Object} name Name of the directive in camel-case (i.e. ngBind which - * will match as ng-bind), or an object map of directives where the keys are the - * names and the values are the factories. - * @param {Function|Array} directiveFactory An injectable directive factory function. See - * {@link guide/directive} for more info. - * @returns {ng.$compileProvider} Self for chaining. - */ - this.directive = function registerDirective(name, directiveFactory) { - assertNotHasOwnProperty(name, 'directive'); - if (isString(name)) { - assertArg(directiveFactory, 'directiveFactory'); - if (!hasDirectives.hasOwnProperty(name)) { - hasDirectives[name] = []; - $provide.factory(name + Suffix, ['$injector', '$exceptionHandler', - function($injector, $exceptionHandler) { - var directives = []; - forEach(hasDirectives[name], function(directiveFactory, index) { - try { - var directive = $injector.invoke(directiveFactory); - if (isFunction(directive)) { - directive = { compile: valueFn(directive) }; - } else if (!directive.compile && directive.link) { - directive.compile = valueFn(directive.link); - } - directive.priority = directive.priority || 0; - directive.index = index; - directive.name = directive.name || name; - directive.require = directive.require || (directive.controller && directive.name); - directive.restrict = directive.restrict || 'A'; - directives.push(directive); - } catch (e) { - $exceptionHandler(e); - } - }); - return directives; - }]); - } - hasDirectives[name].push(directiveFactory); - } else { - forEach(name, reverseParams(registerDirective)); - } - return this; - }; - - - /** - * @ngdoc method - * @name $compileProvider#aHrefSanitizationWhitelist - * @kind function - * - * @description - * Retrieves or overrides the default regular expression that is used for whitelisting of safe - * urls during a[href] sanitization. - * - * The sanitization is a security measure aimed at prevent XSS attacks via html links. - * - * Any url about to be assigned to a[href] via data-binding is first normalized and turned into - * an absolute url. Afterwards, the url is matched against the `aHrefSanitizationWhitelist` - * regular expression. If a match is found, the original url is written into the dom. Otherwise, - * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM. - * - * @param {RegExp=} regexp New regexp to whitelist urls with. - * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for - * chaining otherwise. - */ - this.aHrefSanitizationWhitelist = function(regexp) { - if (isDefined(regexp)) { - $$sanitizeUriProvider.aHrefSanitizationWhitelist(regexp); - return this; - } else { - return $$sanitizeUriProvider.aHrefSanitizationWhitelist(); - } - }; - - - /** - * @ngdoc method - * @name $compileProvider#imgSrcSanitizationWhitelist - * @kind function - * - * @description - * Retrieves or overrides the default regular expression that is used for whitelisting of safe - * urls during img[src] sanitization. - * - * The sanitization is a security measure aimed at prevent XSS attacks via html links. - * - * Any url about to be assigned to img[src] via data-binding is first normalized and turned into - * an absolute url. Afterwards, the url is matched against the `imgSrcSanitizationWhitelist` - * regular expression. If a match is found, the original url is written into the dom. Otherwise, - * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM. - * - * @param {RegExp=} regexp New regexp to whitelist urls with. - * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for - * chaining otherwise. - */ - this.imgSrcSanitizationWhitelist = function(regexp) { - if (isDefined(regexp)) { - $$sanitizeUriProvider.imgSrcSanitizationWhitelist(regexp); - return this; - } else { - return $$sanitizeUriProvider.imgSrcSanitizationWhitelist(); - } - }; - - this.$get = [ - '$injector', '$interpolate', '$exceptionHandler', '$http', '$templateCache', '$parse', - '$controller', '$rootScope', '$document', '$sce', '$animate', '$$sanitizeUri', - function($injector, $interpolate, $exceptionHandler, $http, $templateCache, $parse, - $controller, $rootScope, $document, $sce, $animate, $$sanitizeUri) { - - var Attributes = function(element, attr) { - this.$$element = element; - this.$attr = attr || {}; - }; - - Attributes.prototype = { - $normalize: directiveNormalize, - - - /** - * @ngdoc method - * @name $compile.directive.Attributes#$addClass - * @kind function - * - * @description - * Adds the CSS class value specified by the classVal parameter to the element. If animations - * are enabled then an animation will be triggered for the class addition. - * - * @param {string} classVal The className value that will be added to the element - */ - $addClass : function(classVal) { - if(classVal && classVal.length > 0) { - $animate.addClass(this.$$element, classVal); - } - }, - - /** - * @ngdoc method - * @name $compile.directive.Attributes#$removeClass - * @kind function - * - * @description - * Removes the CSS class value specified by the classVal parameter from the element. If - * animations are enabled then an animation will be triggered for the class removal. - * - * @param {string} classVal The className value that will be removed from the element - */ - $removeClass : function(classVal) { - if(classVal && classVal.length > 0) { - $animate.removeClass(this.$$element, classVal); - } - }, - - /** - * @ngdoc method - * @name $compile.directive.Attributes#$updateClass - * @kind function - * - * @description - * Adds and removes the appropriate CSS class values to the element based on the difference - * between the new and old CSS class values (specified as newClasses and oldClasses). - * - * @param {string} newClasses The current CSS className value - * @param {string} oldClasses The former CSS className value - */ - $updateClass : function(newClasses, oldClasses) { - var toAdd = tokenDifference(newClasses, oldClasses); - var toRemove = tokenDifference(oldClasses, newClasses); - - if(toAdd.length === 0) { - $animate.removeClass(this.$$element, toRemove); - } else if(toRemove.length === 0) { - $animate.addClass(this.$$element, toAdd); - } else { - $animate.setClass(this.$$element, toAdd, toRemove); - } - }, - - /** - * Set a normalized attribute on the element in a way such that all directives - * can share the attribute. This function properly handles boolean attributes. - * @param {string} key Normalized key. (ie ngAttribute) - * @param {string|boolean} value The value to set. If `null` attribute will be deleted. - * @param {boolean=} writeAttr If false, does not write the value to DOM element attribute. - * Defaults to true. - * @param {string=} attrName Optional none normalized name. Defaults to key. - */ - $set: function(key, value, writeAttr, attrName) { - // TODO: decide whether or not to throw an error if "class" - //is set through this function since it may cause $updateClass to - //become unstable. - - var node = this.$$element[0], - booleanKey = getBooleanAttrName(node, key), - aliasedKey = getAliasedAttrName(node, key), - observer = key, - normalizedVal, - nodeName; - - if (booleanKey) { - this.$$element.prop(key, value); - attrName = booleanKey; - } else if(aliasedKey) { - this[aliasedKey] = value; - observer = aliasedKey; - } - - this[key] = value; - - // translate normalized key to actual key - if (attrName) { - this.$attr[key] = attrName; - } else { - attrName = this.$attr[key]; - if (!attrName) { - this.$attr[key] = attrName = snake_case(key, '-'); - } - } - - nodeName = nodeName_(this.$$element); - - // sanitize a[href] and img[src] values - if ((nodeName === 'a' && key === 'href') || - (nodeName === 'img' && key === 'src')) { - this[key] = value = $$sanitizeUri(value, key === 'src'); - } - - if (writeAttr !== false) { - if (value === null || value === undefined) { - this.$$element.removeAttr(attrName); - } else { - this.$$element.attr(attrName, value); - } - } - - // fire observers - var $$observers = this.$$observers; - $$observers && forEach($$observers[observer], function(fn) { - try { - fn(value); - } catch (e) { - $exceptionHandler(e); - } - }); - }, - - - /** - * @ngdoc method - * @name $compile.directive.Attributes#$observe - * @kind function - * - * @description - * Observes an interpolated attribute. - * - * The observer function will be invoked once during the next `$digest` following - * compilation. The observer is then invoked whenever the interpolated value - * changes. - * - * @param {string} key Normalized key. (ie ngAttribute) . - * @param {function(interpolatedValue)} fn Function that will be called whenever - the interpolated value of the attribute changes. - * See the {@link guide/directive#Attributes Directives} guide for more info. - * @returns {function()} Returns a deregistration function for this observer. - */ - $observe: function(key, fn) { - var attrs = this, - $$observers = (attrs.$$observers || (attrs.$$observers = {})), - listeners = ($$observers[key] || ($$observers[key] = [])); - - listeners.push(fn); - $rootScope.$evalAsync(function() { - if (!listeners.$$inter) { - // no one registered attribute interpolation function, so lets call it manually - fn(attrs[key]); - } - }); - - return function() { - arrayRemove(listeners, fn); - }; - } - }; - - var startSymbol = $interpolate.startSymbol(), - endSymbol = $interpolate.endSymbol(), - denormalizeTemplate = (startSymbol == '{{' || endSymbol == '}}') - ? identity - : function denormalizeTemplate(template) { - return template.replace(/\{\{/g, startSymbol).replace(/}}/g, endSymbol); - }, - NG_ATTR_BINDING = /^ngAttr[A-Z]/; - - - return compile; - - //================================ - - function compile($compileNodes, transcludeFn, maxPriority, ignoreDirective, - previousCompileContext) { - if (!($compileNodes instanceof jqLite)) { - // jquery always rewraps, whereas we need to preserve the original selector so that we can - // modify it. - $compileNodes = jqLite($compileNodes); - } - // We can not compile top level text elements since text nodes can be merged and we will - // not be able to attach scope data to them, so we will wrap them in - forEach($compileNodes, function(node, index){ - if (node.nodeType == 3 /* text node */ && node.nodeValue.match(/\S+/) /* non-empty */ ) { - $compileNodes[index] = node = jqLite(node).wrap('').parent()[0]; - } - }); - var compositeLinkFn = - compileNodes($compileNodes, transcludeFn, $compileNodes, - maxPriority, ignoreDirective, previousCompileContext); - safeAddClass($compileNodes, 'ng-scope'); - return function publicLinkFn(scope, cloneConnectFn, transcludeControllers, parentBoundTranscludeFn){ - assertArg(scope, 'scope'); - // important!!: we must call our jqLite.clone() since the jQuery one is trying to be smart - // and sometimes changes the structure of the DOM. - var $linkNode = cloneConnectFn - ? JQLitePrototype.clone.call($compileNodes) // IMPORTANT!!! - : $compileNodes; - - forEach(transcludeControllers, function(instance, name) { - $linkNode.data('$' + name + 'Controller', instance); - }); - - // Attach scope only to non-text nodes. - for(var i = 0, ii = $linkNode.length; i - addDirective(directives, - directiveNormalize(nodeName_(node)), 'E', maxPriority, ignoreDirective); - - // iterate over the attributes - for (var attr, name, nName, ngAttrName, value, isNgAttr, nAttrs = node.attributes, - j = 0, jj = nAttrs && nAttrs.length; j < jj; j++) { - var attrStartName = false; - var attrEndName = false; - - attr = nAttrs[j]; - if (!msie || msie >= 8 || attr.specified) { - name = attr.name; - value = trim(attr.value); - - // support ngAttr attribute binding - ngAttrName = directiveNormalize(name); - if (isNgAttr = NG_ATTR_BINDING.test(ngAttrName)) { - name = snake_case(ngAttrName.substr(6), '-'); - } - - var directiveNName = ngAttrName.replace(/(Start|End)$/, ''); - if (ngAttrName === directiveNName + 'Start') { - attrStartName = name; - attrEndName = name.substr(0, name.length - 5) + 'end'; - name = name.substr(0, name.length - 6); - } - - nName = directiveNormalize(name.toLowerCase()); - attrsMap[nName] = name; - if (isNgAttr || !attrs.hasOwnProperty(nName)) { - attrs[nName] = value; - if (getBooleanAttrName(node, nName)) { - attrs[nName] = true; // presence means true - } - } - addAttrInterpolateDirective(node, directives, value, nName); - addDirective(directives, nName, 'A', maxPriority, ignoreDirective, attrStartName, - attrEndName); - } - } - - // use class as directive - className = node.className; - if (isString(className) && className !== '') { - while (match = CLASS_DIRECTIVE_REGEXP.exec(className)) { - nName = directiveNormalize(match[2]); - if (addDirective(directives, nName, 'C', maxPriority, ignoreDirective)) { - attrs[nName] = trim(match[3]); - } - className = className.substr(match.index + match[0].length); - } - } - break; - case 3: /* Text Node */ - addTextInterpolateDirective(directives, node.nodeValue); - break; - case 8: /* Comment */ - try { - match = COMMENT_DIRECTIVE_REGEXP.exec(node.nodeValue); - if (match) { - nName = directiveNormalize(match[1]); - if (addDirective(directives, nName, 'M', maxPriority, ignoreDirective)) { - attrs[nName] = trim(match[2]); - } - } - } catch (e) { - // turns out that under some circumstances IE9 throws errors when one attempts to read - // comment's node value. - // Just ignore it and continue. (Can't seem to reproduce in test case.) - } - break; - } - - directives.sort(byPriority); - return directives; - } - - /** - * Given a node with an directive-start it collects all of the siblings until it finds - * directive-end. - * @param node - * @param attrStart - * @param attrEnd - * @returns {*} - */ - function groupScan(node, attrStart, attrEnd) { - var nodes = []; - var depth = 0; - if (attrStart && node.hasAttribute && node.hasAttribute(attrStart)) { - var startNode = node; - do { - if (!node) { - throw $compileMinErr('uterdir', - "Unterminated attribute, found '{0}' but no matching '{1}' found.", - attrStart, attrEnd); - } - if (node.nodeType == 1 /** Element **/) { - if (node.hasAttribute(attrStart)) depth++; - if (node.hasAttribute(attrEnd)) depth--; - } - nodes.push(node); - node = node.nextSibling; - } while (depth > 0); - } else { - nodes.push(node); - } - - return jqLite(nodes); - } - - /** - * Wrapper for linking function which converts normal linking function into a grouped - * linking function. - * @param linkFn - * @param attrStart - * @param attrEnd - * @returns {Function} - */ - function groupElementsLinkFnWrapper(linkFn, attrStart, attrEnd) { - return function(scope, element, attrs, controllers, transcludeFn) { - element = groupScan(element[0], attrStart, attrEnd); - return linkFn(scope, element, attrs, controllers, transcludeFn); - }; - } - - /** - * Once the directives have been collected, their compile functions are executed. This method - * is responsible for inlining directive templates as well as terminating the application - * of the directives if the terminal directive has been reached. - * - * @param {Array} directives Array of collected directives to execute their compile function. - * this needs to be pre-sorted by priority order. - * @param {Node} compileNode The raw DOM node to apply the compile functions to - * @param {Object} templateAttrs The shared attribute function - * @param {function(angular.Scope, cloneAttachFn=)} transcludeFn A linking function, where the - * scope argument is auto-generated to the new - * child of the transcluded parent scope. - * @param {JQLite} jqCollection If we are working on the root of the compile tree then this - * argument has the root jqLite array so that we can replace nodes - * on it. - * @param {Object=} originalReplaceDirective An optional directive that will be ignored when - * compiling the transclusion. - * @param {Array.} preLinkFns - * @param {Array.} postLinkFns - * @param {Object} previousCompileContext Context used for previous compilation of the current - * node - * @returns {Function} linkFn - */ - function applyDirectivesToNode(directives, compileNode, templateAttrs, transcludeFn, - jqCollection, originalReplaceDirective, preLinkFns, postLinkFns, - previousCompileContext) { - previousCompileContext = previousCompileContext || {}; - - var terminalPriority = -Number.MAX_VALUE, - newScopeDirective, - controllerDirectives = previousCompileContext.controllerDirectives, - newIsolateScopeDirective = previousCompileContext.newIsolateScopeDirective, - templateDirective = previousCompileContext.templateDirective, - nonTlbTranscludeDirective = previousCompileContext.nonTlbTranscludeDirective, - hasTranscludeDirective = false, - hasTemplate = false, - hasElementTranscludeDirective = previousCompileContext.hasElementTranscludeDirective, - $compileNode = templateAttrs.$$element = jqLite(compileNode), - directive, - directiveName, - $template, - replaceDirective = originalReplaceDirective, - childTranscludeFn = transcludeFn, - linkFn, - directiveValue; - - // executes all directives on the current element - for(var i = 0, ii = directives.length; i < ii; i++) { - directive = directives[i]; - var attrStart = directive.$$start; - var attrEnd = directive.$$end; - - // collect multiblock sections - if (attrStart) { - $compileNode = groupScan(compileNode, attrStart, attrEnd); - } - $template = undefined; - - if (terminalPriority > directive.priority) { - break; // prevent further processing of directives - } - - if (directiveValue = directive.scope) { - - // skip the check for directives with async templates, we'll check the derived sync - // directive when the template arrives - if (!directive.templateUrl) { - if (isObject(directiveValue)) { - // This directive is trying to add an isolated scope. - // Check that there is no scope of any kind already - assertNoDuplicate('new/isolated scope', newIsolateScopeDirective || newScopeDirective, - directive, $compileNode); - newIsolateScopeDirective = directive; - } else { - // This directive is trying to add a child scope. - // Check that there is no isolated scope already - assertNoDuplicate('new/isolated scope', newIsolateScopeDirective, directive, - $compileNode); - } - } - - newScopeDirective = newScopeDirective || directive; - } - - directiveName = directive.name; - - if (!directive.templateUrl && directive.controller) { - directiveValue = directive.controller; - controllerDirectives = controllerDirectives || {}; - assertNoDuplicate("'" + directiveName + "' controller", - controllerDirectives[directiveName], directive, $compileNode); - controllerDirectives[directiveName] = directive; - } - - if (directiveValue = directive.transclude) { - hasTranscludeDirective = true; - - // Special case ngIf and ngRepeat so that we don't complain about duplicate transclusion. - // This option should only be used by directives that know how to safely handle element transclusion, - // where the transcluded nodes are added or replaced after linking. - if (!directive.$$tlb) { - assertNoDuplicate('transclusion', nonTlbTranscludeDirective, directive, $compileNode); - nonTlbTranscludeDirective = directive; - } - - if (directiveValue == 'element') { - hasElementTranscludeDirective = true; - terminalPriority = directive.priority; - $template = groupScan(compileNode, attrStart, attrEnd); - $compileNode = templateAttrs.$$element = - jqLite(document.createComment(' ' + directiveName + ': ' + - templateAttrs[directiveName] + ' ')); - compileNode = $compileNode[0]; - replaceWith(jqCollection, jqLite(sliceArgs($template)), compileNode); - - childTranscludeFn = compile($template, transcludeFn, terminalPriority, - replaceDirective && replaceDirective.name, { - // Don't pass in: - // - controllerDirectives - otherwise we'll create duplicates controllers - // - newIsolateScopeDirective or templateDirective - combining templates with - // element transclusion doesn't make sense. - // - // We need only nonTlbTranscludeDirective so that we prevent putting transclusion - // on the same element more than once. - nonTlbTranscludeDirective: nonTlbTranscludeDirective - }); - } else { - $template = jqLite(jqLiteClone(compileNode)).contents(); - $compileNode.empty(); // clear contents - childTranscludeFn = compile($template, transcludeFn); - } - } - - if (directive.template) { - hasTemplate = true; - assertNoDuplicate('template', templateDirective, directive, $compileNode); - templateDirective = directive; - - directiveValue = (isFunction(directive.template)) - ? directive.template($compileNode, templateAttrs) - : directive.template; - - directiveValue = denormalizeTemplate(directiveValue); - - if (directive.replace) { - replaceDirective = directive; - if (jqLiteIsTextNode(directiveValue)) { - $template = []; - } else { - $template = jqLite(wrapTemplate(directive.type, trim(directiveValue))); - } - compileNode = $template[0]; - - if ($template.length != 1 || compileNode.nodeType !== 1) { - throw $compileMinErr('tplrt', - "Template for directive '{0}' must have exactly one root element. {1}", - directiveName, ''); - } - - replaceWith(jqCollection, $compileNode, compileNode); - - var newTemplateAttrs = {$attr: {}}; - - // combine directives from the original node and from the template: - // - take the array of directives for this element - // - split it into two parts, those that already applied (processed) and those that weren't (unprocessed) - // - collect directives from the template and sort them by priority - // - combine directives as: processed + template + unprocessed - var templateDirectives = collectDirectives(compileNode, [], newTemplateAttrs); - var unprocessedDirectives = directives.splice(i + 1, directives.length - (i + 1)); - - if (newIsolateScopeDirective) { - markDirectivesAsIsolate(templateDirectives); - } - directives = directives.concat(templateDirectives).concat(unprocessedDirectives); - mergeTemplateAttributes(templateAttrs, newTemplateAttrs); - - ii = directives.length; - } else { - $compileNode.html(directiveValue); - } - } - - if (directive.templateUrl) { - hasTemplate = true; - assertNoDuplicate('template', templateDirective, directive, $compileNode); - templateDirective = directive; - - if (directive.replace) { - replaceDirective = directive; - } - - nodeLinkFn = compileTemplateUrl(directives.splice(i, directives.length - i), $compileNode, - templateAttrs, jqCollection, hasTranscludeDirective && childTranscludeFn, preLinkFns, postLinkFns, { - controllerDirectives: controllerDirectives, - newIsolateScopeDirective: newIsolateScopeDirective, - templateDirective: templateDirective, - nonTlbTranscludeDirective: nonTlbTranscludeDirective - }); - ii = directives.length; - } else if (directive.compile) { - try { - linkFn = directive.compile($compileNode, templateAttrs, childTranscludeFn); - if (isFunction(linkFn)) { - addLinkFns(null, linkFn, attrStart, attrEnd); - } else if (linkFn) { - addLinkFns(linkFn.pre, linkFn.post, attrStart, attrEnd); - } - } catch (e) { - $exceptionHandler(e, startingTag($compileNode)); - } - } - - if (directive.terminal) { - nodeLinkFn.terminal = true; - terminalPriority = Math.max(terminalPriority, directive.priority); - } - - } - - nodeLinkFn.scope = newScopeDirective && newScopeDirective.scope === true; - nodeLinkFn.transcludeOnThisElement = hasTranscludeDirective; - nodeLinkFn.templateOnThisElement = hasTemplate; - nodeLinkFn.transclude = childTranscludeFn; - - previousCompileContext.hasElementTranscludeDirective = hasElementTranscludeDirective; - - // might be normal or delayed nodeLinkFn depending on if templateUrl is present - return nodeLinkFn; - - //////////////////// - - function addLinkFns(pre, post, attrStart, attrEnd) { - if (pre) { - if (attrStart) pre = groupElementsLinkFnWrapper(pre, attrStart, attrEnd); - pre.require = directive.require; - pre.directiveName = directiveName; - if (newIsolateScopeDirective === directive || directive.$$isolateScope) { - pre = cloneAndAnnotateFn(pre, {isolateScope: true}); - } - preLinkFns.push(pre); - } - if (post) { - if (attrStart) post = groupElementsLinkFnWrapper(post, attrStart, attrEnd); - post.require = directive.require; - post.directiveName = directiveName; - if (newIsolateScopeDirective === directive || directive.$$isolateScope) { - post = cloneAndAnnotateFn(post, {isolateScope: true}); - } - postLinkFns.push(post); - } - } - - - function getControllers(directiveName, require, $element, elementControllers) { - var value, retrievalMethod = 'data', optional = false; - if (isString(require)) { - while((value = require.charAt(0)) == '^' || value == '?') { - require = require.substr(1); - if (value == '^') { - retrievalMethod = 'inheritedData'; - } - optional = optional || value == '?'; - } - value = null; - - if (elementControllers && retrievalMethod === 'data') { - value = elementControllers[require]; - } - value = value || $element[retrievalMethod]('$' + require + 'Controller'); - - if (!value && !optional) { - throw $compileMinErr('ctreq', - "Controller '{0}', required by directive '{1}', can't be found!", - require, directiveName); - } - return value; - } else if (isArray(require)) { - value = []; - forEach(require, function(require) { - value.push(getControllers(directiveName, require, $element, elementControllers)); - }); - } - return value; - } - - - function nodeLinkFn(childLinkFn, scope, linkNode, $rootElement, boundTranscludeFn) { - var attrs, $element, i, ii, linkFn, controller, isolateScope, elementControllers = {}, transcludeFn; - - if (compileNode === linkNode) { - attrs = templateAttrs; - } else { - attrs = shallowCopy(templateAttrs, new Attributes(jqLite(linkNode), templateAttrs.$attr)); - } - $element = attrs.$$element; - - if (newIsolateScopeDirective) { - var LOCAL_REGEXP = /^\s*([@=&])(\??)\s*(\w*)\s*$/; - var $linkNode = jqLite(linkNode); - - isolateScope = scope.$new(true); - - if (templateDirective && (templateDirective === newIsolateScopeDirective || - templateDirective === newIsolateScopeDirective.$$originalDirective)) { - $linkNode.data('$isolateScope', isolateScope) ; - } else { - $linkNode.data('$isolateScopeNoTemplate', isolateScope); - } - - - - safeAddClass($linkNode, 'ng-isolate-scope'); - - forEach(newIsolateScopeDirective.scope, function(definition, scopeName) { - var match = definition.match(LOCAL_REGEXP) || [], - attrName = match[3] || scopeName, - optional = (match[2] == '?'), - mode = match[1], // @, =, or & - lastValue, - parentGet, parentSet, compare; - - isolateScope.$$isolateBindings[scopeName] = mode + attrName; - - switch (mode) { - - case '@': - attrs.$observe(attrName, function(value) { - isolateScope[scopeName] = value; - }); - attrs.$$observers[attrName].$$scope = scope; - if( attrs[attrName] ) { - // If the attribute has been provided then we trigger an interpolation to ensure - // the value is there for use in the link fn - isolateScope[scopeName] = $interpolate(attrs[attrName])(scope); - } - break; - - case '=': - if (optional && !attrs[attrName]) { - return; - } - parentGet = $parse(attrs[attrName]); - if (parentGet.literal) { - compare = equals; - } else { - compare = function(a,b) { return a === b; }; - } - parentSet = parentGet.assign || function() { - // reset the change, or we will throw this exception on every $digest - lastValue = isolateScope[scopeName] = parentGet(scope); - throw $compileMinErr('nonassign', - "Expression '{0}' used with directive '{1}' is non-assignable!", - attrs[attrName], newIsolateScopeDirective.name); - }; - lastValue = isolateScope[scopeName] = parentGet(scope); - isolateScope.$watch(function parentValueWatch() { - var parentValue = parentGet(scope); - if (!compare(parentValue, isolateScope[scopeName])) { - // we are out of sync and need to copy - if (!compare(parentValue, lastValue)) { - // parent changed and it has precedence - isolateScope[scopeName] = parentValue; - } else { - // if the parent can be assigned then do so - parentSet(scope, parentValue = isolateScope[scopeName]); - } - } - parentValueWatch.$$unwatch = parentGet.$$unwatch; - return lastValue = parentValue; - }, null, parentGet.literal); - break; - - case '&': - parentGet = $parse(attrs[attrName]); - isolateScope[scopeName] = function(locals) { - return parentGet(scope, locals); - }; - break; - - default: - throw $compileMinErr('iscp', - "Invalid isolate scope definition for directive '{0}'." + - " Definition: {... {1}: '{2}' ...}", - newIsolateScopeDirective.name, scopeName, definition); - } - }); - } - transcludeFn = boundTranscludeFn && controllersBoundTransclude; - if (controllerDirectives) { - forEach(controllerDirectives, function(directive) { - var locals = { - $scope: directive === newIsolateScopeDirective || directive.$$isolateScope ? isolateScope : scope, - $element: $element, - $attrs: attrs, - $transclude: transcludeFn - }, controllerInstance; - - controller = directive.controller; - if (controller == '@') { - controller = attrs[directive.name]; - } - - controllerInstance = $controller(controller, locals); - // For directives with element transclusion the element is a comment, - // but jQuery .data doesn't support attaching data to comment nodes as it's hard to - // clean up (http://bugs.jquery.com/ticket/8335). - // Instead, we save the controllers for the element in a local hash and attach to .data - // later, once we have the actual element. - elementControllers[directive.name] = controllerInstance; - if (!hasElementTranscludeDirective) { - $element.data('$' + directive.name + 'Controller', controllerInstance); - } - - if (directive.controllerAs) { - locals.$scope[directive.controllerAs] = controllerInstance; - } - }); - } - - // PRELINKING - for(i = 0, ii = preLinkFns.length; i < ii; i++) { - try { - linkFn = preLinkFns[i]; - linkFn(linkFn.isolateScope ? isolateScope : scope, $element, attrs, - linkFn.require && getControllers(linkFn.directiveName, linkFn.require, $element, elementControllers), transcludeFn); - } catch (e) { - $exceptionHandler(e, startingTag($element)); - } - } - - // RECURSION - // We only pass the isolate scope, if the isolate directive has a template, - // otherwise the child elements do not belong to the isolate directive. - var scopeToChild = scope; - if (newIsolateScopeDirective && (newIsolateScopeDirective.template || newIsolateScopeDirective.templateUrl === null)) { - scopeToChild = isolateScope; - } - childLinkFn && childLinkFn(scopeToChild, linkNode.childNodes, undefined, boundTranscludeFn); - - // POSTLINKING - for(i = postLinkFns.length - 1; i >= 0; i--) { - try { - linkFn = postLinkFns[i]; - linkFn(linkFn.isolateScope ? isolateScope : scope, $element, attrs, - linkFn.require && getControllers(linkFn.directiveName, linkFn.require, $element, elementControllers), transcludeFn); - } catch (e) { - $exceptionHandler(e, startingTag($element)); - } - } - - // This is the function that is injected as `$transclude`. - function controllersBoundTransclude(scope, cloneAttachFn) { - var transcludeControllers; - - // no scope passed - if (arguments.length < 2) { - cloneAttachFn = scope; - scope = undefined; - } - - if (hasElementTranscludeDirective) { - transcludeControllers = elementControllers; - } - - return boundTranscludeFn(scope, cloneAttachFn, transcludeControllers); - } - } - } - - function markDirectivesAsIsolate(directives) { - // mark all directives as needing isolate scope. - for (var j = 0, jj = directives.length; j < jj; j++) { - directives[j] = inherit(directives[j], {$$isolateScope: true}); - } - } - - /** - * looks up the directive and decorates it with exception handling and proper parameters. We - * call this the boundDirective. - * - * @param {string} name name of the directive to look up. - * @param {string} location The directive must be found in specific format. - * String containing any of theses characters: - * - * * `E`: element name - * * `A': attribute - * * `C`: class - * * `M`: comment - * @returns {boolean} true if directive was added. - */ - function addDirective(tDirectives, name, location, maxPriority, ignoreDirective, startAttrName, - endAttrName) { - if (name === ignoreDirective) return null; - var match = null; - if (hasDirectives.hasOwnProperty(name)) { - for(var directive, directives = $injector.get(name + Suffix), - i = 0, ii = directives.length; i directive.priority) && - directive.restrict.indexOf(location) != -1) { - if (startAttrName) { - directive = inherit(directive, {$$start: startAttrName, $$end: endAttrName}); - } - tDirectives.push(directive); - match = directive; - } - } catch(e) { $exceptionHandler(e); } - } - } - return match; - } - - - /** - * When the element is replaced with HTML template then the new attributes - * on the template need to be merged with the existing attributes in the DOM. - * The desired effect is to have both of the attributes present. - * - * @param {object} dst destination attributes (original DOM) - * @param {object} src source attributes (from the directive template) - */ - function mergeTemplateAttributes(dst, src) { - var srcAttr = src.$attr, - dstAttr = dst.$attr, - $element = dst.$$element; - - // reapply the old attributes to the new element - forEach(dst, function(value, key) { - if (key.charAt(0) != '$') { - if (src[key] && src[key] !== value) { - value += (key === 'style' ? ';' : ' ') + src[key]; - } - dst.$set(key, value, true, srcAttr[key]); - } - }); - - // copy the new attributes on the old attrs object - forEach(src, function(value, key) { - if (key == 'class') { - safeAddClass($element, value); - dst['class'] = (dst['class'] ? dst['class'] + ' ' : '') + value; - } else if (key == 'style') { - $element.attr('style', $element.attr('style') + ';' + value); - dst['style'] = (dst['style'] ? dst['style'] + ';' : '') + value; - // `dst` will never contain hasOwnProperty as DOM parser won't let it. - // You will get an "InvalidCharacterError: DOM Exception 5" error if you - // have an attribute like "has-own-property" or "data-has-own-property", etc. - } else if (key.charAt(0) != '$' && !dst.hasOwnProperty(key)) { - dst[key] = value; - dstAttr[key] = srcAttr[key]; - } - }); - } - - - function compileTemplateUrl(directives, $compileNode, tAttrs, - $rootElement, childTranscludeFn, preLinkFns, postLinkFns, previousCompileContext) { - var linkQueue = [], - afterTemplateNodeLinkFn, - afterTemplateChildLinkFn, - beforeTemplateCompileNode = $compileNode[0], - origAsyncDirective = directives.shift(), - // The fact that we have to copy and patch the directive seems wrong! - derivedSyncDirective = extend({}, origAsyncDirective, { - templateUrl: null, transclude: null, replace: null, $$originalDirective: origAsyncDirective - }), - templateUrl = (isFunction(origAsyncDirective.templateUrl)) - ? origAsyncDirective.templateUrl($compileNode, tAttrs) - : origAsyncDirective.templateUrl, - type = origAsyncDirective.type; - - $compileNode.empty(); - - $http.get($sce.getTrustedResourceUrl(templateUrl), {cache: $templateCache}). - success(function(content) { - var compileNode, tempTemplateAttrs, $template, childBoundTranscludeFn; - - content = denormalizeTemplate(content); - - if (origAsyncDirective.replace) { - if (jqLiteIsTextNode(content)) { - $template = []; - } else { - $template = jqLite(wrapTemplate(type, trim(content))); - } - compileNode = $template[0]; - - if ($template.length != 1 || compileNode.nodeType !== 1) { - throw $compileMinErr('tplrt', - "Template for directive '{0}' must have exactly one root element. {1}", - origAsyncDirective.name, templateUrl); - } - - tempTemplateAttrs = {$attr: {}}; - replaceWith($rootElement, $compileNode, compileNode); - var templateDirectives = collectDirectives(compileNode, [], tempTemplateAttrs); - - if (isObject(origAsyncDirective.scope)) { - markDirectivesAsIsolate(templateDirectives); - } - directives = templateDirectives.concat(directives); - mergeTemplateAttributes(tAttrs, tempTemplateAttrs); - } else { - compileNode = beforeTemplateCompileNode; - $compileNode.html(content); - } - - directives.unshift(derivedSyncDirective); - - afterTemplateNodeLinkFn = applyDirectivesToNode(directives, compileNode, tAttrs, - childTranscludeFn, $compileNode, origAsyncDirective, preLinkFns, postLinkFns, - previousCompileContext); - forEach($rootElement, function(node, i) { - if (node == compileNode) { - $rootElement[i] = $compileNode[0]; - } - }); - afterTemplateChildLinkFn = compileNodes($compileNode[0].childNodes, childTranscludeFn); - - while(linkQueue.length) { - var scope = linkQueue.shift(), - beforeTemplateLinkNode = linkQueue.shift(), - linkRootElement = linkQueue.shift(), - boundTranscludeFn = linkQueue.shift(), - linkNode = $compileNode[0]; - - if (beforeTemplateLinkNode !== beforeTemplateCompileNode) { - var oldClasses = beforeTemplateLinkNode.className; - - if (!(previousCompileContext.hasElementTranscludeDirective && - origAsyncDirective.replace)) { - // it was cloned therefore we have to clone as well. - linkNode = jqLiteClone(compileNode); - } - - replaceWith(linkRootElement, jqLite(beforeTemplateLinkNode), linkNode); - - // Copy in CSS classes from original node - safeAddClass(jqLite(linkNode), oldClasses); - } - if (afterTemplateNodeLinkFn.transcludeOnThisElement) { - childBoundTranscludeFn = createBoundTranscludeFn(scope, afterTemplateNodeLinkFn.transclude, boundTranscludeFn); - } else { - childBoundTranscludeFn = boundTranscludeFn; - } - afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, linkNode, $rootElement, - childBoundTranscludeFn); - } - linkQueue = null; - }). - error(function(response, code, headers, config) { - throw $compileMinErr('tpload', 'Failed to load template: {0}', config.url); - }); - - return function delayedNodeLinkFn(ignoreChildLinkFn, scope, node, rootElement, boundTranscludeFn) { - var childBoundTranscludeFn = boundTranscludeFn; - if (linkQueue) { - linkQueue.push(scope); - linkQueue.push(node); - linkQueue.push(rootElement); - linkQueue.push(childBoundTranscludeFn); - } else { - if (afterTemplateNodeLinkFn.transcludeOnThisElement) { - childBoundTranscludeFn = createBoundTranscludeFn(scope, afterTemplateNodeLinkFn.transclude, boundTranscludeFn); - } - afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, node, rootElement, childBoundTranscludeFn); - } - }; - } - - - /** - * Sorting function for bound directives. - */ - function byPriority(a, b) { - var diff = b.priority - a.priority; - if (diff !== 0) return diff; - if (a.name !== b.name) return (a.name < b.name) ? -1 : 1; - return a.index - b.index; - } - - - function assertNoDuplicate(what, previousDirective, directive, element) { - if (previousDirective) { - throw $compileMinErr('multidir', 'Multiple directives [{0}, {1}] asking for {2} on: {3}', - previousDirective.name, directive.name, what, startingTag(element)); - } - } - - - function addTextInterpolateDirective(directives, text) { - var interpolateFn = $interpolate(text, true); - if (interpolateFn) { - directives.push({ - priority: 0, - compile: function textInterpolateCompileFn(templateNode) { - // when transcluding a template that has bindings in the root - // then we don't have a parent and should do this in the linkFn - var parent = templateNode.parent(), hasCompileParent = parent.length; - if (hasCompileParent) safeAddClass(templateNode.parent(), 'ng-binding'); - - return function textInterpolateLinkFn(scope, node) { - var parent = node.parent(), - bindings = parent.data('$binding') || []; - // Need to interpolate again in case this is using one-time bindings in multiple clones - // of transcluded templates. - interpolateFn = $interpolate(text); - bindings.push(interpolateFn); - parent.data('$binding', bindings); - if (!hasCompileParent) safeAddClass(parent, 'ng-binding'); - scope.$watch(interpolateFn, function interpolateFnWatchAction(value) { - node[0].nodeValue = value; - }); - }; - } - }); - } - } - - - function wrapTemplate(type, template) { - type = lowercase(type || 'html'); - switch(type) { - case 'svg': - case 'math': - var wrapper = document.createElement('div'); - wrapper.innerHTML = '<'+type+'>'+template+''; - return wrapper.childNodes[0].childNodes; - default: - return template; - } - } - - - function getTrustedContext(node, attrNormalizedName) { - if (attrNormalizedName == "srcdoc") { - return $sce.HTML; - } - var tag = nodeName_(node); - // maction[xlink:href] can source SVG. It's not limited to . - if (attrNormalizedName == "xlinkHref" || - (tag == "form" && attrNormalizedName == "action") || - (tag != "img" && (attrNormalizedName == "src" || - attrNormalizedName == "ngSrc"))) { - return $sce.RESOURCE_URL; - } - } - - - function addAttrInterpolateDirective(node, directives, value, name) { - var interpolateFn = $interpolate(value, true); - - // no interpolation found -> ignore - if (!interpolateFn) return; - - - if (name === "multiple" && nodeName_(node) === "select") { - throw $compileMinErr("selmulti", - "Binding to the 'multiple' attribute is not supported. Element: {0}", - startingTag(node)); - } - - directives.push({ - priority: 100, - compile: function() { - return { - pre: function attrInterpolatePreLinkFn(scope, element, attr) { - var $$observers = (attr.$$observers || (attr.$$observers = {})); - - if (EVENT_HANDLER_ATTR_REGEXP.test(name)) { - throw $compileMinErr('nodomevents', - "Interpolations for HTML DOM event attributes are disallowed. Please use the " + - "ng- versions (such as ng-click instead of onclick) instead."); - } - - // we need to interpolate again, in case the attribute value has been updated - // (e.g. by another directive's compile function) - interpolateFn = $interpolate(attr[name], true, getTrustedContext(node, name), - ALL_OR_NOTHING_ATTRS[name]); - - // if attribute was updated so that there is no interpolation going on we don't want to - // register any observers - if (!interpolateFn) return; - - // initialize attr object so that it's ready in case we need the value for isolate - // scope initialization, otherwise the value would not be available from isolate - // directive's linking fn during linking phase - attr[name] = interpolateFn(scope); - - ($$observers[name] || ($$observers[name] = [])).$$inter = true; - (attr.$$observers && attr.$$observers[name].$$scope || scope). - $watch(interpolateFn, function interpolateFnWatchAction(newValue, oldValue) { - //special case for class attribute addition + removal - //so that class changes can tap into the animation - //hooks provided by the $animate service. Be sure to - //skip animations when the first digest occurs (when - //both the new and the old values are the same) since - //the CSS classes are the non-interpolated values - if(name === 'class' && newValue != oldValue) { - attr.$updateClass(newValue, oldValue); - } else { - attr.$set(name, newValue); - } - }); - } - }; - } - }); - } - - - /** - * This is a special jqLite.replaceWith, which can replace items which - * have no parents, provided that the containing jqLite collection is provided. - * - * @param {JqLite=} $rootElement The root of the compile tree. Used so that we can replace nodes - * in the root of the tree. - * @param {JqLite} elementsToRemove The jqLite element which we are going to replace. We keep - * the shell, but replace its DOM node reference. - * @param {Node} newNode The new DOM node. - */ - function replaceWith($rootElement, elementsToRemove, newNode) { - var firstElementToRemove = elementsToRemove[0], - removeCount = elementsToRemove.length, - parent = firstElementToRemove.parentNode, - i, ii; - - if ($rootElement) { - for(i = 0, ii = $rootElement.length; i < ii; i++) { - if ($rootElement[i] == firstElementToRemove) { - $rootElement[i++] = newNode; - for (var j = i, j2 = j + removeCount - 1, - jj = $rootElement.length; - j < jj; j++, j2++) { - if (j2 < jj) { - $rootElement[j] = $rootElement[j2]; - } else { - delete $rootElement[j]; - } - } - $rootElement.length -= removeCount - 1; - break; - } - } - } - - if (parent) { - parent.replaceChild(newNode, firstElementToRemove); - } - var fragment = document.createDocumentFragment(); - fragment.appendChild(firstElementToRemove); - newNode[jqLite.expando] = firstElementToRemove[jqLite.expando]; - for (var k = 1, kk = elementsToRemove.length; k < kk; k++) { - var element = elementsToRemove[k]; - jqLite(element).remove(); // must do this way to clean up expando - fragment.appendChild(element); - delete elementsToRemove[k]; - } - - elementsToRemove[0] = newNode; - elementsToRemove.length = 1; - } - - - function cloneAndAnnotateFn(fn, annotation) { - return extend(function() { return fn.apply(null, arguments); }, fn, annotation); - } - }]; -} - -var PREFIX_REGEXP = /^(x[\:\-_]|data[\:\-_])/i; -/** - * Converts all accepted directives format into proper directive name. - * All of these will become 'myDirective': - * my:Directive - * my-directive - * x-my-directive - * data-my:directive - * - * Also there is special case for Moz prefix starting with upper case letter. - * @param name Name to normalize - */ -function directiveNormalize(name) { - return camelCase(name.replace(PREFIX_REGEXP, '')); -} - -/** - * @ngdoc type - * @name $compile.directive.Attributes - * - * @description - * A shared object between directive compile / linking functions which contains normalized DOM - * element attributes. The values reflect current binding state `{{ }}`. The normalization is - * needed since all of these are treated as equivalent in Angular: - * - * ``` - * - * ``` - */ - -/** - * @ngdoc property - * @name $compile.directive.Attributes#$attr - * @returns {object} A map of DOM element attribute names to the normalized name. This is - * needed to do reverse lookup from normalized name back to actual name. - */ - - -/** - * @ngdoc method - * @name $compile.directive.Attributes#$set - * @kind function - * - * @description - * Set DOM element attribute value. - * - * - * @param {string} name Normalized element attribute name of the property to modify. The name is - * reverse-translated using the {@link ng.$compile.directive.Attributes#$attr $attr} - * property to the original name. - * @param {string} value Value to set the attribute to. The value can be an interpolated string. - */ - - - -/** - * Closure compiler type information - */ - -function nodesetLinkingFn( - /* angular.Scope */ scope, - /* NodeList */ nodeList, - /* Element */ rootElement, - /* function(Function) */ boundTranscludeFn -){} - -function directiveLinkingFn( - /* nodesetLinkingFn */ nodesetLinkingFn, - /* angular.Scope */ scope, - /* Node */ node, - /* Element */ rootElement, - /* function(Function) */ boundTranscludeFn -){} - -function tokenDifference(str1, str2) { - var values = '', - tokens1 = str1.split(/\s+/), - tokens2 = str2.split(/\s+/); - - outer: - for(var i = 0; i < tokens1.length; i++) { - var token = tokens1[i]; - for(var j = 0; j < tokens2.length; j++) { - if(token == tokens2[j]) continue outer; - } - values += (values.length > 0 ? ' ' : '') + token; - } - return values; -} - -/** - * @ngdoc provider - * @name $controllerProvider - * @description - * The {@link ng.$controller $controller service} is used by Angular to create new - * controllers. - * - * This provider allows controller registration via the - * {@link ng.$controllerProvider#register register} method. - */ -function $ControllerProvider() { - var controllers = {}, - CNTRL_REG = /^(\S+)(\s+as\s+(\w+))?$/; - - - /** - * @ngdoc method - * @name $controllerProvider#register - * @param {string|Object} name Controller name, or an object map of controllers where the keys are - * the names and the values are the constructors. - * @param {Function|Array} constructor Controller constructor fn (optionally decorated with DI - * annotations in the array notation). - */ - this.register = function(name, constructor) { - assertNotHasOwnProperty(name, 'controller'); - if (isObject(name)) { - extend(controllers, name); - } else { - controllers[name] = constructor; - } - }; - - - this.$get = ['$injector', '$window', function($injector, $window) { - - /** - * @ngdoc service - * @name $controller - * @requires $injector - * - * @param {Function|string} constructor If called with a function then it's considered to be the - * controller constructor function. Otherwise it's considered to be a string which is used - * to retrieve the controller constructor using the following steps: - * - * * check if a controller with given name is registered via `$controllerProvider` - * * check if evaluating the string on the current scope returns a constructor - * * check `window[constructor]` on the global `window` object - * - * @param {Object} locals Injection locals for Controller. - * @return {Object} Instance of given controller. - * - * @description - * `$controller` service is responsible for instantiating controllers. - * - * It's just a simple call to {@link auto.$injector $injector}, but extracted into - * a service, so that one can override this service with [BC version](https://gist.github.com/1649788). - */ - return function(expression, locals) { - var instance, match, constructor, identifier; - - if(isString(expression)) { - match = expression.match(CNTRL_REG), - constructor = match[1], - identifier = match[3]; - expression = controllers.hasOwnProperty(constructor) - ? controllers[constructor] - : getter(locals.$scope, constructor, true) || getter($window, constructor, true); - - assertArgFn(expression, constructor, true); - } - - instance = $injector.instantiate(expression, locals, constructor); - - if (identifier) { - if (!(locals && typeof locals.$scope == 'object')) { - throw minErr('$controller')('noscp', - "Cannot export controller '{0}' as '{1}'! No $scope object provided via `locals`.", - constructor || expression.name, identifier); - } - - locals.$scope[identifier] = instance; - } - - return instance; - }; - }]; -} - -/** - * @ngdoc service - * @name $document - * @requires $window - * - * @description - * A {@link angular.element jQuery or jqLite} wrapper for the browser's `window.document` object. - * - * @example - - -
-

$document title:

-

window.document title:

-
-
- - function MainCtrl($scope, $document) { - $scope.title = $document[0].title; - $scope.windowTitle = angular.element(window.document)[0].title; - } - -
- */ -function $DocumentProvider(){ - this.$get = ['$window', function(window){ - return jqLite(window.document); - }]; -} - -/** - * @ngdoc service - * @name $exceptionHandler - * @requires ng.$log - * - * @description - * Any uncaught exception in angular expressions is delegated to this service. - * The default implementation simply delegates to `$log.error` which logs it into - * the browser console. - * - * In unit tests, if `angular-mocks.js` is loaded, this service is overridden by - * {@link ngMock.$exceptionHandler mock $exceptionHandler} which aids in testing. - * - * ## Example: - * - * ```js - * angular.module('exceptionOverride', []).factory('$exceptionHandler', function () { - * return function (exception, cause) { - * exception.message += ' (caused by "' + cause + '")'; - * throw exception; - * }; - * }); - * ``` - * - * This example will override the normal action of `$exceptionHandler`, to make angular - * exceptions fail hard when they happen, instead of just logging to the console. - * - * @param {Error} exception Exception associated with the error. - * @param {string=} cause optional information about the context in which - * the error was thrown. - * - */ -function $ExceptionHandlerProvider() { - this.$get = ['$log', function($log) { - return function(exception, cause) { - $log.error.apply($log, arguments); - }; - }]; -} - -/** - * Parse headers into key value object - * - * @param {string} headers Raw headers as a string - * @returns {Object} Parsed headers as key value object - */ -function parseHeaders(headers) { - var parsed = {}, key, val, i; - - if (!headers) return parsed; - - forEach(headers.split('\n'), function(line) { - i = line.indexOf(':'); - key = lowercase(trim(line.substr(0, i))); - val = trim(line.substr(i + 1)); - - if (key) { - if (parsed[key]) { - parsed[key] += ', ' + val; - } else { - parsed[key] = val; - } - } - }); - - return parsed; -} - - -/** - * Returns a function that provides access to parsed headers. - * - * Headers are lazy parsed when first requested. - * @see parseHeaders - * - * @param {(string|Object)} headers Headers to provide access to. - * @returns {function(string=)} Returns a getter function which if called with: - * - * - if called with single an argument returns a single header value or null - * - if called with no arguments returns an object containing all headers. - */ -function headersGetter(headers) { - var headersObj = isObject(headers) ? headers : undefined; - - return function(name) { - if (!headersObj) headersObj = parseHeaders(headers); - - if (name) { - return headersObj[lowercase(name)] || null; - } - - return headersObj; - }; -} - - -/** - * Chain all given functions - * - * This function is used for both request and response transforming - * - * @param {*} data Data to transform. - * @param {function(string=)} headers Http headers getter fn. - * @param {(Function|Array.)} fns Function or an array of functions. - * @returns {*} Transformed data. - */ -function transformData(data, headers, fns) { - if (isFunction(fns)) - return fns(data, headers); - - forEach(fns, function(fn) { - data = fn(data, headers); - }); - - return data; -} - - -function isSuccess(status) { - return 200 <= status && status < 300; -} - - -function $HttpProvider() { - var JSON_START = /^\s*(\[|\{[^\{])/, - JSON_END = /[\}\]]\s*$/, - PROTECTION_PREFIX = /^\)\]\}',?\n/, - CONTENT_TYPE_APPLICATION_JSON = {'Content-Type': 'application/json;charset=utf-8'}; - - var defaults = this.defaults = { - // transform incoming response data - transformResponse: [function(data) { - if (isString(data)) { - // strip json vulnerability protection prefix - data = data.replace(PROTECTION_PREFIX, ''); - if (JSON_START.test(data) && JSON_END.test(data)) - data = fromJson(data); - } - return data; - }], - - // transform outgoing request data - transformRequest: [function(d) { - return isObject(d) && !isFile(d) && !isBlob(d) ? toJson(d) : d; - }], - - // default headers - headers: { - common: { - 'Accept': 'application/json, text/plain, */*' - }, - post: shallowCopy(CONTENT_TYPE_APPLICATION_JSON), - put: shallowCopy(CONTENT_TYPE_APPLICATION_JSON), - patch: shallowCopy(CONTENT_TYPE_APPLICATION_JSON) - }, - - xsrfCookieName: 'XSRF-TOKEN', - xsrfHeaderName: 'X-XSRF-TOKEN' - }; - - /** - * Are ordered by request, i.e. they are applied in the same order as the - * array, on request, but reverse order, on response. - */ - var interceptorFactories = this.interceptors = []; - - this.$get = ['$httpBackend', '$browser', '$cacheFactory', '$rootScope', '$q', '$injector', - function($httpBackend, $browser, $cacheFactory, $rootScope, $q, $injector) { - - var defaultCache = $cacheFactory('$http'); - - /** - * Interceptors stored in reverse order. Inner interceptors before outer interceptors. - * The reversal is needed so that we can build up the interception chain around the - * server request. - */ - var reversedInterceptors = []; - - forEach(interceptorFactories, function(interceptorFactory) { - reversedInterceptors.unshift(isString(interceptorFactory) - ? $injector.get(interceptorFactory) : $injector.invoke(interceptorFactory)); - }); - - /** - * @ngdoc service - * @kind function - * @name $http - * @requires ng.$httpBackend - * @requires $cacheFactory - * @requires $rootScope - * @requires $q - * @requires $injector - * - * @description - * The `$http` service is a core Angular service that facilitates communication with the remote - * HTTP servers via the browser's [XMLHttpRequest](https://developer.mozilla.org/en/xmlhttprequest) - * object or via [JSONP](http://en.wikipedia.org/wiki/JSONP). - * - * For unit testing applications that use `$http` service, see - * {@link ngMock.$httpBackend $httpBackend mock}. - * - * For a higher level of abstraction, please check out the {@link ngResource.$resource - * $resource} service. - * - * The $http API is based on the {@link ng.$q deferred/promise APIs} exposed by - * the $q service. While for simple usage patterns this doesn't matter much, for advanced usage - * it is important to familiarize yourself with these APIs and the guarantees they provide. - * - * - * # General usage - * The `$http` service is a function which takes a single argument — a configuration object — - * that is used to generate an HTTP request and returns a {@link ng.$q promise} - * with two $http specific methods: `success` and `error`. - * - * ```js - * $http({method: 'GET', url: '/someUrl'}). - * success(function(data, status, headers, config) { - * // this callback will be called asynchronously - * // when the response is available - * }). - * error(function(data, status, headers, config) { - * // called asynchronously if an error occurs - * // or server returns response with an error status. - * }); - * ``` - * - * Since the returned value of calling the $http function is a `promise`, you can also use - * the `then` method to register callbacks, and these callbacks will receive a single argument – - * an object representing the response. See the API signature and type info below for more - * details. - * - * A response status code between 200 and 299 is considered a success status and - * will result in the success callback being called. Note that if the response is a redirect, - * XMLHttpRequest will transparently follow it, meaning that the error callback will not be - * called for such responses. - * - * # Writing Unit Tests that use $http - * When unit testing (using {@link ngMock ngMock}), it is necessary to call - * {@link ngMock.$httpBackend#flush $httpBackend.flush()} to flush each pending - * request using trained responses. - * - * ``` - * $httpBackend.expectGET(...); - * $http.get(...); - * $httpBackend.flush(); - * ``` - * - * # Shortcut methods - * - * Shortcut methods are also available. All shortcut methods require passing in the URL, and - * request data must be passed in for POST/PUT requests. - * - * ```js - * $http.get('/someUrl').success(successCallback); - * $http.post('/someUrl', data).success(successCallback); - * ``` - * - * Complete list of shortcut methods: - * - * - {@link ng.$http#get $http.get} - * - {@link ng.$http#head $http.head} - * - {@link ng.$http#post $http.post} - * - {@link ng.$http#put $http.put} - * - {@link ng.$http#delete $http.delete} - * - {@link ng.$http#jsonp $http.jsonp} - * - * - * # Setting HTTP Headers - * - * The $http service will automatically add certain HTTP headers to all requests. These defaults - * can be fully configured by accessing the `$httpProvider.defaults.headers` configuration - * object, which currently contains this default configuration: - * - * - `$httpProvider.defaults.headers.common` (headers that are common for all requests): - * - `Accept: application/json, text/plain, * / *` - * - `$httpProvider.defaults.headers.post`: (header defaults for POST requests) - * - `Content-Type: application/json` - * - `$httpProvider.defaults.headers.put` (header defaults for PUT requests) - * - `Content-Type: application/json` - * - * To add or overwrite these defaults, simply add or remove a property from these configuration - * objects. To add headers for an HTTP method other than POST or PUT, simply add a new object - * with the lowercased HTTP method name as the key, e.g. - * `$httpProvider.defaults.headers.get = { 'My-Header' : 'value' }. - * - * The defaults can also be set at runtime via the `$http.defaults` object in the same - * fashion. For example: - * - * ``` - * module.run(function($http) { - * $http.defaults.headers.common.Authorization = 'Basic YmVlcDpib29w' - * }); - * ``` - * - * In addition, you can supply a `headers` property in the config object passed when - * calling `$http(config)`, which overrides the defaults without changing them globally. - * - * - * # Transforming Requests and Responses - * - * Both requests and responses can be transformed using transform functions. By default, Angular - * applies these transformations: - * - * Request transformations: - * - * - If the `data` property of the request configuration object contains an object, serialize it - * into JSON format. - * - * Response transformations: - * - * - If XSRF prefix is detected, strip it (see Security Considerations section below). - * - If JSON response is detected, deserialize it using a JSON parser. - * - * To globally augment or override the default transforms, modify the - * `$httpProvider.defaults.transformRequest` and `$httpProvider.defaults.transformResponse` - * properties. These properties are by default an array of transform functions, which allows you - * to `push` or `unshift` a new transformation function into the transformation chain. You can - * also decide to completely override any default transformations by assigning your - * transformation functions to these properties directly without the array wrapper. These defaults - * are again available on the $http factory at run-time, which may be useful if you have run-time - * services you wish to be involved in your transformations. - * - * Similarly, to locally override the request/response transforms, augment the - * `transformRequest` and/or `transformResponse` properties of the configuration object passed - * into `$http`. - * - * - * # Caching - * - * To enable caching, set the request configuration `cache` property to `true` (to use default - * cache) or to a custom cache object (built with {@link ng.$cacheFactory `$cacheFactory`}). - * When the cache is enabled, `$http` stores the response from the server in the specified - * cache. The next time the same request is made, the response is served from the cache without - * sending a request to the server. - * - * Note that even if the response is served from cache, delivery of the data is asynchronous in - * the same way that real requests are. - * - * If there are multiple GET requests for the same URL that should be cached using the same - * cache, but the cache is not populated yet, only one request to the server will be made and - * the remaining requests will be fulfilled using the response from the first request. - * - * You can change the default cache to a new object (built with - * {@link ng.$cacheFactory `$cacheFactory`}) by updating the - * {@link ng.$http#properties_defaults `$http.defaults.cache`} property. All requests who set - * their `cache` property to `true` will now use this cache object. - * - * If you set the default cache to `false` then only requests that specify their own custom - * cache object will be cached. - * - * # Interceptors - * - * Before you start creating interceptors, be sure to understand the - * {@link ng.$q $q and deferred/promise APIs}. - * - * For purposes of global error handling, authentication, or any kind of synchronous or - * asynchronous pre-processing of request or postprocessing of responses, it is desirable to be - * able to intercept requests before they are handed to the server and - * responses before they are handed over to the application code that - * initiated these requests. The interceptors leverage the {@link ng.$q - * promise APIs} to fulfill this need for both synchronous and asynchronous pre-processing. - * - * The interceptors are service factories that are registered with the `$httpProvider` by - * adding them to the `$httpProvider.interceptors` array. The factory is called and - * injected with dependencies (if specified) and returns the interceptor. - * - * There are two kinds of interceptors (and two kinds of rejection interceptors): - * - * * `request`: interceptors get called with a http `config` object. The function is free to - * modify the `config` object or create a new one. The function needs to return the `config` - * object directly, or a promise containing the `config` or a new `config` object. - * * `requestError`: interceptor gets called when a previous interceptor threw an error or - * resolved with a rejection. - * * `response`: interceptors get called with http `response` object. The function is free to - * modify the `response` object or create a new one. The function needs to return the `response` - * object directly, or as a promise containing the `response` or a new `response` object. - * * `responseError`: interceptor gets called when a previous interceptor threw an error or - * resolved with a rejection. - * - * - * ```js - * // register the interceptor as a service - * $provide.factory('myHttpInterceptor', function($q, dependency1, dependency2) { - * return { - * // optional method - * 'request': function(config) { - * // do something on success - * return config; - * }, - * - * // optional method - * 'requestError': function(rejection) { - * // do something on error - * if (canRecover(rejection)) { - * return responseOrNewPromise - * } - * return $q.reject(rejection); - * }, - * - * - * - * // optional method - * 'response': function(response) { - * // do something on success - * return response; - * }, - * - * // optional method - * 'responseError': function(rejection) { - * // do something on error - * if (canRecover(rejection)) { - * return responseOrNewPromise - * } - * return $q.reject(rejection); - * } - * }; - * }); - * - * $httpProvider.interceptors.push('myHttpInterceptor'); - * - * - * // alternatively, register the interceptor via an anonymous factory - * $httpProvider.interceptors.push(function($q, dependency1, dependency2) { - * return { - * 'request': function(config) { - * // same as above - * }, - * - * 'response': function(response) { - * // same as above - * } - * }; - * }); - * ``` - * - * # Security Considerations - * - * When designing web applications, consider security threats from: - * - * - [JSON vulnerability](http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx) - * - [XSRF](http://en.wikipedia.org/wiki/Cross-site_request_forgery) - * - * Both server and the client must cooperate in order to eliminate these threats. Angular comes - * pre-configured with strategies that address these issues, but for this to work backend server - * cooperation is required. - * - * ## JSON Vulnerability Protection - * - * A [JSON vulnerability](http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx) - * allows third party website to turn your JSON resource URL into - * [JSONP](http://en.wikipedia.org/wiki/JSONP) request under some conditions. To - * counter this your server can prefix all JSON requests with following string `")]}',\n"`. - * Angular will automatically strip the prefix before processing it as JSON. - * - * For example if your server needs to return: - * ```js - * ['one','two'] - * ``` - * - * which is vulnerable to attack, your server can return: - * ```js - * )]}', - * ['one','two'] - * ``` - * - * Angular will strip the prefix, before processing the JSON. - * - * - * ## Cross Site Request Forgery (XSRF) Protection - * - * [XSRF](http://en.wikipedia.org/wiki/Cross-site_request_forgery) is a technique by which - * an unauthorized site can gain your user's private data. Angular provides a mechanism - * to counter XSRF. When performing XHR requests, the $http service reads a token from a cookie - * (by default, `XSRF-TOKEN`) and sets it as an HTTP header (`X-XSRF-TOKEN`). Since only - * JavaScript that runs on your domain could read the cookie, your server can be assured that - * the XHR came from JavaScript running on your domain. The header will not be set for - * cross-domain requests. - * - * To take advantage of this, your server needs to set a token in a JavaScript readable session - * cookie called `XSRF-TOKEN` on the first HTTP GET request. On subsequent XHR requests the - * server can verify that the cookie matches `X-XSRF-TOKEN` HTTP header, and therefore be sure - * that only JavaScript running on your domain could have sent the request. The token must be - * unique for each user and must be verifiable by the server (to prevent the JavaScript from - * making up its own tokens). We recommend that the token is a digest of your site's - * authentication cookie with a [salt](https://en.wikipedia.org/wiki/Salt_(cryptography)) - * for added security. - * - * The name of the headers can be specified using the xsrfHeaderName and xsrfCookieName - * properties of either $httpProvider.defaults at config-time, $http.defaults at run-time, - * or the per-request config object. - * - * - * @param {object} config Object describing the request to be made and how it should be - * processed. The object has following properties: - * - * - **method** – `{string}` – HTTP method (e.g. 'GET', 'POST', etc) - * - **url** – `{string}` – Absolute or relative URL of the resource that is being requested. - * - **params** – `{Object.}` – Map of strings or objects which will be turned - * to `?key1=value1&key2=value2` after the url. If the value is not a string, it will be - * JSONified. - * - **data** – `{string|Object}` – Data to be sent as the request message data. - * - **headers** – `{Object}` – Map of strings or functions which return strings representing - * HTTP headers to send to the server. If the return value of a function is null, the - * header will not be sent. - * - **xsrfHeaderName** – `{string}` – Name of HTTP header to populate with the XSRF token. - * - **xsrfCookieName** – `{string}` – Name of cookie containing the XSRF token. - * - **transformRequest** – - * `{function(data, headersGetter)|Array.}` – - * transform function or an array of such functions. The transform function takes the http - * request body and headers and returns its transformed (typically serialized) version. - * - **transformResponse** – - * `{function(data, headersGetter)|Array.}` – - * transform function or an array of such functions. The transform function takes the http - * response body and headers and returns its transformed (typically deserialized) version. - * - **cache** – `{boolean|Cache}` – If true, a default $http cache will be used to cache the - * GET request, otherwise if a cache instance built with - * {@link ng.$cacheFactory $cacheFactory}, this cache will be used for - * caching. - * - **timeout** – `{number|Promise}` – timeout in milliseconds, or {@link ng.$q promise} - * that should abort the request when resolved. - * - **withCredentials** - `{boolean}` - whether to set the `withCredentials` flag on the - * XHR object. See [requests with credentials]https://developer.mozilla.org/en/http_access_control#section_5 - * for more information. - * - **responseType** - `{string}` - see - * [requestType](https://developer.mozilla.org/en-US/docs/DOM/XMLHttpRequest#responseType). - * - * @returns {HttpPromise} Returns a {@link ng.$q promise} object with the - * standard `then` method and two http specific methods: `success` and `error`. The `then` - * method takes two arguments a success and an error callback which will be called with a - * response object. The `success` and `error` methods take a single argument - a function that - * will be called when the request succeeds or fails respectively. The arguments passed into - * these functions are destructured representation of the response object passed into the - * `then` method. The response object has these properties: - * - * - **data** – `{string|Object}` – The response body transformed with the transform - * functions. - * - **status** – `{number}` – HTTP status code of the response. - * - **headers** – `{function([headerName])}` – Header getter function. - * - **config** – `{Object}` – The configuration object that was used to generate the request. - * - **statusText** – `{string}` – HTTP status text of the response. - * - * @property {Array.} pendingRequests Array of config objects for currently pending - * requests. This is primarily meant to be used for debugging purposes. - * - * - * @example - - -
- - -
- - - -
http status code: {{status}}
-
http response data: {{data}}
-
-
- - function FetchCtrl($scope, $http, $templateCache) { - $scope.method = 'GET'; - $scope.url = 'http-hello.html'; - - $scope.fetch = function() { - $scope.code = null; - $scope.response = null; - - $http({method: $scope.method, url: $scope.url, cache: $templateCache}). - success(function(data, status) { - $scope.status = status; - $scope.data = data; - }). - error(function(data, status) { - $scope.data = data || "Request failed"; - $scope.status = status; - }); - }; - - $scope.updateModel = function(method, url) { - $scope.method = method; - $scope.url = url; - }; - } - - - Hello, $http! - - - var status = element(by.binding('status')); - var data = element(by.binding('data')); - var fetchBtn = element(by.id('fetchbtn')); - var sampleGetBtn = element(by.id('samplegetbtn')); - var sampleJsonpBtn = element(by.id('samplejsonpbtn')); - var invalidJsonpBtn = element(by.id('invalidjsonpbtn')); - - it('should make an xhr GET request', function() { - sampleGetBtn.click(); - fetchBtn.click(); - expect(status.getText()).toMatch('200'); - expect(data.getText()).toMatch(/Hello, \$http!/); - }); - - it('should make a JSONP request to angularjs.org', function() { - sampleJsonpBtn.click(); - fetchBtn.click(); - expect(status.getText()).toMatch('200'); - expect(data.getText()).toMatch(/Super Hero!/); - }); - - it('should make JSONP request to invalid URL and invoke the error handler', - function() { - invalidJsonpBtn.click(); - fetchBtn.click(); - expect(status.getText()).toMatch('0'); - expect(data.getText()).toMatch('Request failed'); - }); - -
- */ - function $http(requestConfig) { - var config = { - method: 'get', - transformRequest: defaults.transformRequest, - transformResponse: defaults.transformResponse - }; - var headers = mergeHeaders(requestConfig); - - extend(config, requestConfig); - config.headers = headers; - config.method = uppercase(config.method); - - var serverRequest = function(config) { - headers = config.headers; - var reqData = transformData(config.data, headersGetter(headers), config.transformRequest); - - // strip content-type if data is undefined - if (isUndefined(config.data)) { - forEach(headers, function(value, header) { - if (lowercase(header) === 'content-type') { - delete headers[header]; - } - }); - } - - if (isUndefined(config.withCredentials) && !isUndefined(defaults.withCredentials)) { - config.withCredentials = defaults.withCredentials; - } - - // send request - return sendReq(config, reqData, headers).then(transformResponse, transformResponse); - }; - - var chain = [serverRequest, undefined]; - var promise = $q.when(config); - - // apply interceptors - forEach(reversedInterceptors, function(interceptor) { - if (interceptor.request || interceptor.requestError) { - chain.unshift(interceptor.request, interceptor.requestError); - } - if (interceptor.response || interceptor.responseError) { - chain.push(interceptor.response, interceptor.responseError); - } - }); - - while(chain.length) { - var thenFn = chain.shift(); - var rejectFn = chain.shift(); - - promise = promise.then(thenFn, rejectFn); - } - - promise.success = function(fn) { - promise.then(function(response) { - fn(response.data, response.status, response.headers, config); - }); - return promise; - }; - - promise.error = function(fn) { - promise.then(null, function(response) { - fn(response.data, response.status, response.headers, config); - }); - return promise; - }; - - return promise; - - function transformResponse(response) { - // make a copy since the response must be cacheable - var resp = extend({}, response, { - data: transformData(response.data, response.headers, config.transformResponse) - }); - return (isSuccess(response.status)) - ? resp - : $q.reject(resp); - } - - function mergeHeaders(config) { - var defHeaders = defaults.headers, - reqHeaders = extend({}, config.headers), - defHeaderName, lowercaseDefHeaderName, reqHeaderName; - - defHeaders = extend({}, defHeaders.common, defHeaders[lowercase(config.method)]); - - // execute if header value is function - execHeaders(defHeaders); - execHeaders(reqHeaders); - - // using for-in instead of forEach to avoid unecessary iteration after header has been found - defaultHeadersIteration: - for (defHeaderName in defHeaders) { - lowercaseDefHeaderName = lowercase(defHeaderName); - - for (reqHeaderName in reqHeaders) { - if (lowercase(reqHeaderName) === lowercaseDefHeaderName) { - continue defaultHeadersIteration; - } - } - - reqHeaders[defHeaderName] = defHeaders[defHeaderName]; - } - - return reqHeaders; - - function execHeaders(headers) { - var headerContent; - - forEach(headers, function(headerFn, header) { - if (isFunction(headerFn)) { - headerContent = headerFn(); - if (headerContent != null) { - headers[header] = headerContent; - } else { - delete headers[header]; - } - } - }); - } - } - } - - $http.pendingRequests = []; - - /** - * @ngdoc method - * @name $http#get - * - * @description - * Shortcut method to perform `GET` request. - * - * @param {string} url Relative or absolute URL specifying the destination of the request - * @param {Object=} config Optional configuration object - * @returns {HttpPromise} Future object - */ - - /** - * @ngdoc method - * @name $http#delete - * - * @description - * Shortcut method to perform `DELETE` request. - * - * @param {string} url Relative or absolute URL specifying the destination of the request - * @param {Object=} config Optional configuration object - * @returns {HttpPromise} Future object - */ - - /** - * @ngdoc method - * @name $http#head - * - * @description - * Shortcut method to perform `HEAD` request. - * - * @param {string} url Relative or absolute URL specifying the destination of the request - * @param {Object=} config Optional configuration object - * @returns {HttpPromise} Future object - */ - - /** - * @ngdoc method - * @name $http#jsonp - * - * @description - * Shortcut method to perform `JSONP` request. - * - * @param {string} url Relative or absolute URL specifying the destination of the request. - * Should contain `JSON_CALLBACK` string. - * @param {Object=} config Optional configuration object - * @returns {HttpPromise} Future object - */ - createShortMethods('get', 'delete', 'head', 'jsonp'); - - /** - * @ngdoc method - * @name $http#post - * - * @description - * Shortcut method to perform `POST` request. - * - * @param {string} url Relative or absolute URL specifying the destination of the request - * @param {*} data Request content - * @param {Object=} config Optional configuration object - * @returns {HttpPromise} Future object - */ - - /** - * @ngdoc method - * @name $http#put - * - * @description - * Shortcut method to perform `PUT` request. - * - * @param {string} url Relative or absolute URL specifying the destination of the request - * @param {*} data Request content - * @param {Object=} config Optional configuration object - * @returns {HttpPromise} Future object - */ - createShortMethodsWithData('post', 'put'); - - /** - * @ngdoc property - * @name $http#defaults - * - * @description - * Runtime equivalent of the `$httpProvider.defaults` property. Allows configuration of - * default headers, withCredentials as well as request and response transformations. - * - * See "Setting HTTP Headers" and "Transforming Requests and Responses" sections above. - */ - $http.defaults = defaults; - - - return $http; - - - function createShortMethods(names) { - forEach(arguments, function(name) { - $http[name] = function(url, config) { - return $http(extend(config || {}, { - method: name, - url: url - })); - }; - }); - } - - - function createShortMethodsWithData(name) { - forEach(arguments, function(name) { - $http[name] = function(url, data, config) { - return $http(extend(config || {}, { - method: name, - url: url, - data: data - })); - }; - }); - } - - - /** - * Makes the request. - * - * !!! ACCESSES CLOSURE VARS: - * $httpBackend, defaults, $log, $rootScope, defaultCache, $http.pendingRequests - */ - function sendReq(config, reqData, reqHeaders) { - var deferred = $q.defer(), - promise = deferred.promise, - cache, - cachedResp, - url = buildUrl(config.url, config.params); - - $http.pendingRequests.push(config); - promise.then(removePendingReq, removePendingReq); - - - if ((config.cache || defaults.cache) && config.cache !== false && config.method == 'GET') { - cache = isObject(config.cache) ? config.cache - : isObject(defaults.cache) ? defaults.cache - : defaultCache; - } - - if (cache) { - cachedResp = cache.get(url); - if (isDefined(cachedResp)) { - if (cachedResp.then) { - // cached request has already been sent, but there is no response yet - cachedResp.then(removePendingReq, removePendingReq); - return cachedResp; - } else { - // serving from cache - if (isArray(cachedResp)) { - resolvePromise(cachedResp[1], cachedResp[0], shallowCopy(cachedResp[2]), cachedResp[3]); - } else { - resolvePromise(cachedResp, 200, {}, 'OK'); - } - } - } else { - // put the promise for the non-transformed response into cache as a placeholder - cache.put(url, promise); - } - } - - - // if we won't have the response in cache, set the xsrf headers and - // send the request to the backend - if (isUndefined(cachedResp)) { - var xsrfValue = urlIsSameOrigin(config.url) - ? $browser.cookies()[config.xsrfCookieName || defaults.xsrfCookieName] - : undefined; - if (xsrfValue) { - reqHeaders[(config.xsrfHeaderName || defaults.xsrfHeaderName)] = xsrfValue; - } - - $httpBackend(config.method, url, reqData, done, reqHeaders, config.timeout, - config.withCredentials, config.responseType); - } - - return promise; - - - /** - * Callback registered to $httpBackend(): - * - caches the response if desired - * - resolves the raw $http promise - * - calls $apply - */ - function done(status, response, headersString, statusText) { - if (cache) { - if (isSuccess(status)) { - cache.put(url, [status, response, parseHeaders(headersString), statusText]); - } else { - // remove promise from the cache - cache.remove(url); - } - } - - resolvePromise(response, status, headersString, statusText); - if (!$rootScope.$$phase) $rootScope.$apply(); - } - - - /** - * Resolves the raw $http promise. - */ - function resolvePromise(response, status, headers, statusText) { - // normalize internal statuses to 0 - status = Math.max(status, 0); - - (isSuccess(status) ? deferred.resolve : deferred.reject)({ - data: response, - status: status, - headers: headersGetter(headers), - config: config, - statusText : statusText - }); - } - - - function removePendingReq() { - var idx = indexOf($http.pendingRequests, config); - if (idx !== -1) $http.pendingRequests.splice(idx, 1); - } - } - - - function buildUrl(url, params) { - if (!params) return url; - var parts = []; - forEachSorted(params, function(value, key) { - if (value === null || isUndefined(value)) return; - if (!isArray(value)) value = [value]; - - forEach(value, function(v) { - if (isObject(v)) { - v = toJson(v); - } - parts.push(encodeUriQuery(key) + '=' + - encodeUriQuery(v)); - }); - }); - if(parts.length > 0) { - url += ((url.indexOf('?') == -1) ? '?' : '&') + parts.join('&'); - } - return url; - } - - - }]; -} - -function createXhr(method) { - //if IE and the method is not RFC2616 compliant, or if XMLHttpRequest - //is not available, try getting an ActiveXObject. Otherwise, use XMLHttpRequest - //if it is available - if (msie <= 8 && (!method.match(/^(get|post|head|put|delete|options)$/i) || - !window.XMLHttpRequest)) { - return new window.ActiveXObject("Microsoft.XMLHTTP"); - } else if (window.XMLHttpRequest) { - return new window.XMLHttpRequest(); - } - - throw minErr('$httpBackend')('noxhr', "This browser does not support XMLHttpRequest."); -} - -/** - * @ngdoc service - * @name $httpBackend - * @requires $window - * @requires $document - * - * @description - * HTTP backend used by the {@link ng.$http service} that delegates to - * XMLHttpRequest object or JSONP and deals with browser incompatibilities. - * - * You should never need to use this service directly, instead use the higher-level abstractions: - * {@link ng.$http $http} or {@link ngResource.$resource $resource}. - * - * During testing this implementation is swapped with {@link ngMock.$httpBackend mock - * $httpBackend} which can be trained with responses. - */ -function $HttpBackendProvider() { - this.$get = ['$browser', '$window', '$document', function($browser, $window, $document) { - return createHttpBackend($browser, createXhr, $browser.defer, $window.angular.callbacks, $document[0]); - }]; -} - -function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDocument) { - var ABORTED = -1; - - // TODO(vojta): fix the signature - return function(method, url, post, callback, headers, timeout, withCredentials, responseType) { - var status; - $browser.$$incOutstandingRequestCount(); - url = url || $browser.url(); - - if (lowercase(method) == 'jsonp') { - var callbackId = '_' + (callbacks.counter++).toString(36); - callbacks[callbackId] = function(data) { - callbacks[callbackId].data = data; - callbacks[callbackId].called = true; - }; - - var jsonpDone = jsonpReq(url.replace('JSON_CALLBACK', 'angular.callbacks.' + callbackId), - callbackId, function(status, text) { - completeRequest(callback, status, callbacks[callbackId].data, "", text); - callbacks[callbackId] = noop; - }); - } else { - - var xhr = createXhr(method); - - xhr.open(method, url, true); - forEach(headers, function(value, key) { - if (isDefined(value)) { - xhr.setRequestHeader(key, value); - } - }); - - // In IE6 and 7, this might be called synchronously when xhr.send below is called and the - // response is in the cache. the promise api will ensure that to the app code the api is - // always async - xhr.onreadystatechange = function() { - // onreadystatechange might get called multiple times with readyState === 4 on mobile webkit caused by - // xhrs that are resolved while the app is in the background (see #5426). - // since calling completeRequest sets the `xhr` variable to null, we just check if it's not null before - // continuing - // - // we can't set xhr.onreadystatechange to undefined or delete it because that breaks IE8 (method=PATCH) and - // Safari respectively. - if (xhr && xhr.readyState == 4) { - var responseHeaders = null, - response = null; - - if(status !== ABORTED) { - responseHeaders = xhr.getAllResponseHeaders(); - - // responseText is the old-school way of retrieving response (supported by IE8 & 9) - // response/responseType properties were introduced in XHR Level2 spec (supported by IE10) - response = ('response' in xhr) ? xhr.response : xhr.responseText; - } - - completeRequest(callback, - status || xhr.status, - response, - responseHeaders, - xhr.statusText || ''); - } - }; - - if (withCredentials) { - xhr.withCredentials = true; - } - - if (responseType) { - try { - xhr.responseType = responseType; - } catch (e) { - // WebKit added support for the json responseType value on 09/03/2013 - // https://bugs.webkit.org/show_bug.cgi?id=73648. Versions of Safari prior to 7 are - // known to throw when setting the value "json" as the response type. Other older - // browsers implementing the responseType - // - // The json response type can be ignored if not supported, because JSON payloads are - // parsed on the client-side regardless. - if (responseType !== 'json') { - throw e; - } - } - } - - xhr.send(post || null); - } - - if (timeout > 0) { - var timeoutId = $browserDefer(timeoutRequest, timeout); - } else if (timeout && timeout.then) { - timeout.then(timeoutRequest); - } - - - function timeoutRequest() { - status = ABORTED; - jsonpDone && jsonpDone(); - xhr && xhr.abort(); - } - - function completeRequest(callback, status, response, headersString, statusText) { - // cancel timeout and subsequent timeout promise resolution - timeoutId && $browserDefer.cancel(timeoutId); - jsonpDone = xhr = null; - - // fix status code when it is 0 (0 status is undocumented). - // Occurs when accessing file resources or on Android 4.1 stock browser - // while retrieving files from application cache. - if (status === 0) { - status = response ? 200 : urlResolve(url).protocol == 'file' ? 404 : 0; - } - - // normalize IE bug (http://bugs.jquery.com/ticket/1450) - status = status === 1223 ? 204 : status; - statusText = statusText || ''; - - callback(status, response, headersString, statusText); - $browser.$$completeOutstandingRequest(noop); - } - }; - - function jsonpReq(url, callbackId, done) { - // we can't use jQuery/jqLite here because jQuery does crazy shit with script elements, e.g.: - // - fetches local scripts via XHR and evals them - // - adds and immediately removes script elements from the document - var script = rawDocument.createElement('script'), callback = null; - script.type = "text/javascript"; - script.src = url; - script.async = true; - - callback = function(event) { - removeEventListenerFn(script, "load", callback); - removeEventListenerFn(script, "error", callback); - rawDocument.body.removeChild(script); - script = null; - var status = -1; - var text = "unknown"; - - if (event) { - if (event.type === "load" && !callbacks[callbackId].called) { - event = { type: "error" }; - } - text = event.type; - status = event.type === "error" ? 404 : 200; - } - - if (done) { - done(status, text); - } - }; - - addEventListenerFn(script, "load", callback); - addEventListenerFn(script, "error", callback); - rawDocument.body.appendChild(script); - return callback; - } -} - -var $interpolateMinErr = minErr('$interpolate'); - -/** - * @ngdoc provider - * @name $interpolateProvider - * @kind function - * - * @description - * - * Used for configuring the interpolation markup. Defaults to `{{` and `}}`. - * - * @example - - - -
- //demo.label// -
-
- - it('should interpolate binding with custom symbols', function() { - expect(element(by.binding('demo.label')).getText()).toBe('This binding is brought you by // interpolation symbols.'); - }); - -
- */ -function $InterpolateProvider() { - var startSymbol = '{{'; - var endSymbol = '}}'; - - /** - * @ngdoc method - * @name $interpolateProvider#startSymbol - * @description - * Symbol to denote start of expression in the interpolated string. Defaults to `{{`. - * - * @param {string=} value new value to set the starting symbol to. - * @returns {string|self} Returns the symbol when used as getter and self if used as setter. - */ - this.startSymbol = function(value){ - if (value) { - startSymbol = value; - return this; - } else { - return startSymbol; - } - }; - - /** - * @ngdoc method - * @name $interpolateProvider#endSymbol - * @description - * Symbol to denote the end of expression in the interpolated string. Defaults to `}}`. - * - * @param {string=} value new value to set the ending symbol to. - * @returns {string|self} Returns the symbol when used as getter and self if used as setter. - */ - this.endSymbol = function(value){ - if (value) { - endSymbol = value; - return this; - } else { - return endSymbol; - } - }; - - - this.$get = ['$parse', '$exceptionHandler', '$sce', function($parse, $exceptionHandler, $sce) { - var startSymbolLength = startSymbol.length, - endSymbolLength = endSymbol.length, - escapedStartRegexp = new RegExp(startSymbol.replace(/./g, escape), 'g'), - escapedEndRegexp = new RegExp(endSymbol.replace(/./g, escape), 'g'); - - function escape(ch) { - return '\\\\\\' + ch; - } - - /** - * @ngdoc service - * @name $interpolate - * @kind function - * - * @requires $parse - * @requires $sce - * - * @description - * - * Compiles a string with markup into an interpolation function. This service is used by the - * HTML {@link ng.$compile $compile} service for data binding. See - * {@link ng.$interpolateProvider $interpolateProvider} for configuring the - * interpolation markup. - * - * - * ```js - * var $interpolate = ...; // injected - * var exp = $interpolate('Hello {{name | uppercase}}!'); - * expect(exp({name:'Angular'}).toEqual('Hello ANGULAR!'); - * ``` - * - * `$interpolate` takes an optional fourth argument, `allOrNothing`. If `allOrNothing` is - * `true`, the interpolation function will return `undefined` unless all embedded expressions - * evaluate to a value other than `undefined`. - * - * ```js - * var $interpolate = ...; // injected - * var context = {greeting: 'Hello', name: undefined }; - * - * // default "forgiving" mode - * var exp = $interpolate('{{greeting}} {{name}}!'); - * expect(exp(context)).toEqual('Hello !'); - * - * // "allOrNothing" mode - * exp = $interpolate('{{greeting}} {{name}}!', false, null, true); - * expect(exp(context, true)).toBeUndefined(); - * context.name = 'Angular'; - * expect(exp(context, true)).toEqual('Hello Angular!'); - * ``` - * - * `allOrNothing` is useful for interpolating URLs. `ngSrc` and `ngSrcset` use this behavior. - * - * ####Escaped Interpolation - * $interpolate provides a mechanism for escaping interpolation markers. Start and end markers - * can be escaped by preceding each of their characters with a REVERSE SOLIDUS U+005C (backslash). - * It will be rendered as a regular start/end marker, and will not be interpreted as an expression - * or binding. - * - * This enables web-servers to prevent script injection attacks and defacing attacks, to some - * degree, while also enabling code examples to work without relying on the - * {@link ng.directive:ngNonBindable ngNonBindable} directive. - * - * **For security purposes, it is strongly encouraged that web servers escape user-supplied data, - * replacing angle brackets (<, >) with &lt; and &gt; respectively, and replacing all - * interpolation start/end markers with their escaped counterparts.** - * - * Escaped interpolation markers are only replaced with the actual interpolation markers in rendered - * output when the $interpolate service processes the text. So, for HTML elements interpolated - * by {@link ng.$compile $compile}, or otherwise interpolated with the `mustHaveExpression` parameter - * set to `true`, the interpolated text must contain an unescaped interpolation expression. As such, - * this is typically useful only when user-data is used in rendering a template from the server, or - * when otherwise untrusted data is used by a directive. - * - * - * - *
- *

{{apptitle}}: \{\{ username = "defaced value"; \}\} - *

- *

{{username}} attempts to inject code which will deface the - * application, but fails to accomplish their task, because the server has correctly - * escaped the interpolation start/end markers with REVERSE SOLIDUS U+005C (backslash) - * characters.

- *

Instead, the result of the attempted script injection is visible, and can be removed - * from the database by an administrator.

- *
- *
- *
- * - * @param {string} text The text with markup to interpolate. - * @param {boolean=} mustHaveExpression if set to true then the interpolation string must have - * embedded expression in order to return an interpolation function. Strings with no - * embedded expression will return null for the interpolation function. - * @param {string=} trustedContext when provided, the returned function passes the interpolated - * result through {@link ng.$sce#getTrusted $sce.getTrusted(interpolatedResult, - * trustedContext)} before returning it. Refer to the {@link ng.$sce $sce} service that - * provides Strict Contextual Escaping for details. - * @param {boolean=} allOrNothing if `true`, then the returned function returns undefined - * unless all embedded expressions evaluate to a value other than `undefined`. - * @returns {function(context)} an interpolation function which is used to compute the - * interpolated string. The function has these parameters: - * - * - `context`: evaluation context for all expressions embedded in the interpolated text - */ - function $interpolate(text, mustHaveExpression, trustedContext, allOrNothing) { - allOrNothing = !!allOrNothing; - var startIndex, - endIndex, - index = 0, - separators = [], - expressions = [], - parseFns = [], - textLength = text.length, - hasInterpolation = false, - hasText = false, - exp, - concat = [], - lastValuesCache = { values: {}, results: {}}; - - while(index < textLength) { - if ( ((startIndex = text.indexOf(startSymbol, index)) != -1) && - ((endIndex = text.indexOf(endSymbol, startIndex + startSymbolLength)) != -1) ) { - if (index !== startIndex) hasText = true; - separators.push(text.substring(index, startIndex)); - exp = text.substring(startIndex + startSymbolLength, endIndex); - expressions.push(exp); - parseFns.push($parse(exp)); - index = endIndex + endSymbolLength; - hasInterpolation = true; - } else { - // we did not find an interpolation, so we have to add the remainder to the separators array - if (index !== textLength) { - hasText = true; - separators.push(text.substring(index)); - } - break; - } - } - - forEach(separators, function(key, i) { - separators[i] = separators[i]. - replace(escapedStartRegexp, startSymbol). - replace(escapedEndRegexp, endSymbol); - }); - - if (separators.length === expressions.length) { - separators.push(''); - } - - // Concatenating expressions makes it hard to reason about whether some combination of - // concatenated values are unsafe to use and could easily lead to XSS. By requiring that a - // single expression be used for iframe[src], object[src], etc., we ensure that the value - // that's used is assigned or constructed by some JS code somewhere that is more testable or - // make it obvious that you bound the value to some user controlled value. This helps reduce - // the load when auditing for XSS issues. - if (trustedContext && hasInterpolation && (hasText || expressions.length > 1)) { - throw $interpolateMinErr('noconcat', - "Error while interpolating: {0}\nStrict Contextual Escaping disallows " + - "interpolations that concatenate multiple expressions when a trusted value is " + - "required. See http://docs.angularjs.org/api/ng.$sce", text); - } - - if (!mustHaveExpression || hasInterpolation) { - concat.length = separators.length + expressions.length; - - var compute = function(values) { - for(var i = 0, ii = expressions.length; i < ii; i++) { - concat[2*i] = separators[i]; - concat[(2*i)+1] = values[i]; - } - concat[2*ii] = separators[ii]; - return concat.join(''); - }; - - var getValue = function (value) { - if (trustedContext) { - value = $sce.getTrusted(trustedContext, value); - } else { - value = $sce.valueOf(value); - } - - return value; - }; - - var stringify = function (value) { - if (value == null) { // null || undefined - return ''; - } - switch (typeof value) { - case 'string': { - break; - } - case 'number': { - value = '' + value; - break; - } - default: { - value = toJson(value); - } - } - - return value; - }; - - return extend(function interpolationFn(context) { - var scopeId = (context && context.$id) || 'notAScope'; - var lastValues = lastValuesCache.values[scopeId]; - var lastResult = lastValuesCache.results[scopeId]; - var i = 0; - var ii = expressions.length; - var values = new Array(ii); - var val; - var inputsChanged = lastResult === undefined ? true: false; - - - // if we haven't seen this context before, initialize the cache and try to setup - // a cleanup routine that purges the cache when the scope goes away. - if (!lastValues) { - lastValues = []; - inputsChanged = true; - if (context && context.$on) { - context.$on('$destroy', function() { - lastValuesCache.values[scopeId] = null; - lastValuesCache.results[scopeId] = null; - }); - } - } - - - try { - interpolationFn.$$unwatch = true; - for (; i < ii; i++) { - val = getValue(parseFns[i](context)); - if (allOrNothing && isUndefined(val)) { - interpolationFn.$$unwatch = undefined; - return; - } - val = stringify(val); - if (val !== lastValues[i]) { - inputsChanged = true; - } - values[i] = val; - interpolationFn.$$unwatch = interpolationFn.$$unwatch && parseFns[i].$$unwatch; - } - - if (inputsChanged) { - lastValuesCache.values[scopeId] = values; - lastValuesCache.results[scopeId] = lastResult = compute(values); - } - } catch(err) { - var newErr = $interpolateMinErr('interr', "Can't interpolate: {0}\n{1}", text, - err.toString()); - $exceptionHandler(newErr); - } - - return lastResult; - }, { - // all of these properties are undocumented for now - exp: text, //just for compatibility with regular watchers created via $watch - separators: separators, - expressions: expressions - }); - } - } - - - /** - * @ngdoc method - * @name $interpolate#startSymbol - * @description - * Symbol to denote the start of expression in the interpolated string. Defaults to `{{`. - * - * Use {@link ng.$interpolateProvider#startSymbol $interpolateProvider#startSymbol} to change - * the symbol. - * - * @returns {string} start symbol. - */ - $interpolate.startSymbol = function() { - return startSymbol; - }; - - - /** - * @ngdoc method - * @name $interpolate#endSymbol - * @description - * Symbol to denote the end of expression in the interpolated string. Defaults to `}}`. - * - * Use {@link ng.$interpolateProvider#endSymbol $interpolateProvider#endSymbol} to change - * the symbol. - * - * @returns {string} end symbol. - */ - $interpolate.endSymbol = function() { - return endSymbol; - }; - - return $interpolate; - }]; -} - -function $IntervalProvider() { - this.$get = ['$rootScope', '$window', '$q', - function($rootScope, $window, $q) { - var intervals = {}; - - - /** - * @ngdoc service - * @name $interval - * - * @description - * Angular's wrapper for `window.setInterval`. The `fn` function is executed every `delay` - * milliseconds. - * - * The return value of registering an interval function is a promise. This promise will be - * notified upon each tick of the interval, and will be resolved after `count` iterations, or - * run indefinitely if `count` is not defined. The value of the notification will be the - * number of iterations that have run. - * To cancel an interval, call `$interval.cancel(promise)`. - * - * In tests you can use {@link ngMock.$interval#flush `$interval.flush(millis)`} to - * move forward by `millis` milliseconds and trigger any functions scheduled to run in that - * time. - * - *
- * **Note**: Intervals created by this service must be explicitly destroyed when you are finished - * with them. In particular they are not automatically destroyed when a controller's scope or a - * directive's element are destroyed. - * You should take this into consideration and make sure to always cancel the interval at the - * appropriate moment. See the example below for more details on how and when to do this. - *
- * - * @param {function()} fn A function that should be called repeatedly. - * @param {number} delay Number of milliseconds between each function call. - * @param {number=} [count=0] Number of times to repeat. If not set, or 0, will repeat - * indefinitely. - * @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise - * will invoke `fn` within the {@link ng.$rootScope.Scope#$apply $apply} block. - * @returns {promise} A promise which will be notified on each iteration. - * - * @example - * - * - * - * - *
- *
- * Date format:
- * Current time is: - *
- * Blood 1 : {{blood_1}} - * Blood 2 : {{blood_2}} - * - * - * - *
- *
- * - *
- *
- */ - function interval(fn, delay, count, invokeApply) { - var setInterval = $window.setInterval, - clearInterval = $window.clearInterval, - deferred = $q.defer(), - promise = deferred.promise, - iteration = 0, - skipApply = (isDefined(invokeApply) && !invokeApply); - - count = isDefined(count) ? count : 0; - - promise.then(null, null, fn); - - promise.$$intervalId = setInterval(function tick() { - deferred.notify(iteration++); - - if (count > 0 && iteration >= count) { - deferred.resolve(iteration); - clearInterval(promise.$$intervalId); - delete intervals[promise.$$intervalId]; - } - - if (!skipApply) $rootScope.$apply(); - - }, delay); - - intervals[promise.$$intervalId] = deferred; - - return promise; - } - - - /** - * @ngdoc method - * @name $interval#cancel - * - * @description - * Cancels a task associated with the `promise`. - * - * @param {promise} promise returned by the `$interval` function. - * @returns {boolean} Returns `true` if the task was successfully canceled. - */ - interval.cancel = function(promise) { - if (promise && promise.$$intervalId in intervals) { - intervals[promise.$$intervalId].reject('canceled'); - clearInterval(promise.$$intervalId); - delete intervals[promise.$$intervalId]; - return true; - } - return false; - }; - - return interval; - }]; -} - -/** - * @ngdoc service - * @name $locale - * - * @description - * $locale service provides localization rules for various Angular components. As of right now the - * only public api is: - * - * * `id` – `{string}` – locale id formatted as `languageId-countryId` (e.g. `en-us`) - */ -function $LocaleProvider(){ - this.$get = function() { - return { - id: 'en-us', - - NUMBER_FORMATS: { - DECIMAL_SEP: '.', - GROUP_SEP: ',', - PATTERNS: [ - { // Decimal Pattern - minInt: 1, - minFrac: 0, - maxFrac: 3, - posPre: '', - posSuf: '', - negPre: '-', - negSuf: '', - gSize: 3, - lgSize: 3 - },{ //Currency Pattern - minInt: 1, - minFrac: 2, - maxFrac: 2, - posPre: '\u00A4', - posSuf: '', - negPre: '(\u00A4', - negSuf: ')', - gSize: 3, - lgSize: 3 - } - ], - CURRENCY_SYM: '$' - }, - - DATETIME_FORMATS: { - MONTH: - 'January,February,March,April,May,June,July,August,September,October,November,December' - .split(','), - SHORTMONTH: 'Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec'.split(','), - DAY: 'Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday'.split(','), - SHORTDAY: 'Sun,Mon,Tue,Wed,Thu,Fri,Sat'.split(','), - AMPMS: ['AM','PM'], - medium: 'MMM d, y h:mm:ss a', - short: 'M/d/yy h:mm a', - fullDate: 'EEEE, MMMM d, y', - longDate: 'MMMM d, y', - mediumDate: 'MMM d, y', - shortDate: 'M/d/yy', - mediumTime: 'h:mm:ss a', - shortTime: 'h:mm a' - }, - - pluralCat: function(num) { - if (num === 1) { - return 'one'; - } - return 'other'; - } - }; - }; -} - -var PATH_MATCH = /^([^\?#]*)(\?([^#]*))?(#(.*))?$/, - DEFAULT_PORTS = {'http': 80, 'https': 443, 'ftp': 21}; -var $locationMinErr = minErr('$location'); - - -/** - * Encode path using encodeUriSegment, ignoring forward slashes - * - * @param {string} path Path to encode - * @returns {string} - */ -function encodePath(path) { - var segments = path.split('/'), - i = segments.length; - - while (i--) { - segments[i] = encodeUriSegment(segments[i]); - } - - return segments.join('/'); -} - -function parseAbsoluteUrl(absoluteUrl, locationObj, appBase) { - var parsedUrl = urlResolve(absoluteUrl, appBase); - - locationObj.$$protocol = parsedUrl.protocol; - locationObj.$$host = parsedUrl.hostname; - locationObj.$$port = int(parsedUrl.port) || DEFAULT_PORTS[parsedUrl.protocol] || null; -} - - -function parseAppUrl(relativeUrl, locationObj, appBase) { - var prefixed = (relativeUrl.charAt(0) !== '/'); - if (prefixed) { - relativeUrl = '/' + relativeUrl; - } - var match = urlResolve(relativeUrl, appBase); - locationObj.$$path = decodeURIComponent(prefixed && match.pathname.charAt(0) === '/' ? - match.pathname.substring(1) : match.pathname); - locationObj.$$search = parseKeyValue(match.search); - locationObj.$$hash = decodeURIComponent(match.hash); - - // make sure path starts with '/'; - if (locationObj.$$path && locationObj.$$path.charAt(0) != '/') { - locationObj.$$path = '/' + locationObj.$$path; - } -} - - -/** - * - * @param {string} begin - * @param {string} whole - * @returns {string} returns text from whole after begin or undefined if it does not begin with - * expected string. - */ -function beginsWith(begin, whole) { - if (whole.indexOf(begin) === 0) { - return whole.substr(begin.length); - } -} - - -function stripHash(url) { - var index = url.indexOf('#'); - return index == -1 ? url : url.substr(0, index); -} - - -function stripFile(url) { - return url.substr(0, stripHash(url).lastIndexOf('/') + 1); -} - -/* return the server only (scheme://host:port) */ -function serverBase(url) { - return url.substring(0, url.indexOf('/', url.indexOf('//') + 2)); -} - - -/** - * LocationHtml5Url represents an url - * This object is exposed as $location service when HTML5 mode is enabled and supported - * - * @constructor - * @param {string} appBase application base URL - * @param {string} basePrefix url path prefix - */ -function LocationHtml5Url(appBase, basePrefix) { - this.$$html5 = true; - basePrefix = basePrefix || ''; - var appBaseNoFile = stripFile(appBase); - parseAbsoluteUrl(appBase, this, appBase); - - - /** - * Parse given html5 (regular) url string into properties - * @param {string} newAbsoluteUrl HTML5 url - * @private - */ - this.$$parse = function(url) { - var pathUrl = beginsWith(appBaseNoFile, url); - if (!isString(pathUrl)) { - throw $locationMinErr('ipthprfx', 'Invalid url "{0}", missing path prefix "{1}".', url, - appBaseNoFile); - } - - parseAppUrl(pathUrl, this, appBase); - - if (!this.$$path) { - this.$$path = '/'; - } - - this.$$compose(); - }; - - /** - * Compose url and update `absUrl` property - * @private - */ - this.$$compose = function() { - var search = toKeyValue(this.$$search), - hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : ''; - - this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash; - this.$$absUrl = appBaseNoFile + this.$$url.substr(1); // first char is always '/' - }; - - this.$$rewrite = function(url) { - var appUrl, prevAppUrl; - - if ( (appUrl = beginsWith(appBase, url)) !== undefined ) { - prevAppUrl = appUrl; - if ( (appUrl = beginsWith(basePrefix, appUrl)) !== undefined ) { - return appBaseNoFile + (beginsWith('/', appUrl) || appUrl); - } else { - return appBase + prevAppUrl; - } - } else if ( (appUrl = beginsWith(appBaseNoFile, url)) !== undefined ) { - return appBaseNoFile + appUrl; - } else if (appBaseNoFile == url + '/') { - return appBaseNoFile; - } - }; -} - - -/** - * LocationHashbangUrl represents url - * This object is exposed as $location service when developer doesn't opt into html5 mode. - * It also serves as the base class for html5 mode fallback on legacy browsers. - * - * @constructor - * @param {string} appBase application base URL - * @param {string} hashPrefix hashbang prefix - */ -function LocationHashbangUrl(appBase, hashPrefix) { - var appBaseNoFile = stripFile(appBase); - - parseAbsoluteUrl(appBase, this, appBase); - - - /** - * Parse given hashbang url into properties - * @param {string} url Hashbang url - * @private - */ - this.$$parse = function(url) { - var withoutBaseUrl = beginsWith(appBase, url) || beginsWith(appBaseNoFile, url); - var withoutHashUrl = withoutBaseUrl.charAt(0) == '#' - ? beginsWith(hashPrefix, withoutBaseUrl) - : (this.$$html5) - ? withoutBaseUrl - : ''; - - if (!isString(withoutHashUrl)) { - throw $locationMinErr('ihshprfx', 'Invalid url "{0}", missing hash prefix "{1}".', url, - hashPrefix); - } - parseAppUrl(withoutHashUrl, this, appBase); - - this.$$path = removeWindowsDriveName(this.$$path, withoutHashUrl, appBase); - - this.$$compose(); - - /* - * In Windows, on an anchor node on documents loaded from - * the filesystem, the browser will return a pathname - * prefixed with the drive name ('/C:/path') when a - * pathname without a drive is set: - * * a.setAttribute('href', '/foo') - * * a.pathname === '/C:/foo' //true - * - * Inside of Angular, we're always using pathnames that - * do not include drive names for routing. - */ - function removeWindowsDriveName (path, url, base) { - /* - Matches paths for file protocol on windows, - such as /C:/foo/bar, and captures only /foo/bar. - */ - var windowsFilePathExp = /^\/[A-Z]:(\/.*)/; - - var firstPathSegmentMatch; - - //Get the relative path from the input URL. - if (url.indexOf(base) === 0) { - url = url.replace(base, ''); - } - - // The input URL intentionally contains a first path segment that ends with a colon. - if (windowsFilePathExp.exec(url)) { - return path; - } - - firstPathSegmentMatch = windowsFilePathExp.exec(path); - return firstPathSegmentMatch ? firstPathSegmentMatch[1] : path; - } - }; - - /** - * Compose hashbang url and update `absUrl` property - * @private - */ - this.$$compose = function() { - var search = toKeyValue(this.$$search), - hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : ''; - - this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash; - this.$$absUrl = appBase + (this.$$url ? hashPrefix + this.$$url : ''); - }; - - this.$$rewrite = function(url) { - if(stripHash(appBase) == stripHash(url)) { - return url; - } - }; -} - - -/** - * LocationHashbangUrl represents url - * This object is exposed as $location service when html5 history api is enabled but the browser - * does not support it. - * - * @constructor - * @param {string} appBase application base URL - * @param {string} hashPrefix hashbang prefix - */ -function LocationHashbangInHtml5Url(appBase, hashPrefix) { - this.$$html5 = true; - LocationHashbangUrl.apply(this, arguments); - - var appBaseNoFile = stripFile(appBase); - - this.$$rewrite = function(url) { - var appUrl; - - if ( appBase == stripHash(url) ) { - return url; - } else if ( (appUrl = beginsWith(appBaseNoFile, url)) ) { - return appBase + hashPrefix + appUrl; - } else if ( appBaseNoFile === url + '/') { - return appBaseNoFile; - } - }; - - this.$$compose = function() { - var search = toKeyValue(this.$$search), - hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : ''; - - this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash; - // include hashPrefix in $$absUrl when $$url is empty so IE8 & 9 do not reload page because of removal of '#' - this.$$absUrl = appBase + hashPrefix + this.$$url; - }; - -} - - -LocationHashbangInHtml5Url.prototype = - LocationHashbangUrl.prototype = - LocationHtml5Url.prototype = { - - /** - * Are we in html5 mode? - * @private - */ - $$html5: false, - - /** - * Has any change been replacing ? - * @private - */ - $$replace: false, - - /** - * @ngdoc method - * @name $location#absUrl - * - * @description - * This method is getter only. - * - * Return full url representation with all segments encoded according to rules specified in - * [RFC 3986](http://www.ietf.org/rfc/rfc3986.txt). - * - * @return {string} full url - */ - absUrl: locationGetter('$$absUrl'), - - /** - * @ngdoc method - * @name $location#url - * - * @description - * This method is getter / setter. - * - * Return url (e.g. `/path?a=b#hash`) when called without any parameter. - * - * Change path, search and hash, when called with parameter and return `$location`. - * - * @param {string=} url New url without base prefix (e.g. `/path?a=b#hash`) - * @param {string=} replace The path that will be changed - * @return {string} url - */ - url: function(url, replace) { - if (isUndefined(url)) - return this.$$url; - - var match = PATH_MATCH.exec(url); - if (match[1]) this.path(decodeURIComponent(match[1])); - if (match[2] || match[1]) this.search(match[3] || ''); - this.hash(match[5] || '', replace); - - return this; - }, - - /** - * @ngdoc method - * @name $location#protocol - * - * @description - * This method is getter only. - * - * Return protocol of current url. - * - * @return {string} protocol of current url - */ - protocol: locationGetter('$$protocol'), - - /** - * @ngdoc method - * @name $location#host - * - * @description - * This method is getter only. - * - * Return host of current url. - * - * @return {string} host of current url. - */ - host: locationGetter('$$host'), - - /** - * @ngdoc method - * @name $location#port - * - * @description - * This method is getter only. - * - * Return port of current url. - * - * @return {Number} port - */ - port: locationGetter('$$port'), - - /** - * @ngdoc method - * @name $location#path - * - * @description - * This method is getter / setter. - * - * Return path of current url when called without any parameter. - * - * Change path when called with parameter and return `$location`. - * - * Note: Path should always begin with forward slash (/), this method will add the forward slash - * if it is missing. - * - * @param {string=} path New path - * @return {string} path - */ - path: locationGetterSetter('$$path', function(path) { - return path.charAt(0) == '/' ? path : '/' + path; - }), - - /** - * @ngdoc method - * @name $location#search - * - * @description - * This method is getter / setter. - * - * Return search part (as object) of current url when called without any parameter. - * - * Change search part when called with parameter and return `$location`. - * - * - * ```js - * // given url http://example.com/#/some/path?foo=bar&baz=xoxo - * var searchObject = $location.search(); - * // => {foo: 'bar', baz: 'xoxo'} - * - * - * // set foo to 'yipee' - * $location.search('foo', 'yipee'); - * // => $location - * ``` - * - * @param {string|Object.|Object.>} search New search params - string or - * hash object. - * - * When called with a single argument the method acts as a setter, setting the `search` component - * of `$location` to the specified value. - * - * If the argument is a hash object containing an array of values, these values will be encoded - * as duplicate search parameters in the url. - * - * @param {(string|Array)=} paramValue If `search` is a string, then `paramValue` will - * override only a single search property. - * - * If `paramValue` is an array, it will override the property of the `search` component of - * `$location` specified via the first argument. - * - * If `paramValue` is `null`, the property specified via the first argument will be deleted. - * - * @return {Object} If called with no arguments returns the parsed `search` object. If called with - * one or more arguments returns `$location` object itself. - */ - search: function(search, paramValue) { - switch (arguments.length) { - case 0: - return this.$$search; - case 1: - if (isString(search)) { - this.$$search = parseKeyValue(search); - } else if (isObject(search)) { - this.$$search = search; - } else { - throw $locationMinErr('isrcharg', - 'The first argument of the `$location#search()` call must be a string or an object.'); - } - break; - default: - if (isUndefined(paramValue) || paramValue === null) { - delete this.$$search[search]; - } else { - this.$$search[search] = paramValue; - } - } - - this.$$compose(); - return this; - }, - - /** - * @ngdoc method - * @name $location#hash - * - * @description - * This method is getter / setter. - * - * Return hash fragment when called without any parameter. - * - * Change hash fragment when called with parameter and return `$location`. - * - * @param {string=} hash New hash fragment - * @return {string} hash - */ - hash: locationGetterSetter('$$hash', identity), - - /** - * @ngdoc method - * @name $location#replace - * - * @description - * If called, all changes to $location during current `$digest` will be replacing current history - * record, instead of adding new one. - */ - replace: function() { - this.$$replace = true; - return this; - } -}; - -function locationGetter(property) { - return function() { - return this[property]; - }; -} - - -function locationGetterSetter(property, preprocess) { - return function(value) { - if (isUndefined(value)) - return this[property]; - - this[property] = preprocess(value); - this.$$compose(); - - return this; - }; -} - - -/** - * @ngdoc service - * @name $location - * - * @requires $rootElement - * - * @description - * The $location service parses the URL in the browser address bar (based on the - * [window.location](https://developer.mozilla.org/en/window.location)) and makes the URL - * available to your application. Changes to the URL in the address bar are reflected into - * $location service and changes to $location are reflected into the browser address bar. - * - * **The $location service:** - * - * - Exposes the current URL in the browser address bar, so you can - * - Watch and observe the URL. - * - Change the URL. - * - Synchronizes the URL with the browser when the user - * - Changes the address bar. - * - Clicks the back or forward button (or clicks a History link). - * - Clicks on a link. - * - Represents the URL object as a set of methods (protocol, host, port, path, search, hash). - * - * For more information see {@link guide/$location Developer Guide: Using $location} - */ - -/** - * @ngdoc provider - * @name $locationProvider - * @description - * Use the `$locationProvider` to configure how the application deep linking paths are stored. - */ -function $LocationProvider(){ - var hashPrefix = '', - html5Mode = false; - - /** - * @ngdoc property - * @name $locationProvider#hashPrefix - * @description - * @param {string=} prefix Prefix for hash part (containing path and search) - * @returns {*} current value if used as getter or itself (chaining) if used as setter - */ - this.hashPrefix = function(prefix) { - if (isDefined(prefix)) { - hashPrefix = prefix; - return this; - } else { - return hashPrefix; - } - }; - - /** - * @ngdoc property - * @name $locationProvider#html5Mode - * @description - * @param {boolean=} mode Use HTML5 strategy if available. - * @returns {*} current value if used as getter or itself (chaining) if used as setter - */ - this.html5Mode = function(mode) { - if (isDefined(mode)) { - html5Mode = mode; - return this; - } else { - return html5Mode; - } - }; - - /** - * @ngdoc event - * @name $location#$locationChangeStart - * @eventType broadcast on root scope - * @description - * Broadcasted before a URL will change. This change can be prevented by calling - * `preventDefault` method of the event. See {@link ng.$rootScope.Scope#$on} for more - * details about event object. Upon successful change - * {@link ng.$location#events_$locationChangeSuccess $locationChangeSuccess} is fired. - * - * @param {Object} angularEvent Synthetic event object. - * @param {string} newUrl New URL - * @param {string=} oldUrl URL that was before it was changed. - */ - - /** - * @ngdoc event - * @name $location#$locationChangeSuccess - * @eventType broadcast on root scope - * @description - * Broadcasted after a URL was changed. - * - * @param {Object} angularEvent Synthetic event object. - * @param {string} newUrl New URL - * @param {string=} oldUrl URL that was before it was changed. - */ - - this.$get = ['$rootScope', '$browser', '$sniffer', '$rootElement', - function( $rootScope, $browser, $sniffer, $rootElement) { - var $location, - LocationMode, - baseHref = $browser.baseHref(), // if base[href] is undefined, it defaults to '' - initialUrl = $browser.url(), - appBase; - - if (html5Mode) { - appBase = serverBase(initialUrl) + (baseHref || '/'); - LocationMode = $sniffer.history ? LocationHtml5Url : LocationHashbangInHtml5Url; - } else { - appBase = stripHash(initialUrl); - LocationMode = LocationHashbangUrl; - } - $location = new LocationMode(appBase, '#' + hashPrefix); - $location.$$parse($location.$$rewrite(initialUrl)); - - $rootElement.on('click', function(event) { - // TODO(vojta): rewrite link when opening in new tab/window (in legacy browser) - // currently we open nice url link and redirect then - - if (event.ctrlKey || event.metaKey || event.which == 2) return; - - var elm = jqLite(event.target); - - // traverse the DOM up to find first A tag - while (nodeName_(elm[0]) !== 'a') { - // ignore rewriting if no A tag (reached root element, or no parent - removed from document) - if (elm[0] === $rootElement[0] || !(elm = elm.parent())[0]) return; - } - - var absHref = elm.prop('href'); - - if (isObject(absHref) && absHref.toString() === '[object SVGAnimatedString]') { - // SVGAnimatedString.animVal should be identical to SVGAnimatedString.baseVal, unless during - // an animation. - absHref = urlResolve(absHref.animVal).href; - } - - // Make relative links work in HTML5 mode for legacy browsers (or at least IE8 & 9) - // The href should be a regular url e.g. /link/somewhere or link/somewhere or ../somewhere or - // somewhere#anchor or http://example.com/somewhere - if (LocationMode === LocationHashbangInHtml5Url) { - // get the actual href attribute - see - // http://msdn.microsoft.com/en-us/library/ie/dd347148(v=vs.85).aspx - var href = elm.attr('href') || elm.attr('xlink:href'); - - if (href.indexOf('://') < 0) { // Ignore absolute URLs - var prefix = '#' + hashPrefix; - if (href[0] == '/') { - // absolute path - replace old path - absHref = appBase + prefix + href; - } else if (href[0] == '#') { - // local anchor - absHref = appBase + prefix + ($location.path() || '/') + href; - } else { - // relative path - join with current path - var stack = $location.path().split("/"), - parts = href.split("/"); - for (var i=0; i html5 url - if ($location.absUrl() != initialUrl) { - $browser.url($location.absUrl(), true); - } - - // update $location when $browser url changes - $browser.onUrlChange(function(newUrl) { - if ($location.absUrl() != newUrl) { - $rootScope.$evalAsync(function() { - var oldUrl = $location.absUrl(); - - $location.$$parse(newUrl); - if ($rootScope.$broadcast('$locationChangeStart', newUrl, - oldUrl).defaultPrevented) { - $location.$$parse(oldUrl); - $browser.url(oldUrl); - } else { - afterLocationChange(oldUrl); - } - }); - if (!$rootScope.$$phase) $rootScope.$digest(); - } - }); - - // update browser - var changeCounter = 0; - $rootScope.$watch(function $locationWatch() { - var oldUrl = $browser.url(); - var currentReplace = $location.$$replace; - - if (!changeCounter || oldUrl != $location.absUrl()) { - changeCounter++; - $rootScope.$evalAsync(function() { - if ($rootScope.$broadcast('$locationChangeStart', $location.absUrl(), oldUrl). - defaultPrevented) { - $location.$$parse(oldUrl); - } else { - $browser.url($location.absUrl(), currentReplace); - afterLocationChange(oldUrl); - } - }); - } - $location.$$replace = false; - - return changeCounter; - }); - - return $location; - - function afterLocationChange(oldUrl) { - $rootScope.$broadcast('$locationChangeSuccess', $location.absUrl(), oldUrl); - } -}]; -} - -/** - * @ngdoc service - * @name $log - * @requires $window - * - * @description - * Simple service for logging. Default implementation safely writes the message - * into the browser's console (if present). - * - * The main purpose of this service is to simplify debugging and troubleshooting. - * - * The default is to log `debug` messages. You can use - * {@link ng.$logProvider ng.$logProvider#debugEnabled} to change this. - * - * @example - - - function LogCtrl($scope, $log) { - $scope.$log = $log; - $scope.message = 'Hello World!'; - } - - -
-

Reload this page with open console, enter text and hit the log button...

- Message: - - - - - -
-
-
- */ - -/** - * @ngdoc provider - * @name $logProvider - * @description - * Use the `$logProvider` to configure how the application logs messages - */ -function $LogProvider(){ - var debug = true, - self = this; - - /** - * @ngdoc property - * @name $logProvider#debugEnabled - * @description - * @param {boolean=} flag enable or disable debug level messages - * @returns {*} current value if used as getter or itself (chaining) if used as setter - */ - this.debugEnabled = function(flag) { - if (isDefined(flag)) { - debug = flag; - return this; - } else { - return debug; - } - }; - - this.$get = ['$window', function($window){ - return { - /** - * @ngdoc method - * @name $log#log - * - * @description - * Write a log message - */ - log: consoleLog('log'), - - /** - * @ngdoc method - * @name $log#info - * - * @description - * Write an information message - */ - info: consoleLog('info'), - - /** - * @ngdoc method - * @name $log#warn - * - * @description - * Write a warning message - */ - warn: consoleLog('warn'), - - /** - * @ngdoc method - * @name $log#error - * - * @description - * Write an error message - */ - error: consoleLog('error'), - - /** - * @ngdoc method - * @name $log#debug - * - * @description - * Write a debug message - */ - debug: (function () { - var fn = consoleLog('debug'); - - return function() { - if (debug) { - fn.apply(self, arguments); - } - }; - }()) - }; - - function formatError(arg) { - if (arg instanceof Error) { - if (arg.stack) { - arg = (arg.message && arg.stack.indexOf(arg.message) === -1) - ? 'Error: ' + arg.message + '\n' + arg.stack - : arg.stack; - } else if (arg.sourceURL) { - arg = arg.message + '\n' + arg.sourceURL + ':' + arg.line; - } - } - return arg; - } - - function consoleLog(type) { - var console = $window.console || {}, - logFn = console[type] || console.log || noop, - hasApply = false; - - // Note: reading logFn.apply throws an error in IE11 in IE8 document mode. - // The reason behind this is that console.log has type "object" in IE8... - try { - hasApply = !!logFn.apply; - } catch (e) {} - - if (hasApply) { - return function() { - var args = []; - forEach(arguments, function(arg) { - args.push(formatError(arg)); - }); - return logFn.apply(console, args); - }; - } - - // we are IE which either doesn't have window.console => this is noop and we do nothing, - // or we are IE where console.log doesn't have apply so we log at least first 2 args - return function(arg1, arg2) { - logFn(arg1, arg2 == null ? '' : arg2); - }; - } - }]; -} - -var $parseMinErr = minErr('$parse'); - -// Sandboxing Angular Expressions -// ------------------------------ -// Angular expressions are generally considered safe because these expressions only have direct -// access to $scope and locals. However, one can obtain the ability to execute arbitrary JS code by -// obtaining a reference to native JS functions such as the Function constructor. -// -// As an example, consider the following Angular expression: -// -// {}.toString.constructor(alert("evil JS code")) -// -// We want to prevent this type of access. For the sake of performance, during the lexing phase we -// disallow any "dotted" access to any member named "constructor". -// -// For reflective calls (a[b]) we check that the value of the lookup is not the Function constructor -// while evaluating the expression, which is a stronger but more expensive test. Since reflective -// calls are expensive anyway, this is not such a big deal compared to static dereferencing. -// -// This sandboxing technique is not perfect and doesn't aim to be. The goal is to prevent exploits -// against the expression language, but not to prevent exploits that were enabled by exposing -// sensitive JavaScript or browser apis on Scope. Exposing such objects on a Scope is never a good -// practice and therefore we are not even trying to protect against interaction with an object -// explicitly exposed in this way. -// -// A developer could foil the name check by aliasing the Function constructor under a different -// name on the scope. -// -// In general, it is not possible to access a Window object from an angular expression unless a -// window or some DOM object that has a reference to window is published onto a Scope. - -function ensureSafeMemberName(name, fullExpression) { - if (name === "constructor") { - throw $parseMinErr('isecfld', - 'Referencing "constructor" field in Angular expressions is disallowed! Expression: {0}', - fullExpression); - } - return name; -} - -function ensureSafeObject(obj, fullExpression) { - // nifty check if obj is Function that is fast and works across iframes and other contexts - if (obj) { - if (obj.constructor === obj) { - throw $parseMinErr('isecfn', - 'Referencing Function in Angular expressions is disallowed! Expression: {0}', - fullExpression); - } else if (// isWindow(obj) - obj.document && obj.location && obj.alert && obj.setInterval) { - throw $parseMinErr('isecwindow', - 'Referencing the Window in Angular expressions is disallowed! Expression: {0}', - fullExpression); - } else if (// isElement(obj) - obj.children && (obj.nodeName || (obj.prop && obj.attr && obj.find))) { - throw $parseMinErr('isecdom', - 'Referencing DOM nodes in Angular expressions is disallowed! Expression: {0}', - fullExpression); - } - } - return obj; -} - -var OPERATORS = { - /* jshint bitwise : false */ - 'null':function(){return null;}, - 'true':function(){return true;}, - 'false':function(){return false;}, - undefined:noop, - '+':function(self, locals, a,b){ - a=a(self, locals); b=b(self, locals); - if (isDefined(a)) { - if (isDefined(b)) { - return a + b; - } - return a; - } - return isDefined(b)?b:undefined;}, - '-':function(self, locals, a,b){ - a=a(self, locals); b=b(self, locals); - return (isDefined(a)?a:0)-(isDefined(b)?b:0); - }, - '*':function(self, locals, a,b){return a(self, locals)*b(self, locals);}, - '/':function(self, locals, a,b){return a(self, locals)/b(self, locals);}, - '%':function(self, locals, a,b){return a(self, locals)%b(self, locals);}, - '^':function(self, locals, a,b){return a(self, locals)^b(self, locals);}, - '=':noop, - '===':function(self, locals, a, b){return a(self, locals)===b(self, locals);}, - '!==':function(self, locals, a, b){return a(self, locals)!==b(self, locals);}, - '==':function(self, locals, a,b){return a(self, locals)==b(self, locals);}, - '!=':function(self, locals, a,b){return a(self, locals)!=b(self, locals);}, - '<':function(self, locals, a,b){return a(self, locals)':function(self, locals, a,b){return a(self, locals)>b(self, locals);}, - '<=':function(self, locals, a,b){return a(self, locals)<=b(self, locals);}, - '>=':function(self, locals, a,b){return a(self, locals)>=b(self, locals);}, - '&&':function(self, locals, a,b){return a(self, locals)&&b(self, locals);}, - '||':function(self, locals, a,b){return a(self, locals)||b(self, locals);}, - '&':function(self, locals, a,b){return a(self, locals)&b(self, locals);}, -// '|':function(self, locals, a,b){return a|b;}, - '|':function(self, locals, a,b){return b(self, locals)(self, locals, a(self, locals));}, - '!':function(self, locals, a){return !a(self, locals);} -}; -/* jshint bitwise: true */ -var ESCAPE = {"n":"\n", "f":"\f", "r":"\r", "t":"\t", "v":"\v", "'":"'", '"':'"'}; - - -///////////////////////////////////////// - - -/** - * @constructor - */ -var Lexer = function (options) { - this.options = options; -}; - -Lexer.prototype = { - constructor: Lexer, - - lex: function (text) { - this.text = text; - this.index = 0; - this.ch = undefined; - this.tokens = []; - - while (this.index < this.text.length) { - this.ch = this.text.charAt(this.index); - if (this.is('"\'')) { - this.readString(this.ch); - } else if (this.isNumber(this.ch) || this.is('.') && this.isNumber(this.peek())) { - this.readNumber(); - } else if (this.isIdent(this.ch)) { - this.readIdent(); - } else if (this.is('(){}[].,;:?')) { - this.tokens.push({ - index: this.index, - text: this.ch - }); - this.index++; - } else if (this.isWhitespace(this.ch)) { - this.index++; - } else { - var ch2 = this.ch + this.peek(); - var ch3 = ch2 + this.peek(2); - var fn = OPERATORS[this.ch]; - var fn2 = OPERATORS[ch2]; - var fn3 = OPERATORS[ch3]; - if (fn3) { - this.tokens.push({index: this.index, text: ch3, fn: fn3}); - this.index += 3; - } else if (fn2) { - this.tokens.push({index: this.index, text: ch2, fn: fn2}); - this.index += 2; - } else if (fn) { - this.tokens.push({ - index: this.index, - text: this.ch, - fn: fn - }); - this.index += 1; - } else { - this.throwError('Unexpected next character ', this.index, this.index + 1); - } - } - } - return this.tokens; - }, - - is: function(chars) { - return chars.indexOf(this.ch) !== -1; - }, - - peek: function(i) { - var num = i || 1; - return (this.index + num < this.text.length) ? this.text.charAt(this.index + num) : false; - }, - - isNumber: function(ch) { - return ('0' <= ch && ch <= '9'); - }, - - isWhitespace: function(ch) { - // IE treats non-breaking space as \u00A0 - return (ch === ' ' || ch === '\r' || ch === '\t' || - ch === '\n' || ch === '\v' || ch === '\u00A0'); - }, - - isIdent: function(ch) { - return ('a' <= ch && ch <= 'z' || - 'A' <= ch && ch <= 'Z' || - '_' === ch || ch === '$'); - }, - - isExpOperator: function(ch) { - return (ch === '-' || ch === '+' || this.isNumber(ch)); - }, - - throwError: function(error, start, end) { - end = end || this.index; - var colStr = (isDefined(start) - ? 's ' + start + '-' + this.index + ' [' + this.text.substring(start, end) + ']' - : ' ' + end); - throw $parseMinErr('lexerr', 'Lexer Error: {0} at column{1} in expression [{2}].', - error, colStr, this.text); - }, - - readNumber: function() { - var number = ''; - var start = this.index; - while (this.index < this.text.length) { - var ch = lowercase(this.text.charAt(this.index)); - if (ch == '.' || this.isNumber(ch)) { - number += ch; - } else { - var peekCh = this.peek(); - if (ch == 'e' && this.isExpOperator(peekCh)) { - number += ch; - } else if (this.isExpOperator(ch) && - peekCh && this.isNumber(peekCh) && - number.charAt(number.length - 1) == 'e') { - number += ch; - } else if (this.isExpOperator(ch) && - (!peekCh || !this.isNumber(peekCh)) && - number.charAt(number.length - 1) == 'e') { - this.throwError('Invalid exponent'); - } else { - break; - } - } - this.index++; - } - number = 1 * number; - this.tokens.push({ - index: start, - text: number, - constant: true, - fn: function() { return number; } - }); - }, - - readIdent: function() { - var parser = this; - - var ident = ''; - var start = this.index; - - var lastDot, peekIndex, methodName, ch; - - while (this.index < this.text.length) { - ch = this.text.charAt(this.index); - if (ch === '.' || this.isIdent(ch) || this.isNumber(ch)) { - if (ch === '.') lastDot = this.index; - ident += ch; - } else { - break; - } - this.index++; - } - - //check if this is not a method invocation and if it is back out to last dot - if (lastDot) { - peekIndex = this.index; - while (peekIndex < this.text.length) { - ch = this.text.charAt(peekIndex); - if (ch === '(') { - methodName = ident.substr(lastDot - start + 1); - ident = ident.substr(0, lastDot - start); - this.index = peekIndex; - break; - } - if (this.isWhitespace(ch)) { - peekIndex++; - } else { - break; - } - } - } - - - var token = { - index: start, - text: ident - }; - - // OPERATORS is our own object so we don't need to use special hasOwnPropertyFn - if (OPERATORS.hasOwnProperty(ident)) { - token.fn = OPERATORS[ident]; - token.constant = true; - } else { - var getter = getterFn(ident, this.options, this.text); - token.fn = extend(function(self, locals) { - return (getter(self, locals)); - }, { - assign: function(self, value) { - return setter(self, ident, value, parser.text); - } - }); - } - - this.tokens.push(token); - - if (methodName) { - this.tokens.push({ - index: lastDot, - text: '.' - }); - this.tokens.push({ - index: lastDot + 1, - text: methodName - }); - } - }, - - readString: function(quote) { - var start = this.index; - this.index++; - var string = ''; - var rawString = quote; - var escape = false; - while (this.index < this.text.length) { - var ch = this.text.charAt(this.index); - rawString += ch; - if (escape) { - if (ch === 'u') { - var hex = this.text.substring(this.index + 1, this.index + 5); - if (!hex.match(/[\da-f]{4}/i)) - this.throwError('Invalid unicode escape [\\u' + hex + ']'); - this.index += 4; - string += String.fromCharCode(parseInt(hex, 16)); - } else { - var rep = ESCAPE[ch]; - if (rep) { - string += rep; - } else { - string += ch; - } - } - escape = false; - } else if (ch === '\\') { - escape = true; - } else if (ch === quote) { - this.index++; - this.tokens.push({ - index: start, - text: rawString, - string: string, - constant: true, - fn: function() { return string; } - }); - return; - } else { - string += ch; - } - this.index++; - } - this.throwError('Unterminated quote', start); - } -}; - - -/** - * @constructor - */ -var Parser = function (lexer, $filter, options) { - this.lexer = lexer; - this.$filter = $filter; - this.options = options; -}; - -Parser.ZERO = extend(function () { - return 0; -}, { - constant: true -}); - -Parser.prototype = { - constructor: Parser, - - parse: function (text) { - this.text = text; - this.tokens = this.lexer.lex(text); - - var value = this.statements(); - - if (this.tokens.length !== 0) { - this.throwError('is an unexpected token', this.tokens[0]); - } - - value.literal = !!value.literal; - value.constant = !!value.constant; - - return value; - }, - - primary: function () { - var primary; - if (this.expect('(')) { - primary = this.filterChain(); - this.consume(')'); - } else if (this.expect('[')) { - primary = this.arrayDeclaration(); - } else if (this.expect('{')) { - primary = this.object(); - } else { - var token = this.expect(); - primary = token.fn; - if (!primary) { - this.throwError('not a primary expression', token); - } - if (token.constant) { - primary.constant = true; - primary.literal = true; - } - } - - var next, context; - while ((next = this.expect('(', '[', '.'))) { - if (next.text === '(') { - primary = this.functionCall(primary, context); - context = null; - } else if (next.text === '[') { - context = primary; - primary = this.objectIndex(primary); - } else if (next.text === '.') { - context = primary; - primary = this.fieldAccess(primary); - } else { - this.throwError('IMPOSSIBLE'); - } - } - return primary; - }, - - throwError: function(msg, token) { - throw $parseMinErr('syntax', - 'Syntax Error: Token \'{0}\' {1} at column {2} of the expression [{3}] starting at [{4}].', - token.text, msg, (token.index + 1), this.text, this.text.substring(token.index)); - }, - - peekToken: function() { - if (this.tokens.length === 0) - throw $parseMinErr('ueoe', 'Unexpected end of expression: {0}', this.text); - return this.tokens[0]; - }, - - peek: function(e1, e2, e3, e4) { - if (this.tokens.length > 0) { - var token = this.tokens[0]; - var t = token.text; - if (t === e1 || t === e2 || t === e3 || t === e4 || - (!e1 && !e2 && !e3 && !e4)) { - return token; - } - } - return false; - }, - - expect: function(e1, e2, e3, e4){ - var token = this.peek(e1, e2, e3, e4); - if (token) { - this.tokens.shift(); - return token; - } - return false; - }, - - consume: function(e1){ - if (!this.expect(e1)) { - this.throwError('is unexpected, expecting [' + e1 + ']', this.peek()); - } - }, - - unaryFn: function(fn, right) { - return extend(function(self, locals) { - return fn(self, locals, right); - }, { - constant:right.constant - }); - }, - - ternaryFn: function(left, middle, right){ - return extend(function(self, locals){ - return left(self, locals) ? middle(self, locals) : right(self, locals); - }, { - constant: left.constant && middle.constant && right.constant - }); - }, - - binaryFn: function(left, fn, right) { - return extend(function(self, locals) { - return fn(self, locals, left, right); - }, { - constant:left.constant && right.constant - }); - }, - - statements: function() { - var statements = []; - while (true) { - if (this.tokens.length > 0 && !this.peek('}', ')', ';', ']')) - statements.push(this.filterChain()); - if (!this.expect(';')) { - // optimize for the common case where there is only one statement. - // TODO(size): maybe we should not support multiple statements? - return (statements.length === 1) - ? statements[0] - : function(self, locals) { - var value; - for (var i = 0; i < statements.length; i++) { - var statement = statements[i]; - if (statement) { - value = statement(self, locals); - } - } - return value; - }; - } - } - }, - - filterChain: function() { - var left = this.expression(); - var token; - while (true) { - if ((token = this.expect('|'))) { - left = this.binaryFn(left, token.fn, this.filter()); - } else { - return left; - } - } - }, - - filter: function() { - var token = this.expect(); - var fn = this.$filter(token.text); - var argsFn = []; - while(this.expect(':')) { - argsFn.push(this.expression()); - } - return valueFn(fnInvoke); - - function fnInvoke(self, locals, input) { - var args = [input]; - for (var i = 0; i < argsFn.length; i++) { - args.push(argsFn[i](self, locals)); - } - return fn.apply(self, args); - } - }, - - expression: function() { - return this.assignment(); - }, - - assignment: function() { - var left = this.ternary(); - var right; - var token; - if ((token = this.expect('='))) { - if (!left.assign) { - this.throwError('implies assignment but [' + - this.text.substring(0, token.index) + '] can not be assigned to', token); - } - right = this.ternary(); - return function(scope, locals) { - return left.assign(scope, right(scope, locals), locals); - }; - } - return left; - }, - - ternary: function() { - var left = this.logicalOR(); - var middle; - var token; - if ((token = this.expect('?'))) { - middle = this.ternary(); - if ((token = this.expect(':'))) { - return this.ternaryFn(left, middle, this.ternary()); - } else { - this.throwError('expected :', token); - } - } else { - return left; - } - }, - - logicalOR: function() { - var left = this.logicalAND(); - var token; - while (true) { - if ((token = this.expect('||'))) { - left = this.binaryFn(left, token.fn, this.logicalAND()); - } else { - return left; - } - } - }, - - logicalAND: function() { - var left = this.equality(); - var token; - if ((token = this.expect('&&'))) { - left = this.binaryFn(left, token.fn, this.logicalAND()); - } - return left; - }, - - equality: function() { - var left = this.relational(); - var token; - if ((token = this.expect('==','!=','===','!=='))) { - left = this.binaryFn(left, token.fn, this.equality()); - } - return left; - }, - - relational: function() { - var left = this.additive(); - var token; - if ((token = this.expect('<', '>', '<=', '>='))) { - left = this.binaryFn(left, token.fn, this.relational()); - } - return left; - }, - - additive: function() { - var left = this.multiplicative(); - var token; - while ((token = this.expect('+','-'))) { - left = this.binaryFn(left, token.fn, this.multiplicative()); - } - return left; - }, - - multiplicative: function() { - var left = this.unary(); - var token; - while ((token = this.expect('*','/','%'))) { - left = this.binaryFn(left, token.fn, this.unary()); - } - return left; - }, - - unary: function() { - var token; - if (this.expect('+')) { - return this.primary(); - } else if ((token = this.expect('-'))) { - return this.binaryFn(Parser.ZERO, token.fn, this.unary()); - } else if ((token = this.expect('!'))) { - return this.unaryFn(token.fn, this.unary()); - } else { - return this.primary(); - } - }, - - fieldAccess: function(object) { - var parser = this; - var field = this.expect().text; - var getter = getterFn(field, this.options, this.text); - - return extend(function(scope, locals, self) { - return getter(self || object(scope, locals)); - }, { - assign: function(scope, value, locals) { - return setter(object(scope, locals), field, value, parser.text); - } - }); - }, - - objectIndex: function(obj) { - var parser = this; - - var indexFn = this.expression(); - this.consume(']'); - - return extend(function(self, locals) { - var o = obj(self, locals), - i = indexFn(self, locals), - v; - - if (!o) return undefined; - v = ensureSafeObject(o[i], parser.text); - return v; - }, { - assign: function(self, value, locals) { - var key = indexFn(self, locals); - // prevent overwriting of Function.constructor which would break ensureSafeObject check - var safe = ensureSafeObject(obj(self, locals), parser.text); - return safe[key] = value; - } - }); - }, - - functionCall: function(fn, contextGetter) { - var argsFn = []; - if (this.peekToken().text !== ')') { - do { - argsFn.push(this.expression()); - } while (this.expect(',')); - } - this.consume(')'); - - var parser = this; - - return function(scope, locals) { - var args = []; - var context = contextGetter ? contextGetter(scope, locals) : scope; - - for (var i = 0; i < argsFn.length; i++) { - args.push(argsFn[i](scope, locals)); - } - var fnPtr = fn(scope, locals, context) || noop; - - ensureSafeObject(context, parser.text); - ensureSafeObject(fnPtr, parser.text); - - // IE stupidity! (IE doesn't have apply for some native functions) - var v = fnPtr.apply - ? fnPtr.apply(context, args) - : fnPtr(args[0], args[1], args[2], args[3], args[4]); - - return ensureSafeObject(v, parser.text); - }; - }, - - // This is used with json array declaration - arrayDeclaration: function () { - var elementFns = []; - var allConstant = true; - if (this.peekToken().text !== ']') { - do { - if (this.peek(']')) { - // Support trailing commas per ES5.1. - break; - } - var elementFn = this.expression(); - elementFns.push(elementFn); - if (!elementFn.constant) { - allConstant = false; - } - } while (this.expect(',')); - } - this.consume(']'); - - return extend(function(self, locals) { - var array = []; - for (var i = 0; i < elementFns.length; i++) { - array.push(elementFns[i](self, locals)); - } - return array; - }, { - literal: true, - constant: allConstant - }); - }, - - object: function () { - var keyValues = []; - var allConstant = true; - if (this.peekToken().text !== '}') { - do { - if (this.peek('}')) { - // Support trailing commas per ES5.1. - break; - } - var token = this.expect(), - key = token.string || token.text; - this.consume(':'); - var value = this.expression(); - keyValues.push({key: key, value: value}); - if (!value.constant) { - allConstant = false; - } - } while (this.expect(',')); - } - this.consume('}'); - - return extend(function(self, locals) { - var object = {}; - for (var i = 0; i < keyValues.length; i++) { - var keyValue = keyValues[i]; - object[keyValue.key] = keyValue.value(self, locals); - } - return object; - }, { - literal: true, - constant: allConstant - }); - } -}; - - -////////////////////////////////////////////////// -// Parser helper functions -////////////////////////////////////////////////// - -function setter(obj, path, setValue, fullExp) { - - var element = path.split('.'), key; - for (var i = 0; element.length > 1; i++) { - key = ensureSafeMemberName(element.shift(), fullExp); - var propertyObj = obj[key]; - if (!propertyObj) { - propertyObj = {}; - obj[key] = propertyObj; - } - obj = propertyObj; - } - key = ensureSafeMemberName(element.shift(), fullExp); - obj[key] = setValue; - return setValue; -} - -var getterFnCache = {}; - -/** - * Implementation of the "Black Hole" variant from: - * - http://jsperf.com/angularjs-parse-getter/4 - * - http://jsperf.com/path-evaluation-simplified/7 - */ -function cspSafeGetterFn(key0, key1, key2, key3, key4, fullExp) { - ensureSafeMemberName(key0, fullExp); - ensureSafeMemberName(key1, fullExp); - ensureSafeMemberName(key2, fullExp); - ensureSafeMemberName(key3, fullExp); - ensureSafeMemberName(key4, fullExp); - - return function cspSafeGetter(scope, locals) { - var pathVal = (locals && locals.hasOwnProperty(key0)) ? locals : scope; - - if (pathVal == null) return pathVal; - pathVal = pathVal[key0]; - - if (!key1) return pathVal; - if (pathVal == null) return undefined; - pathVal = pathVal[key1]; - - if (!key2) return pathVal; - if (pathVal == null) return undefined; - pathVal = pathVal[key2]; - - if (!key3) return pathVal; - if (pathVal == null) return undefined; - pathVal = pathVal[key3]; - - if (!key4) return pathVal; - if (pathVal == null) return undefined; - pathVal = pathVal[key4]; - - return pathVal; - }; -} - -function simpleGetterFn1(key0, fullExp) { - ensureSafeMemberName(key0, fullExp); - - return function simpleGetterFn1(scope, locals) { - if (scope == null) return undefined; - return ((locals && locals.hasOwnProperty(key0)) ? locals : scope)[key0]; - }; -} - -function simpleGetterFn2(key0, key1, fullExp) { - ensureSafeMemberName(key0, fullExp); - ensureSafeMemberName(key1, fullExp); - - return function simpleGetterFn2(scope, locals) { - if (scope == null) return undefined; - scope = ((locals && locals.hasOwnProperty(key0)) ? locals : scope)[key0]; - return scope == null ? undefined : scope[key1]; - }; -} - -function getterFn(path, options, fullExp) { - // Check whether the cache has this getter already. - // We can use hasOwnProperty directly on the cache because we ensure, - // see below, that the cache never stores a path called 'hasOwnProperty' - if (getterFnCache.hasOwnProperty(path)) { - return getterFnCache[path]; - } - - var pathKeys = path.split('.'), - pathKeysLength = pathKeys.length, - fn; - - // When we have only 1 or 2 tokens, use optimized special case closures. - // http://jsperf.com/angularjs-parse-getter/6 - if (pathKeysLength === 1) { - fn = simpleGetterFn1(pathKeys[0], fullExp); - } else if (pathKeysLength === 2) { - fn = simpleGetterFn2(pathKeys[0], pathKeys[1], fullExp); - } else if (options.csp) { - if (pathKeysLength < 6) { - fn = cspSafeGetterFn(pathKeys[0], pathKeys[1], pathKeys[2], pathKeys[3], pathKeys[4], fullExp); - } else { - fn = function(scope, locals) { - var i = 0, val; - do { - val = cspSafeGetterFn(pathKeys[i++], pathKeys[i++], pathKeys[i++], pathKeys[i++], - pathKeys[i++], fullExp)(scope, locals); - - locals = undefined; // clear after first iteration - scope = val; - } while (i < pathKeysLength); - return val; - }; - } - } else { - var code = 'var p;\n'; - forEach(pathKeys, function(key, index) { - ensureSafeMemberName(key, fullExp); - code += 'if(s == null) return undefined;\n' + - 's='+ (index - // we simply dereference 's' on any .dot notation - ? 's' - // but if we are first then we check locals first, and if so read it first - : '((k&&k.hasOwnProperty("' + key + '"))?k:s)') + '["' + key + '"]' + ';\n'; - }); - code += 'return s;'; - - /* jshint -W054 */ - var evaledFnGetter = new Function('s', 'k', code); // s=scope, k=locals - /* jshint +W054 */ - evaledFnGetter.toString = valueFn(code); - fn = evaledFnGetter; - } - - // Only cache the value if it's not going to mess up the cache object - // This is more performant that using Object.prototype.hasOwnProperty.call - if (path !== 'hasOwnProperty') { - getterFnCache[path] = fn; - } - return fn; -} - -/////////////////////////////////// - -/** - * @ngdoc service - * @name $parse - * @kind function - * - * @description - * - * Converts Angular {@link guide/expression expression} into a function. - * - * ```js - * var getter = $parse('user.name'); - * var setter = getter.assign; - * var context = {user:{name:'angular'}}; - * var locals = {user:{name:'local'}}; - * - * expect(getter(context)).toEqual('angular'); - * setter(context, 'newValue'); - * expect(context.user.name).toEqual('newValue'); - * expect(getter(context, locals)).toEqual('local'); - * ``` - * - * - * @param {string} expression String expression to compile. - * @returns {function(context, locals)} a function which represents the compiled expression: - * - * * `context` – `{object}` – an object against which any expressions embedded in the strings - * are evaluated against (typically a scope object). - * * `locals` – `{object=}` – local variables context object, useful for overriding values in - * `context`. - * - * The returned function also has the following properties: - * * `literal` – `{boolean}` – whether the expression's top-level node is a JavaScript - * literal. - * * `constant` – `{boolean}` – whether the expression is made entirely of JavaScript - * constant literals. - * * `assign` – `{?function(context, value)}` – if the expression is assignable, this will be - * set to a function to change its value on the given context. - * - */ - - -/** - * @ngdoc provider - * @name $parseProvider - * @kind function - * - * @description - * `$parseProvider` can be used for configuring the default behavior of the {@link ng.$parse $parse} - * service. - */ -function $ParseProvider() { - var cache = {}; - - var $parseOptions = { - csp: false - }; - - - this.$get = ['$filter', '$sniffer', function($filter, $sniffer) { - $parseOptions.csp = $sniffer.csp; - - return function(exp) { - var parsedExpression, - oneTime; - - switch (typeof exp) { - case 'string': - - exp = trim(exp); - - if (exp.charAt(0) === ':' && exp.charAt(1) === ':') { - oneTime = true; - exp = exp.substring(2); - } - - if (cache.hasOwnProperty(exp)) { - return oneTime ? oneTimeWrapper(cache[exp]) : cache[exp]; - } - - var lexer = new Lexer($parseOptions); - var parser = new Parser(lexer, $filter, $parseOptions); - parsedExpression = parser.parse(exp); - - if (exp !== 'hasOwnProperty') { - // Only cache the value if it's not going to mess up the cache object - // This is more performant that using Object.prototype.hasOwnProperty.call - cache[exp] = parsedExpression; - } - - if (parsedExpression.constant) { - parsedExpression.$$unwatch = true; - } - - return oneTime ? oneTimeWrapper(parsedExpression) : parsedExpression; - - case 'function': - return exp; - - default: - return noop; - } - - function oneTimeWrapper(expression) { - var stable = false, - lastValue; - oneTimeParseFn.literal = expression.literal; - oneTimeParseFn.constant = expression.constant; - oneTimeParseFn.assign = expression.assign; - return oneTimeParseFn; - - function oneTimeParseFn(self, locals) { - if (!stable) { - lastValue = expression(self, locals); - oneTimeParseFn.$$unwatch = isDefined(lastValue); - if (oneTimeParseFn.$$unwatch && self && self.$$postDigestQueue) { - self.$$postDigestQueue.push(function () { - // create a copy if the value is defined and it is not a $sce value - if ((stable = isDefined(lastValue)) && - (lastValue === null || !lastValue.$$unwrapTrustedValue)) { - lastValue = copy(lastValue, null); - } - }); - } - } - return lastValue; - } - } - }; - }]; -} - -/** - * @ngdoc service - * @name $q - * @requires $rootScope - * - * @description - * A promise/deferred implementation inspired by [Kris Kowal's Q](https://github.com/kriskowal/q). - * - * [The CommonJS Promise proposal](http://wiki.commonjs.org/wiki/Promises) describes a promise as an - * interface for interacting with an object that represents the result of an action that is - * performed asynchronously, and may or may not be finished at any given point in time. - * - * From the perspective of dealing with error handling, deferred and promise APIs are to - * asynchronous programming what `try`, `catch` and `throw` keywords are to synchronous programming. - * - * ```js - * // for the purpose of this example let's assume that variables `$q`, `scope` and `okToGreet` - * // are available in the current lexical scope (they could have been injected or passed in). - * - * function asyncGreet(name) { - * var deferred = $q.defer(); - * - * setTimeout(function() { - * // since this fn executes async in a future turn of the event loop, we need to wrap - * // our code into an $apply call so that the model changes are properly observed. - * scope.$apply(function() { - * deferred.notify('About to greet ' + name + '.'); - * - * if (okToGreet(name)) { - * deferred.resolve('Hello, ' + name + '!'); - * } else { - * deferred.reject('Greeting ' + name + ' is not allowed.'); - * } - * }); - * }, 1000); - * - * return deferred.promise; - * } - * - * var promise = asyncGreet('Robin Hood'); - * promise.then(function(greeting) { - * alert('Success: ' + greeting); - * }, function(reason) { - * alert('Failed: ' + reason); - * }, function(update) { - * alert('Got notification: ' + update); - * }); - * ``` - * - * At first it might not be obvious why this extra complexity is worth the trouble. The payoff - * comes in the way of guarantees that promise and deferred APIs make, see - * https://github.com/kriskowal/uncommonjs/blob/master/promises/specification.md. - * - * Additionally the promise api allows for composition that is very hard to do with the - * traditional callback ([CPS](http://en.wikipedia.org/wiki/Continuation-passing_style)) approach. - * For more on this please see the [Q documentation](https://github.com/kriskowal/q) especially the - * section on serial or parallel joining of promises. - * - * - * # The Deferred API - * - * A new instance of deferred is constructed by calling `$q.defer()`. - * - * The purpose of the deferred object is to expose the associated Promise instance as well as APIs - * that can be used for signaling the successful or unsuccessful completion, as well as the status - * of the task. - * - * **Methods** - * - * - `resolve(value)` – resolves the derived promise with the `value`. If the value is a rejection - * constructed via `$q.reject`, the promise will be rejected instead. - * - `reject(reason)` – rejects the derived promise with the `reason`. This is equivalent to - * resolving it with a rejection constructed via `$q.reject`. - * - `notify(value)` - provides updates on the status of the promise's execution. This may be called - * multiple times before the promise is either resolved or rejected. - * - * **Properties** - * - * - promise – `{Promise}` – promise object associated with this deferred. - * - * - * # The Promise API - * - * A new promise instance is created when a deferred instance is created and can be retrieved by - * calling `deferred.promise`. - * - * The purpose of the promise object is to allow for interested parties to get access to the result - * of the deferred task when it completes. - * - * **Methods** - * - * - `then(successCallback, errorCallback, notifyCallback)` – regardless of when the promise was or - * will be resolved or rejected, `then` calls one of the success or error callbacks asynchronously - * as soon as the result is available. The callbacks are called with a single argument: the result - * or rejection reason. Additionally, the notify callback may be called zero or more times to - * provide a progress indication, before the promise is resolved or rejected. - * - * This method *returns a new promise* which is resolved or rejected via the return value of the - * `successCallback`, `errorCallback`. It also notifies via the return value of the - * `notifyCallback` method. The promise can not be resolved or rejected from the notifyCallback - * method. - * - * - `catch(errorCallback)` – shorthand for `promise.then(null, errorCallback)` - * - * - `finally(callback)` – allows you to observe either the fulfillment or rejection of a promise, - * but to do so without modifying the final value. This is useful to release resources or do some - * clean-up that needs to be done whether the promise was rejected or resolved. See the [full - * specification](https://github.com/kriskowal/q/wiki/API-Reference#promisefinallycallback) for - * more information. - * - * Because `finally` is a reserved word in JavaScript and reserved keywords are not supported as - * property names by ES3, you'll need to invoke the method like `promise['finally'](callback)` to - * make your code IE8 and Android 2.x compatible. - * - * # Chaining promises - * - * Because calling the `then` method of a promise returns a new derived promise, it is easily - * possible to create a chain of promises: - * - * ```js - * promiseB = promiseA.then(function(result) { - * return result + 1; - * }); - * - * // promiseB will be resolved immediately after promiseA is resolved and its value - * // will be the result of promiseA incremented by 1 - * ``` - * - * It is possible to create chains of any length and since a promise can be resolved with another - * promise (which will defer its resolution further), it is possible to pause/defer resolution of - * the promises at any point in the chain. This makes it possible to implement powerful APIs like - * $http's response interceptors. - * - * - * # Differences between Kris Kowal's Q and $q - * - * There are two main differences: - * - * - $q is integrated with the {@link ng.$rootScope.Scope} Scope model observation - * mechanism in angular, which means faster propagation of resolution or rejection into your - * models and avoiding unnecessary browser repaints, which would result in flickering UI. - * - Q has many more features than $q, but that comes at a cost of bytes. $q is tiny, but contains - * all the important functionality needed for common async tasks. - * - * # Testing - * - * ```js - * it('should simulate promise', inject(function($q, $rootScope) { - * var deferred = $q.defer(); - * var promise = deferred.promise; - * var resolvedValue; - * - * promise.then(function(value) { resolvedValue = value; }); - * expect(resolvedValue).toBeUndefined(); - * - * // Simulate resolving of promise - * deferred.resolve(123); - * // Note that the 'then' function does not get called synchronously. - * // This is because we want the promise API to always be async, whether or not - * // it got called synchronously or asynchronously. - * expect(resolvedValue).toBeUndefined(); - * - * // Propagate promise resolution to 'then' functions using $apply(). - * $rootScope.$apply(); - * expect(resolvedValue).toEqual(123); - * })); - * ``` - */ -function $QProvider() { - - this.$get = ['$rootScope', '$exceptionHandler', function($rootScope, $exceptionHandler) { - return qFactory(function(callback) { - $rootScope.$evalAsync(callback); - }, $exceptionHandler); - }]; -} - - -/** - * Constructs a promise manager. - * - * @param {function(Function)} nextTick Function for executing functions in the next turn. - * @param {function(...*)} exceptionHandler Function into which unexpected exceptions are passed for - * debugging purposes. - * @returns {object} Promise manager. - */ -function qFactory(nextTick, exceptionHandler) { - - /** - * @ngdoc method - * @name $q#defer - * @kind function - * - * @description - * Creates a `Deferred` object which represents a task which will finish in the future. - * - * @returns {Deferred} Returns a new instance of deferred. - */ - var defer = function() { - var pending = [], - value, deferred; - - deferred = { - - resolve: function(val) { - if (pending) { - var callbacks = pending; - pending = undefined; - value = ref(val); - - if (callbacks.length) { - nextTick(function() { - var callback; - for (var i = 0, ii = callbacks.length; i < ii; i++) { - callback = callbacks[i]; - value.then(callback[0], callback[1], callback[2]); - } - }); - } - } - }, - - - reject: function(reason) { - deferred.resolve(createInternalRejectedPromise(reason)); - }, - - - notify: function(progress) { - if (pending) { - var callbacks = pending; - - if (pending.length) { - nextTick(function() { - var callback; - for (var i = 0, ii = callbacks.length; i < ii; i++) { - callback = callbacks[i]; - callback[2](progress); - } - }); - } - } - }, - - - promise: { - then: function(callback, errback, progressback) { - var result = defer(); - - var wrappedCallback = function(value) { - try { - result.resolve((isFunction(callback) ? callback : defaultCallback)(value)); - } catch(e) { - result.reject(e); - exceptionHandler(e); - } - }; - - var wrappedErrback = function(reason) { - try { - result.resolve((isFunction(errback) ? errback : defaultErrback)(reason)); - } catch(e) { - result.reject(e); - exceptionHandler(e); - } - }; - - var wrappedProgressback = function(progress) { - try { - result.notify((isFunction(progressback) ? progressback : defaultCallback)(progress)); - } catch(e) { - exceptionHandler(e); - } - }; - - if (pending) { - pending.push([wrappedCallback, wrappedErrback, wrappedProgressback]); - } else { - value.then(wrappedCallback, wrappedErrback, wrappedProgressback); - } - - return result.promise; - }, - - "catch": function(callback) { - return this.then(null, callback); - }, - - "finally": function(callback) { - - function makePromise(value, resolved) { - var result = defer(); - if (resolved) { - result.resolve(value); - } else { - result.reject(value); - } - return result.promise; - } - - function handleCallback(value, isResolved) { - var callbackOutput = null; - try { - callbackOutput = (callback ||defaultCallback)(); - } catch(e) { - return makePromise(e, false); - } - if (callbackOutput && isFunction(callbackOutput.then)) { - return callbackOutput.then(function() { - return makePromise(value, isResolved); - }, function(error) { - return makePromise(error, false); - }); - } else { - return makePromise(value, isResolved); - } - } - - return this.then(function(value) { - return handleCallback(value, true); - }, function(error) { - return handleCallback(error, false); - }); - } - } - }; - - return deferred; - }; - - - var ref = function(value) { - if (value && isFunction(value.then)) return value; - return { - then: function(callback) { - var result = defer(); - nextTick(function() { - result.resolve(callback(value)); - }); - return result.promise; - } - }; - }; - - - /** - * @ngdoc method - * @name $q#reject - * @kind function - * - * @description - * Creates a promise that is resolved as rejected with the specified `reason`. This api should be - * used to forward rejection in a chain of promises. If you are dealing with the last promise in - * a promise chain, you don't need to worry about it. - * - * When comparing deferreds/promises to the familiar behavior of try/catch/throw, think of - * `reject` as the `throw` keyword in JavaScript. This also means that if you "catch" an error via - * a promise error callback and you want to forward the error to the promise derived from the - * current promise, you have to "rethrow" the error by returning a rejection constructed via - * `reject`. - * - * ```js - * promiseB = promiseA.then(function(result) { - * // success: do something and resolve promiseB - * // with the old or a new result - * return result; - * }, function(reason) { - * // error: handle the error if possible and - * // resolve promiseB with newPromiseOrValue, - * // otherwise forward the rejection to promiseB - * if (canHandle(reason)) { - * // handle the error and recover - * return newPromiseOrValue; - * } - * return $q.reject(reason); - * }); - * ``` - * - * @param {*} reason Constant, message, exception or an object representing the rejection reason. - * @returns {Promise} Returns a promise that was already resolved as rejected with the `reason`. - */ - var reject = function(reason) { - var result = defer(); - result.reject(reason); - return result.promise; - }; - - var createInternalRejectedPromise = function(reason) { - return { - then: function(callback, errback) { - var result = defer(); - nextTick(function() { - try { - result.resolve((isFunction(errback) ? errback : defaultErrback)(reason)); - } catch(e) { - result.reject(e); - exceptionHandler(e); - } - }); - return result.promise; - } - }; - }; - - - /** - * @ngdoc method - * @name $q#when - * @kind function - * - * @description - * Wraps an object that might be a value or a (3rd party) then-able promise into a $q promise. - * This is useful when you are dealing with an object that might or might not be a promise, or if - * the promise comes from a source that can't be trusted. - * - * @param {*} value Value or a promise - * @returns {Promise} Returns a promise of the passed value or promise - */ - var when = function(value, callback, errback, progressback) { - var result = defer(), - done; - - var wrappedCallback = function(value) { - try { - return (isFunction(callback) ? callback : defaultCallback)(value); - } catch (e) { - exceptionHandler(e); - return reject(e); - } - }; - - var wrappedErrback = function(reason) { - try { - return (isFunction(errback) ? errback : defaultErrback)(reason); - } catch (e) { - exceptionHandler(e); - return reject(e); - } - }; - - var wrappedProgressback = function(progress) { - try { - return (isFunction(progressback) ? progressback : defaultCallback)(progress); - } catch (e) { - exceptionHandler(e); - } - }; - - nextTick(function() { - ref(value).then(function(value) { - if (done) return; - done = true; - result.resolve(ref(value).then(wrappedCallback, wrappedErrback, wrappedProgressback)); - }, function(reason) { - if (done) return; - done = true; - result.resolve(wrappedErrback(reason)); - }, function(progress) { - if (done) return; - result.notify(wrappedProgressback(progress)); - }); - }); - - return result.promise; - }; - - - function defaultCallback(value) { - return value; - } - - - function defaultErrback(reason) { - return reject(reason); - } - - - /** - * @ngdoc method - * @name $q#all - * @kind function - * - * @description - * Combines multiple promises into a single promise that is resolved when all of the input - * promises are resolved. - * - * @param {Array.|Object.} promises An array or hash of promises. - * @returns {Promise} Returns a single promise that will be resolved with an array/hash of values, - * each value corresponding to the promise at the same index/key in the `promises` array/hash. - * If any of the promises is resolved with a rejection, this resulting promise will be rejected - * with the same rejection value. - */ - function all(promises) { - var deferred = defer(), - counter = 0, - results = isArray(promises) ? [] : {}; - - forEach(promises, function(promise, key) { - counter++; - ref(promise).then(function(value) { - if (results.hasOwnProperty(key)) return; - results[key] = value; - if (!(--counter)) deferred.resolve(results); - }, function(reason) { - if (results.hasOwnProperty(key)) return; - deferred.reject(reason); - }); - }); - - if (counter === 0) { - deferred.resolve(results); - } - - return deferred.promise; - } - - return { - defer: defer, - reject: reject, - when: when, - all: all - }; -} - -function $$RAFProvider(){ //rAF - this.$get = ['$window', '$timeout', function($window, $timeout) { - var requestAnimationFrame = $window.requestAnimationFrame || - $window.webkitRequestAnimationFrame || - $window.mozRequestAnimationFrame; - - var cancelAnimationFrame = $window.cancelAnimationFrame || - $window.webkitCancelAnimationFrame || - $window.mozCancelAnimationFrame || - $window.webkitCancelRequestAnimationFrame; - - var rafSupported = !!requestAnimationFrame; - var raf = rafSupported - ? function(fn) { - var id = requestAnimationFrame(fn); - return function() { - cancelAnimationFrame(id); - }; - } - : function(fn) { - var timer = $timeout(fn, 16.66, false); // 1000 / 60 = 16.666 - return function() { - $timeout.cancel(timer); - }; - }; - - raf.supported = rafSupported; - - return raf; - }]; -} - -/** - * DESIGN NOTES - * - * The design decisions behind the scope are heavily favored for speed and memory consumption. - * - * The typical use of scope is to watch the expressions, which most of the time return the same - * value as last time so we optimize the operation. - * - * Closures construction is expensive in terms of speed as well as memory: - * - No closures, instead use prototypical inheritance for API - * - Internal state needs to be stored on scope directly, which means that private state is - * exposed as $$____ properties - * - * Loop operations are optimized by using while(count--) { ... } - * - this means that in order to keep the same order of execution as addition we have to add - * items to the array at the beginning (unshift) instead of at the end (push) - * - * Child scopes are created and removed often - * - Using an array would be slow since inserts in middle are expensive so we use linked list - * - * There are few watches then a lot of observers. This is why you don't want the observer to be - * implemented in the same way as watch. Watch requires return of initialization function which - * are expensive to construct. - */ - - -/** - * @ngdoc provider - * @name $rootScopeProvider - * @description - * - * Provider for the $rootScope service. - */ - -/** - * @ngdoc method - * @name $rootScopeProvider#digestTtl - * @description - * - * Sets the number of `$digest` iterations the scope should attempt to execute before giving up and - * assuming that the model is unstable. - * - * The current default is 10 iterations. - * - * In complex applications it's possible that the dependencies between `$watch`s will result in - * several digest iterations. However if an application needs more than the default 10 digest - * iterations for its model to stabilize then you should investigate what is causing the model to - * continuously change during the digest. - * - * Increasing the TTL could have performance implications, so you should not change it without - * proper justification. - * - * @param {number} limit The number of digest iterations. - */ - - -/** - * @ngdoc service - * @name $rootScope - * @description - * - * Every application has a single root {@link ng.$rootScope.Scope scope}. - * All other scopes are descendant scopes of the root scope. Scopes provide separation - * between the model and the view, via a mechanism for watching the model for changes. - * They also provide an event emission/broadcast and subscription facility. See the - * {@link guide/scope developer guide on scopes}. - */ -function $RootScopeProvider(){ - var TTL = 10; - var $rootScopeMinErr = minErr('$rootScope'); - var lastDirtyWatch = null; - - this.digestTtl = function(value) { - if (arguments.length) { - TTL = value; - } - return TTL; - }; - - this.$get = ['$injector', '$exceptionHandler', '$parse', '$browser', - function( $injector, $exceptionHandler, $parse, $browser) { - - /** - * @ngdoc type - * @name $rootScope.Scope - * - * @description - * A root scope can be retrieved using the {@link ng.$rootScope $rootScope} key from the - * {@link auto.$injector $injector}. Child scopes are created using the - * {@link ng.$rootScope.Scope#$new $new()} method. (Most scopes are created automatically when - * compiled HTML template is executed.) - * - * Here is a simple scope snippet to show how you can interact with the scope. - * ```html - * - * ``` - * - * # Inheritance - * A scope can inherit from a parent scope, as in this example: - * ```js - var parent = $rootScope; - var child = parent.$new(); - - parent.salutation = "Hello"; - child.name = "World"; - expect(child.salutation).toEqual('Hello'); - - child.salutation = "Welcome"; - expect(child.salutation).toEqual('Welcome'); - expect(parent.salutation).toEqual('Hello'); - * ``` - * - * - * @param {Object.=} providers Map of service factory which need to be - * provided for the current scope. Defaults to {@link ng}. - * @param {Object.=} instanceCache Provides pre-instantiated services which should - * append/override services provided by `providers`. This is handy - * when unit-testing and having the need to override a default - * service. - * @returns {Object} Newly created scope. - * - */ - function Scope() { - this.$id = nextUid(); - this.$$phase = this.$parent = this.$$watchers = - this.$$nextSibling = this.$$prevSibling = - this.$$childHead = this.$$childTail = null; - this['this'] = this.$root = this; - this.$$destroyed = false; - this.$$asyncQueue = []; - this.$$postDigestQueue = []; - this.$$listeners = {}; - this.$$listenerCount = {}; - this.$$isolateBindings = {}; - } - - /** - * @ngdoc property - * @name $rootScope.Scope#$id - * @returns {number} Unique scope ID (monotonically increasing alphanumeric sequence) useful for - * debugging. - */ - - - Scope.prototype = { - constructor: Scope, - /** - * @ngdoc method - * @name $rootScope.Scope#$new - * @kind function - * - * @description - * Creates a new child {@link ng.$rootScope.Scope scope}. - * - * The parent scope will propagate the {@link ng.$rootScope.Scope#$digest $digest()} and - * {@link ng.$rootScope.Scope#$digest $digest()} events. The scope can be removed from the - * scope hierarchy using {@link ng.$rootScope.Scope#$destroy $destroy()}. - * - * {@link ng.$rootScope.Scope#$destroy $destroy()} must be called on a scope when it is - * desired for the scope and its child scopes to be permanently detached from the parent and - * thus stop participating in model change detection and listener notification by invoking. - * - * @param {boolean} isolate If true, then the scope does not prototypically inherit from the - * parent scope. The scope is isolated, as it can not see parent scope properties. - * When creating widgets, it is useful for the widget to not accidentally read parent - * state. - * - * @returns {Object} The newly created child scope. - * - */ - $new: function(isolate) { - var ChildScope, - child; - - if (isolate) { - child = new Scope(); - child.$root = this.$root; - // ensure that there is just one async queue per $rootScope and its children - child.$$asyncQueue = this.$$asyncQueue; - child.$$postDigestQueue = this.$$postDigestQueue; - } else { - // Only create a child scope class if somebody asks for one, - // but cache it to allow the VM to optimize lookups. - if (!this.$$childScopeClass) { - this.$$childScopeClass = function() { - this.$$watchers = this.$$nextSibling = - this.$$childHead = this.$$childTail = null; - this.$$listeners = {}; - this.$$listenerCount = {}; - this.$id = nextUid(); - this.$$childScopeClass = null; - }; - this.$$childScopeClass.prototype = this; - } - child = new this.$$childScopeClass(); - } - child['this'] = child; - child.$parent = this; - child.$$prevSibling = this.$$childTail; - if (this.$$childHead) { - this.$$childTail.$$nextSibling = child; - this.$$childTail = child; - } else { - this.$$childHead = this.$$childTail = child; - } - return child; - }, - - /** - * @ngdoc method - * @name $rootScope.Scope#$watch - * @kind function - * - * @description - * Registers a `listener` callback to be executed whenever the `watchExpression` changes. - * - * - The `watchExpression` is called on every call to {@link ng.$rootScope.Scope#$digest - * $digest()} and should return the value that will be watched. (Since - * {@link ng.$rootScope.Scope#$digest $digest()} reruns when it detects changes the - * `watchExpression` can execute multiple times per - * {@link ng.$rootScope.Scope#$digest $digest()} and should be idempotent.) - * - The `listener` is called only when the value from the current `watchExpression` and the - * previous call to `watchExpression` are not equal (with the exception of the initial run, - * see below). Inequality is determined according to reference inequality, - * [strict comparison](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comparison_Operators) - * via the `!==` Javascript operator, unless `objectEquality == true` - * (see next point) - * - When `objectEquality == true`, inequality of the `watchExpression` is determined - * according to the {@link angular.equals} function. To save the value of the object for - * later comparison, the {@link angular.copy} function is used. This therefore means that - * watching complex objects will have adverse memory and performance implications. - * - The watch `listener` may change the model, which may trigger other `listener`s to fire. - * This is achieved by rerunning the watchers until no changes are detected. The rerun - * iteration limit is 10 to prevent an infinite loop deadlock. - * - * - * If you want to be notified whenever {@link ng.$rootScope.Scope#$digest $digest} is called, - * you can register a `watchExpression` function with no `listener`. (Since `watchExpression` - * can execute multiple times per {@link ng.$rootScope.Scope#$digest $digest} cycle when a - * change is detected, be prepared for multiple calls to your listener.) - * - * After a watcher is registered with the scope, the `listener` fn is called asynchronously - * (via {@link ng.$rootScope.Scope#$evalAsync $evalAsync}) to initialize the - * watcher. In rare cases, this is undesirable because the listener is called when the result - * of `watchExpression` didn't change. To detect this scenario within the `listener` fn, you - * can compare the `newVal` and `oldVal`. If these two values are identical (`===`) then the - * listener was called due to initialization. - * - * The example below contains an illustration of using a function as your $watch listener - * - * - * # Example - * ```js - // let's assume that scope was dependency injected as the $rootScope - var scope = $rootScope; - scope.name = 'misko'; - scope.counter = 0; - - expect(scope.counter).toEqual(0); - scope.$watch('name', function(newValue, oldValue) { - scope.counter = scope.counter + 1; - }); - expect(scope.counter).toEqual(0); - - scope.$digest(); - // the listener is always called during the first $digest loop after it was registered - expect(scope.counter).toEqual(1); - - scope.$digest(); - // but now it will not be called unless the value changes - expect(scope.counter).toEqual(1); - - scope.name = 'adam'; - scope.$digest(); - expect(scope.counter).toEqual(2); - - - - // Using a listener function - var food; - scope.foodCounter = 0; - expect(scope.foodCounter).toEqual(0); - scope.$watch( - // This is the listener function - function() { return food; }, - // This is the change handler - function(newValue, oldValue) { - if ( newValue !== oldValue ) { - // Only increment the counter if the value changed - scope.foodCounter = scope.foodCounter + 1; - } - } - ); - // No digest has been run so the counter will be zero - expect(scope.foodCounter).toEqual(0); - - // Run the digest but since food has not changed count will still be zero - scope.$digest(); - expect(scope.foodCounter).toEqual(0); - - // Update food and run digest. Now the counter will increment - food = 'cheeseburger'; - scope.$digest(); - expect(scope.foodCounter).toEqual(1); - - * ``` - * - * - * - * @param {(function()|string)} watchExpression Expression that is evaluated on each - * {@link ng.$rootScope.Scope#$digest $digest} cycle. A change in the return value triggers - * a call to the `listener`. - * - * - `string`: Evaluated as {@link guide/expression expression} - * - `function(scope)`: called with current `scope` as a parameter. - * @param {(function()|string)=} listener Callback called whenever the return value of - * the `watchExpression` changes. - * - * - `string`: Evaluated as {@link guide/expression expression} - * - `function(newValue, oldValue, scope)`: called with current and previous values as - * parameters. - * - * @param {boolean=} objectEquality Compare for object equality using {@link angular.equals} instead of - * comparing for reference equality. - * @returns {function()} Returns a deregistration function for this listener. - */ - $watch: function(watchExp, listener, objectEquality) { - var scope = this, - get = compileToFn(watchExp, 'watch'), - array = scope.$$watchers, - watcher = { - fn: listener, - last: initWatchVal, - get: get, - exp: watchExp, - eq: !!objectEquality - }; - - lastDirtyWatch = null; - - // in the case user pass string, we need to compile it, do we really need this ? - if (!isFunction(listener)) { - var listenFn = compileToFn(listener || noop, 'listener'); - watcher.fn = function(newVal, oldVal, scope) {listenFn(scope);}; - } - - if (!array) { - array = scope.$$watchers = []; - } - // we use unshift since we use a while loop in $digest for speed. - // the while loop reads in reverse order. - array.unshift(watcher); - - return function deregisterWatch() { - arrayRemove(array, watcher); - lastDirtyWatch = null; - }; - }, - - /** - * @ngdoc method - * @name $rootScope.Scope#$watchGroup - * @kind function - * - * @description - * A variant of {@link ng.$rootScope.Scope#$watch $watch()} where it watches an array of `watchExpressions`. - * If any one expression in the collection changes the `listener` is executed. - * - * - The items in the `watchCollection` array are observed via standard $watch operation and are examined on every - * call to $digest() to see if any items changes. - * - The `listener` is called whenever any expression in the `watchExpressions` array changes. - * - * @param {Array.} watchExpressions Array of expressions that will be individually - * watched using {@link ng.$rootScope.Scope#$watch $watch()} - * - * @param {function(newValues, oldValues, scope)} listener Callback called whenever the return value of any - * expression in `watchExpressions` changes - * The `newValues` array contains the current values of the `watchExpressions`, with the indexes matching - * those of `watchExpression` - * and the `oldValues` array contains the previous values of the `watchExpressions`, with the indexes matching - * those of `watchExpression` - * The `scope` refers to the current scope. - * - * @returns {function()} Returns a de-registration function for all listeners. - */ - $watchGroup: function(watchExpressions, listener) { - var oldValues = new Array(watchExpressions.length); - var newValues = new Array(watchExpressions.length); - var deregisterFns = []; - var changeCount = 0; - var self = this; - var unwatchFlags = new Array(watchExpressions.length); - var unwatchCount = watchExpressions.length; - - forEach(watchExpressions, function (expr, i) { - var exprFn = $parse(expr); - deregisterFns.push(self.$watch(exprFn, function (value, oldValue) { - newValues[i] = value; - oldValues[i] = oldValue; - changeCount++; - if (unwatchFlags[i] && !exprFn.$$unwatch) unwatchCount++; - if (!unwatchFlags[i] && exprFn.$$unwatch) unwatchCount--; - unwatchFlags[i] = exprFn.$$unwatch; - })); - }, this); - - deregisterFns.push(self.$watch(watchGroupFn, function () { - listener(newValues, oldValues, self); - if (unwatchCount === 0) { - watchGroupFn.$$unwatch = true; - } else { - watchGroupFn.$$unwatch = false; - } - })); - - return function deregisterWatchGroup() { - forEach(deregisterFns, function (fn) { - fn(); - }); - }; - - function watchGroupFn() {return changeCount;} - }, - - - /** - * @ngdoc method - * @name $rootScope.Scope#$watchCollection - * @kind function - * - * @description - * Shallow watches the properties of an object and fires whenever any of the properties change - * (for arrays, this implies watching the array items; for object maps, this implies watching - * the properties). If a change is detected, the `listener` callback is fired. - * - * - The `obj` collection is observed via standard $watch operation and is examined on every - * call to $digest() to see if any items have been added, removed, or moved. - * - The `listener` is called whenever anything within the `obj` has changed. Examples include - * adding, removing, and moving items belonging to an object or array. - * - * - * # Example - * ```js - $scope.names = ['igor', 'matias', 'misko', 'james']; - $scope.dataCount = 4; - - $scope.$watchCollection('names', function(newNames, oldNames) { - $scope.dataCount = newNames.length; - }); - - expect($scope.dataCount).toEqual(4); - $scope.$digest(); - - //still at 4 ... no changes - expect($scope.dataCount).toEqual(4); - - $scope.names.pop(); - $scope.$digest(); - - //now there's been a change - expect($scope.dataCount).toEqual(3); - * ``` - * - * - * @param {string|function(scope)} obj Evaluated as {@link guide/expression expression}. The - * expression value should evaluate to an object or an array which is observed on each - * {@link ng.$rootScope.Scope#$digest $digest} cycle. Any shallow change within the - * collection will trigger a call to the `listener`. - * - * @param {function(newCollection, oldCollection, scope)} listener a callback function called - * when a change is detected. - * - The `newCollection` object is the newly modified data obtained from the `obj` expression - * - The `oldCollection` object is a copy of the former collection data. - * Due to performance considerations, the`oldCollection` value is computed only if the - * `listener` function declares two or more arguments. - * - The `scope` argument refers to the current scope. - * - * @returns {function()} Returns a de-registration function for this listener. When the - * de-registration function is executed, the internal watch operation is terminated. - */ - $watchCollection: function(obj, listener) { - var self = this; - // the current value, updated on each dirty-check run - var newValue; - // a shallow copy of the newValue from the last dirty-check run, - // updated to match newValue during dirty-check run - var oldValue; - // a shallow copy of the newValue from when the last change happened - var veryOldValue; - // only track veryOldValue if the listener is asking for it - var trackVeryOldValue = (listener.length > 1); - var changeDetected = 0; - var objGetter = $parse(obj); - var internalArray = []; - var internalObject = {}; - var initRun = true; - var oldLength = 0; - - function $watchCollectionWatch() { - newValue = objGetter(self); - var newLength, key; - - if (!isObject(newValue)) { // if primitive - if (oldValue !== newValue) { - oldValue = newValue; - changeDetected++; - } - } else if (isArrayLike(newValue)) { - if (oldValue !== internalArray) { - // we are transitioning from something which was not an array into array. - oldValue = internalArray; - oldLength = oldValue.length = 0; - changeDetected++; - } - - newLength = newValue.length; - - if (oldLength !== newLength) { - // if lengths do not match we need to trigger change notification - changeDetected++; - oldValue.length = oldLength = newLength; - } - // copy the items to oldValue and look for changes. - for (var i = 0; i < newLength; i++) { - var bothNaN = (oldValue[i] !== oldValue[i]) && - (newValue[i] !== newValue[i]); - if (!bothNaN && (oldValue[i] !== newValue[i])) { - changeDetected++; - oldValue[i] = newValue[i]; - } - } - } else { - if (oldValue !== internalObject) { - // we are transitioning from something which was not an object into object. - oldValue = internalObject = {}; - oldLength = 0; - changeDetected++; - } - // copy the items to oldValue and look for changes. - newLength = 0; - for (key in newValue) { - if (newValue.hasOwnProperty(key)) { - newLength++; - if (oldValue.hasOwnProperty(key)) { - if (oldValue[key] !== newValue[key]) { - changeDetected++; - oldValue[key] = newValue[key]; - } - } else { - oldLength++; - oldValue[key] = newValue[key]; - changeDetected++; - } - } - } - if (oldLength > newLength) { - // we used to have more keys, need to find them and destroy them. - changeDetected++; - for(key in oldValue) { - if (oldValue.hasOwnProperty(key) && !newValue.hasOwnProperty(key)) { - oldLength--; - delete oldValue[key]; - } - } - } - } - $watchCollectionWatch.$$unwatch = objGetter.$$unwatch; - return changeDetected; - } - - function $watchCollectionAction() { - if (initRun) { - initRun = false; - listener(newValue, newValue, self); - } else { - listener(newValue, veryOldValue, self); - } - - // make a copy for the next time a collection is changed - if (trackVeryOldValue) { - if (!isObject(newValue)) { - //primitive - veryOldValue = newValue; - } else if (isArrayLike(newValue)) { - veryOldValue = new Array(newValue.length); - for (var i = 0; i < newValue.length; i++) { - veryOldValue[i] = newValue[i]; - } - } else { // if object - veryOldValue = {}; - for (var key in newValue) { - if (hasOwnProperty.call(newValue, key)) { - veryOldValue[key] = newValue[key]; - } - } - } - } - } - - return this.$watch($watchCollectionWatch, $watchCollectionAction); - }, - - /** - * @ngdoc method - * @name $rootScope.Scope#$digest - * @kind function - * - * @description - * Processes all of the {@link ng.$rootScope.Scope#$watch watchers} of the current scope and - * its children. Because a {@link ng.$rootScope.Scope#$watch watcher}'s listener can change - * the model, the `$digest()` keeps calling the {@link ng.$rootScope.Scope#$watch watchers} - * until no more listeners are firing. This means that it is possible to get into an infinite - * loop. This function will throw `'Maximum iteration limit exceeded.'` if the number of - * iterations exceeds 10. - * - * Usually, you don't call `$digest()` directly in - * {@link ng.directive:ngController controllers} or in - * {@link ng.$compileProvider#directive directives}. - * Instead, you should call {@link ng.$rootScope.Scope#$apply $apply()} (typically from within - * a {@link ng.$compileProvider#directive directive}), which will force a `$digest()`. - * - * If you want to be notified whenever `$digest()` is called, - * you can register a `watchExpression` function with - * {@link ng.$rootScope.Scope#$watch $watch()} with no `listener`. - * - * In unit tests, you may need to call `$digest()` to simulate the scope life cycle. - * - * # Example - * ```js - var scope = ...; - scope.name = 'misko'; - scope.counter = 0; - - expect(scope.counter).toEqual(0); - scope.$watch('name', function(newValue, oldValue) { - scope.counter = scope.counter + 1; - }); - expect(scope.counter).toEqual(0); - - scope.$digest(); - // the listener is always called during the first $digest loop after it was registered - expect(scope.counter).toEqual(1); - - scope.$digest(); - // but now it will not be called unless the value changes - expect(scope.counter).toEqual(1); - - scope.name = 'adam'; - scope.$digest(); - expect(scope.counter).toEqual(2); - * ``` - * - */ - $digest: function() { - var watch, value, last, - watchers, - asyncQueue = this.$$asyncQueue, - postDigestQueue = this.$$postDigestQueue, - length, - dirty, ttl = TTL, - next, current, target = this, - watchLog = [], - stableWatchesCandidates = [], - logIdx, logMsg, asyncTask; - - beginPhase('$digest'); - - lastDirtyWatch = null; - - do { // "while dirty" loop - dirty = false; - current = target; - - while(asyncQueue.length) { - try { - asyncTask = asyncQueue.shift(); - asyncTask.scope.$eval(asyncTask.expression); - } catch (e) { - clearPhase(); - $exceptionHandler(e); - } - lastDirtyWatch = null; - } - - traverseScopesLoop: - do { // "traverse the scopes" loop - if ((watchers = current.$$watchers)) { - // process our watches - length = watchers.length; - while (length--) { - try { - watch = watchers[length]; - // Most common watches are on primitives, in which case we can short - // circuit it with === operator, only when === fails do we use .equals - if (watch) { - if ((value = watch.get(current)) !== (last = watch.last) && - !(watch.eq - ? equals(value, last) - : (typeof value == 'number' && typeof last == 'number' - && isNaN(value) && isNaN(last)))) { - dirty = true; - lastDirtyWatch = watch; - watch.last = watch.eq ? copy(value, null) : value; - watch.fn(value, ((last === initWatchVal) ? value : last), current); - if (ttl < 5) { - logIdx = 4 - ttl; - if (!watchLog[logIdx]) watchLog[logIdx] = []; - logMsg = (isFunction(watch.exp)) - ? 'fn: ' + (watch.exp.name || watch.exp.toString()) - : watch.exp; - logMsg += '; newVal: ' + toJson(value) + '; oldVal: ' + toJson(last); - watchLog[logIdx].push(logMsg); - } - if (watch.get.$$unwatch) stableWatchesCandidates.push({watch: watch, array: watchers}); - } else if (watch === lastDirtyWatch) { - // If the most recently dirty watcher is now clean, short circuit since the remaining watchers - // have already been tested. - dirty = false; - break traverseScopesLoop; - } - } - } catch (e) { - clearPhase(); - $exceptionHandler(e); - } - } - } - - // Insanity Warning: scope depth-first traversal - // yes, this code is a bit crazy, but it works and we have tests to prove it! - // this piece should be kept in sync with the traversal in $broadcast - if (!(next = (current.$$childHead || - (current !== target && current.$$nextSibling)))) { - while(current !== target && !(next = current.$$nextSibling)) { - current = current.$parent; - } - } - } while ((current = next)); - - // `break traverseScopesLoop;` takes us to here - - if((dirty || asyncQueue.length) && !(ttl--)) { - clearPhase(); - throw $rootScopeMinErr('infdig', - '{0} $digest() iterations reached. Aborting!\n' + - 'Watchers fired in the last 5 iterations: {1}', - TTL, toJson(watchLog)); - } - - } while (dirty || asyncQueue.length); - - clearPhase(); - - while(postDigestQueue.length) { - try { - postDigestQueue.shift()(); - } catch (e) { - $exceptionHandler(e); - } - } - - for (length = stableWatchesCandidates.length - 1; length >= 0; --length) { - var candidate = stableWatchesCandidates[length]; - if (candidate.watch.get.$$unwatch) { - arrayRemove(candidate.array, candidate.watch); - } - } - }, - - - /** - * @ngdoc event - * @name $rootScope.Scope#$destroy - * @eventType broadcast on scope being destroyed - * - * @description - * Broadcasted when a scope and its children are being destroyed. - * - * Note that, in AngularJS, there is also a `$destroy` jQuery event, which can be used to - * clean up DOM bindings before an element is removed from the DOM. - */ - - /** - * @ngdoc method - * @name $rootScope.Scope#$destroy - * @kind function - * - * @description - * Removes the current scope (and all of its children) from the parent scope. Removal implies - * that calls to {@link ng.$rootScope.Scope#$digest $digest()} will no longer - * propagate to the current scope and its children. Removal also implies that the current - * scope is eligible for garbage collection. - * - * The `$destroy()` is usually used by directives such as - * {@link ng.directive:ngRepeat ngRepeat} for managing the - * unrolling of the loop. - * - * Just before a scope is destroyed, a `$destroy` event is broadcasted on this scope. - * Application code can register a `$destroy` event handler that will give it a chance to - * perform any necessary cleanup. - * - * Note that, in AngularJS, there is also a `$destroy` jQuery event, which can be used to - * clean up DOM bindings before an element is removed from the DOM. - */ - $destroy: function() { - // we can't destroy the root scope or a scope that has been already destroyed - if (this.$$destroyed) return; - var parent = this.$parent; - - this.$broadcast('$destroy'); - this.$$destroyed = true; - if (this === $rootScope) return; - - forEach(this.$$listenerCount, bind(null, decrementListenerCount, this)); - - // sever all the references to parent scopes (after this cleanup, the current scope should - // not be retained by any of our references and should be eligible for garbage collection) - if (parent.$$childHead == this) parent.$$childHead = this.$$nextSibling; - if (parent.$$childTail == this) parent.$$childTail = this.$$prevSibling; - if (this.$$prevSibling) this.$$prevSibling.$$nextSibling = this.$$nextSibling; - if (this.$$nextSibling) this.$$nextSibling.$$prevSibling = this.$$prevSibling; - - - // All of the code below is bogus code that works around V8's memory leak via optimized code - // and inline caches. - // - // see: - // - https://code.google.com/p/v8/issues/detail?id=2073#c26 - // - https://github.com/angular/angular.js/issues/6794#issuecomment-38648909 - // - https://github.com/angular/angular.js/issues/1313#issuecomment-10378451 - - this.$parent = this.$$nextSibling = this.$$prevSibling = this.$$childHead = - this.$$childTail = this.$root = null; - - // don't reset these to null in case some async task tries to register a listener/watch/task - this.$$listeners = {}; - this.$$watchers = this.$$asyncQueue = this.$$postDigestQueue = []; - - // prevent NPEs since these methods have references to properties we nulled out - this.$destroy = this.$digest = this.$apply = noop; - this.$on = this.$watch = this.$watchGroup = function() { return noop; }; - }, - - /** - * @ngdoc method - * @name $rootScope.Scope#$eval - * @kind function - * - * @description - * Executes the `expression` on the current scope and returns the result. Any exceptions in - * the expression are propagated (uncaught). This is useful when evaluating Angular - * expressions. - * - * # Example - * ```js - var scope = ng.$rootScope.Scope(); - scope.a = 1; - scope.b = 2; - - expect(scope.$eval('a+b')).toEqual(3); - expect(scope.$eval(function(scope){ return scope.a + scope.b; })).toEqual(3); - * ``` - * - * @param {(string|function())=} expression An angular expression to be executed. - * - * - `string`: execute using the rules as defined in {@link guide/expression expression}. - * - `function(scope)`: execute the function with the current `scope` parameter. - * - * @param {(object)=} locals Local variables object, useful for overriding values in scope. - * @returns {*} The result of evaluating the expression. - */ - $eval: function(expr, locals) { - return $parse(expr)(this, locals); - }, - - /** - * @ngdoc method - * @name $rootScope.Scope#$evalAsync - * @kind function - * - * @description - * Executes the expression on the current scope at a later point in time. - * - * The `$evalAsync` makes no guarantees as to when the `expression` will be executed, only - * that: - * - * - it will execute after the function that scheduled the evaluation (preferably before DOM - * rendering). - * - at least one {@link ng.$rootScope.Scope#$digest $digest cycle} will be performed after - * `expression` execution. - * - * Any exceptions from the execution of the expression are forwarded to the - * {@link ng.$exceptionHandler $exceptionHandler} service. - * - * __Note:__ if this function is called outside of a `$digest` cycle, a new `$digest` cycle - * will be scheduled. However, it is encouraged to always call code that changes the model - * from within an `$apply` call. That includes code evaluated via `$evalAsync`. - * - * @param {(string|function())=} expression An angular expression to be executed. - * - * - `string`: execute using the rules as defined in {@link guide/expression expression}. - * - `function(scope)`: execute the function with the current `scope` parameter. - * - */ - $evalAsync: function(expr) { - // if we are outside of an $digest loop and this is the first time we are scheduling async - // task also schedule async auto-flush - if (!$rootScope.$$phase && !$rootScope.$$asyncQueue.length) { - $browser.defer(function() { - if ($rootScope.$$asyncQueue.length) { - $rootScope.$digest(); - } - }); - } - - this.$$asyncQueue.push({scope: this, expression: expr}); - }, - - $$postDigest : function(fn) { - this.$$postDigestQueue.push(fn); - }, - - /** - * @ngdoc method - * @name $rootScope.Scope#$apply - * @kind function - * - * @description - * `$apply()` is used to execute an expression in angular from outside of the angular - * framework. (For example from browser DOM events, setTimeout, XHR or third party libraries). - * Because we are calling into the angular framework we need to perform proper scope life - * cycle of {@link ng.$exceptionHandler exception handling}, - * {@link ng.$rootScope.Scope#$digest executing watches}. - * - * ## Life cycle - * - * # Pseudo-Code of `$apply()` - * ```js - function $apply(expr) { - try { - return $eval(expr); - } catch (e) { - $exceptionHandler(e); - } finally { - $root.$digest(); - } - } - * ``` - * - * - * Scope's `$apply()` method transitions through the following stages: - * - * 1. The {@link guide/expression expression} is executed using the - * {@link ng.$rootScope.Scope#$eval $eval()} method. - * 2. Any exceptions from the execution of the expression are forwarded to the - * {@link ng.$exceptionHandler $exceptionHandler} service. - * 3. The {@link ng.$rootScope.Scope#$watch watch} listeners are fired immediately after the - * expression was executed using the {@link ng.$rootScope.Scope#$digest $digest()} method. - * - * - * @param {(string|function())=} exp An angular expression to be executed. - * - * - `string`: execute using the rules as defined in {@link guide/expression expression}. - * - `function(scope)`: execute the function with current `scope` parameter. - * - * @returns {*} The result of evaluating the expression. - */ - $apply: function(expr) { - try { - beginPhase('$apply'); - return this.$eval(expr); - } catch (e) { - $exceptionHandler(e); - } finally { - clearPhase(); - try { - $rootScope.$digest(); - } catch (e) { - $exceptionHandler(e); - throw e; - } - } - }, - - /** - * @ngdoc method - * @name $rootScope.Scope#$on - * @kind function - * - * @description - * Listens on events of a given type. See {@link ng.$rootScope.Scope#$emit $emit} for - * discussion of event life cycle. - * - * The event listener function format is: `function(event, args...)`. The `event` object - * passed into the listener has the following attributes: - * - * - `targetScope` - `{Scope}`: the scope on which the event was `$emit`-ed or - * `$broadcast`-ed. - * - `currentScope` - `{Scope}`: the scope that is currently handling the event. Once the - * event propagates through the scope hierarchy, this property is set to null. - * - `name` - `{string}`: name of the event. - * - `stopPropagation` - `{function=}`: calling `stopPropagation` function will cancel - * further event propagation (available only for events that were `$emit`-ed). - * - `preventDefault` - `{function}`: calling `preventDefault` sets `defaultPrevented` flag - * to true. - * - `defaultPrevented` - `{boolean}`: true if `preventDefault` was called. - * - * @param {string} name Event name to listen on. - * @param {function(event, ...args)} listener Function to call when the event is emitted. - * @returns {function()} Returns a deregistration function for this listener. - */ - $on: function(name, listener) { - var namedListeners = this.$$listeners[name]; - if (!namedListeners) { - this.$$listeners[name] = namedListeners = []; - } - namedListeners.push(listener); - - var current = this; - do { - if (!current.$$listenerCount[name]) { - current.$$listenerCount[name] = 0; - } - current.$$listenerCount[name]++; - } while ((current = current.$parent)); - - var self = this; - return function() { - namedListeners[indexOf(namedListeners, listener)] = null; - decrementListenerCount(self, 1, name); - }; - }, - - - /** - * @ngdoc method - * @name $rootScope.Scope#$emit - * @kind function - * - * @description - * Dispatches an event `name` upwards through the scope hierarchy notifying the - * registered {@link ng.$rootScope.Scope#$on} listeners. - * - * The event life cycle starts at the scope on which `$emit` was called. All - * {@link ng.$rootScope.Scope#$on listeners} listening for `name` event on this scope get - * notified. Afterwards, the event traverses upwards toward the root scope and calls all - * registered listeners along the way. The event will stop propagating if one of the listeners - * cancels it. - * - * Any exception emitted from the {@link ng.$rootScope.Scope#$on listeners} will be passed - * onto the {@link ng.$exceptionHandler $exceptionHandler} service. - * - * @param {string} name Event name to emit. - * @param {...*} args Optional one or more arguments which will be passed onto the event listeners. - * @return {Object} Event object (see {@link ng.$rootScope.Scope#$on}). - */ - $emit: function(name, args) { - var empty = [], - namedListeners, - scope = this, - stopPropagation = false, - event = { - name: name, - targetScope: scope, - stopPropagation: function() {stopPropagation = true;}, - preventDefault: function() { - event.defaultPrevented = true; - }, - defaultPrevented: false - }, - listenerArgs = concat([event], arguments, 1), - i, length; - - do { - namedListeners = scope.$$listeners[name] || empty; - event.currentScope = scope; - for (i=0, length=namedListeners.length; i= 8 ) { - normalizedVal = urlResolve(uri).href; - if (normalizedVal !== '' && !normalizedVal.match(regex)) { - return 'unsafe:'+normalizedVal; - } - } - return uri; - }; - }; -} - -var $sceMinErr = minErr('$sce'); - -var SCE_CONTEXTS = { - HTML: 'html', - CSS: 'css', - URL: 'url', - // RESOURCE_URL is a subtype of URL used in contexts where a privileged resource is sourced from a - // url. (e.g. ng-include, script src, templateUrl) - RESOURCE_URL: 'resourceUrl', - JS: 'js' -}; - -// Helper functions follow. - -// Copied from: -// http://docs.closure-library.googlecode.com/git/closure_goog_string_string.js.source.html#line962 -// Prereq: s is a string. -function escapeForRegexp(s) { - return s.replace(/([-()\[\]{}+?*.$\^|,:# -1) { - throw $sceMinErr('iwcard', - 'Illegal sequence *** in string matcher. String: {0}', matcher); - } - matcher = escapeForRegexp(matcher). - replace('\\*\\*', '.*'). - replace('\\*', '[^:/.?&;]*'); - return new RegExp('^' + matcher + '$'); - } else if (isRegExp(matcher)) { - // The only other type of matcher allowed is a Regexp. - // Match entire URL / disallow partial matches. - // Flags are reset (i.e. no global, ignoreCase or multiline) - return new RegExp('^' + matcher.source + '$'); - } else { - throw $sceMinErr('imatcher', - 'Matchers may only be "self", string patterns or RegExp objects'); - } -} - - -function adjustMatchers(matchers) { - var adjustedMatchers = []; - if (isDefined(matchers)) { - forEach(matchers, function(matcher) { - adjustedMatchers.push(adjustMatcher(matcher)); - }); - } - return adjustedMatchers; -} - - -/** - * @ngdoc service - * @name $sceDelegate - * @kind function - * - * @description - * - * `$sceDelegate` is a service that is used by the `$sce` service to provide {@link ng.$sce Strict - * Contextual Escaping (SCE)} services to AngularJS. - * - * Typically, you would configure or override the {@link ng.$sceDelegate $sceDelegate} instead of - * the `$sce` service to customize the way Strict Contextual Escaping works in AngularJS. This is - * because, while the `$sce` provides numerous shorthand methods, etc., you really only need to - * override 3 core functions (`trustAs`, `getTrusted` and `valueOf`) to replace the way things - * work because `$sce` delegates to `$sceDelegate` for these operations. - * - * Refer {@link ng.$sceDelegateProvider $sceDelegateProvider} to configure this service. - * - * The default instance of `$sceDelegate` should work out of the box with little pain. While you - * can override it completely to change the behavior of `$sce`, the common case would - * involve configuring the {@link ng.$sceDelegateProvider $sceDelegateProvider} instead by setting - * your own whitelists and blacklists for trusting URLs used for loading AngularJS resources such as - * templates. Refer {@link ng.$sceDelegateProvider#resourceUrlWhitelist - * $sceDelegateProvider.resourceUrlWhitelist} and {@link - * ng.$sceDelegateProvider#resourceUrlBlacklist $sceDelegateProvider.resourceUrlBlacklist} - */ - -/** - * @ngdoc provider - * @name $sceDelegateProvider - * @description - * - * The `$sceDelegateProvider` provider allows developers to configure the {@link ng.$sceDelegate - * $sceDelegate} service. This allows one to get/set the whitelists and blacklists used to ensure - * that the URLs used for sourcing Angular templates are safe. Refer {@link - * ng.$sceDelegateProvider#resourceUrlWhitelist $sceDelegateProvider.resourceUrlWhitelist} and - * {@link ng.$sceDelegateProvider#resourceUrlBlacklist $sceDelegateProvider.resourceUrlBlacklist} - * - * For the general details about this service in Angular, read the main page for {@link ng.$sce - * Strict Contextual Escaping (SCE)}. - * - * **Example**: Consider the following case. - * - * - your app is hosted at url `http://myapp.example.com/` - * - but some of your templates are hosted on other domains you control such as - * `http://srv01.assets.example.com/`,  `http://srv02.assets.example.com/`, etc. - * - and you have an open redirect at `http://myapp.example.com/clickThru?...`. - * - * Here is what a secure configuration for this scenario might look like: - * - *
- *    angular.module('myApp', []).config(function($sceDelegateProvider) {
- *      $sceDelegateProvider.resourceUrlWhitelist([
- *        // Allow same origin resource loads.
- *        'self',
- *        // Allow loading from our assets domain.  Notice the difference between * and **.
- *        'http://srv*.assets.example.com/**']);
- *
- *      // The blacklist overrides the whitelist so the open redirect here is blocked.
- *      $sceDelegateProvider.resourceUrlBlacklist([
- *        'http://myapp.example.com/clickThru**']);
- *      });
- * 
- */ - -function $SceDelegateProvider() { - this.SCE_CONTEXTS = SCE_CONTEXTS; - - // Resource URLs can also be trusted by policy. - var resourceUrlWhitelist = ['self'], - resourceUrlBlacklist = []; - - /** - * @ngdoc method - * @name $sceDelegateProvider#resourceUrlWhitelist - * @kind function - * - * @param {Array=} whitelist When provided, replaces the resourceUrlWhitelist with the value - * provided. This must be an array or null. A snapshot of this array is used so further - * changes to the array are ignored. - * - * Follow {@link ng.$sce#resourceUrlPatternItem this link} for a description of the items - * allowed in this array. - * - * Note: **an empty whitelist array will block all URLs**! - * - * @return {Array} the currently set whitelist array. - * - * The **default value** when no whitelist has been explicitly set is `['self']` allowing only - * same origin resource requests. - * - * @description - * Sets/Gets the whitelist of trusted resource URLs. - */ - this.resourceUrlWhitelist = function (value) { - if (arguments.length) { - resourceUrlWhitelist = adjustMatchers(value); - } - return resourceUrlWhitelist; - }; - - /** - * @ngdoc method - * @name $sceDelegateProvider#resourceUrlBlacklist - * @kind function - * - * @param {Array=} blacklist When provided, replaces the resourceUrlBlacklist with the value - * provided. This must be an array or null. A snapshot of this array is used so further - * changes to the array are ignored. - * - * Follow {@link ng.$sce#resourceUrlPatternItem this link} for a description of the items - * allowed in this array. - * - * The typical usage for the blacklist is to **block - * [open redirects](http://cwe.mitre.org/data/definitions/601.html)** served by your domain as - * these would otherwise be trusted but actually return content from the redirected domain. - * - * Finally, **the blacklist overrides the whitelist** and has the final say. - * - * @return {Array} the currently set blacklist array. - * - * The **default value** when no whitelist has been explicitly set is the empty array (i.e. there - * is no blacklist.) - * - * @description - * Sets/Gets the blacklist of trusted resource URLs. - */ - - this.resourceUrlBlacklist = function (value) { - if (arguments.length) { - resourceUrlBlacklist = adjustMatchers(value); - } - return resourceUrlBlacklist; - }; - - this.$get = ['$injector', function($injector) { - - var htmlSanitizer = function htmlSanitizer(html) { - throw $sceMinErr('unsafe', 'Attempting to use an unsafe value in a safe context.'); - }; - - if ($injector.has('$sanitize')) { - htmlSanitizer = $injector.get('$sanitize'); - } - - - function matchUrl(matcher, parsedUrl) { - if (matcher === 'self') { - return urlIsSameOrigin(parsedUrl); - } else { - // definitely a regex. See adjustMatchers() - return !!matcher.exec(parsedUrl.href); - } - } - - function isResourceUrlAllowedByPolicy(url) { - var parsedUrl = urlResolve(url.toString()); - var i, n, allowed = false; - // Ensure that at least one item from the whitelist allows this url. - for (i = 0, n = resourceUrlWhitelist.length; i < n; i++) { - if (matchUrl(resourceUrlWhitelist[i], parsedUrl)) { - allowed = true; - break; - } - } - if (allowed) { - // Ensure that no item from the blacklist blocked this url. - for (i = 0, n = resourceUrlBlacklist.length; i < n; i++) { - if (matchUrl(resourceUrlBlacklist[i], parsedUrl)) { - allowed = false; - break; - } - } - } - return allowed; - } - - function generateHolderType(Base) { - var holderType = function TrustedValueHolderType(trustedValue) { - this.$$unwrapTrustedValue = function() { - return trustedValue; - }; - }; - if (Base) { - holderType.prototype = new Base(); - } - holderType.prototype.valueOf = function sceValueOf() { - return this.$$unwrapTrustedValue(); - }; - holderType.prototype.toString = function sceToString() { - return this.$$unwrapTrustedValue().toString(); - }; - return holderType; - } - - var trustedValueHolderBase = generateHolderType(), - byType = {}; - - byType[SCE_CONTEXTS.HTML] = generateHolderType(trustedValueHolderBase); - byType[SCE_CONTEXTS.CSS] = generateHolderType(trustedValueHolderBase); - byType[SCE_CONTEXTS.URL] = generateHolderType(trustedValueHolderBase); - byType[SCE_CONTEXTS.JS] = generateHolderType(trustedValueHolderBase); - byType[SCE_CONTEXTS.RESOURCE_URL] = generateHolderType(byType[SCE_CONTEXTS.URL]); - - /** - * @ngdoc method - * @name $sceDelegate#trustAs - * - * @description - * Returns an object that is trusted by angular for use in specified strict - * contextual escaping contexts (such as ng-bind-html, ng-include, any src - * attribute interpolation, any dom event binding attribute interpolation - * such as for onclick, etc.) that uses the provided value. - * See {@link ng.$sce $sce} for enabling strict contextual escaping. - * - * @param {string} type The kind of context in which this value is safe for use. e.g. url, - * resourceUrl, html, js and css. - * @param {*} value The value that that should be considered trusted/safe. - * @returns {*} A value that can be used to stand in for the provided `value` in places - * where Angular expects a $sce.trustAs() return value. - */ - function trustAs(type, trustedValue) { - var Constructor = (byType.hasOwnProperty(type) ? byType[type] : null); - if (!Constructor) { - throw $sceMinErr('icontext', - 'Attempted to trust a value in invalid context. Context: {0}; Value: {1}', - type, trustedValue); - } - if (trustedValue === null || trustedValue === undefined || trustedValue === '') { - return trustedValue; - } - // All the current contexts in SCE_CONTEXTS happen to be strings. In order to avoid trusting - // mutable objects, we ensure here that the value passed in is actually a string. - if (typeof trustedValue !== 'string') { - throw $sceMinErr('itype', - 'Attempted to trust a non-string value in a content requiring a string: Context: {0}', - type); - } - return new Constructor(trustedValue); - } - - /** - * @ngdoc method - * @name $sceDelegate#valueOf - * - * @description - * If the passed parameter had been returned by a prior call to {@link ng.$sceDelegate#trustAs - * `$sceDelegate.trustAs`}, returns the value that had been passed to {@link - * ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}. - * - * If the passed parameter is not a value that had been returned by {@link - * ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}, returns it as-is. - * - * @param {*} value The result of a prior {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`} - * call or anything else. - * @returns {*} The `value` that was originally provided to {@link ng.$sceDelegate#trustAs - * `$sceDelegate.trustAs`} if `value` is the result of such a call. Otherwise, returns - * `value` unchanged. - */ - function valueOf(maybeTrusted) { - if (maybeTrusted instanceof trustedValueHolderBase) { - return maybeTrusted.$$unwrapTrustedValue(); - } else { - return maybeTrusted; - } - } - - /** - * @ngdoc method - * @name $sceDelegate#getTrusted - * - * @description - * Takes the result of a {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`} call and - * returns the originally supplied value if the queried context type is a supertype of the - * created type. If this condition isn't satisfied, throws an exception. - * - * @param {string} type The kind of context in which this value is to be used. - * @param {*} maybeTrusted The result of a prior {@link ng.$sceDelegate#trustAs - * `$sceDelegate.trustAs`} call. - * @returns {*} The value the was originally provided to {@link ng.$sceDelegate#trustAs - * `$sceDelegate.trustAs`} if valid in this context. Otherwise, throws an exception. - */ - function getTrusted(type, maybeTrusted) { - if (maybeTrusted === null || maybeTrusted === undefined || maybeTrusted === '') { - return maybeTrusted; - } - var constructor = (byType.hasOwnProperty(type) ? byType[type] : null); - if (constructor && maybeTrusted instanceof constructor) { - return maybeTrusted.$$unwrapTrustedValue(); - } - // If we get here, then we may only take one of two actions. - // 1. sanitize the value for the requested type, or - // 2. throw an exception. - if (type === SCE_CONTEXTS.RESOURCE_URL) { - if (isResourceUrlAllowedByPolicy(maybeTrusted)) { - return maybeTrusted; - } else { - throw $sceMinErr('insecurl', - 'Blocked loading resource from url not allowed by $sceDelegate policy. URL: {0}', - maybeTrusted.toString()); - } - } else if (type === SCE_CONTEXTS.HTML) { - return htmlSanitizer(maybeTrusted); - } - throw $sceMinErr('unsafe', 'Attempting to use an unsafe value in a safe context.'); - } - - return { trustAs: trustAs, - getTrusted: getTrusted, - valueOf: valueOf }; - }]; -} - - -/** - * @ngdoc provider - * @name $sceProvider - * @description - * - * The $sceProvider provider allows developers to configure the {@link ng.$sce $sce} service. - * - enable/disable Strict Contextual Escaping (SCE) in a module - * - override the default implementation with a custom delegate - * - * Read more about {@link ng.$sce Strict Contextual Escaping (SCE)}. - */ - -/* jshint maxlen: false*/ - -/** - * @ngdoc service - * @name $sce - * @kind function - * - * @description - * - * `$sce` is a service that provides Strict Contextual Escaping services to AngularJS. - * - * # Strict Contextual Escaping - * - * Strict Contextual Escaping (SCE) is a mode in which AngularJS requires bindings in certain - * contexts to result in a value that is marked as safe to use for that context. One example of - * such a context is binding arbitrary html controlled by the user via `ng-bind-html`. We refer - * to these contexts as privileged or SCE contexts. - * - * As of version 1.2, Angular ships with SCE enabled by default. - * - * Note: When enabled (the default), IE8 in quirks mode is not supported. In this mode, IE8 allows - * one to execute arbitrary javascript by the use of the expression() syntax. Refer - * to learn more about them. - * You can ensure your document is in standards mode and not quirks mode by adding `` - * to the top of your HTML document. - * - * SCE assists in writing code in way that (a) is secure by default and (b) makes auditing for - * security vulnerabilities such as XSS, clickjacking, etc. a lot easier. - * - * Here's an example of a binding in a privileged context: - * - *
- *     
- *     
- *
- * - * Notice that `ng-bind-html` is bound to `userHtml` controlled by the user. With SCE - * disabled, this application allows the user to render arbitrary HTML into the DIV. - * In a more realistic example, one may be rendering user comments, blog articles, etc. via - * bindings. (HTML is just one example of a context where rendering user controlled input creates - * security vulnerabilities.) - * - * For the case of HTML, you might use a library, either on the client side, or on the server side, - * to sanitize unsafe HTML before binding to the value and rendering it in the document. - * - * How would you ensure that every place that used these types of bindings was bound to a value that - * was sanitized by your library (or returned as safe for rendering by your server?) How can you - * ensure that you didn't accidentally delete the line that sanitized the value, or renamed some - * properties/fields and forgot to update the binding to the sanitized value? - * - * To be secure by default, you want to ensure that any such bindings are disallowed unless you can - * determine that something explicitly says it's safe to use a value for binding in that - * context. You can then audit your code (a simple grep would do) to ensure that this is only done - * for those values that you can easily tell are safe - because they were received from your server, - * sanitized by your library, etc. You can organize your codebase to help with this - perhaps - * allowing only the files in a specific directory to do this. Ensuring that the internal API - * exposed by that code doesn't markup arbitrary values as safe then becomes a more manageable task. - * - * In the case of AngularJS' SCE service, one uses {@link ng.$sce#trustAs $sce.trustAs} - * (and shorthand methods such as {@link ng.$sce#trustAsHtml $sce.trustAsHtml}, etc.) to - * obtain values that will be accepted by SCE / privileged contexts. - * - * - * ## How does it work? - * - * In privileged contexts, directives and code will bind to the result of {@link ng.$sce#getTrusted - * $sce.getTrusted(context, value)} rather than to the value directly. Directives use {@link - * ng.$sce#parse $sce.parseAs} rather than `$parse` to watch attribute bindings, which performs the - * {@link ng.$sce#getTrusted $sce.getTrusted} behind the scenes on non-constant literals. - * - * As an example, {@link ng.directive:ngBindHtml ngBindHtml} uses {@link - * ng.$sce#parseAsHtml $sce.parseAsHtml(binding expression)}. Here's the actual code (slightly - * simplified): - * - *
- *   var ngBindHtmlDirective = ['$sce', function($sce) {
- *     return function(scope, element, attr) {
- *       scope.$watch($sce.parseAsHtml(attr.ngBindHtml), function(value) {
- *         element.html(value || '');
- *       });
- *     };
- *   }];
- * 
- * - * ## Impact on loading templates - * - * This applies both to the {@link ng.directive:ngInclude `ng-include`} directive as well as - * `templateUrl`'s specified by {@link guide/directive directives}. - * - * By default, Angular only loads templates from the same domain and protocol as the application - * document. This is done by calling {@link ng.$sce#getTrustedResourceUrl - * $sce.getTrustedResourceUrl} on the template URL. To load templates from other domains and/or - * protocols, you may either either {@link ng.$sceDelegateProvider#resourceUrlWhitelist whitelist - * them} or {@link ng.$sce#trustAsResourceUrl wrap it} into a trusted value. - * - * *Please note*: - * The browser's - * [Same Origin Policy](https://code.google.com/p/browsersec/wiki/Part2#Same-origin_policy_for_XMLHttpRequest) - * and [Cross-Origin Resource Sharing (CORS)](http://www.w3.org/TR/cors/) - * policy apply in addition to this and may further restrict whether the template is successfully - * loaded. This means that without the right CORS policy, loading templates from a different domain - * won't work on all browsers. Also, loading templates from `file://` URL does not work on some - * browsers. - * - * ## This feels like too much overhead - * - * It's important to remember that SCE only applies to interpolation expressions. - * - * If your expressions are constant literals, they're automatically trusted and you don't need to - * call `$sce.trustAs` on them (remember to include the `ngSanitize` module) (e.g. - * `
`) just works. - * - * Additionally, `a[href]` and `img[src]` automatically sanitize their URLs and do not pass them - * through {@link ng.$sce#getTrusted $sce.getTrusted}. SCE doesn't play a role here. - * - * The included {@link ng.$sceDelegate $sceDelegate} comes with sane defaults to allow you to load - * templates in `ng-include` from your application's domain without having to even know about SCE. - * It blocks loading templates from other domains or loading templates over http from an https - * served document. You can change these by setting your own custom {@link - * ng.$sceDelegateProvider#resourceUrlWhitelist whitelists} and {@link - * ng.$sceDelegateProvider#resourceUrlBlacklist blacklists} for matching such URLs. - * - * This significantly reduces the overhead. It is far easier to pay the small overhead and have an - * application that's secure and can be audited to verify that with much more ease than bolting - * security onto an application later. - * - * - * ## What trusted context types are supported? - * - * | Context | Notes | - * |---------------------|----------------| - * | `$sce.HTML` | For HTML that's safe to source into the application. The {@link ng.directive:ngBindHtml ngBindHtml} directive uses this context for bindings. If an unsafe value is encountered and the {@link ngSanitize $sanitize} module is present this will sanitize the value instead of throwing an error. | - * | `$sce.CSS` | For CSS that's safe to source into the application. Currently unused. Feel free to use it in your own directives. | - * | `$sce.URL` | For URLs that are safe to follow as links. Currently unused (`
Note that `$sce.RESOURCE_URL` makes a stronger statement about the URL than `$sce.URL` does and therefore contexts requiring values trusted for `$sce.RESOURCE_URL` can be used anywhere that values trusted for `$sce.URL` are required. | - * | `$sce.JS` | For JavaScript that is safe to execute in your application's context. Currently unused. Feel free to use it in your own directives. | - * - * ## Format of items in {@link ng.$sceDelegateProvider#resourceUrlWhitelist resourceUrlWhitelist}/{@link ng.$sceDelegateProvider#resourceUrlBlacklist Blacklist}
- * - * Each element in these arrays must be one of the following: - * - * - **'self'** - * - The special **string**, `'self'`, can be used to match against all URLs of the **same - * domain** as the application document using the **same protocol**. - * - **String** (except the special value `'self'`) - * - The string is matched against the full *normalized / absolute URL* of the resource - * being tested (substring matches are not good enough.) - * - There are exactly **two wildcard sequences** - `*` and `**`. All other characters - * match themselves. - * - `*`: matches zero or more occurrences of any character other than one of the following 6 - * characters: '`:`', '`/`', '`.`', '`?`', '`&`' and ';'. It's a useful wildcard for use - * in a whitelist. - * - `**`: matches zero or more occurrences of *any* character. As such, it's not - * not appropriate to use in for a scheme, domain, etc. as it would match too much. (e.g. - * http://**.example.com/ would match http://evil.com/?ignore=.example.com/ and that might - * not have been the intention.) Its usage at the very end of the path is ok. (e.g. - * http://foo.example.com/templates/**). - * - **RegExp** (*see caveat below*) - * - *Caveat*: While regular expressions are powerful and offer great flexibility, their syntax - * (and all the inevitable escaping) makes them *harder to maintain*. It's easy to - * accidentally introduce a bug when one updates a complex expression (imho, all regexes should - * have good test coverage.). For instance, the use of `.` in the regex is correct only in a - * small number of cases. A `.` character in the regex used when matching the scheme or a - * subdomain could be matched against a `:` or literal `.` that was likely not intended. It - * is highly recommended to use the string patterns and only fall back to regular expressions - * if they as a last resort. - * - The regular expression must be an instance of RegExp (i.e. not a string.) It is - * matched against the **entire** *normalized / absolute URL* of the resource being tested - * (even when the RegExp did not have the `^` and `$` codes.) In addition, any flags - * present on the RegExp (such as multiline, global, ignoreCase) are ignored. - * - If you are generating your JavaScript from some other templating engine (not - * recommended, e.g. in issue [#4006](https://github.com/angular/angular.js/issues/4006)), - * remember to escape your regular expression (and be aware that you might need more than - * one level of escaping depending on your templating engine and the way you interpolated - * the value.) Do make use of your platform's escaping mechanism as it might be good - * enough before coding your own. e.g. Ruby has - * [Regexp.escape(str)](http://www.ruby-doc.org/core-2.0.0/Regexp.html#method-c-escape) - * and Python has [re.escape](http://docs.python.org/library/re.html#re.escape). - * Javascript lacks a similar built in function for escaping. Take a look at Google - * Closure library's [goog.string.regExpEscape(s)]( - * http://docs.closure-library.googlecode.com/git/closure_goog_string_string.js.source.html#line962). - * - * Refer {@link ng.$sceDelegateProvider $sceDelegateProvider} for an example. - * - * ## Show me an example using SCE. - * - * @example - - -
-

- User comments
- By default, HTML that isn't explicitly trusted (e.g. Alice's comment) is sanitized when - $sanitize is available. If $sanitize isn't available, this results in an error instead of an - exploit. -
-
- {{userComment.name}}: - -
-
-
-
-
- - - var mySceApp = angular.module('mySceApp', ['ngSanitize']); - - mySceApp.controller("myAppController", function myAppController($http, $templateCache, $sce) { - var self = this; - $http.get("test_data.json", {cache: $templateCache}).success(function(userComments) { - self.userComments = userComments; - }); - self.explicitlyTrustedHtml = $sce.trustAsHtml( - 'Hover over this text.'); - }); - - - -[ - { "name": "Alice", - "htmlComment": - "Is anyone reading this?" - }, - { "name": "Bob", - "htmlComment": "Yes! Am I the only other one?" - } -] - - - - describe('SCE doc demo', function() { - it('should sanitize untrusted values', function() { - expect(element(by.css('.htmlComment')).getInnerHtml()) - .toBe('Is anyone reading this?'); - }); - - it('should NOT sanitize explicitly trusted values', function() { - expect(element(by.id('explicitlyTrustedHtml')).getInnerHtml()).toBe( - 'Hover over this text.'); - }); - }); - -
- * - * - * - * ## Can I disable SCE completely? - * - * Yes, you can. However, this is strongly discouraged. SCE gives you a lot of security benefits - * for little coding overhead. It will be much harder to take an SCE disabled application and - * either secure it on your own or enable SCE at a later stage. It might make sense to disable SCE - * for cases where you have a lot of existing code that was written before SCE was introduced and - * you're migrating them a module at a time. - * - * That said, here's how you can completely disable SCE: - * - *
- *   angular.module('myAppWithSceDisabledmyApp', []).config(function($sceProvider) {
- *     // Completely disable SCE.  For demonstration purposes only!
- *     // Do not use in new projects.
- *     $sceProvider.enabled(false);
- *   });
- * 
- * - */ -/* jshint maxlen: 100 */ - -function $SceProvider() { - var enabled = true; - - /** - * @ngdoc method - * @name $sceProvider#enabled - * @kind function - * - * @param {boolean=} value If provided, then enables/disables SCE. - * @return {boolean} true if SCE is enabled, false otherwise. - * - * @description - * Enables/disables SCE and returns the current value. - */ - this.enabled = function (value) { - if (arguments.length) { - enabled = !!value; - } - return enabled; - }; - - - /* Design notes on the default implementation for SCE. - * - * The API contract for the SCE delegate - * ------------------------------------- - * The SCE delegate object must provide the following 3 methods: - * - * - trustAs(contextEnum, value) - * This method is used to tell the SCE service that the provided value is OK to use in the - * contexts specified by contextEnum. It must return an object that will be accepted by - * getTrusted() for a compatible contextEnum and return this value. - * - * - valueOf(value) - * For values that were not produced by trustAs(), return them as is. For values that were - * produced by trustAs(), return the corresponding input value to trustAs. Basically, if - * trustAs is wrapping the given values into some type, this operation unwraps it when given - * such a value. - * - * - getTrusted(contextEnum, value) - * This function should return the a value that is safe to use in the context specified by - * contextEnum or throw and exception otherwise. - * - * NOTE: This contract deliberately does NOT state that values returned by trustAs() must be - * opaque or wrapped in some holder object. That happens to be an implementation detail. For - * instance, an implementation could maintain a registry of all trusted objects by context. In - * such a case, trustAs() would return the same object that was passed in. getTrusted() would - * return the same object passed in if it was found in the registry under a compatible context or - * throw an exception otherwise. An implementation might only wrap values some of the time based - * on some criteria. getTrusted() might return a value and not throw an exception for special - * constants or objects even if not wrapped. All such implementations fulfill this contract. - * - * - * A note on the inheritance model for SCE contexts - * ------------------------------------------------ - * I've used inheritance and made RESOURCE_URL wrapped types a subtype of URL wrapped types. This - * is purely an implementation details. - * - * The contract is simply this: - * - * getTrusted($sce.RESOURCE_URL, value) succeeding implies that getTrusted($sce.URL, value) - * will also succeed. - * - * Inheritance happens to capture this in a natural way. In some future, we - * may not use inheritance anymore. That is OK because no code outside of - * sce.js and sceSpecs.js would need to be aware of this detail. - */ - - this.$get = ['$parse', '$sniffer', '$sceDelegate', function( - $parse, $sniffer, $sceDelegate) { - // Prereq: Ensure that we're not running in IE8 quirks mode. In that mode, IE allows - // the "expression(javascript expression)" syntax which is insecure. - if (enabled && $sniffer.msie && $sniffer.msieDocumentMode < 8) { - throw $sceMinErr('iequirks', - 'Strict Contextual Escaping does not support Internet Explorer version < 9 in quirks ' + - 'mode. You can fix this by adding the text to the top of your HTML ' + - 'document. See http://docs.angularjs.org/api/ng.$sce for more information.'); - } - - var sce = shallowCopy(SCE_CONTEXTS); - - /** - * @ngdoc method - * @name $sce#isEnabled - * @kind function - * - * @return {Boolean} true if SCE is enabled, false otherwise. If you want to set the value, you - * have to do it at module config time on {@link ng.$sceProvider $sceProvider}. - * - * @description - * Returns a boolean indicating if SCE is enabled. - */ - sce.isEnabled = function () { - return enabled; - }; - sce.trustAs = $sceDelegate.trustAs; - sce.getTrusted = $sceDelegate.getTrusted; - sce.valueOf = $sceDelegate.valueOf; - - if (!enabled) { - sce.trustAs = sce.getTrusted = function(type, value) { return value; }; - sce.valueOf = identity; - } - - /** - * @ngdoc method - * @name $sce#parseAs - * - * @description - * Converts Angular {@link guide/expression expression} into a function. This is like {@link - * ng.$parse $parse} and is identical when the expression is a literal constant. Otherwise, it - * wraps the expression in a call to {@link ng.$sce#getTrusted $sce.getTrusted(*type*, - * *result*)} - * - * @param {string} type The kind of SCE context in which this result will be used. - * @param {string} expression String expression to compile. - * @returns {function(context, locals)} a function which represents the compiled expression: - * - * * `context` – `{object}` – an object against which any expressions embedded in the strings - * are evaluated against (typically a scope object). - * * `locals` – `{object=}` – local variables context object, useful for overriding values in - * `context`. - */ - sce.parseAs = function sceParseAs(type, expr) { - var parsed = $parse(expr); - if (parsed.literal && parsed.constant) { - return parsed; - } else { - return function sceParseAsTrusted(self, locals) { - var result = sce.getTrusted(type, parsed(self, locals)); - sceParseAsTrusted.$$unwatch = parsed.$$unwatch; - return result; - }; - } - }; - - /** - * @ngdoc method - * @name $sce#trustAs - * - * @description - * Delegates to {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}. As such, - * returns an object that is trusted by angular for use in specified strict contextual - * escaping contexts (such as ng-bind-html, ng-include, any src attribute - * interpolation, any dom event binding attribute interpolation such as for onclick, etc.) - * that uses the provided value. See * {@link ng.$sce $sce} for enabling strict contextual - * escaping. - * - * @param {string} type The kind of context in which this value is safe for use. e.g. url, - * resource_url, html, js and css. - * @param {*} value The value that that should be considered trusted/safe. - * @returns {*} A value that can be used to stand in for the provided `value` in places - * where Angular expects a $sce.trustAs() return value. - */ - - /** - * @ngdoc method - * @name $sce#trustAsHtml - * - * @description - * Shorthand method. `$sce.trustAsHtml(value)` → - * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.HTML, value)`} - * - * @param {*} value The value to trustAs. - * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedHtml - * $sce.getTrustedHtml(value)} to obtain the original value. (privileged directives - * only accept expressions that are either literal constants or are the - * return value of {@link ng.$sce#trustAs $sce.trustAs}.) - */ - - /** - * @ngdoc method - * @name $sce#trustAsUrl - * - * @description - * Shorthand method. `$sce.trustAsUrl(value)` → - * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.URL, value)`} - * - * @param {*} value The value to trustAs. - * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedUrl - * $sce.getTrustedUrl(value)} to obtain the original value. (privileged directives - * only accept expressions that are either literal constants or are the - * return value of {@link ng.$sce#trustAs $sce.trustAs}.) - */ - - /** - * @ngdoc method - * @name $sce#trustAsResourceUrl - * - * @description - * Shorthand method. `$sce.trustAsResourceUrl(value)` → - * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.RESOURCE_URL, value)`} - * - * @param {*} value The value to trustAs. - * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedResourceUrl - * $sce.getTrustedResourceUrl(value)} to obtain the original value. (privileged directives - * only accept expressions that are either literal constants or are the return - * value of {@link ng.$sce#trustAs $sce.trustAs}.) - */ - - /** - * @ngdoc method - * @name $sce#trustAsJs - * - * @description - * Shorthand method. `$sce.trustAsJs(value)` → - * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.JS, value)`} - * - * @param {*} value The value to trustAs. - * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedJs - * $sce.getTrustedJs(value)} to obtain the original value. (privileged directives - * only accept expressions that are either literal constants or are the - * return value of {@link ng.$sce#trustAs $sce.trustAs}.) - */ - - /** - * @ngdoc method - * @name $sce#getTrusted - * - * @description - * Delegates to {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted`}. As such, - * takes the result of a {@link ng.$sce#trustAs `$sce.trustAs`}() call and returns the - * originally supplied value if the queried context type is a supertype of the created type. - * If this condition isn't satisfied, throws an exception. - * - * @param {string} type The kind of context in which this value is to be used. - * @param {*} maybeTrusted The result of a prior {@link ng.$sce#trustAs `$sce.trustAs`} - * call. - * @returns {*} The value the was originally provided to - * {@link ng.$sce#trustAs `$sce.trustAs`} if valid in this context. - * Otherwise, throws an exception. - */ - - /** - * @ngdoc method - * @name $sce#getTrustedHtml - * - * @description - * Shorthand method. `$sce.getTrustedHtml(value)` → - * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.HTML, value)`} - * - * @param {*} value The value to pass to `$sce.getTrusted`. - * @returns {*} The return value of `$sce.getTrusted($sce.HTML, value)` - */ - - /** - * @ngdoc method - * @name $sce#getTrustedCss - * - * @description - * Shorthand method. `$sce.getTrustedCss(value)` → - * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.CSS, value)`} - * - * @param {*} value The value to pass to `$sce.getTrusted`. - * @returns {*} The return value of `$sce.getTrusted($sce.CSS, value)` - */ - - /** - * @ngdoc method - * @name $sce#getTrustedUrl - * - * @description - * Shorthand method. `$sce.getTrustedUrl(value)` → - * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.URL, value)`} - * - * @param {*} value The value to pass to `$sce.getTrusted`. - * @returns {*} The return value of `$sce.getTrusted($sce.URL, value)` - */ - - /** - * @ngdoc method - * @name $sce#getTrustedResourceUrl - * - * @description - * Shorthand method. `$sce.getTrustedResourceUrl(value)` → - * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.RESOURCE_URL, value)`} - * - * @param {*} value The value to pass to `$sceDelegate.getTrusted`. - * @returns {*} The return value of `$sce.getTrusted($sce.RESOURCE_URL, value)` - */ - - /** - * @ngdoc method - * @name $sce#getTrustedJs - * - * @description - * Shorthand method. `$sce.getTrustedJs(value)` → - * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.JS, value)`} - * - * @param {*} value The value to pass to `$sce.getTrusted`. - * @returns {*} The return value of `$sce.getTrusted($sce.JS, value)` - */ - - /** - * @ngdoc method - * @name $sce#parseAsHtml - * - * @description - * Shorthand method. `$sce.parseAsHtml(expression string)` → - * {@link ng.$sce#parse `$sce.parseAs($sce.HTML, value)`} - * - * @param {string} expression String expression to compile. - * @returns {function(context, locals)} a function which represents the compiled expression: - * - * * `context` – `{object}` – an object against which any expressions embedded in the strings - * are evaluated against (typically a scope object). - * * `locals` – `{object=}` – local variables context object, useful for overriding values in - * `context`. - */ - - /** - * @ngdoc method - * @name $sce#parseAsCss - * - * @description - * Shorthand method. `$sce.parseAsCss(value)` → - * {@link ng.$sce#parse `$sce.parseAs($sce.CSS, value)`} - * - * @param {string} expression String expression to compile. - * @returns {function(context, locals)} a function which represents the compiled expression: - * - * * `context` – `{object}` – an object against which any expressions embedded in the strings - * are evaluated against (typically a scope object). - * * `locals` – `{object=}` – local variables context object, useful for overriding values in - * `context`. - */ - - /** - * @ngdoc method - * @name $sce#parseAsUrl - * - * @description - * Shorthand method. `$sce.parseAsUrl(value)` → - * {@link ng.$sce#parse `$sce.parseAs($sce.URL, value)`} - * - * @param {string} expression String expression to compile. - * @returns {function(context, locals)} a function which represents the compiled expression: - * - * * `context` – `{object}` – an object against which any expressions embedded in the strings - * are evaluated against (typically a scope object). - * * `locals` – `{object=}` – local variables context object, useful for overriding values in - * `context`. - */ - - /** - * @ngdoc method - * @name $sce#parseAsResourceUrl - * - * @description - * Shorthand method. `$sce.parseAsResourceUrl(value)` → - * {@link ng.$sce#parse `$sce.parseAs($sce.RESOURCE_URL, value)`} - * - * @param {string} expression String expression to compile. - * @returns {function(context, locals)} a function which represents the compiled expression: - * - * * `context` – `{object}` – an object against which any expressions embedded in the strings - * are evaluated against (typically a scope object). - * * `locals` – `{object=}` – local variables context object, useful for overriding values in - * `context`. - */ - - /** - * @ngdoc method - * @name $sce#parseAsJs - * - * @description - * Shorthand method. `$sce.parseAsJs(value)` → - * {@link ng.$sce#parse `$sce.parseAs($sce.JS, value)`} - * - * @param {string} expression String expression to compile. - * @returns {function(context, locals)} a function which represents the compiled expression: - * - * * `context` – `{object}` – an object against which any expressions embedded in the strings - * are evaluated against (typically a scope object). - * * `locals` – `{object=}` – local variables context object, useful for overriding values in - * `context`. - */ - - // Shorthand delegations. - var parse = sce.parseAs, - getTrusted = sce.getTrusted, - trustAs = sce.trustAs; - - forEach(SCE_CONTEXTS, function (enumValue, name) { - var lName = lowercase(name); - sce[camelCase("parse_as_" + lName)] = function (expr) { - return parse(enumValue, expr); - }; - sce[camelCase("get_trusted_" + lName)] = function (value) { - return getTrusted(enumValue, value); - }; - sce[camelCase("trust_as_" + lName)] = function (value) { - return trustAs(enumValue, value); - }; - }); - - return sce; - }]; -} - -/** - * !!! This is an undocumented "private" service !!! - * - * @name $sniffer - * @requires $window - * @requires $document - * - * @property {boolean} history Does the browser support html5 history api ? - * @property {boolean} hashchange Does the browser support hashchange event ? - * @property {boolean} transitions Does the browser support CSS transition events ? - * @property {boolean} animations Does the browser support CSS animation events ? - * - * @description - * This is very simple implementation of testing browser's features. - */ -function $SnifferProvider() { - this.$get = ['$window', '$document', function($window, $document) { - var eventSupport = {}, - android = - int((/android (\d+)/.exec(lowercase(($window.navigator || {}).userAgent)) || [])[1]), - boxee = /Boxee/i.test(($window.navigator || {}).userAgent), - document = $document[0] || {}, - documentMode = document.documentMode, - vendorPrefix, - vendorRegex = /^(Moz|webkit|O|ms)(?=[A-Z])/, - bodyStyle = document.body && document.body.style, - transitions = false, - animations = false, - match; - - if (bodyStyle) { - for(var prop in bodyStyle) { - if(match = vendorRegex.exec(prop)) { - vendorPrefix = match[0]; - vendorPrefix = vendorPrefix.substr(0, 1).toUpperCase() + vendorPrefix.substr(1); - break; - } - } - - if(!vendorPrefix) { - vendorPrefix = ('WebkitOpacity' in bodyStyle) && 'webkit'; - } - - transitions = !!(('transition' in bodyStyle) || (vendorPrefix + 'Transition' in bodyStyle)); - animations = !!(('animation' in bodyStyle) || (vendorPrefix + 'Animation' in bodyStyle)); - - if (android && (!transitions||!animations)) { - transitions = isString(document.body.style.webkitTransition); - animations = isString(document.body.style.webkitAnimation); - } - } - - - return { - // Android has history.pushState, but it does not update location correctly - // so let's not use the history API at all. - // http://code.google.com/p/android/issues/detail?id=17471 - // https://github.com/angular/angular.js/issues/904 - - // older webkit browser (533.9) on Boxee box has exactly the same problem as Android has - // so let's not use the history API also - // We are purposefully using `!(android < 4)` to cover the case when `android` is undefined - // jshint -W018 - history: !!($window.history && $window.history.pushState && !(android < 4) && !boxee), - // jshint +W018 - hashchange: 'onhashchange' in $window && - // IE8 compatible mode lies - (!documentMode || documentMode > 7), - hasEvent: function(event) { - // IE9 implements 'input' event it's so fubared that we rather pretend that it doesn't have - // it. In particular the event is not fired when backspace or delete key are pressed or - // when cut operation is performed. - if (event == 'input' && msie == 9) return false; - - if (isUndefined(eventSupport[event])) { - var divElm = document.createElement('div'); - eventSupport[event] = 'on' + event in divElm; - } - - return eventSupport[event]; - }, - csp: csp(), - vendorPrefix: vendorPrefix, - transitions : transitions, - animations : animations, - android: android, - msie : msie, - msieDocumentMode: documentMode - }; - }]; -} - -function $TimeoutProvider() { - this.$get = ['$rootScope', '$browser', '$q', '$exceptionHandler', - function($rootScope, $browser, $q, $exceptionHandler) { - var deferreds = {}; - - - /** - * @ngdoc service - * @name $timeout - * - * @description - * Angular's wrapper for `window.setTimeout`. The `fn` function is wrapped into a try/catch - * block and delegates any exceptions to - * {@link ng.$exceptionHandler $exceptionHandler} service. - * - * The return value of registering a timeout function is a promise, which will be resolved when - * the timeout is reached and the timeout function is executed. - * - * To cancel a timeout request, call `$timeout.cancel(promise)`. - * - * In tests you can use {@link ngMock.$timeout `$timeout.flush()`} to - * synchronously flush the queue of deferred functions. - * - * @param {function()} fn A function, whose execution should be delayed. - * @param {number=} [delay=0] Delay in milliseconds. - * @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise - * will invoke `fn` within the {@link ng.$rootScope.Scope#$apply $apply} block. - * @returns {Promise} Promise that will be resolved when the timeout is reached. The value this - * promise will be resolved with is the return value of the `fn` function. - * - */ - function timeout(fn, delay, invokeApply) { - var deferred = $q.defer(), - promise = deferred.promise, - skipApply = (isDefined(invokeApply) && !invokeApply), - timeoutId; - - timeoutId = $browser.defer(function() { - try { - deferred.resolve(fn()); - } catch(e) { - deferred.reject(e); - $exceptionHandler(e); - } - finally { - delete deferreds[promise.$$timeoutId]; - } - - if (!skipApply) $rootScope.$apply(); - }, delay); - - promise.$$timeoutId = timeoutId; - deferreds[timeoutId] = deferred; - - return promise; - } - - - /** - * @ngdoc method - * @name $timeout#cancel - * - * @description - * Cancels a task associated with the `promise`. As a result of this, the promise will be - * resolved with a rejection. - * - * @param {Promise=} promise Promise returned by the `$timeout` function. - * @returns {boolean} Returns `true` if the task hasn't executed yet and was successfully - * canceled. - */ - timeout.cancel = function(promise) { - if (promise && promise.$$timeoutId in deferreds) { - deferreds[promise.$$timeoutId].reject('canceled'); - delete deferreds[promise.$$timeoutId]; - return $browser.defer.cancel(promise.$$timeoutId); - } - return false; - }; - - return timeout; - }]; -} - -// NOTE: The usage of window and document instead of $window and $document here is -// deliberate. This service depends on the specific behavior of anchor nodes created by the -// browser (resolving and parsing URLs) that is unlikely to be provided by mock objects and -// cause us to break tests. In addition, when the browser resolves a URL for XHR, it -// doesn't know about mocked locations and resolves URLs to the real document - which is -// exactly the behavior needed here. There is little value is mocking these out for this -// service. -var urlParsingNode = document.createElement("a"); -var originUrl = urlResolve(window.location.href, true); - - -/** - * - * Implementation Notes for non-IE browsers - * ---------------------------------------- - * Assigning a URL to the href property of an anchor DOM node, even one attached to the DOM, - * results both in the normalizing and parsing of the URL. Normalizing means that a relative - * URL will be resolved into an absolute URL in the context of the application document. - * Parsing means that the anchor node's host, hostname, protocol, port, pathname and related - * properties are all populated to reflect the normalized URL. This approach has wide - * compatibility - Safari 1+, Mozilla 1+, Opera 7+,e etc. See - * http://www.aptana.com/reference/html/api/HTMLAnchorElement.html - * - * Implementation Notes for IE - * --------------------------- - * IE >= 8 and <= 10 normalizes the URL when assigned to the anchor node similar to the other - * browsers. However, the parsed components will not be set if the URL assigned did not specify - * them. (e.g. if you assign a.href = "foo", then a.protocol, a.host, etc. will be empty.) We - * work around that by performing the parsing in a 2nd step by taking a previously normalized - * URL (e.g. by assigning to a.href) and assigning it a.href again. This correctly populates the - * properties such as protocol, hostname, port, etc. - * - * IE7 does not normalize the URL when assigned to an anchor node. (Apparently, it does, if one - * uses the inner HTML approach to assign the URL as part of an HTML snippet - - * http://stackoverflow.com/a/472729) However, setting img[src] does normalize the URL. - * Unfortunately, setting img[src] to something like "javascript:foo" on IE throws an exception. - * Since the primary usage for normalizing URLs is to sanitize such URLs, we can't use that - * method and IE < 8 is unsupported. - * - * References: - * http://developer.mozilla.org/en-US/docs/Web/API/HTMLAnchorElement - * http://www.aptana.com/reference/html/api/HTMLAnchorElement.html - * http://url.spec.whatwg.org/#urlutils - * https://github.com/angular/angular.js/pull/2902 - * http://james.padolsey.com/javascript/parsing-urls-with-the-dom/ - * - * @kind function - * @param {string} url The URL to be parsed. - * @description Normalizes and parses a URL. - * @returns {object} Returns the normalized URL as a dictionary. - * - * | member name | Description | - * |---------------|----------------| - * | href | A normalized version of the provided URL if it was not an absolute URL | - * | protocol | The protocol including the trailing colon | - * | host | The host and port (if the port is non-default) of the normalizedUrl | - * | search | The search params, minus the question mark | - * | hash | The hash string, minus the hash symbol - * | hostname | The hostname - * | port | The port, without ":" - * | pathname | The pathname, beginning with "/" - * - */ -function urlResolve(url, base) { - var href = url; - - if (msie) { - // Normalize before parse. Refer Implementation Notes on why this is - // done in two steps on IE. - urlParsingNode.setAttribute("href", href); - href = urlParsingNode.href; - } - - urlParsingNode.setAttribute('href', href); - - // urlParsingNode provides the UrlUtils interface - http://url.spec.whatwg.org/#urlutils - return { - href: urlParsingNode.href, - protocol: urlParsingNode.protocol ? urlParsingNode.protocol.replace(/:$/, '') : '', - host: urlParsingNode.host, - search: urlParsingNode.search ? urlParsingNode.search.replace(/^\?/, '') : '', - hash: urlParsingNode.hash ? urlParsingNode.hash.replace(/^#/, '') : '', - hostname: urlParsingNode.hostname, - port: urlParsingNode.port, - pathname: (urlParsingNode.pathname.charAt(0) === '/') - ? urlParsingNode.pathname - : '/' + urlParsingNode.pathname - }; -} - -/** - * Parse a request URL and determine whether this is a same-origin request as the application document. - * - * @param {string|object} requestUrl The url of the request as a string that will be resolved - * or a parsed URL object. - * @returns {boolean} Whether the request is for the same origin as the application document. - */ -function urlIsSameOrigin(requestUrl) { - var parsed = (isString(requestUrl)) ? urlResolve(requestUrl) : requestUrl; - return (parsed.protocol === originUrl.protocol && - parsed.host === originUrl.host); -} - -/** - * @ngdoc service - * @name $window - * - * @description - * A reference to the browser's `window` object. While `window` - * is globally available in JavaScript, it causes testability problems, because - * it is a global variable. In angular we always refer to it through the - * `$window` service, so it may be overridden, removed or mocked for testing. - * - * Expressions, like the one defined for the `ngClick` directive in the example - * below, are evaluated with respect to the current scope. Therefore, there is - * no risk of inadvertently coding in a dependency on a global value in such an - * expression. - * - * @example - - - -
- - -
-
- - it('should display the greeting in the input box', function() { - element(by.model('greeting')).sendKeys('Hello, E2E Tests'); - // If we click the button it will block the test runner - // element(':button').click(); - }); - -
- */ -function $WindowProvider(){ - this.$get = valueFn(window); -} - -/** - * @ngdoc provider - * @name $filterProvider - * @description - * - * Filters are just functions which transform input to an output. However filters need to be - * Dependency Injected. To achieve this a filter definition consists of a factory function which is - * annotated with dependencies and is responsible for creating a filter function. - * - * ```js - * // Filter registration - * function MyModule($provide, $filterProvider) { - * // create a service to demonstrate injection (not always needed) - * $provide.value('greet', function(name){ - * return 'Hello ' + name + '!'; - * }); - * - * // register a filter factory which uses the - * // greet service to demonstrate DI. - * $filterProvider.register('greet', function(greet){ - * // return the filter function which uses the greet service - * // to generate salutation - * return function(text) { - * // filters need to be forgiving so check input validity - * return text && greet(text) || text; - * }; - * }); - * } - * ``` - * - * The filter function is registered with the `$injector` under the filter name suffix with - * `Filter`. - * - * ```js - * it('should be the same instance', inject( - * function($filterProvider) { - * $filterProvider.register('reverse', function(){ - * return ...; - * }); - * }, - * function($filter, reverseFilter) { - * expect($filter('reverse')).toBe(reverseFilter); - * }); - * ``` - * - * - * For more information about how angular filters work, and how to create your own filters, see - * {@link guide/filter Filters} in the Angular Developer Guide. - */ -/** - * @ngdoc method - * @name $filterProvider#register - * @description - * Register filter factory function. - * - * @param {String} name Name of the filter. - * @param {Function} fn The filter factory function which is injectable. - */ - - -/** - * @ngdoc service - * @name $filter - * @kind function - * @description - * Filters are used for formatting data displayed to the user. - * - * The general syntax in templates is as follows: - * - * {{ expression [| filter_name[:parameter_value] ... ] }} - * - * @param {String} name Name of the filter function to retrieve - * @return {Function} the filter function - * @example - - -
-

{{ originalText }}

-

{{ filteredText }}

-
-
- - - angular.module('filterExample', []) - .controller('MainCtrl', function($scope, $filter) { - $scope.originalText = 'hello'; - $scope.filteredText = $filter('uppercase')($scope.originalText); - }); - -
- */ -$FilterProvider.$inject = ['$provide']; -function $FilterProvider($provide) { - var suffix = 'Filter'; - - /** - * @ngdoc method - * @name $controllerProvider#register - * @param {string|Object} name Name of the filter function, or an object map of filters where - * the keys are the filter names and the values are the filter factories. - * @returns {Object} Registered filter instance, or if a map of filters was provided then a map - * of the registered filter instances. - */ - function register(name, factory) { - if(isObject(name)) { - var filters = {}; - forEach(name, function(filter, key) { - filters[key] = register(key, filter); - }); - return filters; - } else { - return $provide.factory(name + suffix, factory); - } - } - this.register = register; - - this.$get = ['$injector', function($injector) { - return function(name) { - return $injector.get(name + suffix); - }; - }]; - - //////////////////////////////////////// - - /* global - currencyFilter: false, - dateFilter: false, - filterFilter: false, - jsonFilter: false, - limitToFilter: false, - lowercaseFilter: false, - numberFilter: false, - orderByFilter: false, - uppercaseFilter: false, - */ - - register('currency', currencyFilter); - register('date', dateFilter); - register('filter', filterFilter); - register('json', jsonFilter); - register('limitTo', limitToFilter); - register('lowercase', lowercaseFilter); - register('number', numberFilter); - register('orderBy', orderByFilter); - register('uppercase', uppercaseFilter); -} - -/** - * @ngdoc filter - * @name filter - * @kind function - * - * @description - * Selects a subset of items from `array` and returns it as a new array. - * - * @param {Array} array The source array. - * @param {string|Object|function()} expression The predicate to be used for selecting items from - * `array`. - * - * Can be one of: - * - * - `string`: The string is evaluated as an expression and the resulting value is used for substring match against - * the contents of the `array`. All strings or objects with string properties in `array` that contain this string - * will be returned. The predicate can be negated by prefixing the string with `!`. - * - * - `Object`: A pattern object can be used to filter specific properties on objects contained - * by `array`. For example `{name:"M", phone:"1"}` predicate will return an array of items - * which have property `name` containing "M" and property `phone` containing "1". A special - * property name `$` can be used (as in `{$:"text"}`) to accept a match against any - * property of the object. That's equivalent to the simple substring match with a `string` - * as described above. - * - * - `function(value)`: A predicate function can be used to write arbitrary filters. The function is - * called for each element of `array`. The final result is an array of those elements that - * the predicate returned true for. - * - * @param {function(actual, expected)|true|undefined} comparator Comparator which is used in - * determining if the expected value (from the filter expression) and actual value (from - * the object in the array) should be considered a match. - * - * Can be one of: - * - * - `function(actual, expected)`: - * The function will be given the object value and the predicate value to compare and - * should return true if the item should be included in filtered result. - * - * - `true`: A shorthand for `function(actual, expected) { return angular.equals(expected, actual)}`. - * this is essentially strict comparison of expected and actual. - * - * - `false|undefined`: A short hand for a function which will look for a substring match in case - * insensitive way. - * - * @example - - -
- - Search: - - - - - - -
NamePhone
{{friend.name}}{{friend.phone}}
-
- Any:
- Name only
- Phone only
- Equality
- - - - - - -
NamePhone
{{friendObj.name}}{{friendObj.phone}}
-
- - var expectFriendNames = function(expectedNames, key) { - element.all(by.repeater(key + ' in friends').column(key + '.name')).then(function(arr) { - arr.forEach(function(wd, i) { - expect(wd.getText()).toMatch(expectedNames[i]); - }); - }); - }; - - it('should search across all fields when filtering with a string', function() { - var searchText = element(by.model('searchText')); - searchText.clear(); - searchText.sendKeys('m'); - expectFriendNames(['Mary', 'Mike', 'Adam'], 'friend'); - - searchText.clear(); - searchText.sendKeys('76'); - expectFriendNames(['John', 'Julie'], 'friend'); - }); - - it('should search in specific fields when filtering with a predicate object', function() { - var searchAny = element(by.model('search.$')); - searchAny.clear(); - searchAny.sendKeys('i'); - expectFriendNames(['Mary', 'Mike', 'Julie', 'Juliette'], 'friendObj'); - }); - it('should use a equal comparison when comparator is true', function() { - var searchName = element(by.model('search.name')); - var strict = element(by.model('strict')); - searchName.clear(); - searchName.sendKeys('Julie'); - strict.click(); - expectFriendNames(['Julie'], 'friendObj'); - }); - -
- */ -function filterFilter() { - return function(array, expression, comparator) { - if (!isArray(array)) return array; - - var comparatorType = typeof(comparator), - predicates = []; - - predicates.check = function(value) { - for (var j = 0; j < predicates.length; j++) { - if(!predicates[j](value)) { - return false; - } - } - return true; - }; - - if (comparatorType !== 'function') { - if (comparatorType === 'boolean' && comparator) { - comparator = function(obj, text) { - return angular.equals(obj, text); - }; - } else { - comparator = function(obj, text) { - if (obj && text && typeof obj === 'object' && typeof text === 'object') { - for (var objKey in obj) { - if (objKey.charAt(0) !== '$' && hasOwnProperty.call(obj, objKey) && - comparator(obj[objKey], text[objKey])) { - return true; - } - } - return false; - } - text = (''+text).toLowerCase(); - return (''+obj).toLowerCase().indexOf(text) > -1; - }; - } - } - - var search = function(obj, text){ - if (typeof text == 'string' && text.charAt(0) === '!') { - return !search(obj, text.substr(1)); - } - switch (typeof obj) { - case "boolean": - case "number": - case "string": - return comparator(obj, text); - case "object": - switch (typeof text) { - case "object": - return comparator(obj, text); - default: - for ( var objKey in obj) { - if (objKey.charAt(0) !== '$' && search(obj[objKey], text)) { - return true; - } - } - break; - } - return false; - case "array": - for ( var i = 0; i < obj.length; i++) { - if (search(obj[i], text)) { - return true; - } - } - return false; - default: - return false; - } - }; - switch (typeof expression) { - case "boolean": - case "number": - case "string": - // Set up expression object and fall through - expression = {$:expression}; - // jshint -W086 - case "object": - // jshint +W086 - for (var key in expression) { - (function(path) { - if (typeof expression[path] == 'undefined') return; - predicates.push(function(value) { - return search(path == '$' ? value : (value && value[path]), expression[path]); - }); - })(key); - } - break; - case 'function': - predicates.push(expression); - break; - default: - return array; - } - var filtered = []; - for ( var j = 0; j < array.length; j++) { - var value = array[j]; - if (predicates.check(value)) { - filtered.push(value); - } - } - return filtered; - }; -} - -/** - * @ngdoc filter - * @name currency - * @kind function - * - * @description - * Formats a number as a currency (ie $1,234.56). When no currency symbol is provided, default - * symbol for current locale is used. - * - * @param {number} amount Input to filter. - * @param {string=} symbol Currency symbol or identifier to be displayed. - * @returns {string} Formatted number. - * - * - * @example - - - -
-
- default currency symbol ($): {{amount | currency}}
- custom currency identifier (USD$): {{amount | currency:"USD$"}} -
-
- - it('should init with 1234.56', function() { - expect(element(by.id('currency-default')).getText()).toBe('$1,234.56'); - expect(element(by.binding('amount | currency:"USD$"')).getText()).toBe('USD$1,234.56'); - }); - it('should update', function() { - if (browser.params.browser == 'safari') { - // Safari does not understand the minus key. See - // https://github.com/angular/protractor/issues/481 - return; - } - element(by.model('amount')).clear(); - element(by.model('amount')).sendKeys('-1234'); - expect(element(by.id('currency-default')).getText()).toBe('($1,234.00)'); - expect(element(by.binding('amount | currency:"USD$"')).getText()).toBe('(USD$1,234.00)'); - }); - -
- */ -currencyFilter.$inject = ['$locale']; -function currencyFilter($locale) { - var formats = $locale.NUMBER_FORMATS; - return function(amount, currencySymbol){ - if (isUndefined(currencySymbol)) currencySymbol = formats.CURRENCY_SYM; - return formatNumber(amount, formats.PATTERNS[1], formats.GROUP_SEP, formats.DECIMAL_SEP, 2). - replace(/\u00A4/g, currencySymbol); - }; -} - -/** - * @ngdoc filter - * @name number - * @kind function - * - * @description - * Formats a number as text. - * - * If the input is not a number an empty string is returned. - * - * @param {number|string} number Number to format. - * @param {(number|string)=} fractionSize Number of decimal places to round the number to. - * If this is not provided then the fraction size is computed from the current locale's number - * formatting pattern. In the case of the default locale, it will be 3. - * @returns {string} Number rounded to decimalPlaces and places a “,” after each third digit. - * - * @example - - - -
- Enter number:
- Default formatting: {{val | number}}
- No fractions: {{val | number:0}}
- Negative number: {{-val | number:4}} -
-
- - it('should format numbers', function() { - expect(element(by.id('number-default')).getText()).toBe('1,234.568'); - expect(element(by.binding('val | number:0')).getText()).toBe('1,235'); - expect(element(by.binding('-val | number:4')).getText()).toBe('-1,234.5679'); - }); - - it('should update', function() { - element(by.model('val')).clear(); - element(by.model('val')).sendKeys('3374.333'); - expect(element(by.id('number-default')).getText()).toBe('3,374.333'); - expect(element(by.binding('val | number:0')).getText()).toBe('3,374'); - expect(element(by.binding('-val | number:4')).getText()).toBe('-3,374.3330'); - }); - -
- */ - - -numberFilter.$inject = ['$locale']; -function numberFilter($locale) { - var formats = $locale.NUMBER_FORMATS; - return function(number, fractionSize) { - return formatNumber(number, formats.PATTERNS[0], formats.GROUP_SEP, formats.DECIMAL_SEP, - fractionSize); - }; -} - -var DECIMAL_SEP = '.'; -function formatNumber(number, pattern, groupSep, decimalSep, fractionSize) { - if (number == null || !isFinite(number) || isObject(number)) return ''; - - var isNegative = number < 0; - number = Math.abs(number); - var numStr = number + '', - formatedText = '', - parts = []; - - var hasExponent = false; - if (numStr.indexOf('e') !== -1) { - var match = numStr.match(/([\d\.]+)e(-?)(\d+)/); - if (match && match[2] == '-' && match[3] > fractionSize + 1) { - numStr = '0'; - } else { - formatedText = numStr; - hasExponent = true; - } - } - - if (!hasExponent) { - var fractionLen = (numStr.split(DECIMAL_SEP)[1] || '').length; - - // determine fractionSize if it is not specified - if (isUndefined(fractionSize)) { - fractionSize = Math.min(Math.max(pattern.minFrac, fractionLen), pattern.maxFrac); - } - - var pow = Math.pow(10, fractionSize + 1); - number = Math.floor(number * pow + 5) / pow; - var fraction = ('' + number).split(DECIMAL_SEP); - var whole = fraction[0]; - fraction = fraction[1] || ''; - - var i, pos = 0, - lgroup = pattern.lgSize, - group = pattern.gSize; - - if (whole.length >= (lgroup + group)) { - pos = whole.length - lgroup; - for (i = 0; i < pos; i++) { - if ((pos - i)%group === 0 && i !== 0) { - formatedText += groupSep; - } - formatedText += whole.charAt(i); - } - } - - for (i = pos; i < whole.length; i++) { - if ((whole.length - i)%lgroup === 0 && i !== 0) { - formatedText += groupSep; - } - formatedText += whole.charAt(i); - } - - // format fraction part. - while(fraction.length < fractionSize) { - fraction += '0'; - } - - if (fractionSize && fractionSize !== "0") formatedText += decimalSep + fraction.substr(0, fractionSize); - } else { - - if (fractionSize > 0 && number > -1 && number < 1) { - formatedText = number.toFixed(fractionSize); - } - } - - parts.push(isNegative ? pattern.negPre : pattern.posPre); - parts.push(formatedText); - parts.push(isNegative ? pattern.negSuf : pattern.posSuf); - return parts.join(''); -} - -function padNumber(num, digits, trim) { - var neg = ''; - if (num < 0) { - neg = '-'; - num = -num; - } - num = '' + num; - while(num.length < digits) num = '0' + num; - if (trim) - num = num.substr(num.length - digits); - return neg + num; -} - - -function dateGetter(name, size, offset, trim) { - offset = offset || 0; - return function(date) { - var value = date['get' + name](); - if (offset > 0 || value > -offset) - value += offset; - if (value === 0 && offset == -12 ) value = 12; - return padNumber(value, size, trim); - }; -} - -function dateStrGetter(name, shortForm) { - return function(date, formats) { - var value = date['get' + name](); - var get = uppercase(shortForm ? ('SHORT' + name) : name); - - return formats[get][value]; - }; -} - -function timeZoneGetter(date) { - var zone = -1 * date.getTimezoneOffset(); - var paddedZone = (zone >= 0) ? "+" : ""; - - paddedZone += padNumber(Math[zone > 0 ? 'floor' : 'ceil'](zone / 60), 2) + - padNumber(Math.abs(zone % 60), 2); - - return paddedZone; -} - -function getFirstThursdayOfYear(year) { - // 0 = index of January - var dayOfWeekOnFirst = (new Date(year, 0, 1)).getDay(); - // 4 = index of Thursday (+1 to account for 1st = 5) - // 11 = index of *next* Thursday (+1 account for 1st = 12) - return new Date(year, 0, ((dayOfWeekOnFirst <= 4) ? 5 : 12) - dayOfWeekOnFirst); -} - -function getThursdayThisWeek(datetime) { - return new Date(datetime.getFullYear(), datetime.getMonth(), - // 4 = index of Thursday - datetime.getDate() + (4 - datetime.getDay())); -} - -function weekGetter(size) { - return function(date) { - var firstThurs = getFirstThursdayOfYear(date.getFullYear()), - thisThurs = getThursdayThisWeek(date); - - var diff = +thisThurs - +firstThurs, - result = 1 + Math.round(diff / 6.048e8); // 6.048e8 ms per week - - return padNumber(result, size); - }; -} - -function ampmGetter(date, formats) { - return date.getHours() < 12 ? formats.AMPMS[0] : formats.AMPMS[1]; -} - -var DATE_FORMATS = { - yyyy: dateGetter('FullYear', 4), - yy: dateGetter('FullYear', 2, 0, true), - y: dateGetter('FullYear', 1), - MMMM: dateStrGetter('Month'), - MMM: dateStrGetter('Month', true), - MM: dateGetter('Month', 2, 1), - M: dateGetter('Month', 1, 1), - dd: dateGetter('Date', 2), - d: dateGetter('Date', 1), - HH: dateGetter('Hours', 2), - H: dateGetter('Hours', 1), - hh: dateGetter('Hours', 2, -12), - h: dateGetter('Hours', 1, -12), - mm: dateGetter('Minutes', 2), - m: dateGetter('Minutes', 1), - ss: dateGetter('Seconds', 2), - s: dateGetter('Seconds', 1), - // while ISO 8601 requires fractions to be prefixed with `.` or `,` - // we can be just safely rely on using `sss` since we currently don't support single or two digit fractions - sss: dateGetter('Milliseconds', 3), - EEEE: dateStrGetter('Day'), - EEE: dateStrGetter('Day', true), - a: ampmGetter, - Z: timeZoneGetter, - ww: weekGetter(2), - w: weekGetter(1) -}; - -var DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZEw']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+|H+|h+|m+|s+|a|Z|w+))(.*)/, - NUMBER_STRING = /^\-?\d+$/; - -/** - * @ngdoc filter - * @name date - * @kind function - * - * @description - * Formats `date` to a string based on the requested `format`. - * - * `format` string can be composed of the following elements: - * - * * `'yyyy'`: 4 digit representation of year (e.g. AD 1 => 0001, AD 2010 => 2010) - * * `'yy'`: 2 digit representation of year, padded (00-99). (e.g. AD 2001 => 01, AD 2010 => 10) - * * `'y'`: 1 digit representation of year, e.g. (AD 1 => 1, AD 199 => 199) - * * `'MMMM'`: Month in year (January-December) - * * `'MMM'`: Month in year (Jan-Dec) - * * `'MM'`: Month in year, padded (01-12) - * * `'M'`: Month in year (1-12) - * * `'dd'`: Day in month, padded (01-31) - * * `'d'`: Day in month (1-31) - * * `'EEEE'`: Day in Week,(Sunday-Saturday) - * * `'EEE'`: Day in Week, (Sun-Sat) - * * `'HH'`: Hour in day, padded (00-23) - * * `'H'`: Hour in day (0-23) - * * `'hh'`: Hour in am/pm, padded (01-12) - * * `'h'`: Hour in am/pm, (1-12) - * * `'mm'`: Minute in hour, padded (00-59) - * * `'m'`: Minute in hour (0-59) - * * `'ss'`: Second in minute, padded (00-59) - * * `'s'`: Second in minute (0-59) - * * `'.sss' or ',sss'`: Millisecond in second, padded (000-999) - * * `'a'`: am/pm marker - * * `'Z'`: 4 digit (+sign) representation of the timezone offset (-1200-+1200) - * * `'ww'`: ISO-8601 week of year (00-53) - * * `'w'`: ISO-8601 week of year (0-53) - * - * `format` string can also be one of the following predefined - * {@link guide/i18n localizable formats}: - * - * * `'medium'`: equivalent to `'MMM d, y h:mm:ss a'` for en_US locale - * (e.g. Sep 3, 2010 12:05:08 pm) - * * `'short'`: equivalent to `'M/d/yy h:mm a'` for en_US locale (e.g. 9/3/10 12:05 pm) - * * `'fullDate'`: equivalent to `'EEEE, MMMM d, y'` for en_US locale - * (e.g. Friday, September 3, 2010) - * * `'longDate'`: equivalent to `'MMMM d, y'` for en_US locale (e.g. September 3, 2010) - * * `'mediumDate'`: equivalent to `'MMM d, y'` for en_US locale (e.g. Sep 3, 2010) - * * `'shortDate'`: equivalent to `'M/d/yy'` for en_US locale (e.g. 9/3/10) - * * `'mediumTime'`: equivalent to `'h:mm:ss a'` for en_US locale (e.g. 12:05:08 pm) - * * `'shortTime'`: equivalent to `'h:mm a'` for en_US locale (e.g. 12:05 pm) - * - * `format` string can contain literal values. These need to be quoted with single quotes (e.g. - * `"h 'in the morning'"`). In order to output single quote, use two single quotes in a sequence - * (e.g. `"h 'o''clock'"`). - * - * @param {(Date|number|string)} date Date to format either as Date object, milliseconds (string or - * number) or various ISO 8601 datetime string formats (e.g. yyyy-MM-ddTHH:mm:ss.SSSZ and its - * shorter versions like yyyy-MM-ddTHH:mmZ, yyyy-MM-dd or yyyyMMddTHHmmssZ). If no timezone is - * specified in the string input, the time is considered to be in the local timezone. - * @param {string=} format Formatting rules (see Description). If not specified, - * `mediumDate` is used. - * @returns {string} Formatted string or the input if input is not recognized as date/millis. - * - * @example - - - {{1288323623006 | date:'medium'}}: - {{1288323623006 | date:'medium'}}
- {{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}: - {{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}
- {{1288323623006 | date:'MM/dd/yyyy @ h:mma'}}: - {{'1288323623006' | date:'MM/dd/yyyy @ h:mma'}}
-
- - it('should format date', function() { - expect(element(by.binding("1288323623006 | date:'medium'")).getText()). - toMatch(/Oct 2\d, 2010 \d{1,2}:\d{2}:\d{2} (AM|PM)/); - expect(element(by.binding("1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'")).getText()). - toMatch(/2010\-10\-2\d \d{2}:\d{2}:\d{2} (\-|\+)?\d{4}/); - expect(element(by.binding("'1288323623006' | date:'MM/dd/yyyy @ h:mma'")).getText()). - toMatch(/10\/2\d\/2010 @ \d{1,2}:\d{2}(AM|PM)/); - }); - -
- */ -dateFilter.$inject = ['$locale']; -function dateFilter($locale) { - - - var R_ISO8601_STR = /^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d+))?)?)?(Z|([+-])(\d\d):?(\d\d))?)?$/; - // 1 2 3 4 5 6 7 8 9 10 11 - function jsonStringToDate(string) { - var match; - if (match = string.match(R_ISO8601_STR)) { - var date = new Date(0), - tzHour = 0, - tzMin = 0, - dateSetter = match[8] ? date.setUTCFullYear : date.setFullYear, - timeSetter = match[8] ? date.setUTCHours : date.setHours; - - if (match[9]) { - tzHour = int(match[9] + match[10]); - tzMin = int(match[9] + match[11]); - } - dateSetter.call(date, int(match[1]), int(match[2]) - 1, int(match[3])); - var h = int(match[4]||0) - tzHour; - var m = int(match[5]||0) - tzMin; - var s = int(match[6]||0); - var ms = Math.round(parseFloat('0.' + (match[7]||0)) * 1000); - timeSetter.call(date, h, m, s, ms); - return date; - } - return string; - } - - - return function(date, format) { - var text = '', - parts = [], - fn, match; - - format = format || 'mediumDate'; - format = $locale.DATETIME_FORMATS[format] || format; - if (isString(date)) { - if (NUMBER_STRING.test(date)) { - date = int(date); - } else { - date = jsonStringToDate(date); - } - } - - if (isNumber(date)) { - date = new Date(date); - } - - if (!isDate(date)) { - return date; - } - - while(format) { - match = DATE_FORMATS_SPLIT.exec(format); - if (match) { - parts = concat(parts, match, 1); - format = parts.pop(); - } else { - parts.push(format); - format = null; - } - } - - forEach(parts, function(value){ - fn = DATE_FORMATS[value]; - text += fn ? fn(date, $locale.DATETIME_FORMATS) - : value.replace(/(^'|'$)/g, '').replace(/''/g, "'"); - }); - - return text; - }; -} - - -/** - * @ngdoc filter - * @name json - * @kind function - * - * @description - * Allows you to convert a JavaScript object into JSON string. - * - * This filter is mostly useful for debugging. When using the double curly {{value}} notation - * the binding is automatically converted to JSON. - * - * @param {*} object Any JavaScript object (including arrays and primitive types) to filter. - * @returns {string} JSON string. - * - * - * @example - - -
{{ {'name':'value'} | json }}
-
- - it('should jsonify filtered objects', function() { - expect(element(by.binding("{'name':'value'}")).getText()).toMatch(/\{\n "name": ?"value"\n}/); - }); - -
- * - */ -function jsonFilter() { - return function(object) { - return toJson(object, true); - }; -} - - -/** - * @ngdoc filter - * @name lowercase - * @kind function - * @description - * Converts string to lowercase. - * @see angular.lowercase - */ -var lowercaseFilter = valueFn(lowercase); - - -/** - * @ngdoc filter - * @name uppercase - * @kind function - * @description - * Converts string to uppercase. - * @see angular.uppercase - */ -var uppercaseFilter = valueFn(uppercase); - -/** - * @ngdoc filter - * @name limitTo - * @kind function - * - * @description - * Creates a new array or string containing only a specified number of elements. The elements - * are taken from either the beginning or the end of the source array or string, as specified by - * the value and sign (positive or negative) of `limit`. - * - * @param {Array|string} input Source array or string to be limited. - * @param {string|number} limit The length of the returned array or string. If the `limit` number - * is positive, `limit` number of items from the beginning of the source array/string are copied. - * If the number is negative, `limit` number of items from the end of the source array/string - * are copied. The `limit` will be trimmed if it exceeds `array.length` - * @returns {Array|string} A new sub-array or substring of length `limit` or less if input array - * had less than `limit` elements. - * - * @example - - - -
- Limit {{numbers}} to: -

Output numbers: {{ numbers | limitTo:numLimit }}

- Limit {{letters}} to: -

Output letters: {{ letters | limitTo:letterLimit }}

-
-
- - var numLimitInput = element(by.model('numLimit')); - var letterLimitInput = element(by.model('letterLimit')); - var limitedNumbers = element(by.binding('numbers | limitTo:numLimit')); - var limitedLetters = element(by.binding('letters | limitTo:letterLimit')); - - it('should limit the number array to first three items', function() { - expect(numLimitInput.getAttribute('value')).toBe('3'); - expect(letterLimitInput.getAttribute('value')).toBe('3'); - expect(limitedNumbers.getText()).toEqual('Output numbers: [1,2,3]'); - expect(limitedLetters.getText()).toEqual('Output letters: abc'); - }); - - it('should update the output when -3 is entered', function() { - numLimitInput.clear(); - numLimitInput.sendKeys('-3'); - letterLimitInput.clear(); - letterLimitInput.sendKeys('-3'); - expect(limitedNumbers.getText()).toEqual('Output numbers: [7,8,9]'); - expect(limitedLetters.getText()).toEqual('Output letters: ghi'); - }); - - it('should not exceed the maximum size of input array', function() { - numLimitInput.clear(); - numLimitInput.sendKeys('100'); - letterLimitInput.clear(); - letterLimitInput.sendKeys('100'); - expect(limitedNumbers.getText()).toEqual('Output numbers: [1,2,3,4,5,6,7,8,9]'); - expect(limitedLetters.getText()).toEqual('Output letters: abcdefghi'); - }); - -
- */ -function limitToFilter(){ - return function(input, limit) { - if (!isArray(input) && !isString(input)) return input; - - if (Math.abs(Number(limit)) === Infinity) { - limit = Number(limit); - } else { - limit = int(limit); - } - - if (isString(input)) { - //NaN check on limit - if (limit) { - return limit >= 0 ? input.slice(0, limit) : input.slice(limit, input.length); - } else { - return ""; - } - } - - var out = [], - i, n; - - // if abs(limit) exceeds maximum length, trim it - if (limit > input.length) - limit = input.length; - else if (limit < -input.length) - limit = -input.length; - - if (limit > 0) { - i = 0; - n = limit; - } else { - i = input.length + limit; - n = input.length; - } - - for (; i} expression A predicate to be - * used by the comparator to determine the order of elements. - * - * Can be one of: - * - * - `function`: Getter function. The result of this function will be sorted using the - * `<`, `=`, `>` operator. - * - `string`: An Angular expression which evaluates to an object to order by, such as 'name' - * to sort by a property called 'name'. Optionally prefixed with `+` or `-` to control - * ascending or descending sort order (for example, +name or -name). - * - `Array`: An array of function or string predicates. The first predicate in the array - * is used for sorting, but when two items are equivalent, the next predicate is used. - * - * @param {boolean=} reverse Reverse the order of the array. - * @returns {Array} Sorted copy of the source array. - * - * @example - - - -
-
Sorting predicate = {{predicate}}; reverse = {{reverse}}
-
- [ unsorted ] - - - - - - - - - - - -
Name - (^)Phone NumberAge
{{friend.name}}{{friend.phone}}{{friend.age}}
-
-
-
- * - * It's also possible to call the orderBy filter manually, by injecting `$filter`, retrieving the - * filter routine with `$filter('orderBy')`, and calling the returned filter routine with the - * desired parameters. - * - * Example: - * - * @example - - -
- - - - - - - - - - - -
Name - (^)Phone NumberAge
{{friend.name}}{{friend.phone}}{{friend.age}}
-
-
- - - function Ctrl($scope, $filter) { - var orderBy = $filter('orderBy'); - $scope.friends = [ - { name: 'John', phone: '555-1212', age: 10 }, - { name: 'Mary', phone: '555-9876', age: 19 }, - { name: 'Mike', phone: '555-4321', age: 21 }, - { name: 'Adam', phone: '555-5678', age: 35 }, - { name: 'Julie', phone: '555-8765', age: 29 } - ]; - - $scope.order = function(predicate, reverse) { - $scope.friends = orderBy($scope.friends, predicate, reverse); - }; - $scope.order('-age',false); - } - -
- */ -orderByFilter.$inject = ['$parse']; -function orderByFilter($parse){ - return function(array, sortPredicate, reverseOrder) { - if (!isArray(array)) return array; - if (!sortPredicate) return array; - sortPredicate = isArray(sortPredicate) ? sortPredicate: [sortPredicate]; - sortPredicate = map(sortPredicate, function(predicate){ - var descending = false, get = predicate || identity; - if (isString(predicate)) { - if ((predicate.charAt(0) == '+' || predicate.charAt(0) == '-')) { - descending = predicate.charAt(0) == '-'; - predicate = predicate.substring(1); - } - get = $parse(predicate); - if (get.constant) { - var key = get(); - return reverseComparator(function(a,b) { - return compare(a[key], b[key]); - }, descending); - } - } - return reverseComparator(function(a,b){ - return compare(get(a),get(b)); - }, descending); - }); - var arrayCopy = []; - for ( var i = 0; i < array.length; i++) { arrayCopy.push(array[i]); } - return arrayCopy.sort(reverseComparator(comparator, reverseOrder)); - - function comparator(o1, o2){ - for ( var i = 0; i < sortPredicate.length; i++) { - var comp = sortPredicate[i](o1, o2); - if (comp !== 0) return comp; - } - return 0; - } - function reverseComparator(comp, descending) { - return toBoolean(descending) - ? function(a,b){return comp(b,a);} - : comp; - } - function compare(v1, v2){ - var t1 = typeof v1; - var t2 = typeof v2; - if (t1 == t2) { - if (t1 == "string") { - v1 = v1.toLowerCase(); - v2 = v2.toLowerCase(); - } - if (v1 === v2) return 0; - return v1 < v2 ? -1 : 1; - } else { - return t1 < t2 ? -1 : 1; - } - } - }; -} - -function ngDirective(directive) { - if (isFunction(directive)) { - directive = { - link: directive - }; - } - directive.restrict = directive.restrict || 'AC'; - return valueFn(directive); -} - -/** - * @ngdoc directive - * @name a - * @restrict E - * - * @description - * Modifies the default behavior of the html A tag so that the default action is prevented when - * the href attribute is empty. - * - * This change permits the easy creation of action links with the `ngClick` directive - * without changing the location or causing page reloads, e.g.: - * `Add Item` - */ -var htmlAnchorDirective = valueFn({ - restrict: 'E', - compile: function(element, attr) { - - if (msie <= 8) { - - // turn link into a stylable link in IE - // but only if it doesn't have name attribute, in which case it's an anchor - if (!attr.href && !attr.name) { - attr.$set('href', ''); - } - - // add a comment node to anchors to workaround IE bug that causes element content to be reset - // to new attribute content if attribute is updated with value containing @ and element also - // contains value with @ - // see issue #1949 - element.append(document.createComment('IE fix')); - } - - if (!attr.href && !attr.xlinkHref && !attr.name) { - return function(scope, element) { - // SVGAElement does not use the href attribute, but rather the 'xlinkHref' attribute. - var href = toString.call(element.prop('href')) === '[object SVGAnimatedString]' ? - 'xlink:href' : 'href'; - element.on('click', function(event){ - // if we have no href url, then don't navigate anywhere. - if (!element.attr(href)) { - event.preventDefault(); - } - }); - }; - } - } -}); - -/** - * @ngdoc directive - * @name ngHref - * @restrict A - * @priority 99 - * - * @description - * Using Angular markup like `{{hash}}` in an href attribute will - * make the link go to the wrong URL if the user clicks it before - * Angular has a chance to replace the `{{hash}}` markup with its - * value. Until Angular replaces the markup the link will be broken - * and will most likely return a 404 error. - * - * The `ngHref` directive solves this problem. - * - * The wrong way to write it: - * ```html - * - * ``` - * - * The correct way to write it: - * ```html - * - * ``` - * - * @element A - * @param {template} ngHref any string which can contain `{{}}` markup. - * - * @example - * This example shows various combinations of `href`, `ng-href` and `ng-click` attributes - * in links and their different behaviors: - - -
-
link 1 (link, don't reload)
- link 2 (link, don't reload)
- link 3 (link, reload!)
- anchor (link, don't reload)
- anchor (no link)
- link (link, change location) -
- - it('should execute ng-click but not reload when href without value', function() { - element(by.id('link-1')).click(); - expect(element(by.model('value')).getAttribute('value')).toEqual('1'); - expect(element(by.id('link-1')).getAttribute('href')).toBe(''); - }); - - it('should execute ng-click but not reload when href empty string', function() { - element(by.id('link-2')).click(); - expect(element(by.model('value')).getAttribute('value')).toEqual('2'); - expect(element(by.id('link-2')).getAttribute('href')).toBe(''); - }); - - it('should execute ng-click and change url when ng-href specified', function() { - expect(element(by.id('link-3')).getAttribute('href')).toMatch(/\/123$/); - - element(by.id('link-3')).click(); - - // At this point, we navigate away from an Angular page, so we need - // to use browser.driver to get the base webdriver. - - browser.wait(function() { - return browser.driver.getCurrentUrl().then(function(url) { - return url.match(/\/123$/); - }); - }, 1000, 'page should navigate to /123'); - }); - - xit('should execute ng-click but not reload when href empty string and name specified', function() { - element(by.id('link-4')).click(); - expect(element(by.model('value')).getAttribute('value')).toEqual('4'); - expect(element(by.id('link-4')).getAttribute('href')).toBe(''); - }); - - it('should execute ng-click but not reload when no href but name specified', function() { - element(by.id('link-5')).click(); - expect(element(by.model('value')).getAttribute('value')).toEqual('5'); - expect(element(by.id('link-5')).getAttribute('href')).toBe(null); - }); - - it('should only change url when only ng-href', function() { - element(by.model('value')).clear(); - element(by.model('value')).sendKeys('6'); - expect(element(by.id('link-6')).getAttribute('href')).toMatch(/\/6$/); - - element(by.id('link-6')).click(); - - // At this point, we navigate away from an Angular page, so we need - // to use browser.driver to get the base webdriver. - browser.wait(function() { - return browser.driver.getCurrentUrl().then(function(url) { - return url.match(/\/6$/); - }); - }, 1000, 'page should navigate to /6'); - }); - - - */ - -/** - * @ngdoc directive - * @name ngSrc - * @restrict A - * @priority 99 - * - * @description - * Using Angular markup like `{{hash}}` in a `src` attribute doesn't - * work right: The browser will fetch from the URL with the literal - * text `{{hash}}` until Angular replaces the expression inside - * `{{hash}}`. The `ngSrc` directive solves this problem. - * - * The buggy way to write it: - * ```html - * - * ``` - * - * The correct way to write it: - * ```html - * - * ``` - * - * @element IMG - * @param {template} ngSrc any string which can contain `{{}}` markup. - */ - -/** - * @ngdoc directive - * @name ngSrcset - * @restrict A - * @priority 99 - * - * @description - * Using Angular markup like `{{hash}}` in a `srcset` attribute doesn't - * work right: The browser will fetch from the URL with the literal - * text `{{hash}}` until Angular replaces the expression inside - * `{{hash}}`. The `ngSrcset` directive solves this problem. - * - * The buggy way to write it: - * ```html - * - * ``` - * - * The correct way to write it: - * ```html - * - * ``` - * - * @element IMG - * @param {template} ngSrcset any string which can contain `{{}}` markup. - */ - -/** - * @ngdoc directive - * @name ngDisabled - * @restrict A - * @priority 100 - * - * @description - * - * The following markup will make the button enabled on Chrome/Firefox but not on IE8 and older IEs: - * ```html - *
- * - *
- * ``` - * - * The HTML specification does not require browsers to preserve the values of boolean attributes - * such as disabled. (Their presence means true and their absence means false.) - * If we put an Angular interpolation expression into such an attribute then the - * binding information would be lost when the browser removes the attribute. - * The `ngDisabled` directive solves this problem for the `disabled` attribute. - * This complementary directive is not removed by the browser and so provides - * a permanent reliable place to store the binding information. - * - * @example - - - Click me to toggle:
- -
- - it('should toggle button', function() { - expect(element(by.css('button')).getAttribute('disabled')).toBeFalsy(); - element(by.model('checked')).click(); - expect(element(by.css('button')).getAttribute('disabled')).toBeTruthy(); - }); - -
- * - * @element INPUT - * @param {expression} ngDisabled If the {@link guide/expression expression} is truthy, - * then special attribute "disabled" will be set on the element - */ - - -/** - * @ngdoc directive - * @name ngChecked - * @restrict A - * @priority 100 - * - * @description - * The HTML specification does not require browsers to preserve the values of boolean attributes - * such as checked. (Their presence means true and their absence means false.) - * If we put an Angular interpolation expression into such an attribute then the - * binding information would be lost when the browser removes the attribute. - * The `ngChecked` directive solves this problem for the `checked` attribute. - * This complementary directive is not removed by the browser and so provides - * a permanent reliable place to store the binding information. - * @example - - - Check me to check both:
- -
- - it('should check both checkBoxes', function() { - expect(element(by.id('checkSlave')).getAttribute('checked')).toBeFalsy(); - element(by.model('master')).click(); - expect(element(by.id('checkSlave')).getAttribute('checked')).toBeTruthy(); - }); - -
- * - * @element INPUT - * @param {expression} ngChecked If the {@link guide/expression expression} is truthy, - * then special attribute "checked" will be set on the element - */ - - -/** - * @ngdoc directive - * @name ngReadonly - * @restrict A - * @priority 100 - * - * @description - * The HTML specification does not require browsers to preserve the values of boolean attributes - * such as readonly. (Their presence means true and their absence means false.) - * If we put an Angular interpolation expression into such an attribute then the - * binding information would be lost when the browser removes the attribute. - * The `ngReadonly` directive solves this problem for the `readonly` attribute. - * This complementary directive is not removed by the browser and so provides - * a permanent reliable place to store the binding information. - * @example - - - Check me to make text readonly:
- -
- - it('should toggle readonly attr', function() { - expect(element(by.css('[type="text"]')).getAttribute('readonly')).toBeFalsy(); - element(by.model('checked')).click(); - expect(element(by.css('[type="text"]')).getAttribute('readonly')).toBeTruthy(); - }); - -
- * - * @element INPUT - * @param {expression} ngReadonly If the {@link guide/expression expression} is truthy, - * then special attribute "readonly" will be set on the element - */ - - -/** - * @ngdoc directive - * @name ngSelected - * @restrict A - * @priority 100 - * - * @description - * The HTML specification does not require browsers to preserve the values of boolean attributes - * such as selected. (Their presence means true and their absence means false.) - * If we put an Angular interpolation expression into such an attribute then the - * binding information would be lost when the browser removes the attribute. - * The `ngSelected` directive solves this problem for the `selected` attribute. - * This complementary directive is not removed by the browser and so provides - * a permanent reliable place to store the binding information. - * - * @example - - - Check me to select:
- -
- - it('should select Greetings!', function() { - expect(element(by.id('greet')).getAttribute('selected')).toBeFalsy(); - element(by.model('selected')).click(); - expect(element(by.id('greet')).getAttribute('selected')).toBeTruthy(); - }); - -
- * - * @element OPTION - * @param {expression} ngSelected If the {@link guide/expression expression} is truthy, - * then special attribute "selected" will be set on the element - */ - -/** - * @ngdoc directive - * @name ngOpen - * @restrict A - * @priority 100 - * - * @description - * The HTML specification does not require browsers to preserve the values of boolean attributes - * such as open. (Their presence means true and their absence means false.) - * If we put an Angular interpolation expression into such an attribute then the - * binding information would be lost when the browser removes the attribute. - * The `ngOpen` directive solves this problem for the `open` attribute. - * This complementary directive is not removed by the browser and so provides - * a permanent reliable place to store the binding information. - * @example - - - Check me check multiple:
-
- Show/Hide me -
-
- - it('should toggle open', function() { - expect(element(by.id('details')).getAttribute('open')).toBeFalsy(); - element(by.model('open')).click(); - expect(element(by.id('details')).getAttribute('open')).toBeTruthy(); - }); - -
- * - * @element DETAILS - * @param {expression} ngOpen If the {@link guide/expression expression} is truthy, - * then special attribute "open" will be set on the element - */ - -var ngAttributeAliasDirectives = {}; - - -// boolean attrs are evaluated -forEach(BOOLEAN_ATTR, function(propName, attrName) { - // binding to multiple is not supported - if (propName == "multiple") return; - - var normalized = directiveNormalize('ng-' + attrName); - ngAttributeAliasDirectives[normalized] = function() { - return { - priority: 100, - link: function(scope, element, attr) { - scope.$watch(attr[normalized], function ngBooleanAttrWatchAction(value) { - attr.$set(attrName, !!value); - }); - } - }; - }; -}); - -// aliased input attrs are evaluated -forEach(ALIASED_ATTR, function(htmlAttr, ngAttr) { - ngAttributeAliasDirectives[ngAttr] = function() { - return { - priority: 100, - link: function(scope, element, attr) { - //special case ngPattern when a literal regular expression value - //is used as the expression (this way we don't have to watch anything). - if (ngAttr === "ngPattern" && attr.ngPattern.charAt(0) == "/") { - var match = attr.ngPattern.match(REGEX_STRING_REGEXP); - if (match) { - attr.$set("ngPattern", new RegExp(match[1], match[2])); - return; - } - } - - scope.$watch(attr[ngAttr], function ngAttrAliasWatchAction(value) { - attr.$set(ngAttr, value); - }); - } - }; - }; -}); - -// ng-src, ng-srcset, ng-href are interpolated -forEach(['src', 'srcset', 'href'], function(attrName) { - var normalized = directiveNormalize('ng-' + attrName); - ngAttributeAliasDirectives[normalized] = function() { - return { - priority: 99, // it needs to run after the attributes are interpolated - link: function(scope, element, attr) { - var propName = attrName, - name = attrName; - - if (attrName === 'href' && - toString.call(element.prop('href')) === '[object SVGAnimatedString]') { - name = 'xlinkHref'; - attr.$attr[name] = 'xlink:href'; - propName = null; - } - - attr.$observe(normalized, function(value) { - if (!value) - return; - - attr.$set(name, value); - - // on IE, if "ng:src" directive declaration is used and "src" attribute doesn't exist - // then calling element.setAttribute('src', 'foo') doesn't do anything, so we need - // to set the property as well to achieve the desired effect. - // we use attr[attrName] value since $set can sanitize the url. - if (msie && propName) element.prop(propName, attr[name]); - }); - } - }; - }; -}); - -/* global -nullFormCtrl */ -var nullFormCtrl = { - $addControl: noop, - $removeControl: noop, - $setValidity: noop, - $setDirty: noop, - $setPristine: noop -}; - -/** - * @ngdoc type - * @name form.FormController - * - * @property {boolean} $pristine True if user has not interacted with the form yet. - * @property {boolean} $dirty True if user has already interacted with the form. - * @property {boolean} $valid True if all of the containing forms and controls are valid. - * @property {boolean} $invalid True if at least one containing control or form is invalid. - * - * @property {Object} $error Is an object hash, containing references to all invalid controls or - * forms, where: - * - * - keys are validation tokens (error names), - * - values are arrays of controls or forms that are invalid for given error name. - * - * - * Built-in validation tokens: - * - * - `email` - * - `max` - * - `maxlength` - * - `min` - * - `minlength` - * - `number` - * - `pattern` - * - `required` - * - `url` - * - * @description - * `FormController` keeps track of all its controls and nested forms as well as the state of them, - * such as being valid/invalid or dirty/pristine. - * - * Each {@link ng.directive:form form} directive creates an instance - * of `FormController`. - * - */ -//asks for $scope to fool the BC controller module -FormController.$inject = ['$element', '$attrs', '$scope', '$animate']; -function FormController(element, attrs, $scope, $animate) { - var form = this, - parentForm = element.parent().controller('form') || nullFormCtrl, - invalidCount = 0, // used to easily determine if we are valid - errors = form.$error = {}, - controls = []; - - // init state - form.$name = attrs.name || attrs.ngForm; - form.$dirty = false; - form.$pristine = true; - form.$valid = true; - form.$invalid = false; - - parentForm.$addControl(form); - - // Setup initial state of the control - element.addClass(PRISTINE_CLASS); - toggleValidCss(true); - - // convenience method for easy toggling of classes - function toggleValidCss(isValid, validationErrorKey) { - validationErrorKey = validationErrorKey ? '-' + snake_case(validationErrorKey, '-') : ''; - $animate.removeClass(element, (isValid ? INVALID_CLASS : VALID_CLASS) + validationErrorKey); - $animate.addClass(element, (isValid ? VALID_CLASS : INVALID_CLASS) + validationErrorKey); - } - - /** - * @ngdoc method - * @name form.FormController#$commitViewValue - * - * @description - * Commit all form controls pending updates to the `$modelValue`. - * - * Updates may be pending by a debounced event or because the input is waiting for a some future - * event defined in `ng-model-options`. This method is rarely needed as `NgModelController` - * usually handles calling this in response to input events. - */ - form.$commitViewValue = function() { - forEach(controls, function(control) { - control.$commitViewValue(); - }); - }; - - /** - * @ngdoc method - * @name form.FormController#$addControl - * - * @description - * Register a control with the form. - * - * Input elements using ngModelController do this automatically when they are linked. - */ - form.$addControl = function(control) { - // Breaking change - before, inputs whose name was "hasOwnProperty" were quietly ignored - // and not added to the scope. Now we throw an error. - assertNotHasOwnProperty(control.$name, 'input'); - controls.push(control); - - if (control.$name) { - form[control.$name] = control; - } - }; - - /** - * @ngdoc method - * @name form.FormController#$removeControl - * - * @description - * Deregister a control from the form. - * - * Input elements using ngModelController do this automatically when they are destroyed. - */ - form.$removeControl = function(control) { - if (control.$name && form[control.$name] === control) { - delete form[control.$name]; - } - forEach(errors, function(queue, validationToken) { - form.$setValidity(validationToken, true, control); - }); - - arrayRemove(controls, control); - }; - - /** - * @ngdoc method - * @name form.FormController#$setValidity - * - * @description - * Sets the validity of a form control. - * - * This method will also propagate to parent forms. - */ - form.$setValidity = function(validationToken, isValid, control) { - var queue = errors[validationToken]; - - if (isValid) { - if (queue) { - arrayRemove(queue, control); - if (!queue.length) { - invalidCount--; - if (!invalidCount) { - toggleValidCss(isValid); - form.$valid = true; - form.$invalid = false; - } - errors[validationToken] = false; - toggleValidCss(true, validationToken); - parentForm.$setValidity(validationToken, true, form); - } - } - - } else { - if (!invalidCount) { - toggleValidCss(isValid); - } - if (queue) { - if (includes(queue, control)) return; - } else { - errors[validationToken] = queue = []; - invalidCount++; - toggleValidCss(false, validationToken); - parentForm.$setValidity(validationToken, false, form); - } - queue.push(control); - - form.$valid = false; - form.$invalid = true; - } - }; - - /** - * @ngdoc method - * @name form.FormController#$setDirty - * - * @description - * Sets the form to a dirty state. - * - * This method can be called to add the 'ng-dirty' class and set the form to a dirty - * state (ng-dirty class). This method will also propagate to parent forms. - */ - form.$setDirty = function() { - $animate.removeClass(element, PRISTINE_CLASS); - $animate.addClass(element, DIRTY_CLASS); - form.$dirty = true; - form.$pristine = false; - parentForm.$setDirty(); - }; - - /** - * @ngdoc method - * @name form.FormController#$setPristine - * - * @description - * Sets the form to its pristine state. - * - * This method can be called to remove the 'ng-dirty' class and set the form to its pristine - * state (ng-pristine class). This method will also propagate to all the controls contained - * in this form. - * - * Setting a form back to a pristine state is often useful when we want to 'reuse' a form after - * saving or resetting it. - */ - form.$setPristine = function () { - $animate.removeClass(element, DIRTY_CLASS); - $animate.addClass(element, PRISTINE_CLASS); - form.$dirty = false; - form.$pristine = true; - forEach(controls, function(control) { - control.$setPristine(); - }); - }; -} - - -/** - * @ngdoc directive - * @name ngForm - * @restrict EAC - * - * @description - * Nestable alias of {@link ng.directive:form `form`} directive. HTML - * does not allow nesting of form elements. It is useful to nest forms, for example if the validity of a - * sub-group of controls needs to be determined. - * - * Note: the purpose of `ngForm` is to group controls, - * but not to be a replacement for the `
` tag with all of its capabilities - * (e.g. posting to the server, ...). - * - * @param {string=} ngForm|name Name of the form. If specified, the form controller will be published into - * related scope, under this name. - * - */ - - /** - * @ngdoc directive - * @name form - * @restrict E - * - * @description - * Directive that instantiates - * {@link form.FormController FormController}. - * - * If the `name` attribute is specified, the form controller is published onto the current scope under - * this name. - * - * # Alias: {@link ng.directive:ngForm `ngForm`} - * - * In Angular forms can be nested. This means that the outer form is valid when all of the child - * forms are valid as well. However, browsers do not allow nesting of `` elements, so - * Angular provides the {@link ng.directive:ngForm `ngForm`} directive which behaves identically to - * `` but can be nested. This allows you to have nested forms, which is very useful when - * using Angular validation directives in forms that are dynamically generated using the - * {@link ng.directive:ngRepeat `ngRepeat`} directive. Since you cannot dynamically generate the `name` - * attribute of input elements using interpolation, you have to wrap each set of repeated inputs in an - * `ngForm` directive and nest these in an outer `form` element. - * - * - * # CSS classes - * - `ng-valid` is set if the form is valid. - * - `ng-invalid` is set if the form is invalid. - * - `ng-pristine` is set if the form is pristine. - * - `ng-dirty` is set if the form is dirty. - * - * Keep in mind that ngAnimate can detect each of these classes when added and removed. - * - * - * # Submitting a form and preventing the default action - * - * Since the role of forms in client-side Angular applications is different than in classical - * roundtrip apps, it is desirable for the browser not to translate the form submission into a full - * page reload that sends the data to the server. Instead some javascript logic should be triggered - * to handle the form submission in an application-specific way. - * - * For this reason, Angular prevents the default action (form submission to the server) unless the - * `` element has an `action` attribute specified. - * - * You can use one of the following two ways to specify what javascript method should be called when - * a form is submitted: - * - * - {@link ng.directive:ngSubmit ngSubmit} directive on the form element - * - {@link ng.directive:ngClick ngClick} directive on the first - * button or input field of type submit (input[type=submit]) - * - * To prevent double execution of the handler, use only one of the {@link ng.directive:ngSubmit ngSubmit} - * or {@link ng.directive:ngClick ngClick} directives. - * This is because of the following form submission rules in the HTML specification: - * - * - If a form has only one input field then hitting enter in this field triggers form submit - * (`ngSubmit`) - * - if a form has 2+ input fields and no buttons or input[type=submit] then hitting enter - * doesn't trigger submit - * - if a form has one or more input fields and one or more buttons or input[type=submit] then - * hitting enter in any of the input fields will trigger the click handler on the *first* button or - * input[type=submit] (`ngClick`) *and* a submit handler on the enclosing form (`ngSubmit`) - * - * Any pending `ngModelOptions` changes will take place immediately when an enclosing form is - * submitted. Note that `ngClick` events will occur before the model is updated. Use `ngSubmit` - * to have access to the updated model. - * - * @param {string=} name Name of the form. If specified, the form controller will be published into - * related scope, under this name. - * - * ## Animation Hooks - * - * Animations in ngForm are triggered when any of the associated CSS classes are added and removed. - * These classes are: `.ng-pristine`, `.ng-dirty`, `.ng-invalid` and `.ng-valid` as well as any - * other validations that are performed within the form. Animations in ngForm are similar to how - * they work in ngClass and animations can be hooked into using CSS transitions, keyframes as well - * as JS animations. - * - * The following example shows a simple way to utilize CSS transitions to style a form element - * that has been rendered as invalid after it has been validated: - * - *
- * //be sure to include ngAnimate as a module to hook into more
- * //advanced animations
- * .my-form {
- *   transition:0.5s linear all;
- *   background: white;
- * }
- * .my-form.ng-invalid {
- *   background: red;
- *   color:white;
- * }
- * 
- * - * @example - - - - - - userType: - Required!
- userType = {{userType}}
- myForm.input.$valid = {{myForm.input.$valid}}
- myForm.input.$error = {{myForm.input.$error}}
- myForm.$valid = {{myForm.$valid}}
- myForm.$error.required = {{!!myForm.$error.required}}
- -
- - it('should initialize to model', function() { - var userType = element(by.binding('userType')); - var valid = element(by.binding('myForm.input.$valid')); - - expect(userType.getText()).toContain('guest'); - expect(valid.getText()).toContain('true'); - }); - - it('should be invalid if empty', function() { - var userType = element(by.binding('userType')); - var valid = element(by.binding('myForm.input.$valid')); - var userInput = element(by.model('userType')); - - userInput.clear(); - userInput.sendKeys(''); - - expect(userType.getText()).toEqual('userType ='); - expect(valid.getText()).toContain('false'); - }); - -
- * - */ -var formDirectiveFactory = function(isNgForm) { - return ['$timeout', function($timeout) { - var formDirective = { - name: 'form', - restrict: isNgForm ? 'EAC' : 'E', - controller: FormController, - compile: function() { - return { - pre: function(scope, formElement, attr, controller) { - if (!attr.action) { - // we can't use jq events because if a form is destroyed during submission the default - // action is not prevented. see #1238 - // - // IE 9 is not affected because it doesn't fire a submit event and try to do a full - // page reload if the form was destroyed by submission of the form via a click handler - // on a button in the form. Looks like an IE9 specific bug. - var handleFormSubmission = function(event) { - scope.$apply(function() { - controller.$commitViewValue(); - }); - - event.preventDefault - ? event.preventDefault() - : event.returnValue = false; // IE - }; - - addEventListenerFn(formElement[0], 'submit', handleFormSubmission); - - // unregister the preventDefault listener so that we don't not leak memory but in a - // way that will achieve the prevention of the default action. - formElement.on('$destroy', function() { - $timeout(function() { - removeEventListenerFn(formElement[0], 'submit', handleFormSubmission); - }, 0, false); - }); - } - - var parentFormCtrl = formElement.parent().controller('form'), - alias = attr.name || attr.ngForm; - - if (alias) { - setter(scope, alias, controller, alias); - } - if (parentFormCtrl) { - formElement.on('$destroy', function() { - parentFormCtrl.$removeControl(controller); - if (alias) { - setter(scope, alias, undefined, alias); - } - extend(controller, nullFormCtrl); //stop propagating child destruction handlers upwards - }); - } - } - }; - } - }; - - return formDirective; - }]; -}; - -var formDirective = formDirectiveFactory(); -var ngFormDirective = formDirectiveFactory(true); - -/* global - - -VALID_CLASS, - -INVALID_CLASS, - -PRISTINE_CLASS, - -DIRTY_CLASS, - -UNTOUCHED_CLASS, - -TOUCHED_CLASS -*/ - -var URL_REGEXP = /^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/; -var EMAIL_REGEXP = /^[a-z0-9!#$%&'*+\/=?^_`{|}~.-]+@[a-z0-9-]+(\.[a-z0-9-]+)*$/i; -var NUMBER_REGEXP = /^\s*(\-|\+)?(\d+|(\d*(\.\d*)))\s*$/; -var DATE_REGEXP = /^(\d{4})-(\d{2})-(\d{2})$/; -var DATETIMELOCAL_REGEXP = /^(\d{4})-(\d\d)-(\d\d)T(\d\d):(\d\d)$/; -var WEEK_REGEXP = /^(\d{4})-W(\d\d)$/; -var MONTH_REGEXP = /^(\d{4})-(\d\d)$/; -var TIME_REGEXP = /^(\d\d):(\d\d)$/; -var DEFAULT_REGEXP = /(\s+|^)default(\s+|$)/; - -var inputType = { - - /** - * @ngdoc input - * @name input[text] - * - * @description - * Standard HTML text input with angular data binding. - * - * @param {string} ngModel Assignable angular expression to data-bind to. - * @param {string=} name Property name of the form under which the control is published. - * @param {string=} required Adds `required` validation error key if the value is not entered. - * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to - * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of - * `required` when you want to data-bind to the `required` attribute. - * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than - * minlength. - * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than - * maxlength. - * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the - * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for - * patterns defined as scope expressions. - * @param {string=} ngChange Angular expression to be executed when input changes due to user - * interaction with the input element. - * @param {boolean=} [ngTrim=true] If set to false Angular will not automatically trim the input. - * - * @example - - - -
- Single word: - - Required! - - Single word only! - - text = {{text}}
- myForm.input.$valid = {{myForm.input.$valid}}
- myForm.input.$error = {{myForm.input.$error}}
- myForm.$valid = {{myForm.$valid}}
- myForm.$error.required = {{!!myForm.$error.required}}
-
-
- - var text = element(by.binding('text')); - var valid = element(by.binding('myForm.input.$valid')); - var input = element(by.model('text')); - - it('should initialize to model', function() { - expect(text.getText()).toContain('guest'); - expect(valid.getText()).toContain('true'); - }); - - it('should be invalid if empty', function() { - input.clear(); - input.sendKeys(''); - - expect(text.getText()).toEqual('text ='); - expect(valid.getText()).toContain('false'); - }); - - it('should be invalid if multi word', function() { - input.clear(); - input.sendKeys('hello world'); - - expect(valid.getText()).toContain('false'); - }); - -
- */ - 'text': textInputType, - - /** - * @ngdoc input - * @name input[date] - * - * @description - * Input with date validation and transformation. In browsers that do not yet support - * the HTML5 date input, a text element will be used. In that case, text must be entered in a valid ISO-8601 - * date format (yyyy-MM-dd), for example: `2009-01-06`. The model must always be a Date object. - * - * @param {string} ngModel Assignable angular expression to data-bind to. - * @param {string=} name Property name of the form under which the control is published. - * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. This must be a - * valid ISO date string (yyyy-MM-dd). - * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. This must be - * a valid ISO date string (yyyy-MM-dd). - * @param {string=} required Sets `required` validation error key if the value is not entered. - * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to - * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of - * `required` when you want to data-bind to the `required` attribute. - * @param {string=} ngChange Angular expression to be executed when input changes due to user - * interaction with the input element. - * - * @example - - - -
- Pick a date between in 2013: - - - Required! - - Not a valid date! - value = {{value | date: "yyyy-MM-dd"}}
- myForm.input.$valid = {{myForm.input.$valid}}
- myForm.input.$error = {{myForm.input.$error}}
- myForm.$valid = {{myForm.$valid}}
- myForm.$error.required = {{!!myForm.$error.required}}
-
-
- - var value = element(by.binding('value | date: "yyyy-MM-dd"')); - var valid = element(by.binding('myForm.input.$valid')); - var input = element(by.model('value')); - - // currently protractor/webdriver does not support - // sending keys to all known HTML5 input controls - // for various browsers (see https://github.com/angular/protractor/issues/562). - function setInput(val) { - // set the value of the element and force validation. - var scr = "var ipt = document.getElementById('exampleInput'); " + - "ipt.value = '" + val + "';" + - "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });"; - browser.executeScript(scr); - } - - it('should initialize to model', function() { - expect(value.getText()).toContain('2013-10-22'); - expect(valid.getText()).toContain('myForm.input.$valid = true'); - }); - - it('should be invalid if empty', function() { - setInput(''); - expect(value.getText()).toEqual('value ='); - expect(valid.getText()).toContain('myForm.input.$valid = false'); - }); - - it('should be invalid if over max', function() { - setInput('2015-01-01'); - expect(value.getText()).toContain(''); - expect(valid.getText()).toContain('myForm.input.$valid = false'); - }); - -
f - */ - 'date': createDateInputType('date', DATE_REGEXP, - createDateParser(DATE_REGEXP, ['yyyy', 'MM', 'dd']), - 'yyyy-MM-dd'), - - /** - * @ngdoc input - * @name input[dateTimeLocal] - * - * @description - * Input with datetime validation and transformation. In browsers that do not yet support - * the HTML5 date input, a text element will be used. In that case, the text must be entered in a valid ISO-8601 - * local datetime format (yyyy-MM-ddTHH:mm), for example: `2010-12-28T14:57`. The model must be a Date object. - * - * @param {string} ngModel Assignable angular expression to data-bind to. - * @param {string=} name Property name of the form under which the control is published. - * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. This must be a - * valid ISO datetime format (yyyy-MM-ddTHH:mm). - * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. This must be - * a valid ISO datetime format (yyyy-MM-ddTHH:mm). - * @param {string=} required Sets `required` validation error key if the value is not entered. - * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to - * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of - * `required` when you want to data-bind to the `required` attribute. - * @param {string=} ngChange Angular expression to be executed when input changes due to user - * interaction with the input element. - * - * @example - - - -
- Pick a date between in 2013: - - - Required! - - Not a valid date! - value = {{value | date: "yyyy-MM-ddTHH:mm"}}
- myForm.input.$valid = {{myForm.input.$valid}}
- myForm.input.$error = {{myForm.input.$error}}
- myForm.$valid = {{myForm.$valid}}
- myForm.$error.required = {{!!myForm.$error.required}}
-
-
- - var value = element(by.binding('value | date: "yyyy-MM-ddTHH:mm"')); - var valid = element(by.binding('myForm.input.$valid')); - var input = element(by.model('value')); - - // currently protractor/webdriver does not support - // sending keys to all known HTML5 input controls - // for various browsers (https://github.com/angular/protractor/issues/562). - function setInput(val) { - // set the value of the element and force validation. - var scr = "var ipt = document.getElementById('exampleInput'); " + - "ipt.value = '" + val + "';" + - "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });"; - browser.executeScript(scr); - } - - it('should initialize to model', function() { - expect(value.getText()).toContain('2010-12-28T14:57'); - expect(valid.getText()).toContain('myForm.input.$valid = true'); - }); - - it('should be invalid if empty', function() { - setInput(''); - expect(value.getText()).toEqual('value ='); - expect(valid.getText()).toContain('myForm.input.$valid = false'); - }); - - it('should be invalid if over max', function() { - setInput('2015-01-01T23:59'); - expect(value.getText()).toContain(''); - expect(valid.getText()).toContain('myForm.input.$valid = false'); - }); - -
- */ - 'datetime-local': createDateInputType('datetimelocal', DATETIMELOCAL_REGEXP, - createDateParser(DATETIMELOCAL_REGEXP, ['yyyy', 'MM', 'dd', 'HH', 'mm']), - 'yyyy-MM-ddTHH:mm'), - - /** - * @ngdoc input - * @name input[time] - * - * @description - * Input with time validation and transformation. In browsers that do not yet support - * the HTML5 date input, a text element will be used. In that case, the text must be entered in a valid ISO-8601 - * local time format (HH:mm), for example: `14:57`. Model must be a Date object. This binding will always output a - * Date object to the model of January 1, 1900, or local date `new Date(0, 0, 1, HH, mm)`. - * - * @param {string} ngModel Assignable angular expression to data-bind to. - * @param {string=} name Property name of the form under which the control is published. - * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. This must be a - * valid ISO time format (HH:mm). - * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. This must be a - * valid ISO time format (HH:mm). - * @param {string=} required Sets `required` validation error key if the value is not entered. - * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to - * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of - * `required` when you want to data-bind to the `required` attribute. - * @param {string=} ngChange Angular expression to be executed when input changes due to user - * interaction with the input element. - * - * @example - - - -
- Pick a between 8am and 5pm: - - - Required! - - Not a valid date! - value = {{value | date: "HH:mm"}}
- myForm.input.$valid = {{myForm.input.$valid}}
- myForm.input.$error = {{myForm.input.$error}}
- myForm.$valid = {{myForm.$valid}}
- myForm.$error.required = {{!!myForm.$error.required}}
-
-
- - var value = element(by.binding('value | date: "HH:mm"')); - var valid = element(by.binding('myForm.input.$valid')); - var input = element(by.model('value')); - - // currently protractor/webdriver does not support - // sending keys to all known HTML5 input controls - // for various browsers (https://github.com/angular/protractor/issues/562). - function setInput(val) { - // set the value of the element and force validation. - var scr = "var ipt = document.getElementById('exampleInput'); " + - "ipt.value = '" + val + "';" + - "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });"; - browser.executeScript(scr); - } - - it('should initialize to model', function() { - expect(value.getText()).toContain('14:57'); - expect(valid.getText()).toContain('myForm.input.$valid = true'); - }); - - it('should be invalid if empty', function() { - setInput(''); - expect(value.getText()).toEqual('value ='); - expect(valid.getText()).toContain('myForm.input.$valid = false'); - }); - - it('should be invalid if over max', function() { - setInput('23:59'); - expect(value.getText()).toContain(''); - expect(valid.getText()).toContain('myForm.input.$valid = false'); - }); - -
- */ - 'time': createDateInputType('time', TIME_REGEXP, - createDateParser(TIME_REGEXP, ['HH', 'mm']), - 'HH:mm'), - - /** - * @ngdoc input - * @name input[week] - * - * @description - * Input with week-of-the-year validation and transformation to Date. In browsers that do not yet support - * the HTML5 week input, a text element will be used. In that case, the text must be entered in a valid ISO-8601 - * week format (yyyy-W##), for example: `2013-W02`. The model must always be a Date object. - * - * @param {string} ngModel Assignable angular expression to data-bind to. - * @param {string=} name Property name of the form under which the control is published. - * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. This must be a - * valid ISO week format (yyyy-W##). - * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. This must be - * a valid ISO week format (yyyy-W##). - * @param {string=} required Sets `required` validation error key if the value is not entered. - * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to - * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of - * `required` when you want to data-bind to the `required` attribute. - * @param {string=} ngChange Angular expression to be executed when input changes due to user - * interaction with the input element. - * - * @example - - - -
- Pick a date between in 2013: - - - Required! - - Not a valid date! - value = {{value | date: "yyyy-Www"}}
- myForm.input.$valid = {{myForm.input.$valid}}
- myForm.input.$error = {{myForm.input.$error}}
- myForm.$valid = {{myForm.$valid}}
- myForm.$error.required = {{!!myForm.$error.required}}
-
-
- - var value = element(by.binding('value | date: "yyyy-Www"')); - var valid = element(by.binding('myForm.input.$valid')); - var input = element(by.model('value')); - - // currently protractor/webdriver does not support - // sending keys to all known HTML5 input controls - // for various browsers (https://github.com/angular/protractor/issues/562). - function setInput(val) { - // set the value of the element and force validation. - var scr = "var ipt = document.getElementById('exampleInput'); " + - "ipt.value = '" + val + "';" + - "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });"; - browser.executeScript(scr); - } - - it('should initialize to model', function() { - expect(value.getText()).toContain('2013-W01'); - expect(valid.getText()).toContain('myForm.input.$valid = true'); - }); - - it('should be invalid if empty', function() { - setInput(''); - expect(value.getText()).toEqual('value ='); - expect(valid.getText()).toContain('myForm.input.$valid = false'); - }); - - it('should be invalid if over max', function() { - setInput('2015-W01'); - expect(value.getText()).toContain(''); - expect(valid.getText()).toContain('myForm.input.$valid = false'); - }); - -
- */ - 'week': createDateInputType('week', WEEK_REGEXP, weekParser, 'yyyy-Www'), - - /** - * @ngdoc input - * @name input[month] - * - * @description - * Input with month validation and transformation. In browsers that do not yet support - * the HTML5 month input, a text element will be used. In that case, the text must be entered in a valid ISO-8601 - * month format (yyyy-MM), for example: `2009-01`. The model must always be a Date object. In the event the model is - * not set to the first of the month, the first of that model's month is assumed. - * - * @param {string} ngModel Assignable angular expression to data-bind to. - * @param {string=} name Property name of the form under which the control is published. - * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. This must be - * a valid ISO month format (yyyy-MM). - * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. This must - * be a valid ISO month format (yyyy-MM). - * @param {string=} required Sets `required` validation error key if the value is not entered. - * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to - * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of - * `required` when you want to data-bind to the `required` attribute. - * @param {string=} ngChange Angular expression to be executed when input changes due to user - * interaction with the input element. - * - * @example - - - -
- Pick a month int 2013: - - - Required! - - Not a valid month! - value = {{value | date: "yyyy-MM"}}
- myForm.input.$valid = {{myForm.input.$valid}}
- myForm.input.$error = {{myForm.input.$error}}
- myForm.$valid = {{myForm.$valid}}
- myForm.$error.required = {{!!myForm.$error.required}}
-
-
- - var value = element(by.binding('value | date: "yyyy-MM"')); - var valid = element(by.binding('myForm.input.$valid')); - var input = element(by.model('value')); - - // currently protractor/webdriver does not support - // sending keys to all known HTML5 input controls - // for various browsers (https://github.com/angular/protractor/issues/562). - function setInput(val) { - // set the value of the element and force validation. - var scr = "var ipt = document.getElementById('exampleInput'); " + - "ipt.value = '" + val + "';" + - "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });"; - browser.executeScript(scr); - } - - it('should initialize to model', function() { - expect(value.getText()).toContain('2013-10'); - expect(valid.getText()).toContain('myForm.input.$valid = true'); - }); - - it('should be invalid if empty', function() { - setInput(''); - expect(value.getText()).toEqual('value ='); - expect(valid.getText()).toContain('myForm.input.$valid = false'); - }); - - it('should be invalid if over max', function() { - setInput('2015-01'); - expect(value.getText()).toContain(''); - expect(valid.getText()).toContain('myForm.input.$valid = false'); - }); - -
- */ - 'month': createDateInputType('month', MONTH_REGEXP, - createDateParser(MONTH_REGEXP, ['yyyy', 'MM']), - 'yyyy-MM'), - - /** - * @ngdoc input - * @name input[number] - * - * @description - * Text input with number validation and transformation. Sets the `number` validation - * error if not a valid number. - * - * @param {string} ngModel Assignable angular expression to data-bind to. - * @param {string=} name Property name of the form under which the control is published. - * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. - * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. - * @param {string=} required Sets `required` validation error key if the value is not entered. - * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to - * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of - * `required` when you want to data-bind to the `required` attribute. - * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than - * minlength. - * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than - * maxlength. - * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the - * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for - * patterns defined as scope expressions. - * @param {string=} ngChange Angular expression to be executed when input changes due to user - * interaction with the input element. - * - * @example - - - -
- Number: - - Required! - - Not valid number! - value = {{value}}
- myForm.input.$valid = {{myForm.input.$valid}}
- myForm.input.$error = {{myForm.input.$error}}
- myForm.$valid = {{myForm.$valid}}
- myForm.$error.required = {{!!myForm.$error.required}}
-
-
- - var value = element(by.binding('value')); - var valid = element(by.binding('myForm.input.$valid')); - var input = element(by.model('value')); - - it('should initialize to model', function() { - expect(value.getText()).toContain('12'); - expect(valid.getText()).toContain('true'); - }); - - it('should be invalid if empty', function() { - input.clear(); - input.sendKeys(''); - expect(value.getText()).toEqual('value ='); - expect(valid.getText()).toContain('false'); - }); - - it('should be invalid if over max', function() { - input.clear(); - input.sendKeys('123'); - expect(value.getText()).toEqual('value ='); - expect(valid.getText()).toContain('false'); - }); - -
- */ - 'number': numberInputType, - - - /** - * @ngdoc input - * @name input[url] - * - * @description - * Text input with URL validation. Sets the `url` validation error key if the content is not a - * valid URL. - * - * @param {string} ngModel Assignable angular expression to data-bind to. - * @param {string=} name Property name of the form under which the control is published. - * @param {string=} required Sets `required` validation error key if the value is not entered. - * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to - * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of - * `required` when you want to data-bind to the `required` attribute. - * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than - * minlength. - * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than - * maxlength. - * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the - * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for - * patterns defined as scope expressions. - * @param {string=} ngChange Angular expression to be executed when input changes due to user - * interaction with the input element. - * - * @example - - - -
- URL: - - Required! - - Not valid url! - text = {{text}}
- myForm.input.$valid = {{myForm.input.$valid}}
- myForm.input.$error = {{myForm.input.$error}}
- myForm.$valid = {{myForm.$valid}}
- myForm.$error.required = {{!!myForm.$error.required}}
- myForm.$error.url = {{!!myForm.$error.url}}
-
-
- - var text = element(by.binding('text')); - var valid = element(by.binding('myForm.input.$valid')); - var input = element(by.model('text')); - - it('should initialize to model', function() { - expect(text.getText()).toContain('http://google.com'); - expect(valid.getText()).toContain('true'); - }); - - it('should be invalid if empty', function() { - input.clear(); - input.sendKeys(''); - - expect(text.getText()).toEqual('text ='); - expect(valid.getText()).toContain('false'); - }); - - it('should be invalid if not url', function() { - input.clear(); - input.sendKeys('box'); - - expect(valid.getText()).toContain('false'); - }); - -
- */ - 'url': urlInputType, - - - /** - * @ngdoc input - * @name input[email] - * - * @description - * Text input with email validation. Sets the `email` validation error key if not a valid email - * address. - * - * @param {string} ngModel Assignable angular expression to data-bind to. - * @param {string=} name Property name of the form under which the control is published. - * @param {string=} required Sets `required` validation error key if the value is not entered. - * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to - * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of - * `required` when you want to data-bind to the `required` attribute. - * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than - * minlength. - * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than - * maxlength. - * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the - * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for - * patterns defined as scope expressions. - * @param {string=} ngChange Angular expression to be executed when input changes due to user - * interaction with the input element. - * - * @example - - - -
- Email: - - Required! - - Not valid email! - text = {{text}}
- myForm.input.$valid = {{myForm.input.$valid}}
- myForm.input.$error = {{myForm.input.$error}}
- myForm.$valid = {{myForm.$valid}}
- myForm.$error.required = {{!!myForm.$error.required}}
- myForm.$error.email = {{!!myForm.$error.email}}
-
-
- - var text = element(by.binding('text')); - var valid = element(by.binding('myForm.input.$valid')); - var input = element(by.model('text')); - - it('should initialize to model', function() { - expect(text.getText()).toContain('me@example.com'); - expect(valid.getText()).toContain('true'); - }); - - it('should be invalid if empty', function() { - input.clear(); - input.sendKeys(''); - expect(text.getText()).toEqual('text ='); - expect(valid.getText()).toContain('false'); - }); - - it('should be invalid if not email', function() { - input.clear(); - input.sendKeys('xxx'); - - expect(valid.getText()).toContain('false'); - }); - -
- */ - 'email': emailInputType, - - - /** - * @ngdoc input - * @name input[radio] - * - * @description - * HTML radio button. - * - * @param {string} ngModel Assignable angular expression to data-bind to. - * @param {string} value The value to which the expression should be set when selected. - * @param {string=} name Property name of the form under which the control is published. - * @param {string=} ngChange Angular expression to be executed when input changes due to user - * interaction with the input element. - * @param {string} ngValue Angular expression which sets the value to which the expression should - * be set when selected. - * - * @example - - - -
- Red
- Green
- Blue
- color = {{color | json}}
-
- Note that `ng-value="specialValue"` sets radio item's value to be the value of `$scope.specialValue`. -
- - it('should change state', function() { - var color = element(by.binding('color')); - - expect(color.getText()).toContain('blue'); - - element.all(by.model('color')).get(0).click(); - - expect(color.getText()).toContain('red'); - }); - -
- */ - 'radio': radioInputType, - - - /** - * @ngdoc input - * @name input[checkbox] - * - * @description - * HTML checkbox. - * - * @param {string} ngModel Assignable angular expression to data-bind to. - * @param {string=} name Property name of the form under which the control is published. - * @param {string=} ngTrueValue The value to which the expression should be set when selected. - * @param {string=} ngFalseValue The value to which the expression should be set when not selected. - * @param {string=} ngChange Angular expression to be executed when input changes due to user - * interaction with the input element. - * - * @example - - - -
- Value1:
- Value2:
- value1 = {{value1}}
- value2 = {{value2}}
-
-
- - it('should change state', function() { - var value1 = element(by.binding('value1')); - var value2 = element(by.binding('value2')); - - expect(value1.getText()).toContain('true'); - expect(value2.getText()).toContain('YES'); - - element(by.model('value1')).click(); - element(by.model('value2')).click(); - - expect(value1.getText()).toContain('false'); - expect(value2.getText()).toContain('NO'); - }); - -
- */ - 'checkbox': checkboxInputType, - - 'hidden': noop, - 'button': noop, - 'submit': noop, - 'reset': noop, - 'file': noop -}; - -// A helper function to call $setValidity and return the value / undefined, -// a pattern that is repeated a lot in the input validation logic. -function validate(ctrl, validatorName, validity, value){ - ctrl.$setValidity(validatorName, validity); - return validity ? value : undefined; -} - - -function addNativeHtml5Validators(ctrl, validatorName, element) { - var validity = element.prop('validity'); - if (isObject(validity)) { - var validator = function(value) { - // Don't overwrite previous validation, don't consider valueMissing to apply (ng-required can - // perform the required validation) - if (!ctrl.$error[validatorName] && (validity.badInput || validity.customError || - validity.typeMismatch) && !validity.valueMissing) { - ctrl.$setValidity(validatorName, false); - return; - } - return value; - }; - ctrl.$parsers.push(validator); - } -} - -function textInputType(scope, element, attr, ctrl, $sniffer, $browser) { - var validity = element.prop('validity'); - var placeholder = element[0].placeholder, noevent = {}; - - // In composition mode, users are still inputing intermediate text buffer, - // hold the listener until composition is done. - // More about composition events: https://developer.mozilla.org/en-US/docs/Web/API/CompositionEvent - if (!$sniffer.android) { - var composing = false; - - element.on('compositionstart', function(data) { - composing = true; - }); - - element.on('compositionend', function() { - composing = false; - listener(); - }); - } - - var listener = function(ev) { - if (composing) return; - var value = element.val(), - event = ev && ev.type; - - // IE (11 and under) seem to emit an 'input' event if the placeholder value changes. - // We don't want to dirty the value when this happens, so we abort here. Unfortunately, - // IE also sends input events for other non-input-related things, (such as focusing on a - // form control), so this change is not entirely enough to solve this. - if (msie && (ev || noevent).type === 'input' && element[0].placeholder !== placeholder) { - placeholder = element[0].placeholder; - return; - } - - // By default we will trim the value - // If the attribute ng-trim exists we will avoid trimming - // e.g. - if (toBoolean(attr.ngTrim || 'T')) { - value = trim(value); - } - - if (ctrl.$viewValue !== value || - // If the value is still empty/falsy, and there is no `required` error, run validators - // again. This enables HTML5 constraint validation errors to affect Angular validation - // even when the first character entered causes an error. - (validity && value === '' && !validity.valueMissing)) { - if (scope.$$phase) { - ctrl.$setViewValue(value, event); - } else { - scope.$apply(function() { - ctrl.$setViewValue(value, event); - }); - } - } - }; - - // if the browser does support "input" event, we are fine - except on IE9 which doesn't fire the - // input event on backspace, delete or cut - if ($sniffer.hasEvent('input')) { - element.on('input', listener); - } else { - var timeout; - - var deferListener = function(ev) { - if (!timeout) { - timeout = $browser.defer(function() { - listener(ev); - timeout = null; - }); - } - }; - - element.on('keydown', function(event) { - var key = event.keyCode; - - // ignore - // command modifiers arrows - if (key === 91 || (15 < key && key < 19) || (37 <= key && key <= 40)) return; - - deferListener(event); - }); - - // if user modifies input value using context menu in IE, we need "paste" and "cut" events to catch it - if ($sniffer.hasEvent('paste')) { - element.on('paste cut', deferListener); - } - } - - // if user paste into input using mouse on older browser - // or form autocomplete on newer browser, we need "change" event to catch it - element.on('change', listener); - - ctrl.$render = function() { - element.val(ctrl.$isEmpty(ctrl.$viewValue) ? '' : ctrl.$viewValue); - }; -} - -function weekParser(isoWeek) { - if(isDate(isoWeek)) { - return isoWeek; - } - - if(isString(isoWeek)) { - WEEK_REGEXP.lastIndex = 0; - var parts = WEEK_REGEXP.exec(isoWeek); - if(parts) { - var year = +parts[1], - week = +parts[2], - firstThurs = getFirstThursdayOfYear(year), - addDays = (week - 1) * 7; - return new Date(year, 0, firstThurs.getDate() + addDays); - } - } - - return NaN; -} - -function createDateParser(regexp, mapping) { - return function(iso) { - var parts, map; - - if(isDate(iso)) { - return iso; - } - - if(isString(iso)) { - regexp.lastIndex = 0; - parts = regexp.exec(iso); - - if(parts) { - parts.shift(); - map = { yyyy: 0, MM: 1, dd: 1, HH: 0, mm: 0 }; - - forEach(parts, function(part, index) { - if(index < mapping.length) { - map[mapping[index]] = +part; - } - }); - - return new Date(map.yyyy, map.MM - 1, map.dd, map.HH, map.mm); - } - } - - return NaN; - }; -} - -function createDateInputType(type, regexp, parseDate, format) { - return function dynamicDateInputType(scope, element, attr, ctrl, $sniffer, $browser, $filter) { - textInputType(scope, element, attr, ctrl, $sniffer, $browser); - - ctrl.$parsers.push(function(value) { - if(ctrl.$isEmpty(value)) { - ctrl.$setValidity(type, true); - return null; - } - - if(regexp.test(value)) { - ctrl.$setValidity(type, true); - return parseDate(value); - } - - ctrl.$setValidity(type, false); - return undefined; - }); - - ctrl.$formatters.push(function(value) { - if(isDate(value)) { - return $filter('date')(value, format); - } - return ''; - }); - - if(attr.min) { - var minValidator = function(value) { - var valid = ctrl.$isEmpty(value) || - (parseDate(value) >= parseDate(attr.min)); - ctrl.$setValidity('min', valid); - return valid ? value : undefined; - }; - - ctrl.$parsers.push(minValidator); - ctrl.$formatters.push(minValidator); - } - - if(attr.max) { - var maxValidator = function(value) { - var valid = ctrl.$isEmpty(value) || - (parseDate(value) <= parseDate(attr.max)); - ctrl.$setValidity('max', valid); - return valid ? value : undefined; - }; - - ctrl.$parsers.push(maxValidator); - ctrl.$formatters.push(maxValidator); - } - }; -} - -function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) { - textInputType(scope, element, attr, ctrl, $sniffer, $browser); - - ctrl.$parsers.push(function(value) { - var empty = ctrl.$isEmpty(value); - if (empty || NUMBER_REGEXP.test(value)) { - ctrl.$setValidity('number', true); - return value === '' ? null : (empty ? value : parseFloat(value)); - } else { - ctrl.$setValidity('number', false); - return undefined; - } - }); - - addNativeHtml5Validators(ctrl, 'number', element); - - ctrl.$formatters.push(function(value) { - return ctrl.$isEmpty(value) ? '' : '' + value; - }); - - if (attr.min) { - var minValidator = function(value) { - var min = parseFloat(attr.min); - return validate(ctrl, 'min', ctrl.$isEmpty(value) || value >= min, value); - }; - - ctrl.$parsers.push(minValidator); - ctrl.$formatters.push(minValidator); - } - - if (attr.max) { - var maxValidator = function(value) { - var max = parseFloat(attr.max); - return validate(ctrl, 'max', ctrl.$isEmpty(value) || value <= max, value); - }; - - ctrl.$parsers.push(maxValidator); - ctrl.$formatters.push(maxValidator); - } - - ctrl.$formatters.push(function(value) { - return validate(ctrl, 'number', ctrl.$isEmpty(value) || isNumber(value), value); - }); -} - -function urlInputType(scope, element, attr, ctrl, $sniffer, $browser) { - textInputType(scope, element, attr, ctrl, $sniffer, $browser); - - var urlValidator = function(value) { - return validate(ctrl, 'url', ctrl.$isEmpty(value) || URL_REGEXP.test(value), value); - }; - - ctrl.$formatters.push(urlValidator); - ctrl.$parsers.push(urlValidator); -} - -function emailInputType(scope, element, attr, ctrl, $sniffer, $browser) { - textInputType(scope, element, attr, ctrl, $sniffer, $browser); - - var emailValidator = function(value) { - return validate(ctrl, 'email', ctrl.$isEmpty(value) || EMAIL_REGEXP.test(value), value); - }; - - ctrl.$formatters.push(emailValidator); - ctrl.$parsers.push(emailValidator); -} - -function radioInputType(scope, element, attr, ctrl) { - // make the name unique, if not defined - if (isUndefined(attr.name)) { - element.attr('name', nextUid()); - } - - var listener = function(ev) { - if (element[0].checked) { - scope.$apply(function() { - ctrl.$setViewValue(attr.value, ev && ev.type); - }); - } - }; - - element.on('click', listener); - - ctrl.$render = function() { - var value = attr.value; - element[0].checked = (value == ctrl.$viewValue); - }; - - attr.$observe('value', ctrl.$render); -} - -function checkboxInputType(scope, element, attr, ctrl) { - var trueValue = attr.ngTrueValue, - falseValue = attr.ngFalseValue; - - if (!isString(trueValue)) trueValue = true; - if (!isString(falseValue)) falseValue = false; - - var listener = function(ev) { - scope.$apply(function() { - ctrl.$setViewValue(element[0].checked, ev && ev.type); - }); - }; - - element.on('click', listener); - - ctrl.$render = function() { - element[0].checked = ctrl.$viewValue; - }; - - // Override the standard `$isEmpty` because a value of `false` means empty in a checkbox. - ctrl.$isEmpty = function(value) { - return value !== trueValue; - }; - - ctrl.$formatters.push(function(value) { - return value === trueValue; - }); - - ctrl.$parsers.push(function(value) { - return value ? trueValue : falseValue; - }); -} - - -/** - * @ngdoc directive - * @name textarea - * @restrict E - * - * @description - * HTML textarea element control with angular data-binding. The data-binding and validation - * properties of this element are exactly the same as those of the - * {@link ng.directive:input input element}. - * - * @param {string} ngModel Assignable angular expression to data-bind to. - * @param {string=} name Property name of the form under which the control is published. - * @param {string=} required Sets `required` validation error key if the value is not entered. - * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to - * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of - * `required` when you want to data-bind to the `required` attribute. - * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than - * minlength. - * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than - * maxlength. - * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the - * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for - * patterns defined as scope expressions. - * @param {string=} ngChange Angular expression to be executed when input changes due to user - * interaction with the input element. - * @param {boolean=} [ngTrim=true] If set to false Angular will not automatically trim the input. - */ - - -/** - * @ngdoc directive - * @name input - * @restrict E - * - * @description - * HTML input element control with angular data-binding. Input control follows HTML5 input types - * and polyfills the HTML5 validation behavior for older browsers. - * - * @param {string} ngModel Assignable angular expression to data-bind to. - * @param {string=} name Property name of the form under which the control is published. - * @param {string=} required Sets `required` validation error key if the value is not entered. - * @param {boolean=} ngRequired Sets `required` attribute if set to true - * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than - * minlength. - * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than - * maxlength. - * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the - * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for - * patterns defined as scope expressions. - * @param {string=} ngChange Angular expression to be executed when input changes due to user - * interaction with the input element. - * - * @example - - - -
-
- User name: - - Required!
- Last name: - - Too short! - - Too long!
-
-
- user = {{user}}
- myForm.userName.$valid = {{myForm.userName.$valid}}
- myForm.userName.$error = {{myForm.userName.$error}}
- myForm.lastName.$valid = {{myForm.lastName.$valid}}
- myForm.lastName.$error = {{myForm.lastName.$error}}
- myForm.$valid = {{myForm.$valid}}
- myForm.$error.required = {{!!myForm.$error.required}}
- myForm.$error.minlength = {{!!myForm.$error.minlength}}
- myForm.$error.maxlength = {{!!myForm.$error.maxlength}}
-
-
- - var user = element(by.binding('{{user}}')); - var userNameValid = element(by.binding('myForm.userName.$valid')); - var lastNameValid = element(by.binding('myForm.lastName.$valid')); - var lastNameError = element(by.binding('myForm.lastName.$error')); - var formValid = element(by.binding('myForm.$valid')); - var userNameInput = element(by.model('user.name')); - var userLastInput = element(by.model('user.last')); - - it('should initialize to model', function() { - expect(user.getText()).toContain('{"name":"guest","last":"visitor"}'); - expect(userNameValid.getText()).toContain('true'); - expect(formValid.getText()).toContain('true'); - }); - - it('should be invalid if empty when required', function() { - userNameInput.clear(); - userNameInput.sendKeys(''); - - expect(user.getText()).toContain('{"last":"visitor"}'); - expect(userNameValid.getText()).toContain('false'); - expect(formValid.getText()).toContain('false'); - }); - - it('should be valid if empty when min length is set', function() { - userLastInput.clear(); - userLastInput.sendKeys(''); - - expect(user.getText()).toContain('{"name":"guest","last":""}'); - expect(lastNameValid.getText()).toContain('true'); - expect(formValid.getText()).toContain('true'); - }); - - it('should be invalid if less than required min length', function() { - userLastInput.clear(); - userLastInput.sendKeys('xx'); - - expect(user.getText()).toContain('{"name":"guest"}'); - expect(lastNameValid.getText()).toContain('false'); - expect(lastNameError.getText()).toContain('minlength'); - expect(formValid.getText()).toContain('false'); - }); - - it('should be invalid if longer than max length', function() { - userLastInput.clear(); - userLastInput.sendKeys('some ridiculously long name'); - - expect(user.getText()).toContain('{"name":"guest"}'); - expect(lastNameValid.getText()).toContain('false'); - expect(lastNameError.getText()).toContain('maxlength'); - expect(formValid.getText()).toContain('false'); - }); - -
- */ -var inputDirective = ['$browser', '$sniffer', '$filter', function($browser, $sniffer, $filter) { - return { - restrict: 'E', - require: ['?ngModel'], - link: function(scope, element, attr, ctrls) { - if (ctrls[0]) { - (inputType[lowercase(attr.type)] || inputType.text)(scope, element, attr, ctrls[0], $sniffer, - $browser, $filter); - } - } - }; -}]; - -var VALID_CLASS = 'ng-valid', - INVALID_CLASS = 'ng-invalid', - PRISTINE_CLASS = 'ng-pristine', - DIRTY_CLASS = 'ng-dirty', - UNTOUCHED_CLASS = 'ng-untouched', - TOUCHED_CLASS = 'ng-touched'; - -/** - * @ngdoc type - * @name ngModel.NgModelController - * - * @property {string} $viewValue Actual string value in the view. - * @property {*} $modelValue The value in the model, that the control is bound to. - * @property {Array.} $parsers Array of functions to execute, as a pipeline, whenever - the control reads value from the DOM. Each function is called, in turn, passing the value - through to the next. The last return value is used to populate the model. - Used to sanitize / convert the value as well as validation. For validation, - the parsers should update the validity state using - {@link ngModel.NgModelController#$setValidity $setValidity()}, - and return `undefined` for invalid values. - - * - * @property {Array.} $formatters Array of functions to execute, as a pipeline, whenever - the model value changes. Each function is called, in turn, passing the value through to the - next. Used to format / convert values for display in the control and validation. - * ```js - * function formatter(value) { - * if (value) { - * return value.toUpperCase(); - * } - * } - * ngModel.$formatters.push(formatter); - * ``` - * - * @property {Object.} $validators A collection of validators that are applied - * whenever the model value changes. The key value within the object refers to the name of the - * validator while the function refers to the validation operation. The validation operation is - * provided with the model value as an argument and must return a true or false value depending - * on the response of that validation. - * - * @property {Array.} $viewChangeListeners Array of functions to execute whenever the - * view value has changed. It is called with no arguments, and its return value is ignored. - * This can be used in place of additional $watches against the model value. - * - * @property {Object} $error An object hash with all errors as keys. - * - * @property {boolean} $untouched True if control has not lost focus yet. - * @property {boolean} $touched True if control has lost focus. - * @property {boolean} $pristine True if user has not interacted with the control yet. - * @property {boolean} $dirty True if user has already interacted with the control. - * @property {boolean} $valid True if there is no error. - * @property {boolean} $invalid True if at least one error on the control. - * - * @description - * - * `NgModelController` provides API for the `ng-model` directive. The controller contains - * services for data-binding, validation, CSS updates, and value formatting and parsing. It - * purposefully does not contain any logic which deals with DOM rendering or listening to - * DOM events. Such DOM related logic should be provided by other directives which make use of - * `NgModelController` for data-binding. - * - * ## Custom Control Example - * This example shows how to use `NgModelController` with a custom control to achieve - * data-binding. Notice how different directives (`contenteditable`, `ng-model`, and `required`) - * collaborate together to achieve the desired result. - * - * Note that `contenteditable` is an HTML5 attribute, which tells the browser to let the element - * contents be edited in place by the user. This will not work on older browsers. - * - * We are using the {@link ng.service:$sce $sce} service here and include the {@link ngSanitize $sanitize} - * module to automatically remove "bad" content like inline event listener (e.g. ``). - * However, as we are using `$sce` the model can still decide to to provide unsafe content if it marks - * that content using the `$sce` service. - * - * - - [contenteditable] { - border: 1px solid black; - background-color: white; - min-height: 20px; - } - - .ng-invalid { - border: 1px solid red; - } - - - - angular.module('customControl', ['ngSanitize']). - directive('contenteditable', ['$sce', function($sce) { - return { - restrict: 'A', // only activate on element attribute - require: '?ngModel', // get a hold of NgModelController - link: function(scope, element, attrs, ngModel) { - if(!ngModel) return; // do nothing if no ng-model - - // Specify how UI should be updated - ngModel.$render = function() { - element.html($sce.getTrustedHtml(ngModel.$viewValue || '')); - }; - - // Listen for change events to enable binding - element.on('blur keyup change', function() { - scope.$apply(read); - }); - read(); // initialize - - // Write data to the model - function read() { - var html = element.html(); - // When we clear the content editable the browser leaves a
behind - // If strip-br attribute is provided then we strip this out - if( attrs.stripBr && html == '
' ) { - html = ''; - } - ngModel.$setViewValue(html); - } - } - }; - }]); -
- -
-
Change me!
- Required! -
- -
-
- - it('should data-bind and become invalid', function() { - if (browser.params.browser == 'safari' || browser.params.browser == 'firefox') { - // SafariDriver can't handle contenteditable - // and Firefox driver can't clear contenteditables very well - return; - } - var contentEditable = element(by.css('[contenteditable]')); - var content = 'Change me!'; - - expect(contentEditable.getText()).toEqual(content); - - contentEditable.clear(); - contentEditable.sendKeys(protractor.Key.BACK_SPACE); - expect(contentEditable.getText()).toEqual(''); - expect(contentEditable.getAttribute('class')).toMatch(/ng-invalid-required/); - }); - - *
- * - * - */ -var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$parse', '$animate', '$timeout', - function($scope, $exceptionHandler, $attr, $element, $parse, $animate, $timeout) { - this.$viewValue = Number.NaN; - this.$modelValue = Number.NaN; - this.$validators = {}; - this.$parsers = []; - this.$formatters = []; - this.$viewChangeListeners = []; - this.$untouched = true; - this.$touched = false; - this.$pristine = true; - this.$dirty = false; - this.$valid = true; - this.$invalid = false; - this.$name = $attr.name; - - - var ngModelGet = $parse($attr.ngModel), - ngModelSet = ngModelGet.assign, - pendingDebounce = null, - ctrl = this; - - if (!ngModelSet) { - throw minErr('ngModel')('nonassign', "Expression '{0}' is non-assignable. Element: {1}", - $attr.ngModel, startingTag($element)); - } - - /** - * @ngdoc method - * @name ngModel.NgModelController#$render - * - * @description - * Called when the view needs to be updated. It is expected that the user of the ng-model - * directive will implement this method. - */ - this.$render = noop; - - /** - * @ngdoc method - * @name ngModel.NgModelController#$isEmpty - * - * @description - * This is called when we need to determine if the value of the input is empty. - * - * For instance, the required directive does this to work out if the input has data or not. - * The default `$isEmpty` function checks whether the value is `undefined`, `''`, `null` or `NaN`. - * - * You can override this for input directives whose concept of being empty is different to the - * default. The `checkboxInputType` directive does this because in its case a value of `false` - * implies empty. - * - * @param {*} value Reference to check. - * @returns {boolean} True if `value` is empty. - */ - this.$isEmpty = function(value) { - return isUndefined(value) || value === '' || value === null || value !== value; - }; - - var parentForm = $element.inheritedData('$formController') || nullFormCtrl, - invalidCount = 0, // used to easily determine if we are valid - $error = this.$error = {}; // keep invalid keys here - - - // Setup initial state of the control - $element - .addClass(PRISTINE_CLASS) - .addClass(UNTOUCHED_CLASS); - toggleValidCss(true); - - // convenience method for easy toggling of classes - function toggleValidCss(isValid, validationErrorKey) { - validationErrorKey = validationErrorKey ? '-' + snake_case(validationErrorKey, '-') : ''; - $animate.removeClass($element, (isValid ? INVALID_CLASS : VALID_CLASS) + validationErrorKey); - $animate.addClass($element, (isValid ? VALID_CLASS : INVALID_CLASS) + validationErrorKey); - } - - /** - * @ngdoc method - * @name ngModel.NgModelController#$setValidity - * - * @description - * Change the validity state, and notifies the form when the control changes validity. (i.e. it - * does not notify form if given validator is already marked as invalid). - * - * This method can be called within $parsers/$formatters. However, if possible, please use the - * `ngModel.$validators` pipeline which is designed to handle validations with true/false values. - * - * @param {string} validationErrorKey Name of the validator. the `validationErrorKey` will assign - * to `$error[validationErrorKey]=!isValid` so that it is available for data-binding. - * The `validationErrorKey` should be in camelCase and will get converted into dash-case - * for class name. Example: `myError` will result in `ng-valid-my-error` and `ng-invalid-my-error` - * class and can be bound to as `{{someForm.someControl.$error.myError}}` . - * @param {boolean} isValid Whether the current state is valid (true) or invalid (false). - */ - this.$setValidity = function(validationErrorKey, isValid) { - // Purposeful use of ! here to cast isValid to boolean in case it is undefined - // jshint -W018 - if ($error[validationErrorKey] === !isValid) return; - // jshint +W018 - - if (isValid) { - if ($error[validationErrorKey]) invalidCount--; - if (!invalidCount) { - toggleValidCss(true); - ctrl.$valid = true; - ctrl.$invalid = false; - } - } else { - toggleValidCss(false); - ctrl.$invalid = true; - ctrl.$valid = false; - invalidCount++; - } - - $error[validationErrorKey] = !isValid; - toggleValidCss(isValid, validationErrorKey); - - parentForm.$setValidity(validationErrorKey, isValid, ctrl); - }; - - /** - * @ngdoc method - * @name ngModel.NgModelController#$setPristine - * - * @description - * Sets the control to its pristine state. - * - * This method can be called to remove the 'ng-dirty' class and set the control to its pristine - * state (ng-pristine class). A model is considered to be pristine when the model has not been changed - * from when first compiled within then form. - */ - this.$setPristine = function () { - ctrl.$dirty = false; - ctrl.$pristine = true; - $animate.removeClass($element, DIRTY_CLASS); - $animate.addClass($element, PRISTINE_CLASS); - }; - - /** - * @ngdoc method - * @name ngModel.NgModelController#$setUntouched - * - * @description - * Sets the control to its untouched state. - * - * This method can be called to remove the 'ng-touched' class and set the control to its - * untouched state (ng-untouched class). Upon compilation, a model is set as untouched - * by default, however this function can be used to restore that state if the model has - * already been touched by the user. - */ - this.$setUntouched = function() { - ctrl.$touched = false; - ctrl.$untouched = true; - $animate.setClass($element, UNTOUCHED_CLASS, TOUCHED_CLASS); - }; - - /** - * @ngdoc method - * @name ngModel.NgModelController#$setTouched - * - * @description - * Sets the control to its touched state. - * - * This method can be called to remove the 'ng-untouched' class and set the control to its - * touched state (ng-touched class). A model is considered to be touched when the user has - * first interacted (focussed) on the model input element and then shifted focus away (blurred) - * from the input element. - */ - this.$setTouched = function() { - ctrl.$touched = true; - ctrl.$untouched = false; - $animate.setClass($element, TOUCHED_CLASS, UNTOUCHED_CLASS); - }; - - /** - * @ngdoc method - * @name ngModel.NgModelController#$rollbackViewValue - * - * @description - * Cancel an update and reset the input element's value to prevent an update to the `$modelValue`, - * which may be caused by a pending debounced event or because the input is waiting for a some - * future event. - * - * If you have an input that uses `ng-model-options` to set up debounced events or events such - * as blur you can have a situation where there is a period when the `$viewValue` - * is out of synch with the ngModel's `$modelValue`. - * - * In this case, you can run into difficulties if you try to update the ngModel's `$modelValue` - * programmatically before these debounced/future events have resolved/occurred, because Angular's - * dirty checking mechanism is not able to tell whether the model has actually changed or not. - * - * The `$rollbackViewValue()` method should be called before programmatically changing the model of an - * input which may have such events pending. This is important in order to make sure that the - * input field will be updated with the new model value and any pending operations are cancelled. - * - * - * - * angular.module('cancel-update-example', []) - * - * .controller('CancelUpdateCtrl', function($scope) { - * $scope.resetWithCancel = function (e) { - * if (e.keyCode == 27) { - * $scope.myForm.myInput1.$rollbackViewValue(); - * $scope.myValue = ''; - * } - * }; - * $scope.resetWithoutCancel = function (e) { - * if (e.keyCode == 27) { - * $scope.myValue = ''; - * } - * }; - * }); - * - * - *
- *

Try typing something in each input. See that the model only updates when you - * blur off the input. - *

- *

Now see what happens if you start typing then press the Escape key

- * - *
- *

With $rollbackViewValue()

- *
- * myValue: "{{ myValue }}" - * - *

Without $rollbackViewValue()

- *
- * myValue: "{{ myValue }}" - *
- *
- *
- *
- */ - this.$rollbackViewValue = function() { - $timeout.cancel(pendingDebounce); - ctrl.$viewValue = ctrl.$$lastCommittedViewValue; - ctrl.$render(); - }; - - /** - * @ngdoc method - * @name ngModel.NgModelController#$validate - * - * @description - * Runs each of the registered validations set on the $validators object. - */ - this.$validate = function() { - this.$$runValidators(ctrl.$modelValue, ctrl.$viewValue); - }; - - this.$$runValidators = function(modelValue, viewValue) { - forEach(ctrl.$validators, function(fn, name) { - ctrl.$setValidity(name, fn(modelValue, viewValue)); - }); - }; - - /** - * @ngdoc method - * @name ngModel.NgModelController#$commitViewValue - * - * @description - * Commit a pending update to the `$modelValue`. - * - * Updates may be pending by a debounced event or because the input is waiting for a some future - * event defined in `ng-model-options`. this method is rarely needed as `NgModelController` - * usually handles calling this in response to input events. - */ - this.$commitViewValue = function() { - var viewValue = ctrl.$viewValue; - - $timeout.cancel(pendingDebounce); - if (ctrl.$$lastCommittedViewValue === viewValue) { - return; - } - ctrl.$$lastCommittedViewValue = viewValue; - - // change to dirty - if (ctrl.$pristine) { - ctrl.$dirty = true; - ctrl.$pristine = false; - $animate.removeClass($element, PRISTINE_CLASS); - $animate.addClass($element, DIRTY_CLASS); - parentForm.$setDirty(); - } - - var modelValue = viewValue; - forEach(ctrl.$parsers, function(fn) { - modelValue = fn(modelValue); - }); - - if (ctrl.$modelValue !== modelValue && - (isUndefined(ctrl.$$invalidModelValue) || ctrl.$$invalidModelValue != modelValue)) { - - ctrl.$$runValidators(modelValue, viewValue); - ctrl.$modelValue = ctrl.$valid ? modelValue : undefined; - ctrl.$$invalidModelValue = ctrl.$valid ? undefined : modelValue; - - ngModelSet($scope, ctrl.$modelValue); - forEach(ctrl.$viewChangeListeners, function(listener) { - try { - listener(); - } catch(e) { - $exceptionHandler(e); - } - }); - } - }; - - /** - * @ngdoc method - * @name ngModel.NgModelController#$setViewValue - * - * @description - * Update the view value. - * - * This method should be called when the view value changes, typically from within a DOM event handler. - * For example {@link ng.directive:input input} and - * {@link ng.directive:select select} directives call it. - * - * It will update the $viewValue, then pass this value through each of the functions in `$parsers`, - * which includes any validators. The value that comes out of this `$parsers` pipeline, be applied to - * `$modelValue` and the **expression** specified in the `ng-model` attribute. - * - * Lastly, all the registered change listeners, in the `$viewChangeListeners` list, are called. - * - * In case the {@link ng.directive:ngModelOptions ngModelOptions} directive is used with `updateOn` - * and the `default` trigger is not listed, all those actions will remain pending until one of the - * `updateOn` events is triggered on the DOM element. - * All these actions will be debounced if the {@link ng.directive:ngModelOptions ngModelOptions} - * directive is used with a custom debounce for this particular event. - * - * Note that calling this function does not trigger a `$digest`. - * - * @param {string} value Value from the view. - * @param {string} trigger Event that triggered the update. - */ - this.$setViewValue = function(value, trigger) { - ctrl.$viewValue = value; - if (!ctrl.$options || ctrl.$options.updateOnDefault) { - ctrl.$$debounceViewValueCommit(trigger); - } - }; - - this.$$debounceViewValueCommit = function(trigger) { - var debounceDelay = 0, - options = ctrl.$options, - debounce; - - if(options && isDefined(options.debounce)) { - debounce = options.debounce; - if(isNumber(debounce)) { - debounceDelay = debounce; - } else if(isNumber(debounce[trigger])) { - debounceDelay = debounce[trigger]; - } else if (isNumber(debounce['default'])) { - debounceDelay = debounce['default']; - } - } - - $timeout.cancel(pendingDebounce); - if (debounceDelay) { - pendingDebounce = $timeout(function() { - ctrl.$commitViewValue(); - }, debounceDelay); - } else { - ctrl.$commitViewValue(); - } - }; - - // model -> value - $scope.$watch(function ngModelWatch() { - var modelValue = ngModelGet($scope); - - // if scope model value and ngModel value are out of sync - if (ctrl.$modelValue !== modelValue && - (isUndefined(ctrl.$$invalidModelValue) || ctrl.$$invalidModelValue != modelValue)) { - - var formatters = ctrl.$formatters, - idx = formatters.length; - - var viewValue = modelValue; - while(idx--) { - viewValue = formatters[idx](viewValue); - } - - ctrl.$$runValidators(modelValue, viewValue); - ctrl.$modelValue = ctrl.$valid ? modelValue : undefined; - ctrl.$$invalidModelValue = ctrl.$valid ? undefined : modelValue; - - if (ctrl.$viewValue !== viewValue) { - ctrl.$viewValue = ctrl.$$lastCommittedViewValue = viewValue; - ctrl.$render(); - } - } - - return modelValue; - }); -}]; - - -/** - * @ngdoc directive - * @name ngModel - * - * @element input - * - * @description - * The `ngModel` directive binds an `input`,`select`, `textarea` (or custom form control) to a - * property on the scope using {@link ngModel.NgModelController NgModelController}, - * which is created and exposed by this directive. - * - * `ngModel` is responsible for: - * - * - Binding the view into the model, which other directives such as `input`, `textarea` or `select` - * require. - * - Providing validation behavior (i.e. required, number, email, url). - * - Keeping the state of the control (valid/invalid, dirty/pristine, touched/untouched, validation errors). - * - Setting related css classes on the element (`ng-valid`, `ng-invalid`, `ng-dirty`, `ng-pristine`, `ng-touched`, `ng-untouched`) including animations. - * - Registering the control with its parent {@link ng.directive:form form}. - * - * Note: `ngModel` will try to bind to the property given by evaluating the expression on the - * current scope. If the property doesn't already exist on this scope, it will be created - * implicitly and added to the scope. - * - * For best practices on using `ngModel`, see: - * - * - [https://github.com/angular/angular.js/wiki/Understanding-Scopes] - * - * For basic examples, how to use `ngModel`, see: - * - * - {@link ng.directive:input input} - * - {@link input[text] text} - * - {@link input[checkbox] checkbox} - * - {@link input[radio] radio} - * - {@link input[number] number} - * - {@link input[email] email} - * - {@link input[url] url} - * - {@link input[date] date} - * - {@link input[dateTimeLocal] dateTimeLocal} - * - {@link input[time] time} - * - {@link input[month] month} - * - {@link input[week] week} - * - {@link ng.directive:select select} - * - {@link ng.directive:textarea textarea} - * - * # CSS classes - * The following CSS classes are added and removed on the associated input/select/textarea element - * depending on the validity of the model. - * - * - `ng-valid` is set if the model is valid. - * - `ng-invalid` is set if the model is invalid. - * - `ng-pristine` is set if the model is pristine. - * - `ng-dirty` is set if the model is dirty. - * - * Keep in mind that ngAnimate can detect each of these classes when added and removed. - * - * ## Animation Hooks - * - * Animations within models are triggered when any of the associated CSS classes are added and removed - * on the input element which is attached to the model. These classes are: `.ng-pristine`, `.ng-dirty`, - * `.ng-invalid` and `.ng-valid` as well as any other validations that are performed on the model itself. - * The animations that are triggered within ngModel are similar to how they work in ngClass and - * animations can be hooked into using CSS transitions, keyframes as well as JS animations. - * - * The following example shows a simple way to utilize CSS transitions to style an input element - * that has been rendered as invalid after it has been validated: - * - *
- * //be sure to include ngAnimate as a module to hook into more
- * //advanced animations
- * .my-input {
- *   transition:0.5s linear all;
- *   background: white;
- * }
- * .my-input.ng-invalid {
- *   background: red;
- *   color:white;
- * }
- * 
- * - * @example - * - - - - Update input to see transitions when valid/invalid. - Integer is a valid value. -
- -
-
- *
- */ -var ngModelDirective = function() { - return { - require: ['ngModel', '^?form', '^?ngModelOptions'], - controller: NgModelController, - link: { - pre: function(scope, element, attr, ctrls) { - // Pass the ng-model-options to the ng-model controller - if (ctrls[2]) { - ctrls[0].$options = ctrls[2].$options; - } - - // notify others, especially parent forms - - var modelCtrl = ctrls[0], - formCtrl = ctrls[1] || nullFormCtrl; - - formCtrl.$addControl(modelCtrl); - - scope.$on('$destroy', function() { - formCtrl.$removeControl(modelCtrl); - }); - }, - post: function(scope, element, attr, ctrls) { - var modelCtrl = ctrls[0]; - if (modelCtrl.$options && modelCtrl.$options.updateOn) { - element.on(modelCtrl.$options.updateOn, function(ev) { - scope.$apply(function() { - modelCtrl.$$debounceViewValueCommit(ev && ev.type); - }); - }); - } - - element.on('blur', function(ev) { - scope.$apply(function() { - modelCtrl.$setTouched(); - }); - }); - } - } - }; -}; - - -/** - * @ngdoc directive - * @name ngChange - * - * @description - * Evaluate the given expression when the user changes the input. - * The expression is evaluated immediately, unlike the JavaScript onchange event - * which only triggers at the end of a change (usually, when the user leaves the - * form element or presses the return key). - * The expression is not evaluated when the value change is coming from the model. - * - * Note, this directive requires `ngModel` to be present. - * - * @element input - * @param {expression} ngChange {@link guide/expression Expression} to evaluate upon change - * in input value. - * - * @example - * - * - * - *
- * - * - *
- * debug = {{confirmed}}
- * counter = {{counter}}
- *
- *
- * - * var counter = element(by.binding('counter')); - * var debug = element(by.binding('confirmed')); - * - * it('should evaluate the expression if changing from view', function() { - * expect(counter.getText()).toContain('0'); - * - * element(by.id('ng-change-example1')).click(); - * - * expect(counter.getText()).toContain('1'); - * expect(debug.getText()).toContain('true'); - * }); - * - * it('should not evaluate the expression if changing from model', function() { - * element(by.id('ng-change-example2')).click(); - - * expect(counter.getText()).toContain('0'); - * expect(debug.getText()).toContain('true'); - * }); - * - *
- */ -var ngChangeDirective = valueFn({ - require: 'ngModel', - link: function(scope, element, attr, ctrl) { - ctrl.$viewChangeListeners.push(function() { - scope.$eval(attr.ngChange); - }); - } -}); - - -var requiredDirective = function() { - return { - require: '?ngModel', - link: function(scope, elm, attr, ctrl) { - if (!ctrl) return; - attr.required = true; // force truthy in case we are on non input element - - ctrl.$validators.required = function(modelValue, viewValue) { - return !attr.required || !ctrl.$isEmpty(viewValue); - }; - - attr.$observe('required', function() { - ctrl.$validate(); - }); - } - }; -}; - - -var patternDirective = function() { - return { - require: '?ngModel', - link: function(scope, elm, attr, ctrl) { - if (!ctrl) return; - - var regexp, patternExp = attr.ngPattern || attr.pattern; - attr.$observe('pattern', function(regex) { - if(isString(regex) && regex.length > 0) { - regex = new RegExp(regex); - } - - if (regex && !regex.test) { - throw minErr('ngPattern')('noregexp', - 'Expected {0} to be a RegExp but was {1}. Element: {2}', patternExp, - regex, startingTag(elm)); - } - - regexp = regex || undefined; - ctrl.$validate(); - }); - - ctrl.$validators.pattern = function(value) { - return ctrl.$isEmpty(value) || isUndefined(regexp) || regexp.test(value); - }; - } - }; -}; - - -var maxlengthDirective = function() { - return { - require: '?ngModel', - link: function(scope, elm, attr, ctrl) { - if (!ctrl) return; - - var maxlength = 0; - attr.$observe('maxlength', function(value) { - maxlength = int(value) || 0; - ctrl.$validate(); - }); - ctrl.$validators.maxlength = function(value) { - return ctrl.$isEmpty(value) || value.length <= maxlength; - }; - } - }; -}; - -var minlengthDirective = function() { - return { - require: '?ngModel', - link: function(scope, elm, attr, ctrl) { - if (!ctrl) return; - - var minlength = 0; - attr.$observe('minlength', function(value) { - minlength = int(value) || 0; - ctrl.$validate(); - }); - ctrl.$validators.minlength = function(value) { - return ctrl.$isEmpty(value) || value.length >= minlength; - }; - } - }; -}; - - -/** - * @ngdoc directive - * @name ngList - * - * @description - * Text input that converts between a delimited string and an array of strings. The delimiter - * can be a fixed string (by default a comma) or a regular expression. - * - * @element input - * @param {string=} ngList optional delimiter that should be used to split the value. If - * specified in form `/something/` then the value will be converted into a regular expression. - * - * @example - - - -
- List: - - Required! -
- names = {{names}}
- myForm.namesInput.$valid = {{myForm.namesInput.$valid}}
- myForm.namesInput.$error = {{myForm.namesInput.$error}}
- myForm.$valid = {{myForm.$valid}}
- myForm.$error.required = {{!!myForm.$error.required}}
-
-
- - var listInput = element(by.model('names')); - var names = element(by.binding('{{names}}')); - var valid = element(by.binding('myForm.namesInput.$valid')); - var error = element(by.css('span.error')); - - it('should initialize to model', function() { - expect(names.getText()).toContain('["igor","misko","vojta"]'); - expect(valid.getText()).toContain('true'); - expect(error.getCssValue('display')).toBe('none'); - }); - - it('should be invalid if empty', function() { - listInput.clear(); - listInput.sendKeys(''); - - expect(names.getText()).toContain(''); - expect(valid.getText()).toContain('false'); - expect(error.getCssValue('display')).not.toBe('none'); }); - -
- */ -var ngListDirective = function() { - return { - require: 'ngModel', - link: function(scope, element, attr, ctrl) { - var match = /\/(.*)\//.exec(attr.ngList), - separator = match && new RegExp(match[1]) || attr.ngList || ','; - - var parse = function(viewValue) { - // If the viewValue is invalid (say required but empty) it will be `undefined` - if (isUndefined(viewValue)) return; - - var list = []; - - if (viewValue) { - forEach(viewValue.split(separator), function(value) { - if (value) list.push(trim(value)); - }); - } - - return list; - }; - - ctrl.$parsers.push(parse); - ctrl.$formatters.push(function(value) { - if (isArray(value)) { - return value.join(', '); - } - - return undefined; - }); - - // Override the standard $isEmpty because an empty array means the input is empty. - ctrl.$isEmpty = function(value) { - return !value || !value.length; - }; - } - }; -}; - - -var CONSTANT_VALUE_REGEXP = /^(true|false|\d+)$/; -/** - * @ngdoc directive - * @name ngValue - * - * @description - * Binds the given expression to the value of `input[select]` or `input[radio]`, so - * that when the element is selected, the `ngModel` of that element is set to the - * bound value. - * - * `ngValue` is useful when dynamically generating lists of radio buttons using `ng-repeat`, as - * shown below. - * - * @element input - * @param {string=} ngValue angular expression, whose value will be bound to the `value` attribute - * of the `input` element - * - * @example - - - -
-

Which is your favorite?

- -
You chose {{my.favorite}}
-
-
- - var favorite = element(by.binding('my.favorite')); - - it('should initialize to model', function() { - expect(favorite.getText()).toContain('unicorns'); - }); - it('should bind the values to the inputs', function() { - element.all(by.model('my.favorite')).get(0).click(); - expect(favorite.getText()).toContain('pizza'); - }); - -
- */ -var ngValueDirective = function() { - return { - priority: 100, - compile: function(tpl, tplAttr) { - if (CONSTANT_VALUE_REGEXP.test(tplAttr.ngValue)) { - return function ngValueConstantLink(scope, elm, attr) { - attr.$set('value', scope.$eval(attr.ngValue)); - }; - } else { - return function ngValueLink(scope, elm, attr) { - scope.$watch(attr.ngValue, function valueWatchAction(value) { - attr.$set('value', value); - }); - }; - } - } - }; -}; - -/** - * @ngdoc directive - * @name ngModelOptions - * - * @description - * Allows tuning how model updates are done. Using `ngModelOptions` you can specify a custom list of - * events that will trigger a model update and/or a debouncing delay so that the actual update only - * takes place when a timer expires; this timer will be reset after another change takes place. - * - * Given the nature of `ngModelOptions`, the value displayed inside input fields in the view might - * be different than the value in the actual model. This means that if you update the model you - * should also invoke {@link ngModel.NgModelController `$rollbackViewValue`} on the relevant input field in - * order to make sure it is synchronized with the model and that any debounced action is canceled. - * - * The easiest way to reference the control's {@link ngModel.NgModelController `$rollbackViewValue`} - * method is by making sure the input is placed inside a form that has a `name` attribute. This is - * important because `form` controllers are published to the related scope under the name in their - * `name` attribute. - * - * Any pending changes will take place immediately when an enclosing form is submitted via the - * `submit` event. Note that `ngClick` events will occur before the model is updated. Use `ngSubmit` - * to have access to the updated model. - * - * @param {Object} ngModelOptions options to apply to the current model. Valid keys are: - * - `updateOn`: string specifying which event should be the input bound to. You can set several - * events using an space delimited list. There is a special event called `default` that - * matches the default events belonging of the control. - * - `debounce`: integer value which contains the debounce model update value in milliseconds. A - * value of 0 triggers an immediate update. If an object is supplied instead, you can specify a - * custom value for each event. For example: - * `ngModelOptions="{ updateOn: 'default blur', debounce: {'default': 500, 'blur': 0} }"` - * - * @example - - The following example shows how to override immediate updates. Changes on the inputs within the - form will update the model only when the control loses focus (blur event). If `escape` key is - pressed while the input field is focused, the value is reset to the value in the current model. - - - -
-
- Name: -
- - Other data: -
-
-
user.name = 
-
-
- - function Ctrl($scope) { - $scope.user = { name: 'say', data: '' }; - - $scope.cancel = function (e) { - if (e.keyCode == 27) { - $scope.userForm.userName.$rollbackViewValue(); - } - }; - } - - - var model = element(by.binding('user.name')); - var input = element(by.model('user.name')); - var other = element(by.model('user.data')); - - it('should allow custom events', function() { - input.sendKeys(' hello'); - input.click(); - expect(model.getText()).toEqual('say'); - other.click(); - expect(model.getText()).toEqual('say hello'); - }); - - it('should $rollbackViewValue when model changes', function() { - input.sendKeys(' hello'); - expect(input.getAttribute('value')).toEqual('say hello'); - input.sendKeys(protractor.Key.ESCAPE); - expect(input.getAttribute('value')).toEqual('say'); - other.click(); - expect(model.getText()).toEqual('say'); - }); - -
- - This one shows how to debounce model changes. Model will be updated only 1 sec after last change. - If the `Clear` button is pressed, any debounced action is canceled and the value becomes empty. - - - -
-
- Name: - -
-
-
user.name = 
-
-
- - function Ctrl($scope) { - $scope.user = { name: 'say' }; - } - -
- */ -var ngModelOptionsDirective = function() { - return { - controller: ['$scope', '$attrs', function($scope, $attrs) { - var that = this; - this.$options = $scope.$eval($attrs.ngModelOptions); - // Allow adding/overriding bound events - if (this.$options.updateOn !== undefined) { - this.$options.updateOnDefault = false; - // extract "default" pseudo-event from list of events that can trigger a model update - this.$options.updateOn = trim(this.$options.updateOn.replace(DEFAULT_REGEXP, function() { - that.$options.updateOnDefault = true; - return ' '; - })); - } else { - this.$options.updateOnDefault = true; - } - }] - }; -}; - -/** - * @ngdoc directive - * @name ngBind - * @restrict AC - * - * @description - * The `ngBind` attribute tells Angular to replace the text content of the specified HTML element - * with the value of a given expression, and to update the text content when the value of that - * expression changes. - * - * Typically, you don't use `ngBind` directly, but instead you use the double curly markup like - * `{{ expression }}` which is similar but less verbose. - * - * It is preferable to use `ngBind` instead of `{{ expression }}` when a template is momentarily - * displayed by the browser in its raw state before Angular compiles it. Since `ngBind` is an - * element attribute, it makes the bindings invisible to the user while the page is loading. - * - * An alternative solution to this problem would be using the - * {@link ng.directive:ngCloak ngCloak} directive. - * - * - * @element ANY - * @param {expression} ngBind {@link guide/expression Expression} to evaluate. - * - * @example - * Enter a name in the Live Preview text box; the greeting below the text box changes instantly. - - - -
- Enter name:
- Hello ! -
-
- - it('should check ng-bind', function() { - var nameInput = element(by.model('name')); - - expect(element(by.binding('name')).getText()).toBe('Whirled'); - nameInput.clear(); - nameInput.sendKeys('world'); - expect(element(by.binding('name')).getText()).toBe('world'); - }); - -
- */ -var ngBindDirective = ngDirective({ - compile: function(templateElement) { - templateElement.addClass('ng-binding'); - return function (scope, element, attr) { - element.data('$binding', attr.ngBind); - scope.$watch(attr.ngBind, function ngBindWatchAction(value) { - // We are purposefully using == here rather than === because we want to - // catch when value is "null or undefined" - // jshint -W041 - element.text(value == undefined ? '' : value); - }); - }; - } -}); - - -/** - * @ngdoc directive - * @name ngBindTemplate - * - * @description - * The `ngBindTemplate` directive specifies that the element - * text content should be replaced with the interpolation of the template - * in the `ngBindTemplate` attribute. - * Unlike `ngBind`, the `ngBindTemplate` can contain multiple `{{` `}}` - * expressions. This directive is needed since some HTML elements - * (such as TITLE and OPTION) cannot contain SPAN elements. - * - * @element ANY - * @param {string} ngBindTemplate template of form - * {{ expression }} to eval. - * - * @example - * Try it here: enter text in text box and watch the greeting change. - - - -
- Salutation:
- Name:
-

-       
-
- - it('should check ng-bind', function() { - var salutationElem = element(by.binding('salutation')); - var salutationInput = element(by.model('salutation')); - var nameInput = element(by.model('name')); - - expect(salutationElem.getText()).toBe('Hello World!'); - - salutationInput.clear(); - salutationInput.sendKeys('Greetings'); - nameInput.clear(); - nameInput.sendKeys('user'); - - expect(salutationElem.getText()).toBe('Greetings user!'); - }); - -
- */ -var ngBindTemplateDirective = ['$interpolate', function($interpolate) { - return function(scope, element, attr) { - // TODO: move this to scenario runner - var interpolateFn = $interpolate(element.attr(attr.$attr.ngBindTemplate)); - element.addClass('ng-binding').data('$binding', interpolateFn); - attr.$observe('ngBindTemplate', function(value) { - element.text(value); - }); - }; -}]; - - -/** - * @ngdoc directive - * @name ngBindHtml - * - * @description - * Creates a binding that will innerHTML the result of evaluating the `expression` into the current - * element in a secure way. By default, the innerHTML-ed content will be sanitized using the {@link - * ngSanitize.$sanitize $sanitize} service. To utilize this functionality, ensure that `$sanitize` - * is available, for example, by including {@link ngSanitize} in your module's dependencies (not in - * core Angular.) You may also bypass sanitization for values you know are safe. To do so, bind to - * an explicitly trusted value via {@link ng.$sce#trustAsHtml $sce.trustAsHtml}. See the example - * under {@link ng.$sce#Example Strict Contextual Escaping (SCE)}. - * - * Note: If a `$sanitize` service is unavailable and the bound value isn't explicitly trusted, you - * will have an exception (instead of an exploit.) - * - * @element ANY - * @param {expression} ngBindHtml {@link guide/expression Expression} to evaluate. - * - * @example - Try it here: enter text in text box and watch the greeting change. - - - -
-

-
-
- - - angular.module('ngBindHtmlExample', ['ngSanitize']) - - .controller('ngBindHtmlCtrl', ['$scope', function ngBindHtmlCtrl($scope) { - $scope.myHTML = - 'I am an HTMLstring with links! and other stuff'; - }]); - - - - it('should check ng-bind-html', function() { - expect(element(by.binding('myHTML')).getText()).toBe( - 'I am an HTMLstring with links! and other stuff'); - }); - -
- */ -var ngBindHtmlDirective = ['$sce', '$parse', function($sce, $parse) { - return function(scope, element, attr) { - element.addClass('ng-binding').data('$binding', attr.ngBindHtml); - - var parsed = $parse(attr.ngBindHtml); - function getStringValue() { - var value = parsed(scope); - getStringValue.$$unwatch = parsed.$$unwatch; - return (value || '').toString(); - } - - scope.$watch(getStringValue, function ngBindHtmlWatchAction(value) { - element.html($sce.getTrustedHtml(parsed(scope)) || ''); - }); - }; -}]; - -function classDirective(name, selector) { - name = 'ngClass' + name; - return ['$animate', function($animate) { - return { - restrict: 'AC', - link: function(scope, element, attr) { - var oldVal; - - scope.$watch(attr[name], ngClassWatchAction, true); - - attr.$observe('class', function(value) { - ngClassWatchAction(scope.$eval(attr[name])); - }); - - - if (name !== 'ngClass') { - scope.$watch('$index', function($index, old$index) { - // jshint bitwise: false - var mod = $index & 1; - if (mod !== (old$index & 1)) { - var classes = arrayClasses(scope.$eval(attr[name])); - mod === selector ? - addClasses(classes) : - removeClasses(classes); - } - }); - } - - function addClasses(classes) { - var newClasses = digestClassCounts(classes, 1); - attr.$addClass(newClasses); - } - - function removeClasses(classes) { - var newClasses = digestClassCounts(classes, -1); - attr.$removeClass(newClasses); - } - - function digestClassCounts (classes, count) { - var classCounts = element.data('$classCounts') || {}; - var classesToUpdate = []; - forEach(classes, function (className) { - if (count > 0 || classCounts[className]) { - classCounts[className] = (classCounts[className] || 0) + count; - if (classCounts[className] === +(count > 0)) { - classesToUpdate.push(className); - } - } - }); - element.data('$classCounts', classCounts); - return classesToUpdate.join(' '); - } - - function updateClasses (oldClasses, newClasses) { - var toAdd = arrayDifference(newClasses, oldClasses); - var toRemove = arrayDifference(oldClasses, newClasses); - toRemove = digestClassCounts(toRemove, -1); - toAdd = digestClassCounts(toAdd, 1); - - if (toAdd.length === 0) { - $animate.removeClass(element, toRemove); - } else if (toRemove.length === 0) { - $animate.addClass(element, toAdd); - } else { - $animate.setClass(element, toAdd, toRemove); - } - } - - function ngClassWatchAction(newVal) { - if (selector === true || scope.$index % 2 === selector) { - var newClasses = arrayClasses(newVal || []); - if (!oldVal) { - addClasses(newClasses); - } else if (!equals(newVal,oldVal)) { - var oldClasses = arrayClasses(oldVal); - updateClasses(oldClasses, newClasses); - } - } - oldVal = shallowCopy(newVal); - } - } - }; - - function arrayDifference(tokens1, tokens2) { - var values = []; - - outer: - for(var i = 0; i < tokens1.length; i++) { - var token = tokens1[i]; - for(var j = 0; j < tokens2.length; j++) { - if(token == tokens2[j]) continue outer; - } - values.push(token); - } - return values; - } - - function arrayClasses (classVal) { - if (isArray(classVal)) { - return classVal; - } else if (isString(classVal)) { - return classVal.split(' '); - } else if (isObject(classVal)) { - var classes = [], i = 0; - forEach(classVal, function(v, k) { - if (v) { - classes = classes.concat(k.split(' ')); - } - }); - return classes; - } - return classVal; - } - }]; -} - -/** - * @ngdoc directive - * @name ngClass - * @restrict AC - * - * @description - * The `ngClass` directive allows you to dynamically set CSS classes on an HTML element by databinding - * an expression that represents all classes to be added. - * - * The directive operates in three different ways, depending on which of three types the expression - * evaluates to: - * - * 1. If the expression evaluates to a string, the string should be one or more space-delimited class - * names. - * - * 2. If the expression evaluates to an array, each element of the array should be a string that is - * one or more space-delimited class names. - * - * 3. If the expression evaluates to an object, then for each key-value pair of the - * object with a truthy value the corresponding key is used as a class name. - * - * The directive won't add duplicate classes if a particular class was already set. - * - * When the expression changes, the previously added classes are removed and only then the - * new classes are added. - * - * @animations - * add - happens just before the class is applied to the element - * remove - happens just before the class is removed from the element - * - * @element ANY - * @param {expression} ngClass {@link guide/expression Expression} to eval. The result - * of the evaluation can be a string representing space delimited class - * names, an array, or a map of class names to boolean values. In the case of a map, the - * names of the properties whose values are truthy will be added as css classes to the - * element. - * - * @example Example that demonstrates basic bindings via ngClass directive. - - -

Map Syntax Example

- deleted (apply "strike" class)
- important (apply "bold" class)
- error (apply "red" class) -
-

Using String Syntax

- -
-

Using Array Syntax

-
-
-
-
- - .strike { - text-decoration: line-through; - } - .bold { - font-weight: bold; - } - .red { - color: red; - } - - - var ps = element.all(by.css('p')); - - it('should let you toggle the class', function() { - - expect(ps.first().getAttribute('class')).not.toMatch(/bold/); - expect(ps.first().getAttribute('class')).not.toMatch(/red/); - - element(by.model('important')).click(); - expect(ps.first().getAttribute('class')).toMatch(/bold/); - - element(by.model('error')).click(); - expect(ps.first().getAttribute('class')).toMatch(/red/); - }); - - it('should let you toggle string example', function() { - expect(ps.get(1).getAttribute('class')).toBe(''); - element(by.model('style')).clear(); - element(by.model('style')).sendKeys('red'); - expect(ps.get(1).getAttribute('class')).toBe('red'); - }); - - it('array example should have 3 classes', function() { - expect(ps.last().getAttribute('class')).toBe(''); - element(by.model('style1')).sendKeys('bold'); - element(by.model('style2')).sendKeys('strike'); - element(by.model('style3')).sendKeys('red'); - expect(ps.last().getAttribute('class')).toBe('bold strike red'); - }); - -
- - ## Animations - - The example below demonstrates how to perform animations using ngClass. - - - - - -
- Sample Text -
- - .base-class { - -webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; - transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; - } - - .base-class.my-class { - color: red; - font-size:3em; - } - - - it('should check ng-class', function() { - expect(element(by.css('.base-class')).getAttribute('class')).not. - toMatch(/my-class/); - - element(by.id('setbtn')).click(); - - expect(element(by.css('.base-class')).getAttribute('class')). - toMatch(/my-class/); - - element(by.id('clearbtn')).click(); - - expect(element(by.css('.base-class')).getAttribute('class')).not. - toMatch(/my-class/); - }); - -
- - - ## ngClass and pre-existing CSS3 Transitions/Animations - The ngClass directive still supports CSS3 Transitions/Animations even if they do not follow the ngAnimate CSS naming structure. - Upon animation ngAnimate will apply supplementary CSS classes to track the start and end of an animation, but this will not hinder - any pre-existing CSS transitions already on the element. To get an idea of what happens during a class-based animation, be sure - to view the step by step details of {@link ngAnimate.$animate#addclass $animate.addClass} and - {@link ngAnimate.$animate#removeclass $animate.removeClass}. - */ -var ngClassDirective = classDirective('', true); - -/** - * @ngdoc directive - * @name ngClassOdd - * @restrict AC - * - * @description - * The `ngClassOdd` and `ngClassEven` directives work exactly as - * {@link ng.directive:ngClass ngClass}, except they work in - * conjunction with `ngRepeat` and take effect only on odd (even) rows. - * - * This directive can be applied only within the scope of an - * {@link ng.directive:ngRepeat ngRepeat}. - * - * @element ANY - * @param {expression} ngClassOdd {@link guide/expression Expression} to eval. The result - * of the evaluation can be a string representing space delimited class names or an array. - * - * @example - - -
    -
  1. - - {{name}} - -
  2. -
-
- - .odd { - color: red; - } - .even { - color: blue; - } - - - it('should check ng-class-odd and ng-class-even', function() { - expect(element(by.repeater('name in names').row(0).column('name')).getAttribute('class')). - toMatch(/odd/); - expect(element(by.repeater('name in names').row(1).column('name')).getAttribute('class')). - toMatch(/even/); - }); - -
- */ -var ngClassOddDirective = classDirective('Odd', 0); - -/** - * @ngdoc directive - * @name ngClassEven - * @restrict AC - * - * @description - * The `ngClassOdd` and `ngClassEven` directives work exactly as - * {@link ng.directive:ngClass ngClass}, except they work in - * conjunction with `ngRepeat` and take effect only on odd (even) rows. - * - * This directive can be applied only within the scope of an - * {@link ng.directive:ngRepeat ngRepeat}. - * - * @element ANY - * @param {expression} ngClassEven {@link guide/expression Expression} to eval. The - * result of the evaluation can be a string representing space delimited class names or an array. - * - * @example - - -
    -
  1. - - {{name}}       - -
  2. -
-
- - .odd { - color: red; - } - .even { - color: blue; - } - - - it('should check ng-class-odd and ng-class-even', function() { - expect(element(by.repeater('name in names').row(0).column('name')).getAttribute('class')). - toMatch(/odd/); - expect(element(by.repeater('name in names').row(1).column('name')).getAttribute('class')). - toMatch(/even/); - }); - -
- */ -var ngClassEvenDirective = classDirective('Even', 1); - -/** - * @ngdoc directive - * @name ngCloak - * @restrict AC - * - * @description - * The `ngCloak` directive is used to prevent the Angular html template from being briefly - * displayed by the browser in its raw (uncompiled) form while your application is loading. Use this - * directive to avoid the undesirable flicker effect caused by the html template display. - * - * The directive can be applied to the `` element, but the preferred usage is to apply - * multiple `ngCloak` directives to small portions of the page to permit progressive rendering - * of the browser view. - * - * `ngCloak` works in cooperation with the following css rule embedded within `angular.js` and - * `angular.min.js`. - * For CSP mode please add `angular-csp.css` to your html file (see {@link ng.directive:ngCsp ngCsp}). - * - * ```css - * [ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak { - * display: none !important; - * } - * ``` - * - * When this css rule is loaded by the browser, all html elements (including their children) that - * are tagged with the `ngCloak` directive are hidden. When Angular encounters this directive - * during the compilation of the template it deletes the `ngCloak` element attribute, making - * the compiled element visible. - * - * For the best result, the `angular.js` script must be loaded in the head section of the html - * document; alternatively, the css rule above must be included in the external stylesheet of the - * application. - * - * Legacy browsers, like IE7, do not provide attribute selector support (added in CSS 2.1) so they - * cannot match the `[ng\:cloak]` selector. To work around this limitation, you must add the css - * class `ng-cloak` in addition to the `ngCloak` directive as shown in the example below. - * - * @element ANY - * - * @example - - -
{{ 'hello' }}
-
{{ 'hello IE7' }}
-
- - it('should remove the template directive and css class', function() { - expect($('#template1').getAttribute('ng-cloak')). - toBeNull(); - expect($('#template2').getAttribute('ng-cloak')). - toBeNull(); - }); - -
- * - */ -var ngCloakDirective = ngDirective({ - compile: function(element, attr) { - attr.$set('ngCloak', undefined); - element.removeClass('ng-cloak'); - } -}); - -/** - * @ngdoc directive - * @name ngController - * - * @description - * The `ngController` directive attaches a controller class to the view. This is a key aspect of how angular - * supports the principles behind the Model-View-Controller design pattern. - * - * MVC components in angular: - * - * * Model — Models are the properties of a scope; scopes are attached to the DOM where scope properties - * are accessed through bindings. - * * View — The template (HTML with data bindings) that is rendered into the View. - * * Controller — The `ngController` directive specifies a Controller class; the class contains business - * logic behind the application to decorate the scope with functions and values - * - * Note that you can also attach controllers to the DOM by declaring it in a route definition - * via the {@link ngRoute.$route $route} service. A common mistake is to declare the controller - * again using `ng-controller` in the template itself. This will cause the controller to be attached - * and executed twice. - * - * @element ANY - * @scope - * @param {expression} ngController Name of a globally accessible constructor function or an - * {@link guide/expression expression} that on the current scope evaluates to a - * constructor function. The controller instance can be published into a scope property - * by specifying `as propertyName`. - * - * @example - * Here is a simple form for editing user contact information. Adding, removing, clearing, and - * greeting are methods declared on the controller (see source tab). These methods can - * easily be called from the angular markup. Any changes to the data are automatically reflected - * in the View without the need for a manual update. - * - * Two different declaration styles are included below: - * - * * one binds methods and properties directly onto the controller using `this`: - * `ng-controller="SettingsController1 as settings"` - * * one injects `$scope` into the controller: - * `ng-controller="SettingsController2"` - * - * The second option is more common in the Angular community, and is generally used in boilerplates - * and in this guide. However, there are advantages to binding properties directly to the controller - * and avoiding scope. - * - * * Using `controller as` makes it obvious which controller you are accessing in the template when - * multiple controllers apply to an element. - * * If you are writing your controllers as classes you have easier access to the properties and - * methods, which will appear on the scope, from inside the controller code. - * * Since there is always a `.` in the bindings, you don't have to worry about prototypal - * inheritance masking primitives. - * - * This example demonstrates the `controller as` syntax. - * - * - * - *
- * Name: - * [ greet ]
- * Contact: - *
    - *
  • - * - * - * [ clear - * | X ] - *
  • - *
  • [ add ]
  • - *
- *
- *
- * - * function SettingsController1() { - * this.name = "John Smith"; - * this.contacts = [ - * {type: 'phone', value: '408 555 1212'}, - * {type: 'email', value: 'john.smith@example.org'} ]; - * } - * - * SettingsController1.prototype.greet = function() { - * alert(this.name); - * }; - * - * SettingsController1.prototype.addContact = function() { - * this.contacts.push({type: 'email', value: 'yourname@example.org'}); - * }; - * - * SettingsController1.prototype.removeContact = function(contactToRemove) { - * var index = this.contacts.indexOf(contactToRemove); - * this.contacts.splice(index, 1); - * }; - * - * SettingsController1.prototype.clearContact = function(contact) { - * contact.type = 'phone'; - * contact.value = ''; - * }; - * - * - * it('should check controller as', function() { - * var container = element(by.id('ctrl-as-exmpl')); - * expect(container.findElement(by.model('settings.name')) - * .getAttribute('value')).toBe('John Smith'); - * - * var firstRepeat = - * container.findElement(by.repeater('contact in settings.contacts').row(0)); - * var secondRepeat = - * container.findElement(by.repeater('contact in settings.contacts').row(1)); - * - * expect(firstRepeat.findElement(by.model('contact.value')).getAttribute('value')) - * .toBe('408 555 1212'); - * - * expect(secondRepeat.findElement(by.model('contact.value')).getAttribute('value')) - * .toBe('john.smith@example.org'); - * - * firstRepeat.findElement(by.linkText('clear')).click(); - * - * expect(firstRepeat.findElement(by.model('contact.value')).getAttribute('value')) - * .toBe(''); - * - * container.findElement(by.linkText('add')).click(); - * - * expect(container.findElement(by.repeater('contact in settings.contacts').row(2)) - * .findElement(by.model('contact.value')) - * .getAttribute('value')) - * .toBe('yourname@example.org'); - * }); - * - *
- * - * This example demonstrates the "attach to `$scope`" style of controller. - * - * - * - *
- * Name: - * [ greet ]
- * Contact: - *
    - *
  • - * - * - * [ clear - * | X ] - *
  • - *
  • [ add ]
  • - *
- *
- *
- * - * function SettingsController2($scope) { - * $scope.name = "John Smith"; - * $scope.contacts = [ - * {type:'phone', value:'408 555 1212'}, - * {type:'email', value:'john.smith@example.org'} ]; - * - * $scope.greet = function() { - * alert($scope.name); - * }; - * - * $scope.addContact = function() { - * $scope.contacts.push({type:'email', value:'yourname@example.org'}); - * }; - * - * $scope.removeContact = function(contactToRemove) { - * var index = $scope.contacts.indexOf(contactToRemove); - * $scope.contacts.splice(index, 1); - * }; - * - * $scope.clearContact = function(contact) { - * contact.type = 'phone'; - * contact.value = ''; - * }; - * } - * - * - * it('should check controller', function() { - * var container = element(by.id('ctrl-exmpl')); - * - * expect(container.findElement(by.model('name')) - * .getAttribute('value')).toBe('John Smith'); - * - * var firstRepeat = - * container.findElement(by.repeater('contact in contacts').row(0)); - * var secondRepeat = - * container.findElement(by.repeater('contact in contacts').row(1)); - * - * expect(firstRepeat.findElement(by.model('contact.value')).getAttribute('value')) - * .toBe('408 555 1212'); - * expect(secondRepeat.findElement(by.model('contact.value')).getAttribute('value')) - * .toBe('john.smith@example.org'); - * - * firstRepeat.findElement(by.linkText('clear')).click(); - * - * expect(firstRepeat.findElement(by.model('contact.value')).getAttribute('value')) - * .toBe(''); - * - * container.findElement(by.linkText('add')).click(); - * - * expect(container.findElement(by.repeater('contact in contacts').row(2)) - * .findElement(by.model('contact.value')) - * .getAttribute('value')) - * .toBe('yourname@example.org'); - * }); - * - *
- - */ -var ngControllerDirective = [function() { - return { - scope: true, - controller: '@', - priority: 500 - }; -}]; - -/** - * @ngdoc directive - * @name ngCsp - * - * @element html - * @description - * Enables [CSP (Content Security Policy)](https://developer.mozilla.org/en/Security/CSP) support. - * - * This is necessary when developing things like Google Chrome Extensions. - * - * CSP forbids apps to use `eval` or `Function(string)` generated functions (among other things). - * For us to be compatible, we just need to implement the "getterFn" in $parse without violating - * any of these restrictions. - * - * AngularJS uses `Function(string)` generated functions as a speed optimization. Applying the `ngCsp` - * directive will cause Angular to use CSP compatibility mode. When this mode is on AngularJS will - * evaluate all expressions up to 30% slower than in non-CSP mode, but no security violations will - * be raised. - * - * CSP forbids JavaScript to inline stylesheet rules. In non CSP mode Angular automatically - * includes some CSS rules (e.g. {@link ng.directive:ngCloak ngCloak}). - * To make those directives work in CSP mode, include the `angular-csp.css` manually. - * - * In order to use this feature put the `ngCsp` directive on the root element of the application. - * - * *Note: This directive is only available in the `ng-csp` and `data-ng-csp` attribute form.* - * - * @example - * This example shows how to apply the `ngCsp` directive to the `html` tag. - ```html - - - ... - ... - - ``` - */ - -// ngCsp is not implemented as a proper directive any more, because we need it be processed while we bootstrap -// the system (before $parse is instantiated), for this reason we just have a csp() fn that looks for ng-csp attribute -// anywhere in the current doc - -/** - * @ngdoc directive - * @name ngClick - * - * @description - * The ngClick directive allows you to specify custom behavior when - * an element is clicked. - * - * @element ANY - * @priority 0 - * @param {expression} ngClick {@link guide/expression Expression} to evaluate upon - * click. ({@link guide/expression#-event- Event object is available as `$event`}) - * - * @example - - - - count: {{count}} - - - it('should check ng-click', function() { - expect(element(by.binding('count')).getText()).toMatch('0'); - element(by.css('button')).click(); - expect(element(by.binding('count')).getText()).toMatch('1'); - }); - - - */ -/* - * A directive that allows creation of custom onclick handlers that are defined as angular - * expressions and are compiled and executed within the current scope. - * - * Events that are handled via these handler are always configured not to propagate further. - */ -var ngEventDirectives = {}; -forEach( - 'click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress submit focus blur copy cut paste'.split(' '), - function(name) { - var directiveName = directiveNormalize('ng-' + name); - ngEventDirectives[directiveName] = ['$parse', function($parse) { - return { - compile: function($element, attr) { - var fn = $parse(attr[directiveName]); - return function ngEventHandler(scope, element) { - element.on(lowercase(name), function(event) { - scope.$apply(function() { - fn(scope, {$event:event}); - }); - }); - }; - } - }; - }]; - } -); - -/** - * @ngdoc directive - * @name ngDblclick - * - * @description - * The `ngDblclick` directive allows you to specify custom behavior on a dblclick event. - * - * @element ANY - * @priority 0 - * @param {expression} ngDblclick {@link guide/expression Expression} to evaluate upon - * a dblclick. (The Event object is available as `$event`) - * - * @example - - - - count: {{count}} - - - */ - - -/** - * @ngdoc directive - * @name ngMousedown - * - * @description - * The ngMousedown directive allows you to specify custom behavior on mousedown event. - * - * @element ANY - * @priority 0 - * @param {expression} ngMousedown {@link guide/expression Expression} to evaluate upon - * mousedown. ({@link guide/expression#-event- Event object is available as `$event`}) - * - * @example - - - - count: {{count}} - - - */ - - -/** - * @ngdoc directive - * @name ngMouseup - * - * @description - * Specify custom behavior on mouseup event. - * - * @element ANY - * @priority 0 - * @param {expression} ngMouseup {@link guide/expression Expression} to evaluate upon - * mouseup. ({@link guide/expression#-event- Event object is available as `$event`}) - * - * @example - - - - count: {{count}} - - - */ - -/** - * @ngdoc directive - * @name ngMouseover - * - * @description - * Specify custom behavior on mouseover event. - * - * @element ANY - * @priority 0 - * @param {expression} ngMouseover {@link guide/expression Expression} to evaluate upon - * mouseover. ({@link guide/expression#-event- Event object is available as `$event`}) - * - * @example - - - - count: {{count}} - - - */ - - -/** - * @ngdoc directive - * @name ngMouseenter - * - * @description - * Specify custom behavior on mouseenter event. - * - * @element ANY - * @priority 0 - * @param {expression} ngMouseenter {@link guide/expression Expression} to evaluate upon - * mouseenter. ({@link guide/expression#-event- Event object is available as `$event`}) - * - * @example - - - - count: {{count}} - - - */ - - -/** - * @ngdoc directive - * @name ngMouseleave - * - * @description - * Specify custom behavior on mouseleave event. - * - * @element ANY - * @priority 0 - * @param {expression} ngMouseleave {@link guide/expression Expression} to evaluate upon - * mouseleave. ({@link guide/expression#-event- Event object is available as `$event`}) - * - * @example - - - - count: {{count}} - - - */ - - -/** - * @ngdoc directive - * @name ngMousemove - * - * @description - * Specify custom behavior on mousemove event. - * - * @element ANY - * @priority 0 - * @param {expression} ngMousemove {@link guide/expression Expression} to evaluate upon - * mousemove. ({@link guide/expression#-event- Event object is available as `$event`}) - * - * @example - - - - count: {{count}} - - - */ - - -/** - * @ngdoc directive - * @name ngKeydown - * - * @description - * Specify custom behavior on keydown event. - * - * @element ANY - * @priority 0 - * @param {expression} ngKeydown {@link guide/expression Expression} to evaluate upon - * keydown. (Event object is available as `$event` and can be interrogated for keyCode, altKey, etc.) - * - * @example - - - - key down count: {{count}} - - - */ - - -/** - * @ngdoc directive - * @name ngKeyup - * - * @description - * Specify custom behavior on keyup event. - * - * @element ANY - * @priority 0 - * @param {expression} ngKeyup {@link guide/expression Expression} to evaluate upon - * keyup. (Event object is available as `$event` and can be interrogated for keyCode, altKey, etc.) - * - * @example - - -

Typing in the input box below updates the key count

- key up count: {{count}} - -

Typing in the input box below updates the keycode

- -

event keyCode: {{ event.keyCode }}

-

event altKey: {{ event.altKey }}

-
-
- */ - - -/** - * @ngdoc directive - * @name ngKeypress - * - * @description - * Specify custom behavior on keypress event. - * - * @element ANY - * @param {expression} ngKeypress {@link guide/expression Expression} to evaluate upon - * keypress. ({@link guide/expression#-event- Event object is available as `$event`} - * and can be interrogated for keyCode, altKey, etc.) - * - * @example - - - - key press count: {{count}} - - - */ - - -/** - * @ngdoc directive - * @name ngSubmit - * - * @description - * Enables binding angular expressions to onsubmit events. - * - * Additionally it prevents the default action (which for form means sending the request to the - * server and reloading the current page), but only if the form does not contain `action`, - * `data-action`, or `x-action` attributes. - * - * @element form - * @priority 0 - * @param {expression} ngSubmit {@link guide/expression Expression} to eval. - * ({@link guide/expression#-event- Event object is available as `$event`}) - * - * @example - - - -
- Enter text and hit enter: - - -
list={{list}}
-
-
- - it('should check ng-submit', function() { - expect(element(by.binding('list')).getText()).toBe('list=[]'); - element(by.css('#submit')).click(); - expect(element(by.binding('list')).getText()).toContain('hello'); - expect(element(by.input('text')).getAttribute('value')).toBe(''); - }); - it('should ignore empty strings', function() { - expect(element(by.binding('list')).getText()).toBe('list=[]'); - element(by.css('#submit')).click(); - element(by.css('#submit')).click(); - expect(element(by.binding('list')).getText()).toContain('hello'); - }); - -
- */ - -/** - * @ngdoc directive - * @name ngFocus - * - * @description - * Specify custom behavior on focus event. - * - * @element window, input, select, textarea, a - * @priority 0 - * @param {expression} ngFocus {@link guide/expression Expression} to evaluate upon - * focus. ({@link guide/expression#-event- Event object is available as `$event`}) - * - * @example - * See {@link ng.directive:ngClick ngClick} - */ - -/** - * @ngdoc directive - * @name ngBlur - * - * @description - * Specify custom behavior on blur event. - * - * @element window, input, select, textarea, a - * @priority 0 - * @param {expression} ngBlur {@link guide/expression Expression} to evaluate upon - * blur. ({@link guide/expression#-event- Event object is available as `$event`}) - * - * @example - * See {@link ng.directive:ngClick ngClick} - */ - -/** - * @ngdoc directive - * @name ngCopy - * - * @description - * Specify custom behavior on copy event. - * - * @element window, input, select, textarea, a - * @priority 0 - * @param {expression} ngCopy {@link guide/expression Expression} to evaluate upon - * copy. ({@link guide/expression#-event- Event object is available as `$event`}) - * - * @example - - - - copied: {{copied}} - - - */ - -/** - * @ngdoc directive - * @name ngCut - * - * @description - * Specify custom behavior on cut event. - * - * @element window, input, select, textarea, a - * @priority 0 - * @param {expression} ngCut {@link guide/expression Expression} to evaluate upon - * cut. ({@link guide/expression#-event- Event object is available as `$event`}) - * - * @example - - - - cut: {{cut}} - - - */ - -/** - * @ngdoc directive - * @name ngPaste - * - * @description - * Specify custom behavior on paste event. - * - * @element window, input, select, textarea, a - * @priority 0 - * @param {expression} ngPaste {@link guide/expression Expression} to evaluate upon - * paste. ({@link guide/expression#-event- Event object is available as `$event`}) - * - * @example - - - - pasted: {{paste}} - - - */ - -/** - * @ngdoc directive - * @name ngIf - * @restrict A - * - * @description - * The `ngIf` directive removes or recreates a portion of the DOM tree based on an - * {expression}. If the expression assigned to `ngIf` evaluates to a false - * value then the element is removed from the DOM, otherwise a clone of the - * element is reinserted into the DOM. - * - * `ngIf` differs from `ngShow` and `ngHide` in that `ngIf` completely removes and recreates the - * element in the DOM rather than changing its visibility via the `display` css property. A common - * case when this difference is significant is when using css selectors that rely on an element's - * position within the DOM, such as the `:first-child` or `:last-child` pseudo-classes. - * - * Note that when an element is removed using `ngIf` its scope is destroyed and a new scope - * is created when the element is restored. The scope created within `ngIf` inherits from - * its parent scope using - * [prototypal inheritance](https://github.com/angular/angular.js/wiki/The-Nuances-of-Scope-Prototypal-Inheritance). - * An important implication of this is if `ngModel` is used within `ngIf` to bind to - * a javascript primitive defined in the parent scope. In this case any modifications made to the - * variable within the child scope will override (hide) the value in the parent scope. - * - * Also, `ngIf` recreates elements using their compiled state. An example of this behavior - * is if an element's class attribute is directly modified after it's compiled, using something like - * jQuery's `.addClass()` method, and the element is later removed. When `ngIf` recreates the element - * the added class will be lost because the original compiled state is used to regenerate the element. - * - * Additionally, you can provide animations via the `ngAnimate` module to animate the `enter` - * and `leave` effects. - * - * @animations - * enter - happens just after the ngIf contents change and a new DOM element is created and injected into the ngIf container - * leave - happens just before the ngIf contents are removed from the DOM - * - * @element ANY - * @scope - * @priority 600 - * @param {expression} ngIf If the {@link guide/expression expression} is falsy then - * the element is removed from the DOM tree. If it is truthy a copy of the compiled - * element is added to the DOM tree. - * - * @example - - - Click me:
- Show when checked: - - I'm removed when the checkbox is unchecked. - -
- - .animate-if { - background:white; - border:1px solid black; - padding:10px; - } - - .animate-if.ng-enter, .animate-if.ng-leave { - -webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; - transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; - } - - .animate-if.ng-enter, - .animate-if.ng-leave.ng-leave-active { - opacity:0; - } - - .animate-if.ng-leave, - .animate-if.ng-enter.ng-enter-active { - opacity:1; - } - -
- */ -var ngIfDirective = ['$animate', function($animate) { - return { - transclude: 'element', - priority: 600, - terminal: true, - restrict: 'A', - $$tlb: true, - link: function ($scope, $element, $attr, ctrl, $transclude) { - var block, childScope, previousElements; - $scope.$watch($attr.ngIf, function ngIfWatchAction(value) { - - if (toBoolean(value)) { - if (!childScope) { - $transclude(function (clone, newScope) { - childScope = newScope; - clone[clone.length++] = document.createComment(' end ngIf: ' + $attr.ngIf + ' '); - // Note: We only need the first/last node of the cloned nodes. - // However, we need to keep the reference to the jqlite wrapper as it might be changed later - // by a directive with templateUrl when its template arrives. - block = { - clone: clone - }; - $animate.enter(clone, $element.parent(), $element); - }); - } - } else { - if(previousElements) { - previousElements.remove(); - previousElements = null; - } - if(childScope) { - childScope.$destroy(); - childScope = null; - } - if(block) { - previousElements = getBlockElements(block.clone); - $animate.leave(previousElements, function() { - previousElements = null; - }); - block = null; - } - } - }); - } - }; -}]; - -/** - * @ngdoc directive - * @name ngInclude - * @restrict ECA - * - * @description - * Fetches, compiles and includes an external HTML fragment. - * - * By default, the template URL is restricted to the same domain and protocol as the - * application document. This is done by calling {@link ng.$sce#getTrustedResourceUrl - * $sce.getTrustedResourceUrl} on it. To load templates from other domains or protocols - * you may either {@link ng.$sceDelegateProvider#resourceUrlWhitelist whitelist them} or - * [wrap them](ng.$sce#trustAsResourceUrl) as trusted values. Refer to Angular's {@link - * ng.$sce Strict Contextual Escaping}. - * - * In addition, the browser's - * [Same Origin Policy](https://code.google.com/p/browsersec/wiki/Part2#Same-origin_policy_for_XMLHttpRequest) - * and [Cross-Origin Resource Sharing (CORS)](http://www.w3.org/TR/cors/) - * policy may further restrict whether the template is successfully loaded. - * For example, `ngInclude` won't work for cross-domain requests on all browsers and for `file://` - * access on some browsers. - * - * @animations - * enter - animation is used to bring new content into the browser. - * leave - animation is used to animate existing content away. - * - * The enter and leave animation occur concurrently. - * - * @scope - * @priority 400 - * - * @param {string} ngInclude|src angular expression evaluating to URL. If the source is a string constant, - * make sure you wrap it in **single** quotes, e.g. `src="'myPartialTemplate.html'"`. - * @param {string=} onload Expression to evaluate when a new partial is loaded. - * - * @param {string=} autoscroll Whether `ngInclude` should call {@link ng.$anchorScroll - * $anchorScroll} to scroll the viewport after the content is loaded. - * - * - If the attribute is not set, disable scrolling. - * - If the attribute is set without value, enable scrolling. - * - Otherwise enable scrolling only if the expression evaluates to truthy value. - * - * @example - - -
- - url of the template: {{template.url}} -
-
-
-
-
-
- - function Ctrl($scope) { - $scope.templates = - [ { name: 'template1.html', url: 'template1.html'}, - { name: 'template2.html', url: 'template2.html'} ]; - $scope.template = $scope.templates[0]; - } - - - Content of template1.html - - - Content of template2.html - - - .slide-animate-container { - position:relative; - background:white; - border:1px solid black; - height:40px; - overflow:hidden; - } - - .slide-animate { - padding:10px; - } - - .slide-animate.ng-enter, .slide-animate.ng-leave { - -webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; - transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; - - position:absolute; - top:0; - left:0; - right:0; - bottom:0; - display:block; - padding:10px; - } - - .slide-animate.ng-enter { - top:-50px; - } - .slide-animate.ng-enter.ng-enter-active { - top:0; - } - - .slide-animate.ng-leave { - top:0; - } - .slide-animate.ng-leave.ng-leave-active { - top:50px; - } - - - var templateSelect = element(by.model('template')); - var includeElem = element(by.css('[ng-include]')); - - it('should load template1.html', function() { - expect(includeElem.getText()).toMatch(/Content of template1.html/); - }); - - it('should load template2.html', function() { - if (browser.params.browser == 'firefox') { - // Firefox can't handle using selects - // See https://github.com/angular/protractor/issues/480 - return; - } - templateSelect.click(); - templateSelect.element.all(by.css('option')).get(2).click(); - expect(includeElem.getText()).toMatch(/Content of template2.html/); - }); - - it('should change to blank', function() { - if (browser.params.browser == 'firefox') { - // Firefox can't handle using selects - return; - } - templateSelect.click(); - templateSelect.element.all(by.css('option')).get(0).click(); - expect(includeElem.isPresent()).toBe(false); - }); - -
- */ - - -/** - * @ngdoc event - * @name ngInclude#$includeContentRequested - * @eventType emit on the scope ngInclude was declared in - * @description - * Emitted every time the ngInclude content is requested. - */ - - -/** - * @ngdoc event - * @name ngInclude#$includeContentLoaded - * @eventType emit on the current ngInclude scope - * @description - * Emitted every time the ngInclude content is reloaded. - */ - - -/** - * @ngdoc event - * @name ng.directive:ngInclude#$includeContentError - * @eventOf ng.directive:ngInclude - * @eventType emit on the scope ngInclude was declared in - * @description - * Emitted when a template HTTP request yields an erronous response (status < 200 || status > 299) - */ -var ngIncludeDirective = ['$http', '$templateCache', '$anchorScroll', '$animate', '$sce', - function($http, $templateCache, $anchorScroll, $animate, $sce) { - return { - restrict: 'ECA', - priority: 400, - terminal: true, - transclude: 'element', - controller: angular.noop, - compile: function(element, attr) { - var srcExp = attr.ngInclude || attr.src, - onloadExp = attr.onload || '', - autoScrollExp = attr.autoscroll; - - return function(scope, $element, $attr, ctrl, $transclude) { - var changeCounter = 0, - currentScope, - previousElement, - currentElement; - - var cleanupLastIncludeContent = function() { - if(previousElement) { - previousElement.remove(); - previousElement = null; - } - if(currentScope) { - currentScope.$destroy(); - currentScope = null; - } - if(currentElement) { - $animate.leave(currentElement, function() { - previousElement = null; - }); - previousElement = currentElement; - currentElement = null; - } - }; - - scope.$watch($sce.parseAsResourceUrl(srcExp), function ngIncludeWatchAction(src) { - var afterAnimation = function() { - if (isDefined(autoScrollExp) && (!autoScrollExp || scope.$eval(autoScrollExp))) { - $anchorScroll(); - } - }; - var thisChangeId = ++changeCounter; - - if (src) { - $http.get(src, {cache: $templateCache}).success(function(response) { - if (thisChangeId !== changeCounter) return; - var newScope = scope.$new(); - ctrl.template = response; - - // Note: This will also link all children of ng-include that were contained in the original - // html. If that content contains controllers, ... they could pollute/change the scope. - // However, using ng-include on an element with additional content does not make sense... - // Note: We can't remove them in the cloneAttchFn of $transclude as that - // function is called before linking the content, which would apply child - // directives to non existing elements. - var clone = $transclude(newScope, function(clone) { - cleanupLastIncludeContent(); - $animate.enter(clone, null, $element, afterAnimation); - }); - - currentScope = newScope; - currentElement = clone; - - currentScope.$emit('$includeContentLoaded'); - scope.$eval(onloadExp); - }).error(function() { - if (thisChangeId === changeCounter) { - cleanupLastIncludeContent(); - scope.$emit('$includeContentError'); - } - }); - scope.$emit('$includeContentRequested'); - } else { - cleanupLastIncludeContent(); - ctrl.template = null; - } - }); - }; - } - }; -}]; - -// This directive is called during the $transclude call of the first `ngInclude` directive. -// It will replace and compile the content of the element with the loaded template. -// We need this directive so that the element content is already filled when -// the link function of another directive on the same element as ngInclude -// is called. -var ngIncludeFillContentDirective = ['$compile', - function($compile) { - return { - restrict: 'ECA', - priority: -400, - require: 'ngInclude', - link: function(scope, $element, $attr, ctrl) { - $element.html(ctrl.template); - $compile($element.contents())(scope); - } - }; - }]; - -/** - * @ngdoc directive - * @name ngInit - * @restrict AC - * - * @description - * The `ngInit` directive allows you to evaluate an expression in the - * current scope. - * - *
- * The only appropriate use of `ngInit` is for aliasing special properties of - * {@link ng.directive:ngRepeat `ngRepeat`}, as seen in the demo below. Besides this case, you - * should use {@link guide/controller controllers} rather than `ngInit` - * to initialize values on a scope. - *
- *
- * **Note**: If you have assignment in `ngInit` along with {@link ng.$filter `$filter`}, make - * sure you have parenthesis for correct precedence: - *
- *   
- *
- *
- * - * @priority 450 - * - * @element ANY - * @param {expression} ngInit {@link guide/expression Expression} to eval. - * - * @example - - - -
-
-
- list[ {{outerIndex}} ][ {{innerIndex}} ] = {{value}}; -
-
-
-
- - it('should alias index positions', function() { - var elements = element.all(by.css('.example-init')); - expect(elements.get(0).getText()).toBe('list[ 0 ][ 0 ] = a;'); - expect(elements.get(1).getText()).toBe('list[ 0 ][ 1 ] = b;'); - expect(elements.get(2).getText()).toBe('list[ 1 ][ 0 ] = c;'); - expect(elements.get(3).getText()).toBe('list[ 1 ][ 1 ] = d;'); - }); - -
- */ -var ngInitDirective = ngDirective({ - priority: 450, - compile: function() { - return { - pre: function(scope, element, attrs) { - scope.$eval(attrs.ngInit); - } - }; - } -}); - -/** - * @ngdoc directive - * @name ngNonBindable - * @restrict AC - * @priority 1000 - * - * @description - * The `ngNonBindable` directive tells Angular not to compile or bind the contents of the current - * DOM element. This is useful if the element contains what appears to be Angular directives and - * bindings but which should be ignored by Angular. This could be the case if you have a site that - * displays snippets of code, for instance. - * - * @element ANY - * - * @example - * In this example there are two locations where a simple interpolation binding (`{{}}`) is present, - * but the one wrapped in `ngNonBindable` is left alone. - * - * @example - - -
Normal: {{1 + 2}}
-
Ignored: {{1 + 2}}
-
- - it('should check ng-non-bindable', function() { - expect(element(by.binding('1 + 2')).getText()).toContain('3'); - expect(element.all(by.css('div')).last().getText()).toMatch(/1 \+ 2/); - }); - -
- */ -var ngNonBindableDirective = ngDirective({ terminal: true, priority: 1000 }); - -/** - * @ngdoc directive - * @name ngPluralize - * @restrict EA - * - * @description - * `ngPluralize` is a directive that displays messages according to en-US localization rules. - * These rules are bundled with angular.js, but can be overridden - * (see {@link guide/i18n Angular i18n} dev guide). You configure ngPluralize directive - * by specifying the mappings between - * [plural categories](http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html) - * and the strings to be displayed. - * - * # Plural categories and explicit number rules - * There are two - * [plural categories](http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html) - * in Angular's default en-US locale: "one" and "other". - * - * While a plural category may match many numbers (for example, in en-US locale, "other" can match - * any number that is not 1), an explicit number rule can only match one number. For example, the - * explicit number rule for "3" matches the number 3. There are examples of plural categories - * and explicit number rules throughout the rest of this documentation. - * - * # Configuring ngPluralize - * You configure ngPluralize by providing 2 attributes: `count` and `when`. - * You can also provide an optional attribute, `offset`. - * - * The value of the `count` attribute can be either a string or an {@link guide/expression - * Angular expression}; these are evaluated on the current scope for its bound value. - * - * The `when` attribute specifies the mappings between plural categories and the actual - * string to be displayed. The value of the attribute should be a JSON object. - * - * The following example shows how to configure ngPluralize: - * - * ```html - * - * - *``` - * - * In the example, `"0: Nobody is viewing."` is an explicit number rule. If you did not - * specify this rule, 0 would be matched to the "other" category and "0 people are viewing" - * would be shown instead of "Nobody is viewing". You can specify an explicit number rule for - * other numbers, for example 12, so that instead of showing "12 people are viewing", you can - * show "a dozen people are viewing". - * - * You can use a set of closed braces (`{}`) as a placeholder for the number that you want substituted - * into pluralized strings. In the previous example, Angular will replace `{}` with - * `{{personCount}}`. The closed braces `{}` is a placeholder - * for {{numberExpression}}. - * - * # Configuring ngPluralize with offset - * The `offset` attribute allows further customization of pluralized text, which can result in - * a better user experience. For example, instead of the message "4 people are viewing this document", - * you might display "John, Kate and 2 others are viewing this document". - * The offset attribute allows you to offset a number by any desired value. - * Let's take a look at an example: - * - * ```html - * - * - * ``` - * - * Notice that we are still using two plural categories(one, other), but we added - * three explicit number rules 0, 1 and 2. - * When one person, perhaps John, views the document, "John is viewing" will be shown. - * When three people view the document, no explicit number rule is found, so - * an offset of 2 is taken off 3, and Angular uses 1 to decide the plural category. - * In this case, plural category 'one' is matched and "John, Mary and one other person are viewing" - * is shown. - * - * Note that when you specify offsets, you must provide explicit number rules for - * numbers from 0 up to and including the offset. If you use an offset of 3, for example, - * you must provide explicit number rules for 0, 1, 2 and 3. You must also provide plural strings for - * plural categories "one" and "other". - * - * @param {string|expression} count The variable to be bound to. - * @param {string} when The mapping between plural category to its corresponding strings. - * @param {number=} offset Offset to deduct from the total number. - * - * @example - - - -
- Person 1:
- Person 2:
- Number of People:
- - - Without Offset: - -
- - - With Offset(2): - - -
-
- - it('should show correct pluralized string', function() { - var withoutOffset = element.all(by.css('ng-pluralize')).get(0); - var withOffset = element.all(by.css('ng-pluralize')).get(1); - var countInput = element(by.model('personCount')); - - expect(withoutOffset.getText()).toEqual('1 person is viewing.'); - expect(withOffset.getText()).toEqual('Igor is viewing.'); - - countInput.clear(); - countInput.sendKeys('0'); - - expect(withoutOffset.getText()).toEqual('Nobody is viewing.'); - expect(withOffset.getText()).toEqual('Nobody is viewing.'); - - countInput.clear(); - countInput.sendKeys('2'); - - expect(withoutOffset.getText()).toEqual('2 people are viewing.'); - expect(withOffset.getText()).toEqual('Igor and Misko are viewing.'); - - countInput.clear(); - countInput.sendKeys('3'); - - expect(withoutOffset.getText()).toEqual('3 people are viewing.'); - expect(withOffset.getText()).toEqual('Igor, Misko and one other person are viewing.'); - - countInput.clear(); - countInput.sendKeys('4'); - - expect(withoutOffset.getText()).toEqual('4 people are viewing.'); - expect(withOffset.getText()).toEqual('Igor, Misko and 2 other people are viewing.'); - }); - it('should show data-bound names', function() { - var withOffset = element.all(by.css('ng-pluralize')).get(1); - var personCount = element(by.model('personCount')); - var person1 = element(by.model('person1')); - var person2 = element(by.model('person2')); - personCount.clear(); - personCount.sendKeys('4'); - person1.clear(); - person1.sendKeys('Di'); - person2.clear(); - person2.sendKeys('Vojta'); - expect(withOffset.getText()).toEqual('Di, Vojta and 2 other people are viewing.'); - }); - -
- */ -var ngPluralizeDirective = ['$locale', '$interpolate', function($locale, $interpolate) { - var BRACE = /{}/g; - return { - restrict: 'EA', - link: function(scope, element, attr) { - var numberExp = attr.count, - whenExp = attr.$attr.when && element.attr(attr.$attr.when), // we have {{}} in attrs - offset = attr.offset || 0, - whens = scope.$eval(whenExp) || {}, - whensExpFns = {}, - startSymbol = $interpolate.startSymbol(), - endSymbol = $interpolate.endSymbol(), - isWhen = /^when(Minus)?(.+)$/; - - forEach(attr, function(expression, attributeName) { - if (isWhen.test(attributeName)) { - whens[lowercase(attributeName.replace('when', '').replace('Minus', '-'))] = - element.attr(attr.$attr[attributeName]); - } - }); - forEach(whens, function(expression, key) { - whensExpFns[key] = - $interpolate(expression.replace(BRACE, startSymbol + numberExp + '-' + - offset + endSymbol)); - }); - - scope.$watch(function ngPluralizeWatch() { - var value = parseFloat(scope.$eval(numberExp)); - - if (!isNaN(value)) { - //if explicit number rule such as 1, 2, 3... is defined, just use it. Otherwise, - //check it against pluralization rules in $locale service - if (!(value in whens)) value = $locale.pluralCat(value - offset); - return whensExpFns[value](scope); - } else { - return ''; - } - }, function ngPluralizeWatchAction(newVal) { - element.text(newVal); - }); - } - }; -}]; - -/** - * @ngdoc directive - * @name ngRepeat - * - * @description - * The `ngRepeat` directive instantiates a template once per item from a collection. Each template - * instance gets its own scope, where the given loop variable is set to the current collection item, - * and `$index` is set to the item index or key. - * - * Special properties are exposed on the local scope of each template instance, including: - * - * | Variable | Type | Details | - * |-----------|-----------------|-----------------------------------------------------------------------------| - * | `$index` | {@type number} | iterator offset of the repeated element (0..length-1) | - * | `$first` | {@type boolean} | true if the repeated element is first in the iterator. | - * | `$middle` | {@type boolean} | true if the repeated element is between the first and last in the iterator. | - * | `$last` | {@type boolean} | true if the repeated element is last in the iterator. | - * | `$even` | {@type boolean} | true if the iterator position `$index` is even (otherwise false). | - * | `$odd` | {@type boolean} | true if the iterator position `$index` is odd (otherwise false). | - * - * Creating aliases for these properties is possible with {@link ng.directive:ngInit `ngInit`}. - * This may be useful when, for instance, nesting ngRepeats. - * - * # Special repeat start and end points - * To repeat a series of elements instead of just one parent element, ngRepeat (as well as other ng directives) supports extending - * the range of the repeater by defining explicit start and end points by using **ng-repeat-start** and **ng-repeat-end** respectively. - * The **ng-repeat-start** directive works the same as **ng-repeat**, but will repeat all the HTML code (including the tag it's defined on) - * up to and including the ending HTML tag where **ng-repeat-end** is placed. - * - * The example below makes use of this feature: - * ```html - *
- * Header {{ item }} - *
- *
- * Body {{ item }} - *
- *
- * Footer {{ item }} - *
- * ``` - * - * And with an input of {@type ['A','B']} for the items variable in the example above, the output will evaluate to: - * ```html - *
- * Header A - *
- *
- * Body A - *
- *
- * Footer A - *
- *
- * Header B - *
- *
- * Body B - *
- *
- * Footer B - *
- * ``` - * - * The custom start and end points for ngRepeat also support all other HTML directive syntax flavors provided in AngularJS (such - * as **data-ng-repeat-start**, **x-ng-repeat-start** and **ng:repeat-start**). - * - * @animations - * **.enter** - when a new item is added to the list or when an item is revealed after a filter - * - * **.leave** - when an item is removed from the list or when an item is filtered out - * - * **.move** - when an adjacent item is filtered out causing a reorder or when the item contents are reordered - * - * @element ANY - * @scope - * @priority 1000 - * @param {repeat_expression} ngRepeat The expression indicating how to enumerate a collection. These - * formats are currently supported: - * - * * `variable in expression` – where variable is the user defined loop variable and `expression` - * is a scope expression giving the collection to enumerate. - * - * For example: `album in artist.albums`. - * - * * `(key, value) in expression` – where `key` and `value` can be any user defined identifiers, - * and `expression` is the scope expression giving the collection to enumerate. - * - * For example: `(name, age) in {'adam':10, 'amalie':12}`. - * - * * `variable in expression track by tracking_expression` – You can also provide an optional tracking function - * which can be used to associate the objects in the collection with the DOM elements. If no tracking function - * is specified the ng-repeat associates elements by identity in the collection. It is an error to have - * more than one tracking function to resolve to the same key. (This would mean that two distinct objects are - * mapped to the same DOM element, which is not possible.) Filters should be applied to the expression, - * before specifying a tracking expression. - * - * For example: `item in items` is equivalent to `item in items track by $id(item)`. This implies that the DOM elements - * will be associated by item identity in the array. - * - * For example: `item in items track by $id(item)`. A built in `$id()` function can be used to assign a unique - * `$$hashKey` property to each item in the array. This property is then used as a key to associated DOM elements - * with the corresponding item in the array by identity. Moving the same object in array would move the DOM - * element in the same way in the DOM. - * - * For example: `item in items track by item.id` is a typical pattern when the items come from the database. In this - * case the object identity does not matter. Two objects are considered equivalent as long as their `id` - * property is same. - * - * For example: `item in items | filter:searchText track by item.id` is a pattern that might be used to apply a filter - * to items in conjunction with a tracking expression. - * - * @example - * This example initializes the scope to a list of names and - * then uses `ngRepeat` to display every person: - - -
- I have {{friends.length}} friends. They are: - -
    -
  • - [{{$index + 1}}] {{friend.name}} who is {{friend.age}} years old. -
  • -
-
-
- - .example-animate-container { - background:white; - border:1px solid black; - list-style:none; - margin:0; - padding:0 10px; - } - - .animate-repeat { - line-height:40px; - list-style:none; - box-sizing:border-box; - } - - .animate-repeat.ng-move, - .animate-repeat.ng-enter, - .animate-repeat.ng-leave { - -webkit-transition:all linear 0.5s; - transition:all linear 0.5s; - } - - .animate-repeat.ng-leave.ng-leave-active, - .animate-repeat.ng-move, - .animate-repeat.ng-enter { - opacity:0; - max-height:0; - } - - .animate-repeat.ng-leave, - .animate-repeat.ng-move.ng-move-active, - .animate-repeat.ng-enter.ng-enter-active { - opacity:1; - max-height:40px; - } - - - var friends = element.all(by.repeater('friend in friends')); - - it('should render initial data set', function() { - expect(friends.count()).toBe(10); - expect(friends.get(0).getText()).toEqual('[1] John who is 25 years old.'); - expect(friends.get(1).getText()).toEqual('[2] Jessie who is 30 years old.'); - expect(friends.last().getText()).toEqual('[10] Samantha who is 60 years old.'); - expect(element(by.binding('friends.length')).getText()) - .toMatch("I have 10 friends. They are:"); - }); - - it('should update repeater when filter predicate changes', function() { - expect(friends.count()).toBe(10); - - element(by.model('q')).sendKeys('ma'); - - expect(friends.count()).toBe(2); - expect(friends.get(0).getText()).toEqual('[1] Mary who is 28 years old.'); - expect(friends.last().getText()).toEqual('[2] Samantha who is 60 years old.'); - }); - -
- */ -var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) { - var NG_REMOVED = '$$NG_REMOVED'; - var ngRepeatMinErr = minErr('ngRepeat'); - return { - transclude: 'element', - priority: 1000, - terminal: true, - $$tlb: true, - link: function($scope, $element, $attr, ctrl, $transclude){ - var expression = $attr.ngRepeat; - var match = expression.match(/^\s*([\s\S]+?)\s+in\s+([\s\S]+?)(?:\s+track\s+by\s+([\s\S]+?))?\s*$/), - trackByExp, trackByExpGetter, trackByIdExpFn, trackByIdArrayFn, trackByIdObjFn, - lhs, rhs, valueIdentifier, keyIdentifier, - hashFnLocals = {$id: hashKey}; - - if (!match) { - throw ngRepeatMinErr('iexp', "Expected expression in form of '_item_ in _collection_[ track by _id_]' but got '{0}'.", - expression); - } - - lhs = match[1]; - rhs = match[2]; - trackByExp = match[3]; - - if (trackByExp) { - trackByExpGetter = $parse(trackByExp); - trackByIdExpFn = function(key, value, index) { - // assign key, value, and $index to the locals so that they can be used in hash functions - if (keyIdentifier) hashFnLocals[keyIdentifier] = key; - hashFnLocals[valueIdentifier] = value; - hashFnLocals.$index = index; - return trackByExpGetter($scope, hashFnLocals); - }; - } else { - trackByIdArrayFn = function(key, value) { - return hashKey(value); - }; - trackByIdObjFn = function(key) { - return key; - }; - } - - match = lhs.match(/^(?:([\$\w]+)|\(([\$\w]+)\s*,\s*([\$\w]+)\))$/); - if (!match) { - throw ngRepeatMinErr('iidexp', "'_item_' in '_item_ in _collection_' should be an identifier or '(_key_, _value_)' expression, but got '{0}'.", - lhs); - } - valueIdentifier = match[3] || match[1]; - keyIdentifier = match[2]; - - // Store a list of elements from previous run. This is a hash where key is the item from the - // iterator, and the value is objects with following properties. - // - scope: bound scope - // - element: previous element. - // - index: position - var lastBlockMap = {}; - - //watch props - $scope.$watchCollection(rhs, function ngRepeatAction(collection){ - var index, length, - previousNode = $element[0], // current position of the node - nextNode, - // Same as lastBlockMap but it has the current state. It will become the - // lastBlockMap on the next iteration. - nextBlockMap = {}, - arrayLength, - key, value, // key/value of iteration - trackById, - trackByIdFn, - collectionKeys, - block, // last object information {scope, element, id} - nextBlockOrder = [], - elementsToRemove; - - var updateScope = function(scope, index) { - scope[valueIdentifier] = value; - if (keyIdentifier) scope[keyIdentifier] = key; - scope.$index = index; - scope.$first = (index === 0); - scope.$last = (index === (arrayLength - 1)); - scope.$middle = !(scope.$first || scope.$last); - // jshint bitwise: false - scope.$odd = !(scope.$even = (index&1) === 0); - // jshint bitwise: true - }; - - if (isArrayLike(collection)) { - collectionKeys = collection; - trackByIdFn = trackByIdExpFn || trackByIdArrayFn; - } else { - trackByIdFn = trackByIdExpFn || trackByIdObjFn; - // if object, extract keys, sort them and use to determine order of iteration over obj props - collectionKeys = []; - for (var itemKey in collection) { - if (collection.hasOwnProperty(itemKey) && itemKey.charAt(0) != '$') { - collectionKeys.push(itemKey); - } - } - collectionKeys.sort(); - } - - arrayLength = collectionKeys.length; - - // locate existing items - length = nextBlockOrder.length = collectionKeys.length; - for(index = 0; index < length; index++) { - key = (collection === collectionKeys) ? index : collectionKeys[index]; - value = collection[key]; - trackById = trackByIdFn(key, value, index); - assertNotHasOwnProperty(trackById, '`track by` id'); - if(lastBlockMap.hasOwnProperty(trackById)) { - block = lastBlockMap[trackById]; - delete lastBlockMap[trackById]; - nextBlockMap[trackById] = block; - nextBlockOrder[index] = block; - } else if (nextBlockMap.hasOwnProperty(trackById)) { - // restore lastBlockMap - forEach(nextBlockOrder, function(block) { - if (block && block.scope) lastBlockMap[block.id] = block; - }); - // This is a duplicate and we need to throw an error - throw ngRepeatMinErr('dupes', "Duplicates in a repeater are not allowed. Use 'track by' expression to specify unique keys. Repeater: {0}, Duplicate key: {1}", - expression, trackById); - } else { - // new never before seen block - nextBlockOrder[index] = { id: trackById }; - nextBlockMap[trackById] = false; - } - } - - // remove existing items - for (var blockKey in lastBlockMap) { - // lastBlockMap is our own object so we don't need to use special hasOwnPropertyFn - if (lastBlockMap.hasOwnProperty(blockKey)) { - block = lastBlockMap[blockKey]; - elementsToRemove = getBlockElements(block.clone); - $animate.leave(elementsToRemove); - forEach(elementsToRemove, function(element) { element[NG_REMOVED] = true; }); - block.scope.$destroy(); - } - } - - // we are not using forEach for perf reasons (trying to avoid #call) - for (index = 0, length = collectionKeys.length; index < length; index++) { - key = (collection === collectionKeys) ? index : collectionKeys[index]; - value = collection[key]; - block = nextBlockOrder[index]; - if (nextBlockOrder[index - 1]) previousNode = getBlockEnd(nextBlockOrder[index - 1]); - - if (block.scope) { - // if we have already seen this object, then we need to reuse the - // associated scope/element - nextNode = previousNode; - do { - nextNode = nextNode.nextSibling; - } while(nextNode && nextNode[NG_REMOVED]); - - if (getBlockStart(block) != nextNode) { - // existing item which got moved - $animate.move(getBlockElements(block.clone), null, jqLite(previousNode)); - } - previousNode = getBlockEnd(block); - updateScope(block.scope, index); - } else { - // new item which we don't know about - $transclude(function(clone, scope) { - block.scope = scope; - clone[clone.length++] = document.createComment(' end ngRepeat: ' + expression + ' '); - $animate.enter(clone, null, jqLite(previousNode)); - previousNode = clone; - // Note: We only need the first/last node of the cloned nodes. - // However, we need to keep the reference to the jqlite wrapper as it might be changed later - // by a directive with templateUrl when its template arrives. - block.clone = clone; - nextBlockMap[block.id] = block; - updateScope(block.scope, index); - }); - } - } - lastBlockMap = nextBlockMap; - }); - } - }; - - function getBlockStart(block) { - return block.clone[0]; - } - - function getBlockEnd(block) { - return block.clone[block.clone.length - 1]; - } -}]; - -/** - * @ngdoc directive - * @name ngShow - * - * @description - * The `ngShow` directive shows or hides the given HTML element based on the expression - * provided to the ngShow attribute. The element is shown or hidden by removing or adding - * the `ng-hide` CSS class onto the element. The `.ng-hide` CSS class is predefined - * in AngularJS and sets the display style to none (using an !important flag). - * For CSP mode please add `angular-csp.css` to your html file (see {@link ng.directive:ngCsp ngCsp}). - * - * ```html - * - *
- * - * - *
- * ``` - * - * When the ngShow expression evaluates to false then the ng-hide CSS class is added to the class attribute - * on the element causing it to become hidden. When true, the ng-hide CSS class is removed - * from the element causing the element not to appear hidden. - * - *
- * **Note:** Here is a list of values that ngShow will consider as a falsy value (case insensitive):
- * "f" / "0" / "false" / "no" / "n" / "[]" - *
- * - * ## Why is !important used? - * - * You may be wondering why !important is used for the .ng-hide CSS class. This is because the `.ng-hide` selector - * can be easily overridden by heavier selectors. For example, something as simple - * as changing the display style on a HTML list item would make hidden elements appear visible. - * This also becomes a bigger issue when dealing with CSS frameworks. - * - * By using !important, the show and hide behavior will work as expected despite any clash between CSS selector - * specificity (when !important isn't used with any conflicting styles). If a developer chooses to override the - * styling to change how to hide an element then it is just a matter of using !important in their own CSS code. - * - * ### Overriding .ng-hide - * - * By default, the `.ng-hide` class will style the element with `display:none!important`. If you wish to change - * the hide behavior with ngShow/ngHide then this can be achieved by restating the styles for the `.ng-hide` - * class in CSS: - * - * ```css - * .ng-hide { - * /* this is just another form of hiding an element */ - * display:block!important; - * position:absolute; - * top:-9999px; - * left:-9999px; - * } - * ``` - * - * By default you don't need to override in CSS anything and the animations will work around the display style. - * - * ## A note about animations with ngShow - * - * Animations in ngShow/ngHide work with the show and hide events that are triggered when the directive expression - * is true and false. This system works like the animation system present with ngClass except that - * you must also include the !important flag to override the display property - * so that you can perform an animation when the element is hidden during the time of the animation. - * - * ```css - * // - * //a working example can be found at the bottom of this page - * // - * .my-element.ng-hide-add, .my-element.ng-hide-remove { - * /* this is required as of 1.3x to properly - * apply all styling in a show/hide animation */ - * transition:0s linear all; - * } - * - * .my-element.ng-hide-add-active, - * .my-element.ng-hide-remove-active { - * /* the transition is defined in the active class */ - * transition:1s linear all; - * } - * - * .my-element.ng-hide-add { ... } - * .my-element.ng-hide-add.ng-hide-add-active { ... } - * .my-element.ng-hide-remove { ... } - * .my-element.ng-hide-remove.ng-hide-remove-active { ... } - * ``` - * - * Keep in mind that, as of AngularJS version 1.3.0-beta.11, there is no need to change the display - * property to block during animation states--ngAnimate will handle the style toggling automatically for you. - * - * @animations - * addClass: .ng-hide - happens after the ngShow expression evaluates to a truthy value and the just before contents are set to visible - * removeClass: .ng-hide - happens after the ngShow expression evaluates to a non truthy value and just before the contents are set to hidden - * - * @element ANY - * @param {expression} ngShow If the {@link guide/expression expression} is truthy - * then the element is shown or hidden respectively. - * - * @example - - - Click me:
-
- Show: -
- I show up when your checkbox is checked. -
-
-
- Hide: -
- I hide when your checkbox is checked. -
-
-
- - @import url(//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap-glyphicons.css); - - - .animate-show { - line-height:20px; - opacity:1; - padding:10px; - border:1px solid black; - background:white; - } - - .animate-show.ng-hide-add.ng-hide-add-active, - .animate-show.ng-hide-remove.ng-hide-remove-active { - -webkit-transition:all linear 0.5s; - transition:all linear 0.5s; - } - - .animate-show.ng-hide { - line-height:0; - opacity:0; - padding:0 10px; - } - - .check-element { - padding:10px; - border:1px solid black; - background:white; - } - - - var thumbsUp = element(by.css('span.glyphicon-thumbs-up')); - var thumbsDown = element(by.css('span.glyphicon-thumbs-down')); - - it('should check ng-show / ng-hide', function() { - expect(thumbsUp.isDisplayed()).toBeFalsy(); - expect(thumbsDown.isDisplayed()).toBeTruthy(); - - element(by.model('checked')).click(); - - expect(thumbsUp.isDisplayed()).toBeTruthy(); - expect(thumbsDown.isDisplayed()).toBeFalsy(); - }); - -
- */ -var ngShowDirective = ['$animate', function($animate) { - return function(scope, element, attr) { - scope.$watch(attr.ngShow, function ngShowWatchAction(value){ - $animate[toBoolean(value) ? 'removeClass' : 'addClass'](element, 'ng-hide'); - }); - }; -}]; - - -/** - * @ngdoc directive - * @name ngHide - * - * @description - * The `ngHide` directive shows or hides the given HTML element based on the expression - * provided to the ngHide attribute. The element is shown or hidden by removing or adding - * the `ng-hide` CSS class onto the element. The `.ng-hide` CSS class is predefined - * in AngularJS and sets the display style to none (using an !important flag). - * For CSP mode please add `angular-csp.css` to your html file (see {@link ng.directive:ngCsp ngCsp}). - * - * ```html - * - *
- * - * - *
- * ``` - * - * When the ngHide expression evaluates to true then the .ng-hide CSS class is added to the class attribute - * on the element causing it to become hidden. When false, the ng-hide CSS class is removed - * from the element causing the element not to appear hidden. - * - *
- * **Note:** Here is a list of values that ngHide will consider as a falsy value (case insensitive):
- * "f" / "0" / "false" / "no" / "n" / "[]" - *
- * - * ## Why is !important used? - * - * You may be wondering why !important is used for the .ng-hide CSS class. This is because the `.ng-hide` selector - * can be easily overridden by heavier selectors. For example, something as simple - * as changing the display style on a HTML list item would make hidden elements appear visible. - * This also becomes a bigger issue when dealing with CSS frameworks. - * - * By using !important, the show and hide behavior will work as expected despite any clash between CSS selector - * specificity (when !important isn't used with any conflicting styles). If a developer chooses to override the - * styling to change how to hide an element then it is just a matter of using !important in their own CSS code. - * - * ### Overriding .ng-hide - * - * By default, the `.ng-hide` class will style the element with `display:none!important`. If you wish to change - * the hide behavior with ngShow/ngHide then this can be achieved by restating the styles for the `.ng-hide` - * class in CSS: - * - * ```css - * .ng-hide { - * /* this is just another form of hiding an element */ - * display:block!important; - * position:absolute; - * top:-9999px; - * left:-9999px; - * } - * ``` - * - * By default you don't need to override in CSS anything and the animations will work around the display style. - * - * ## A note about animations with ngHide - * - * Animations in ngShow/ngHide work with the show and hide events that are triggered when the directive expression - * is true and false. This system works like the animation system present with ngClass, except that the `.ng-hide` - * CSS class is added and removed for you instead of your own CSS class. - * - * ```css - * // - * //a working example can be found at the bottom of this page - * // - * .my-element.ng-hide-add, .my-element.ng-hide-remove { - * transition:0.5s linear all; - * } - * - * .my-element.ng-hide-add { ... } - * .my-element.ng-hide-add.ng-hide-add-active { ... } - * .my-element.ng-hide-remove { ... } - * .my-element.ng-hide-remove.ng-hide-remove-active { ... } - * ``` - * - * Keep in mind that, as of AngularJS version 1.3.0-beta.11, there is no need to change the display - * property to block during animation states--ngAnimate will handle the style toggling automatically for you. - * - * @animations - * removeClass: .ng-hide - happens after the ngHide expression evaluates to a truthy value and just before the contents are set to hidden - * addClass: .ng-hide - happens after the ngHide expression evaluates to a non truthy value and just before the contents are set to visible - * - * @element ANY - * @param {expression} ngHide If the {@link guide/expression expression} is truthy then - * the element is shown or hidden respectively. - * - * @example - - - Click me:
-
- Show: -
- I show up when your checkbox is checked. -
-
-
- Hide: -
- I hide when your checkbox is checked. -
-
-
- - @import url(//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap-glyphicons.css); - - - .animate-hide { - -webkit-transition:all linear 0.5s; - transition:all linear 0.5s; - line-height:20px; - opacity:1; - padding:10px; - border:1px solid black; - background:white; - } - - .animate-hide.ng-hide { - line-height:0; - opacity:0; - padding:0 10px; - } - - .check-element { - padding:10px; - border:1px solid black; - background:white; - } - - - var thumbsUp = element(by.css('span.glyphicon-thumbs-up')); - var thumbsDown = element(by.css('span.glyphicon-thumbs-down')); - - it('should check ng-show / ng-hide', function() { - expect(thumbsUp.isDisplayed()).toBeFalsy(); - expect(thumbsDown.isDisplayed()).toBeTruthy(); - - element(by.model('checked')).click(); - - expect(thumbsUp.isDisplayed()).toBeTruthy(); - expect(thumbsDown.isDisplayed()).toBeFalsy(); - }); - -
- */ -var ngHideDirective = ['$animate', function($animate) { - return function(scope, element, attr) { - scope.$watch(attr.ngHide, function ngHideWatchAction(value){ - $animate[toBoolean(value) ? 'addClass' : 'removeClass'](element, 'ng-hide'); - }); - }; -}]; - -/** - * @ngdoc directive - * @name ngStyle - * @restrict AC - * - * @description - * The `ngStyle` directive allows you to set CSS style on an HTML element conditionally. - * - * @element ANY - * @param {expression} ngStyle - * - * {@link guide/expression Expression} which evals to an - * object whose keys are CSS style names and values are corresponding values for those CSS - * keys. - * - * Since some CSS style names are not valid keys for an object, they must be quoted. - * See the 'background-color' style in the example below. - * - * @example - - - - - -
- Sample Text -
myStyle={{myStyle}}
-
- - span { - color: black; - } - - - var colorSpan = element(by.css('span')); - - it('should check ng-style', function() { - expect(colorSpan.getCssValue('color')).toBe('rgba(0, 0, 0, 1)'); - element(by.css('input[value=\'set color\']')).click(); - expect(colorSpan.getCssValue('color')).toBe('rgba(255, 0, 0, 1)'); - element(by.css('input[value=clear]')).click(); - expect(colorSpan.getCssValue('color')).toBe('rgba(0, 0, 0, 1)'); - }); - -
- */ -var ngStyleDirective = ngDirective(function(scope, element, attr) { - scope.$watch(attr.ngStyle, function ngStyleWatchAction(newStyles, oldStyles) { - if (oldStyles && (newStyles !== oldStyles)) { - forEach(oldStyles, function(val, style) { element.css(style, '');}); - } - if (newStyles) element.css(newStyles); - }, true); -}); - -/** - * @ngdoc directive - * @name ngSwitch - * @restrict EA - * - * @description - * The `ngSwitch` directive is used to conditionally swap DOM structure on your template based on a scope expression. - * Elements within `ngSwitch` but without `ngSwitchWhen` or `ngSwitchDefault` directives will be preserved at the location - * as specified in the template. - * - * The directive itself works similar to ngInclude, however, instead of downloading template code (or loading it - * from the template cache), `ngSwitch` simply chooses one of the nested elements and makes it visible based on which element - * matches the value obtained from the evaluated expression. In other words, you define a container element - * (where you place the directive), place an expression on the **`on="..."` attribute** - * (or the **`ng-switch="..."` attribute**), define any inner elements inside of the directive and place - * a when attribute per element. The when attribute is used to inform ngSwitch which element to display when the on - * expression is evaluated. If a matching expression is not found via a when attribute then an element with the default - * attribute is displayed. - * - *
- * Be aware that the attribute values to match against cannot be expressions. They are interpreted - * as literal string values to match against. - * For example, **`ng-switch-when="someVal"`** will match against the string `"someVal"` not against the - * value of the expression `$scope.someVal`. - *
- - * @animations - * enter - happens after the ngSwitch contents change and the matched child element is placed inside the container - * leave - happens just after the ngSwitch contents change and just before the former contents are removed from the DOM - * - * @usage - * - * ``` - * - * ... - * ... - * ... - * - * ``` - * - * - * @scope - * @priority 800 - * @param {*} ngSwitch|on expression to match against ng-switch-when. - * On child elements add: - * - * * `ngSwitchWhen`: the case statement to match against. If match then this - * case will be displayed. If the same match appears multiple times, all the - * elements will be displayed. - * * `ngSwitchDefault`: the default case when no other case match. If there - * are multiple default cases, all of them will be displayed when no other - * case match. - * - * - * @example - - -
- - selection={{selection}} -
-
-
Settings Div
-
Home Span
-
default
-
-
-
- - function Ctrl($scope) { - $scope.items = ['settings', 'home', 'other']; - $scope.selection = $scope.items[0]; - } - - - .animate-switch-container { - position:relative; - background:white; - border:1px solid black; - height:40px; - overflow:hidden; - } - - .animate-switch { - padding:10px; - } - - .animate-switch.ng-animate { - -webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; - transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; - - position:absolute; - top:0; - left:0; - right:0; - bottom:0; - } - - .animate-switch.ng-leave.ng-leave-active, - .animate-switch.ng-enter { - top:-50px; - } - .animate-switch.ng-leave, - .animate-switch.ng-enter.ng-enter-active { - top:0; - } - - - var switchElem = element(by.css('[ng-switch]')); - var select = element(by.model('selection')); - - it('should start in settings', function() { - expect(switchElem.getText()).toMatch(/Settings Div/); - }); - it('should change to home', function() { - select.element.all(by.css('option')).get(1).click(); - expect(switchElem.getText()).toMatch(/Home Span/); - }); - it('should select default', function() { - select.element.all(by.css('option')).get(2).click(); - expect(switchElem.getText()).toMatch(/default/); - }); - -
- */ -var ngSwitchDirective = ['$animate', function($animate) { - return { - restrict: 'EA', - require: 'ngSwitch', - - // asks for $scope to fool the BC controller module - controller: ['$scope', function ngSwitchController() { - this.cases = {}; - }], - link: function(scope, element, attr, ngSwitchController) { - var watchExpr = attr.ngSwitch || attr.on, - selectedTranscludes = [], - selectedElements = [], - previousElements = [], - selectedScopes = []; - - scope.$watch(watchExpr, function ngSwitchWatchAction(value) { - var i, ii; - for (i = 0, ii = previousElements.length; i < ii; ++i) { - previousElements[i].remove(); - } - previousElements.length = 0; - - for (i = 0, ii = selectedScopes.length; i < ii; ++i) { - var selected = selectedElements[i]; - selectedScopes[i].$destroy(); - previousElements[i] = selected; - $animate.leave(selected, function() { - previousElements.splice(i, 1); - }); - } - - selectedElements.length = 0; - selectedScopes.length = 0; - - if ((selectedTranscludes = ngSwitchController.cases['!' + value] || ngSwitchController.cases['?'])) { - scope.$eval(attr.change); - forEach(selectedTranscludes, function(selectedTransclude) { - var selectedScope = scope.$new(); - selectedScopes.push(selectedScope); - selectedTransclude.transclude(selectedScope, function(caseElement) { - var anchor = selectedTransclude.element; - - selectedElements.push(caseElement); - $animate.enter(caseElement, anchor.parent(), anchor); - }); - }); - } - }); - } - }; -}]; - -var ngSwitchWhenDirective = ngDirective({ - transclude: 'element', - priority: 800, - require: '^ngSwitch', - link: function(scope, element, attrs, ctrl, $transclude) { - ctrl.cases['!' + attrs.ngSwitchWhen] = (ctrl.cases['!' + attrs.ngSwitchWhen] || []); - ctrl.cases['!' + attrs.ngSwitchWhen].push({ transclude: $transclude, element: element }); - } -}); - -var ngSwitchDefaultDirective = ngDirective({ - transclude: 'element', - priority: 800, - require: '^ngSwitch', - link: function(scope, element, attr, ctrl, $transclude) { - ctrl.cases['?'] = (ctrl.cases['?'] || []); - ctrl.cases['?'].push({ transclude: $transclude, element: element }); - } -}); - -/** - * @ngdoc directive - * @name ngTransclude - * @restrict AC - * - * @description - * Directive that marks the insertion point for the transcluded DOM of the nearest parent directive that uses transclusion. - * - * Any existing content of the element that this directive is placed on will be removed before the transcluded content is inserted. - * - * @element ANY - * - * @example - - - -
-
-
- {{text}} -
-
- - it('should have transcluded', function() { - var titleElement = element(by.model('title')); - titleElement.clear(); - titleElement.sendKeys('TITLE'); - var textElement = element(by.model('text')); - textElement.clear(); - textElement.sendKeys('TEXT'); - expect(element(by.binding('title')).getText()).toEqual('TITLE'); - expect(element(by.binding('text')).getText()).toEqual('TEXT'); - }); - -
- * - */ -var ngTranscludeDirective = ngDirective({ - link: function($scope, $element, $attrs, controller, $transclude) { - if (!$transclude) { - throw minErr('ngTransclude')('orphan', - 'Illegal use of ngTransclude directive in the template! ' + - 'No parent directive that requires a transclusion found. ' + - 'Element: {0}', - startingTag($element)); - } - - $transclude(function(clone) { - $element.empty(); - $element.append(clone); - }); - } -}); - -/** - * @ngdoc directive - * @name script - * @restrict E - * - * @description - * Load the content of a ` - - Load inlined template -
- - - it('should load template defined inside script tag', function() { - element(by.css('#tpl-link')).click(); - expect(element(by.css('#tpl-content')).getText()).toMatch(/Content of the template/); - }); - - - */ -var scriptDirective = ['$templateCache', function($templateCache) { - return { - restrict: 'E', - terminal: true, - compile: function(element, attr) { - if (attr.type == 'text/ng-template') { - var templateUrl = attr.id, - // IE is not consistent, in scripts we have to read .text but in other nodes we have to read .textContent - text = element[0].text; - - $templateCache.put(templateUrl, text); - } - } - }; -}]; - -var ngOptionsMinErr = minErr('ngOptions'); -/** - * @ngdoc directive - * @name select - * @restrict E - * - * @description - * HTML `SELECT` element with angular data-binding. - * - * # `ngOptions` - * - * The `ngOptions` attribute can be used to dynamically generate a list of `` - * DOM element. - * * `trackexpr`: Used when working with an array of objects. The result of this expression will be - * used to identify the objects in the array. The `trackexpr` will most likely refer to the - * `value` variable (e.g. `value.propertyName`). - * - * @example - - - -
-
    -
  • - Name: - [X] -
  • -
  • - [add] -
  • -
-
- Color (null not allowed): -
- - Color (null allowed): - - -
- - Color grouped by shade: -
- - - Select bogus.
-
- Currently selected: {{ {selected_color:myColor} }} -
-
-
-
- - it('should check ng-options', function() { - expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('red'); - element.all(by.select('myColor')).first().click(); - element.all(by.css('select[ng-model="myColor"] option')).first().click(); - expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('black'); - element(by.css('.nullable select[ng-model="myColor"]')).click(); - element.all(by.css('.nullable select[ng-model="myColor"] option')).first().click(); - expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('null'); - }); - -
- */ - -var ngOptionsDirective = valueFn({ terminal: true }); -// jshint maxlen: false -var selectDirective = ['$compile', '$parse', function($compile, $parse) { - //000011111111110000000000022222222220000000000000000000003333333333000000000000004444444444444440000000005555555555555550000000666666666666666000000000000000777777777700000000000000000008888888888 - var NG_OPTIONS_REGEXP = /^\s*([\s\S]+?)(?:\s+as\s+([\s\S]+?))?(?:\s+group\s+by\s+([\s\S]+?))?\s+for\s+(?:([\$\w][\$\w]*)|(?:\(\s*([\$\w][\$\w]*)\s*,\s*([\$\w][\$\w]*)\s*\)))\s+in\s+([\s\S]+?)(?:\s+track\s+by\s+([\s\S]+?))?$/, - nullModelCtrl = {$setViewValue: noop}; -// jshint maxlen: 100 - - return { - restrict: 'E', - require: ['select', '?ngModel'], - controller: ['$element', '$scope', '$attrs', function($element, $scope, $attrs) { - var self = this, - optionsMap = {}, - ngModelCtrl = nullModelCtrl, - nullOption, - unknownOption; - - - self.databound = $attrs.ngModel; - - - self.init = function(ngModelCtrl_, nullOption_, unknownOption_) { - ngModelCtrl = ngModelCtrl_; - nullOption = nullOption_; - unknownOption = unknownOption_; - }; - - - self.addOption = function(value) { - assertNotHasOwnProperty(value, '"option value"'); - optionsMap[value] = true; - - if (ngModelCtrl.$viewValue == value) { - $element.val(value); - if (unknownOption.parent()) unknownOption.remove(); - } - }; - - - self.removeOption = function(value) { - if (this.hasOption(value)) { - delete optionsMap[value]; - if (ngModelCtrl.$viewValue == value) { - this.renderUnknownOption(value); - } - } - }; - - - self.renderUnknownOption = function(val) { - var unknownVal = '? ' + hashKey(val) + ' ?'; - unknownOption.val(unknownVal); - $element.prepend(unknownOption); - $element.val(unknownVal); - unknownOption.prop('selected', true); // needed for IE - }; - - - self.hasOption = function(value) { - return optionsMap.hasOwnProperty(value); - }; - - $scope.$on('$destroy', function() { - // disable unknown option so that we don't do work when the whole select is being destroyed - self.renderUnknownOption = noop; - }); - }], - - link: function(scope, element, attr, ctrls) { - // if ngModel is not defined, we don't need to do anything - if (!ctrls[1]) return; - - var selectCtrl = ctrls[0], - ngModelCtrl = ctrls[1], - multiple = attr.multiple, - optionsExp = attr.ngOptions, - nullOption = false, // if false, user will not be able to select it (used by ngOptions) - emptyOption, - // we can't just jqLite('