diff --git a/README.md b/README.md
index 94b0832406c..470767a1623 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,6 @@
# core-java
-The Java implementation of the framework core.
[](https://codecov.io/github/SpineEventEngine/core-java?branch=master)
+[](http://www.apache.org/licenses/LICENSE-2.0)
+
+The Java implementation of the framework core.
diff --git a/build.gradle b/build.gradle
index 2ffd2f3cc7c..9a080eebfb8 100644
--- a/build.gradle
+++ b/build.gradle
@@ -15,7 +15,7 @@ allprojects {
apply plugin: 'jacoco'
group = 'org.spine3'
- version = '0.4.2'
+ version = '0.4.4-SNAPSHOT'
}
project.ext {
@@ -24,7 +24,7 @@ project.ext {
PROTOBUF_VERSION = '3.0.0-beta-3';
PROTOBUF_DEPENDENCY = "com.google.protobuf:protoc:${project.PROTOBUF_VERSION}";
MAVEN_REPOSITORY_URL = 'http://maven.teamdev.com/repository/spine';
- SPINE_PROTOBUF_PLUGIN_VERSION = "1.4.3"
+ SPINE_PROTOBUF_PLUGIN_VERSION = "1.4.4"
GRPC_VERSION = '0.14.0'
}
diff --git a/server/src/main/java/org/spine3/server/failure/FailureThrowable.java b/client/src/main/java/org/spine3/base/FailureThrowable.java
similarity index 97%
rename from server/src/main/java/org/spine3/server/failure/FailureThrowable.java
rename to client/src/main/java/org/spine3/base/FailureThrowable.java
index f0ef6f92d92..427e359acb2 100644
--- a/server/src/main/java/org/spine3/server/failure/FailureThrowable.java
+++ b/client/src/main/java/org/spine3/base/FailureThrowable.java
@@ -18,14 +18,13 @@
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
-package org.spine3.server.failure;
+package org.spine3.base;
import com.google.common.base.Throwables;
import com.google.protobuf.Any;
import com.google.protobuf.GeneratedMessage;
import com.google.protobuf.Timestamp;
import com.google.protobuf.util.TimeUtil;
-import org.spine3.base.Failure;
/**
* Abstract base for throwable business failures.
diff --git a/client/src/main/java/org/spine3/base/Mismatch.java b/client/src/main/java/org/spine3/base/Mismatch.java
new file mode 100644
index 00000000000..f3ff500761b
--- /dev/null
+++ b/client/src/main/java/org/spine3/base/Mismatch.java
@@ -0,0 +1,187 @@
+/*
+ * Copyright 2016, TeamDev Ltd. All rights reserved.
+ *
+ * Redistribution and use in source and/or binary forms, with or without
+ * modification, must retain the above copyright notice and the following
+ * disclaimer.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.spine3.base;
+
+import com.google.protobuf.Any;
+import com.google.protobuf.Message;
+
+import javax.annotation.Nullable;
+
+import static org.spine3.protobuf.Values.pack;
+import static org.spine3.protobuf.Messages.toAny;
+
+
+/**
+ * Factories for constructing {@link ValueMismatch} instances for different types of attributes.
+ */
+public class Mismatch {
+
+ private Mismatch() {
+ }
+
+ /**
+ * Creates a {@link ValueMismatch} instance for a string attribute.
+ *
+ * @param expected the value expected by a command, or {@code null} if the command expects not populated field
+ * @param actual the value found in an entity, or {@code null} if the value is not set
+ * @param requested the value requested as new one in the original command
+ * @param version the current version of the entity
+ * @return info on the mismatch
+ */
+ public static ValueMismatch of(@Nullable String expected, @Nullable String actual, String requested, int version) {
+ final ValueMismatch.Builder builder = ValueMismatch.newBuilder();
+ if (expected != null) {
+ builder.setExpected(pack(expected));
+ }
+
+ if (actual != null) {
+ builder.setActual(pack(actual));
+ }
+
+ builder.setRequested(pack(requested));
+ builder.setVersion(version);
+
+ return builder.build();
+ }
+
+ /**
+ * Creates a {@link ValueMismatch} instance for a integer attribute.
+ *
+ * @param expected the value expected by a command
+ * @param actual the value actual in an entity
+ * @param requested the value requested as new one in the original command
+ * @param version the current version of the entity
+ * @return info on the mismatch
+ */
+ public static ValueMismatch of(int expected, int actual, int requested, int version) {
+ final ValueMismatch.Builder builder = ValueMismatch.newBuilder();
+ builder.setExpected(pack(expected));
+ builder.setActual(pack(actual));
+ builder.setRequested(pack(requested));
+ builder.setVersion(version);
+
+ return builder.build();
+ }
+
+ /**
+ * Creates a {@link ValueMismatch} instance for a long integer attribute.
+ *
+ * @param expected the value expected by a command
+ * @param actual the value actual in an entity
+ * @param requested the value requested as new one in the original command
+ * @param version the current version of the entity
+ * @return info on the mismatch
+ */
+ public static ValueMismatch of(long expected, long actual, long requested, int version) {
+ final ValueMismatch.Builder builder = ValueMismatch.newBuilder();
+ builder.setExpected(pack(expected));
+ builder.setActual(pack(actual));
+ builder.setRequested(pack(requested));
+ builder.setVersion(version);
+
+ return builder.build();
+ }
+
+ /**
+ * Creates a {@link ValueMismatch} instance for a float attribute.
+ *
+ * @param expected the value expected by a command
+ * @param actual the value actual in an entity
+ * @param requested the value requested as new one in the original command
+ * @param version the current version of the entity
+ * @return info on the mismatch
+ */
+ public static ValueMismatch of(float expected, float actual, float requested, int version) {
+ final ValueMismatch.Builder builder = ValueMismatch.newBuilder();
+ builder.setExpected(pack(expected));
+ builder.setActual(pack(actual));
+ builder.setRequested(pack(requested));
+ builder.setVersion(version);
+
+ return builder.build();
+ }
+
+ /**
+ * Creates a {@link ValueMismatch} instance for a double attribute.
+ *
+ * @param expected the value expected by a command
+ * @param actual the value actual in an entity
+ * @param requested the value requested as new one in the original command
+ * @param version the current version of the entity
+ * @return info on the mismatch
+ */
+ public static ValueMismatch of(double expected, double actual, double requested, int version) {
+ final ValueMismatch.Builder builder = ValueMismatch.newBuilder();
+ builder.setExpected(pack(expected));
+ builder.setActual(pack(actual));
+ builder.setRequested(pack(requested));
+ builder.setVersion(version);
+
+ return builder.build();
+ }
+
+ /**
+ * Creates a {@link ValueMismatch} instance for a boolean attribute.
+ *
+ * @param expected the value expected by a command
+ * @param actual the value actual in an entity
+ * @param requested the value requested as new one in the original command
+ * @param version the current version of the entity
+ * @return info on the mismatch
+ */
+ public static ValueMismatch of(boolean expected, boolean actual, boolean requested, int version) {
+ final ValueMismatch.Builder builder = ValueMismatch.newBuilder();
+ builder.setExpected(pack(expected));
+ builder.setActual(pack(actual));
+ builder.setRequested(pack(requested));
+ builder.setVersion(version);
+
+ return builder.build();
+ }
+
+ /**
+ * Creates a {@link ValueMismatch} instance for a Message attribute.
+ *
+ * @param expected the value expected by a command, or {@code null} if the command expects not populated field
+ * @param actual the value actual in an entity, or {@code null} if the value is not set
+ * @param requested the value requested as new one in the original command
+ * @param version the current version of the entity
+ * @return info on the mismatch
+ */
+ public static ValueMismatch of(@Nullable Message expected, @Nullable Message actual, Message requested, int version) {
+ final ValueMismatch.Builder builder = ValueMismatch.newBuilder();
+
+ if (expected != null) {
+ builder.setExpected(toAny(expected));
+ }
+
+ if (actual != null) {
+ builder.setActual(toAny(actual));
+ }
+
+ final Any requestedAny = toAny(requested);
+
+ builder.setRequested(requestedAny);
+ builder.setVersion(version);
+
+ return builder.build();
+ }
+}
diff --git a/client/src/main/java/org/spine3/protobuf/Values.java b/client/src/main/java/org/spine3/protobuf/Values.java
index ef3b223cdd8..41a2d8cb33b 100644
--- a/client/src/main/java/org/spine3/protobuf/Values.java
+++ b/client/src/main/java/org/spine3/protobuf/Values.java
@@ -19,6 +19,7 @@
*/
package org.spine3.protobuf;
+import com.google.protobuf.Any;
import com.google.protobuf.BoolValue;
import com.google.protobuf.DoubleValue;
import com.google.protobuf.FloatValue;
@@ -37,7 +38,7 @@ public class Values {
private Values() {}
/**
- * Creates a new StringValue wrapping the passed string.
+ * Creates a new {@code StringValue} wrapping the passed string.
*
* @param value the value to wrap
* @return a new StringValue instance
@@ -50,7 +51,15 @@ public static StringValue newStringValue(String value) {
}
/**
- * Creates a new DoubleValue wrapping the passed number.
+ * Packs the passed value into {@link Any}.
+ */
+ public static Any pack(String value) {
+ final Any result = Any.pack(newStringValue(value));
+ return result;
+ }
+
+ /**
+ * Creates a new {@code DoubleValue} wrapping the passed number.
*
* @param value the value to wrap
* @return a new DoubleValue instance
@@ -63,7 +72,15 @@ public static DoubleValue newDoubleValue(double value) {
}
/**
- * Creates a new FloatValue wrapping the passed number.
+ * Packs the passed value into {@link Any}.
+ */
+ public static Any pack(double value) {
+ final Any result = Any.pack(newDoubleValue(value));
+ return result;
+ }
+
+ /**
+ * Creates a new {@code FloatValue} wrapping the passed number.
*
* @param value the value to wrap
* @return a new FloatValue instance
@@ -76,7 +93,15 @@ public static FloatValue newFloatValue(float value) {
}
/**
- * Creates a new Int32Value wrapping the passed number.
+ * Packs the passed value into {@link Any}.
+ */
+ public static Any pack(float value) {
+ final Any result = Any.pack(newFloatValue(value));
+ return result;
+ }
+
+ /**
+ * Creates a new {@code Int32Value} wrapping the passed number.
*
* @param value the value to wrap
* @return a new Int32Value instance
@@ -89,7 +114,15 @@ public static Int32Value newIntegerValue(int value) {
}
/**
- * Creates a new Int64Value wrapping the passed number.
+ * Packs the passed value into {@link Any}.
+ */
+ public static Any pack(int value) {
+ final Any result = Any.pack(newIntegerValue(value));
+ return result;
+ }
+
+ /**
+ * Creates a new {@code Int64Value} wrapping the passed number.
*
* @param value the value to wrap
* @return a new Int64Value instance
@@ -102,7 +135,15 @@ public static Int64Value newLongValue(long value) {
}
/**
- * Creates a new BoolValue wrapping the passed value.
+ * Packs the passed value into {@link Any}.
+ */
+ public static Any pack(long value) {
+ final Any result = Any.pack(newLongValue(value));
+ return result;
+ }
+
+ /**
+ * Creates a new {@code BoolValue} wrapping the passed value.
*
* @param value the value to wrap
* @return a new BoolValue instance
@@ -113,4 +154,12 @@ public static BoolValue newBoolValue(boolean value) {
.build();
return result;
}
+
+ /**
+ * Packs the passed value into {@link Any}.
+ */
+ public static Any pack(boolean value) {
+ final Any result = Any.pack(newBoolValue(value));
+ return result;
+ }
}
diff --git a/client/src/main/proto/spine/base/failure.proto b/client/src/main/proto/spine/base/failure.proto
index 2be5ff2ad5e..7a3e402b7d1 100644
--- a/client/src/main/proto/spine/base/failure.proto
+++ b/client/src/main/proto/spine/base/failure.proto
@@ -50,3 +50,21 @@ message Failure {
// Additional information on the failure.
map attributes = 4;
}
+
+// A failure to handle a command because an entity contains a value different than requested in a command.
+message ValueMismatch {
+ // An expected value.
+ // This field is not populated if the command expects to initialize the attribute.
+ google.protobuf.Any expected = 1;
+
+ // An actual value in the entity.
+ // This field is not populated if the entity does not have the attribute set.
+ google.protobuf.Any actual = 2;
+
+ // An optional attribute that may contain a value requested as the replacement
+ // for the `expected` value in the failed command.
+ google.protobuf.Any requested = 3;
+
+ // A version of the entity, which generated the failure.
+ int32 version = 4;
+}
\ No newline at end of file
diff --git a/client/src/main/proto/spine/command_annotations.proto b/client/src/main/proto/spine/command_annotations.proto
new file mode 100644
index 00000000000..dd3245c4933
--- /dev/null
+++ b/client/src/main/proto/spine/command_annotations.proto
@@ -0,0 +1,55 @@
+//
+// Copyright 2016, TeamDev Ltd. All rights reserved.
+//
+// Redistribution and use in source and/or binary forms, with or without
+// modification, must retain the above copyright notice and the following
+// disclaimer.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+syntax = "proto3";
+
+// We do not define the package for this file to allow shorter options for user-defined types.
+// This would allow to write:
+//
+// option (events) = "MyFirstEvent, MySecondEvent";
+//
+// instead of:
+//
+// option (spine.annotations.events) = "MyFirstEvent, MySecondEvent";
+//
+
+option java_generate_equals_and_hash = false;
+option java_multiple_files = true;
+option java_outer_classname = "CommandAnnotationsProto";
+option java_package = "org.spine3.annotations";
+
+import "google/protobuf/descriptor.proto";
+
+
+//TODO:2016-06-10:alexander.yevsyukov: Obtain globally unique field numbers for options from Google.
+
+extend google.protobuf.MessageOptions {
+ // Specifies Protobuf type names of the events that are produced after a command is handled.
+ // The type names should be separated with commas. Space characters are allowed.
+ // If the events are of the same aggregate with the command, non-qualified names can be used.
+ // Otherwise, please use fully qualified names.
+ string events = 59000;
+
+ // Specifies Protobuf type names of the failures that can be returned to the attempt to handle
+ // a command if business conditions do not allow the command execution.
+ // The type names should be separated with commas. Space characters are allowed.
+ // If the failures are of the same aggregate with the command, non-qualified names can be used.
+ // Otherwise, please use fully qualified names.
+ string failures = 59001;
+}
\ No newline at end of file
diff --git a/client/src/test/java/org/spine3/base/MismatchShould.java b/client/src/test/java/org/spine3/base/MismatchShould.java
new file mode 100644
index 00000000000..7fb483ec098
--- /dev/null
+++ b/client/src/test/java/org/spine3/base/MismatchShould.java
@@ -0,0 +1,187 @@
+/*
+ * Copyright 2016, TeamDev Ltd. All rights reserved.
+ *
+ * Redistribution and use in source and/or binary forms, with or without
+ * modification, must retain the above copyright notice and the following
+ * disclaimer.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.spine3.base;
+
+import com.google.protobuf.BoolValue;
+import com.google.protobuf.DoubleValue;
+import com.google.protobuf.FloatValue;
+import com.google.protobuf.Int32Value;
+import com.google.protobuf.Int64Value;
+import org.junit.Test;
+import com.google.protobuf.StringValue;
+
+import static org.junit.Assert.*;
+import static org.spine3.protobuf.Messages.fromAny;
+import static org.spine3.protobuf.Values.newStringValue;
+import static org.spine3.test.Tests.hasPrivateUtilityConstructor;
+
+/**
+ * @author Andrey Lavrov
+ */
+public class MismatchShould {
+
+ private static final String REQUESTED = "requested";
+ private static final String EXPECTED = "expected";
+ private static final String ACTUAL = "ACTUAL";
+ private static final int VERSION = 0;
+ private static final String DEFAULT_VALUE = "";
+ public static final double DELTA = 0.01;
+
+ @Test
+ public void has_private_constructor() {
+ assertTrue(hasPrivateUtilityConstructor(Mismatch.class));
+ }
+
+ @Test
+ public void set_default_expected_value_if_it_was_passed_as_null() {
+ final ValueMismatch mismatch = Mismatch.of(null, ACTUAL, REQUESTED, VERSION);
+ final String expected = mismatch.getExpected()
+ .toString();
+
+ assertEquals(DEFAULT_VALUE, expected);
+ }
+
+ @Test
+ public void set_default_actual_value_if_it_was_passed_as_null() {
+ final ValueMismatch mismatch = Mismatch.of(EXPECTED, null, REQUESTED, VERSION);
+ final String actual = mismatch.getActual()
+ .toString();
+
+ assertEquals(DEFAULT_VALUE, actual);
+ }
+
+ @Test
+ public void return_mismatch_object_with_string_values() {
+ final ValueMismatch mismatch = Mismatch.of(EXPECTED, ACTUAL, REQUESTED, VERSION);
+ final StringValue expected = fromAny(mismatch.getExpected());
+ final StringValue actual = fromAny(mismatch.getActual());
+ final StringValue requested = fromAny(mismatch.getRequested());
+
+ assertEquals(EXPECTED, expected.getValue());
+ assertEquals(ACTUAL, actual.getValue());
+ assertEquals(REQUESTED, requested.getValue());
+ }
+
+ @Test
+ public void return_mismatch_object_with_int32_values() {
+ final int expected = 0;
+ final int actual = 1;
+ final int requested = 2;
+ final ValueMismatch mismatch = Mismatch.of(expected, actual, requested, VERSION);
+ final Int32Value expectedWrapper = fromAny(mismatch.getExpected());
+ final Int32Value actualWrapper = fromAny(mismatch.getActual());
+ final Int32Value requestedWrapper = fromAny(mismatch.getRequested());
+
+ assertEquals(expected, expectedWrapper.getValue());
+ assertEquals(actual, actualWrapper.getValue());
+ assertEquals(requested, requestedWrapper.getValue());
+ }
+
+ @Test
+ public void return_mismatch_object_with_int64_values() {
+ final long expected = 0L;
+ final long actual = 1L;
+ final long requested = 2L;
+ final ValueMismatch mismatch = Mismatch.of(expected, actual, requested, VERSION);
+ final Int64Value expectedWrapped = fromAny(mismatch.getExpected());
+ final Int64Value actualWrapped = fromAny(mismatch.getActual());
+ final Int64Value requestedWrapped = fromAny(mismatch.getRequested());
+
+ assertEquals(expected, expectedWrapped.getValue());
+ assertEquals(actual, actualWrapped.getValue());
+ assertEquals(requested, requestedWrapped.getValue());
+ }
+
+ @Test
+ public void return_mismatch_object_with_float_values() {
+ final float expected = 0.0F;
+ final float actual = 1.0F;
+ final float requested = 2.0F;
+ final ValueMismatch mismatch = Mismatch.of(expected, actual, requested, VERSION);
+ final FloatValue expectedWrapped = fromAny(mismatch.getExpected());
+ final FloatValue actualWrapped = fromAny(mismatch.getActual());
+ final FloatValue requestedWrapped = fromAny(mismatch.getRequested());
+
+ assertEquals(expected, expectedWrapped.getValue(), DELTA);
+ assertEquals(actual, actualWrapped.getValue(), DELTA);
+ assertEquals(requested, requestedWrapped.getValue(), DELTA);
+ }
+
+ @Test
+ public void return_mismatch_object_with_double_values() {
+ final double expected = 0.1;
+ final double actual = 0.2;
+ final double requested = 0.3;
+ final ValueMismatch mismatch = Mismatch.of(expected, actual, requested, VERSION);
+ final DoubleValue expectedWrapped = fromAny(mismatch.getExpected());
+ final DoubleValue actualWrapped = fromAny(mismatch.getActual());
+ final DoubleValue requestedWrapped = fromAny(mismatch.getRequested());
+
+ assertEquals(expected, expectedWrapped.getValue(), DELTA);
+ assertEquals(actual, actualWrapped.getValue(), DELTA);
+ assertEquals(requested, requestedWrapped.getValue(), DELTA);
+ }
+
+ @Test
+ public void return_mismatch_object_with_boolean_values() {
+ final boolean expected = true;
+ final boolean actual = false;
+ final boolean requested = true;
+ final ValueMismatch mismatch = Mismatch.of(expected, actual, requested, VERSION);
+ final BoolValue expectedWrapped = fromAny(mismatch.getExpected());
+ final BoolValue actualWrapped = fromAny(mismatch.getActual());
+ final BoolValue requestedWrapped = fromAny(mismatch.getRequested());
+
+ assertEquals(expected, expectedWrapped.getValue());
+ assertEquals(actual, actualWrapped.getValue());
+ assertEquals(requested, requestedWrapped.getValue());
+ }
+
+ @Test
+ public void set_default_expected_value_if_it_was_passed_as_null_message_overload() {
+ final ValueMismatch mismatch = Mismatch.of(null, newStringValue(ACTUAL), newStringValue(REQUESTED), VERSION);
+ final String expected = mismatch.getExpected()
+ .toString();
+
+ assertEquals(DEFAULT_VALUE, expected);
+ }
+
+ @Test
+ public void set_default_actual_value_if_it_was_passed_as_null_message_overload() {
+ final ValueMismatch mismatch = Mismatch.of(newStringValue(EXPECTED), null, newStringValue(REQUESTED), VERSION);
+ final String actual = mismatch.getActual()
+ .toString();
+
+ assertEquals(DEFAULT_VALUE, actual);
+ }
+
+ @Test
+ public void return_mismatch_object_with_message_values() {
+ final ValueMismatch mismatch = Mismatch.of(newStringValue(EXPECTED), newStringValue(ACTUAL), newStringValue(REQUESTED), VERSION);
+ final StringValue expected = fromAny(mismatch.getExpected());
+ final StringValue actual = fromAny(mismatch.getActual());
+ final StringValue requested = fromAny(mismatch.getRequested());
+
+ assertEquals(EXPECTED, expected.getValue());
+ assertEquals(ACTUAL, actual.getValue());
+ assertEquals(REQUESTED, requested.getValue());
+ }
+}
diff --git a/client/src/test/java/org/spine3/protobuf/ValuesShould.java b/client/src/test/java/org/spine3/protobuf/ValuesShould.java
index 3fbae9aee6d..b2de59ec0dc 100644
--- a/client/src/test/java/org/spine3/protobuf/ValuesShould.java
+++ b/client/src/test/java/org/spine3/protobuf/ValuesShould.java
@@ -20,17 +20,20 @@
package org.spine3.protobuf;
+import com.google.protobuf.Any;
import com.google.protobuf.BoolValue;
import com.google.protobuf.DoubleValue;
import com.google.protobuf.FloatValue;
import com.google.protobuf.Int32Value;
import com.google.protobuf.Int64Value;
+import com.google.protobuf.Message;
import com.google.protobuf.StringValue;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.spine3.base.Identifiers.newUuid;
+import static org.spine3.protobuf.Messages.fromAny;
import static org.spine3.test.Tests.hasPrivateUtilityConstructor;
/**
@@ -39,6 +42,8 @@
@SuppressWarnings("InstanceMethodNamingConvention")
public class ValuesShould {
+ public static final double DELTA = 0.01;
+
@Test
public void have_private_constructor() {
assertTrue(hasPrivateUtilityConstructor(Values.class));
@@ -51,6 +56,14 @@ public void create_new_StringValue() {
assertEquals(value, msg.getValue());
}
+ @Test
+ public void create_new_Any_from_String() {
+ final String value = newUuid();
+ final Any msg = Values.pack(value);
+ final StringValue unpackedMsg = fromAny(msg);
+ assertEquals(value, unpackedMsg.getValue());
+ }
+
@Test
public void create_new_DoubleValue() {
final double value = 0.5;
@@ -58,6 +71,14 @@ public void create_new_DoubleValue() {
assertEquals(value, msg.getValue(), 0);
}
+ @Test
+ public void create_new_Any_from_double() {
+ final double value = 0.5;
+ final Any msg = Values.pack(value);
+ final DoubleValue unpackedMsg = fromAny(msg);
+ assertEquals(value, unpackedMsg.getValue(), DELTA);
+ }
+
@Test
public void create_new_FloatValue() {
final float value = 0.5F;
@@ -65,6 +86,14 @@ public void create_new_FloatValue() {
assertEquals(value, msg.getValue(), 0);
}
+ @Test
+ public void create_new_Any_from_float() {
+ final float value = 0.5F;
+ final Any msg = Values.pack(value);
+ final FloatValue unpackedMsg = fromAny(msg);
+ assertEquals(value, unpackedMsg.getValue(), DELTA);
+ }
+
@Test
public void create_new_Int32Value() {
final int value = 2;
@@ -72,6 +101,14 @@ public void create_new_Int32Value() {
assertEquals(value, msg.getValue());
}
+ @Test
+ public void create_new_Any_from_int32() {
+ final int value = 2;
+ final Any msg = Values.pack(value);
+ final Int32Value unpackedMsg = fromAny(msg);
+ assertEquals(value, unpackedMsg.getValue());
+ }
+
@Test
public void create_new_Int64Value() {
final long value = 2L;
@@ -79,10 +116,26 @@ public void create_new_Int64Value() {
assertEquals(value, msg.getValue());
}
+ @Test
+ public void create_new_Any_from_int64() {
+ final long value = 2L;
+ final Any msg = Values.pack(value);
+ final Int64Value unpackedMsg = fromAny(msg);
+ assertEquals(value, unpackedMsg.getValue());
+ }
+
@Test
public void create_new_BoolValue() {
final boolean value = true;
final BoolValue msg = Values.newBoolValue(value);
assertEquals(value, msg.getValue());
}
+
+ @Test
+ public void create_new_Any_from_boolean() {
+ final boolean value = true;
+ final Any msg = Values.pack(value);
+ final BoolValue unpackedMsg = fromAny(msg);
+ assertEquals(value, unpackedMsg.getValue());
+ }
}
diff --git a/examples/build.gradle b/examples/build.gradle
index 090830b0057..d8c8256f7d8 100644
--- a/examples/build.gradle
+++ b/examples/build.gradle
@@ -16,6 +16,14 @@ dependencies {
compile project(path: ':server');
}
+sourceSets {
+ main {
+ java {
+ srcDir "$projectDir/generated/main/spine"
+ }
+ }
+}
+
protobuf {
generateProtoTasks {
all().each { final task ->
diff --git a/examples/src/main/java/org/spine3/examples/failure/failures/CannotCancelTaskInProgress.java b/examples/src/main/java/org/spine3/examples/failure/failures/CannotCancelTaskInProgress.java
deleted file mode 100644
index af7b1c8d9d4..00000000000
--- a/examples/src/main/java/org/spine3/examples/failure/failures/CannotCancelTaskInProgress.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright 2016, TeamDev Ltd. All rights reserved.
- *
- * Redistribution and use in source and/or binary forms, with or without
- * modification, must retain the above copyright notice and the following
- * disclaimer.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-package org.spine3.examples.failure.failures;
-
-import org.spine3.examples.failure.TaskId;
-import org.spine3.server.failure.FailureThrowable;
-
-/**
- * @author Alexander Yevsyukov
- */
-public class CannotCancelTaskInProgress extends FailureThrowable {
-
- private static final long serialVersionUID = 0L;
-
- public CannotCancelTaskInProgress(TaskId taskId) {
- super(Failures.CannotCancelTaskInProgress.newBuilder().setId(taskId).build());
- }
-
- @Override
- public Failures.CannotCancelTaskInProgress getFailure() {
- return (Failures.CannotCancelTaskInProgress) super.getFailure();
- }
-}
diff --git a/examples/src/main/proto/spine_examples/failure/failures.proto b/examples/src/main/proto/spine_examples/failure/failures.proto
index 0b2d5b231fd..12cf273cacd 100644
--- a/examples/src/main/proto/spine_examples/failure/failures.proto
+++ b/examples/src/main/proto/spine_examples/failure/failures.proto
@@ -22,7 +22,12 @@ package spine_examples.failure;
option java_package="org.spine3.examples.failure.failures";
option java_multiple_files = false;
-// Do not specify java_outer_classname option as we're not generating multiple Java classes for failures.
+
+// Do not specify `java_outer_classname` option as we're not generating multiple Java classes for failures.
+// All failure messages will be generated as sub-classes of the Failure class by Protobuf compiler.
+// Then Spine Perotobuf Gradle plugin for Java will generate `FailureThrowable` classes for these messages.
+// See `org.spine3.examples.failure.TaskAggregate` for using failure throwables in an aggregate.
+
option java_generate_equals_and_hash = true;
import "spine_examples/failure/task.proto";
diff --git a/server/src/main/java/org/spine3/server/aggregate/AggregateRepository.java b/server/src/main/java/org/spine3/server/aggregate/AggregateRepository.java
index 45f21218a1c..bb78ced563e 100644
--- a/server/src/main/java/org/spine3/server/aggregate/AggregateRepository.java
+++ b/server/src/main/java/org/spine3/server/aggregate/AggregateRepository.java
@@ -25,13 +25,13 @@
import org.spine3.base.CommandId;
import org.spine3.base.Errors;
import org.spine3.base.Event;
+import org.spine3.base.FailureThrowable;
import org.spine3.server.BoundedContext;
import org.spine3.server.command.CommandDispatcher;
import org.spine3.server.command.CommandStatusService;
import org.spine3.server.entity.GetTargetIdFromCommand;
import org.spine3.server.entity.Repository;
import org.spine3.server.event.EventBus;
-import org.spine3.server.failure.FailureThrowable;
import org.spine3.server.storage.AggregateEvents;
import org.spine3.server.storage.AggregateStorage;
import org.spine3.server.storage.Storage;
diff --git a/server/src/main/java/org/spine3/server/command/CommandBus.java b/server/src/main/java/org/spine3/server/command/CommandBus.java
index 18e34e621a3..b8bbb9b0e37 100644
--- a/server/src/main/java/org/spine3/server/command/CommandBus.java
+++ b/server/src/main/java/org/spine3/server/command/CommandBus.java
@@ -34,6 +34,7 @@
import org.spine3.base.CommandId;
import org.spine3.base.Error;
import org.spine3.base.Errors;
+import org.spine3.base.FailureThrowable;
import org.spine3.base.Response;
import org.spine3.base.Responses;
import org.spine3.server.BoundedContext;
@@ -41,7 +42,6 @@
import org.spine3.server.command.error.CommandException;
import org.spine3.server.command.error.InvalidCommandException;
import org.spine3.server.command.error.UnsupportedCommandException;
-import org.spine3.server.failure.FailureThrowable;
import org.spine3.server.type.CommandClass;
import org.spine3.server.users.CurrentTenant;
import org.spine3.time.Interval;
diff --git a/server/src/main/java/org/spine3/server/command/CommandHandler.java b/server/src/main/java/org/spine3/server/command/CommandHandler.java
index 5466e97ca53..ac28b085827 100644
--- a/server/src/main/java/org/spine3/server/command/CommandHandler.java
+++ b/server/src/main/java/org/spine3/server/command/CommandHandler.java
@@ -31,9 +31,9 @@
import org.spine3.base.EventContext;
import org.spine3.base.EventId;
import org.spine3.base.Events;
+import org.spine3.base.FailureThrowable;
import org.spine3.server.entity.Entity;
import org.spine3.server.event.EventBus;
-import org.spine3.server.failure.FailureThrowable;
import org.spine3.server.reflect.CommandHandlerMethod;
import org.spine3.server.reflect.MethodRegistry;
diff --git a/server/src/main/java/org/spine3/server/command/CommandStatusService.java b/server/src/main/java/org/spine3/server/command/CommandStatusService.java
index 1e8e150f1cb..c4dfefcfa56 100644
--- a/server/src/main/java/org/spine3/server/command/CommandStatusService.java
+++ b/server/src/main/java/org/spine3/server/command/CommandStatusService.java
@@ -21,7 +21,7 @@
package org.spine3.server.command;
import org.spine3.base.CommandId;
-import org.spine3.server.failure.FailureThrowable;
+import org.spine3.base.FailureThrowable;
/**
* The service for updating a status of a command.
diff --git a/server/src/main/java/org/spine3/server/command/ProblemLog.java b/server/src/main/java/org/spine3/server/command/ProblemLog.java
index a5ee3e1c581..ea1e09e5b2e 100644
--- a/server/src/main/java/org/spine3/server/command/ProblemLog.java
+++ b/server/src/main/java/org/spine3/server/command/ProblemLog.java
@@ -23,7 +23,7 @@
import com.google.protobuf.Message;
import org.spine3.base.Command;
import org.spine3.base.CommandId;
-import org.spine3.server.failure.FailureThrowable;
+import org.spine3.base.FailureThrowable;
import static org.spine3.base.Commands.formatCommandTypeAndId;
import static org.spine3.base.Commands.formatMessageTypeAndId;
diff --git a/server/src/test/java/org/spine3/server/command/CommandBusShould.java b/server/src/test/java/org/spine3/server/command/CommandBusShould.java
index 5186b24853f..c7015ea5ea3 100644
--- a/server/src/test/java/org/spine3/server/command/CommandBusShould.java
+++ b/server/src/test/java/org/spine3/server/command/CommandBusShould.java
@@ -35,6 +35,7 @@
import org.spine3.base.CommandValidationError;
import org.spine3.base.Error;
import org.spine3.base.Errors;
+import org.spine3.base.FailureThrowable;
import org.spine3.base.Response;
import org.spine3.base.Responses;
import org.spine3.client.CommandFactory;
@@ -44,7 +45,6 @@
import org.spine3.server.command.error.InvalidCommandException;
import org.spine3.server.command.error.UnsupportedCommandException;
import org.spine3.server.event.EventBus;
-import org.spine3.server.failure.FailureThrowable;
import org.spine3.server.storage.memory.InMemoryStorageFactory;
import org.spine3.server.type.CommandClass;
import org.spine3.server.users.CurrentTenant;
diff --git a/server/src/test/java/org/spine3/server/procman/ProcessManagerRepositoryShould.java b/server/src/test/java/org/spine3/server/procman/ProcessManagerRepositoryShould.java
index a542f81a6a1..f9b6898a2ea 100644
--- a/server/src/test/java/org/spine3/server/procman/ProcessManagerRepositoryShould.java
+++ b/server/src/test/java/org/spine3/server/procman/ProcessManagerRepositoryShould.java
@@ -34,6 +34,7 @@
import org.spine3.base.Event;
import org.spine3.base.EventContext;
import org.spine3.base.Events;
+import org.spine3.base.FailureThrowable;
import org.spine3.server.BoundedContext;
import org.spine3.server.BoundedContextTestStubs;
import org.spine3.server.command.Assign;
@@ -41,7 +42,6 @@
import org.spine3.server.entity.IdFunction;
import org.spine3.server.event.GetProducerIdFromEvent;
import org.spine3.server.event.Subscribe;
-import org.spine3.server.failure.FailureThrowable;
import org.spine3.server.storage.memory.InMemoryStorageFactory;
import org.spine3.server.type.CommandClass;
import org.spine3.server.type.EventClass;
@@ -64,8 +64,8 @@
import static org.mockito.Mockito.verify;
import static org.spine3.protobuf.Messages.fromAny;
import static org.spine3.testdata.TestAggregateIdFactory.newProjectId;
-import static org.spine3.testdata.TestCommands.*;
import static org.spine3.testdata.TestCommandContextFactory.createCommandContext;
+import static org.spine3.testdata.TestCommands.*;
import static org.spine3.testdata.TestEventMessageFactory.*;
/**
diff --git a/values/src/main/proto/spine/time/time.proto b/values/src/main/proto/spine/time/time.proto
index aad103a8c90..5aaaa7f1f7f 100644
--- a/values/src/main/proto/spine/time/time.proto
+++ b/values/src/main/proto/spine/time/time.proto
@@ -26,6 +26,8 @@ option java_multiple_files = true;
option java_outer_classname = "TimeProto";
option java_package = "org.spine3.time";
+import "spine/validate.proto";
+
import "spine/time/zone_offset.proto";
import "google/protobuf/timestamp.proto";
import "google/protobuf/duration.proto";
@@ -51,17 +53,17 @@ enum MonthOfYear {
//
// Use this message for describing a date (e.g. a birthday).
message LocalDate {
- int32 year = 1;
- MonthOfYear month = 2;
- int32 day = 3;
+ int32 year = 1 [(required).value = true];
+ MonthOfYear month = 2 [(required).value = true];
+ int32 day = 3 [(required).value = true];
}
// A time without time-zone.
//
// It is a description of a time, not an instant on a time-line.
message LocalTime {
- int32 hours = 1;
- int32 minutes = 2;
+ int32 hours = 1 [(required).value = true];
+ int32 minutes = 2 [(required).value = true];
int32 seconds = 3;
int32 millis = 4;
int64 nanos = 5;
@@ -69,21 +71,21 @@ message LocalTime {
// A time with an offset from UTC.
message OffsetTime {
- LocalTime time = 1;
- ZoneOffset offset = 2;
+ LocalTime time = 1 [(required).value = true];
+ ZoneOffset offset = 2 [(required).value = true];
}
// A date with an offset from UTC.
message OffsetDate {
- LocalDate date = 1;
- ZoneOffset offset = 2;
+ LocalDate date = 1 [(required).value = true];
+ ZoneOffset offset = 2 [(required).value = true];
}
// A date-time with an offset from UTC.
message OffsetDateTime {
- LocalDate date = 1;
- LocalTime time = 2;
- ZoneOffset offset = 3;
+ LocalDate date = 1 [(required).value = true];
+ LocalTime time = 2; // A time field is optional for more flexibility.
+ ZoneOffset offset = 3 [(required).value = true];
}
// An interval between two points in time.