From 7a1f8f18bdb0a84c24d6981161c3b531046d7661 Mon Sep 17 00:00:00 2001 From: David Edwards Date: Fri, 19 Feb 2016 14:38:28 +0000 Subject: [PATCH] Add basic message classes and JsonObject utils --- messaging/messaging-core/pom.xml | 53 ++++ .../services/messaging/DefaultEnvelope.java | 32 +++ .../justice/services/messaging/Envelope.java | 13 + .../messaging/JsonObjectMetadata.java | 93 +++++++ .../services/messaging/JsonObjects.java | 231 +++++++++++++++++ .../justice/services/messaging/Metadata.java | 87 +++++++ .../messaging/DefaultEnvelopeTest.java | 43 ++++ .../messaging/JsonObjectMetadataTest.java | 165 ++++++++++++ .../services/messaging/JsonObjectsTest.java | 242 ++++++++++++++++++ messaging/pom.xml | 18 ++ pom.xml | 5 + 11 files changed, 982 insertions(+) create mode 100644 messaging/messaging-core/pom.xml create mode 100644 messaging/messaging-core/src/main/java/uk/gov/justice/services/messaging/DefaultEnvelope.java create mode 100644 messaging/messaging-core/src/main/java/uk/gov/justice/services/messaging/Envelope.java create mode 100644 messaging/messaging-core/src/main/java/uk/gov/justice/services/messaging/JsonObjectMetadata.java create mode 100644 messaging/messaging-core/src/main/java/uk/gov/justice/services/messaging/JsonObjects.java create mode 100644 messaging/messaging-core/src/main/java/uk/gov/justice/services/messaging/Metadata.java create mode 100644 messaging/messaging-core/src/test/java/uk/gov/justice/services/messaging/DefaultEnvelopeTest.java create mode 100644 messaging/messaging-core/src/test/java/uk/gov/justice/services/messaging/JsonObjectMetadataTest.java create mode 100644 messaging/messaging-core/src/test/java/uk/gov/justice/services/messaging/JsonObjectsTest.java create mode 100644 messaging/pom.xml diff --git a/messaging/messaging-core/pom.xml b/messaging/messaging-core/pom.xml new file mode 100644 index 000000000..2d40ec38c --- /dev/null +++ b/messaging/messaging-core/pom.xml @@ -0,0 +1,53 @@ + + + + messaging + uk.gov.justice.services + 0.1.0-SNAPSHOT + + 4.0.0 + + messaging-core + + + + javax.json + javax.json-api + + + com.google.guava + guava + + + + junit + junit + test + + + org.hamcrest + hamcrest-library + test + + + org.mockito + mockito-core + test + + + org.glassfish + javax.json + 1.0.4 + test + + + net.trajano.commons + commons-testing + test + + + + + diff --git a/messaging/messaging-core/src/main/java/uk/gov/justice/services/messaging/DefaultEnvelope.java b/messaging/messaging-core/src/main/java/uk/gov/justice/services/messaging/DefaultEnvelope.java new file mode 100644 index 000000000..b45d100a7 --- /dev/null +++ b/messaging/messaging-core/src/main/java/uk/gov/justice/services/messaging/DefaultEnvelope.java @@ -0,0 +1,32 @@ +package uk.gov.justice.services.messaging; + +import javax.json.JsonObject; + +/** + * Default implementation of an envelope. + */ +public class DefaultEnvelope implements Envelope { + + private Metadata metadata; + + private JsonObject payload; + + private DefaultEnvelope(final Metadata metadata, final JsonObject payload) { + this.metadata = metadata; + this.payload = payload; + } + + @Override + public Metadata metadata() { + return metadata; + } + + @Override + public JsonObject payload() { + return payload; + } + + public static Envelope envelopeFrom(final Metadata metadata, final JsonObject payload) { + return new DefaultEnvelope(metadata, payload); + } +} diff --git a/messaging/messaging-core/src/main/java/uk/gov/justice/services/messaging/Envelope.java b/messaging/messaging-core/src/main/java/uk/gov/justice/services/messaging/Envelope.java new file mode 100644 index 000000000..1f9563f3c --- /dev/null +++ b/messaging/messaging-core/src/main/java/uk/gov/justice/services/messaging/Envelope.java @@ -0,0 +1,13 @@ +package uk.gov.justice.services.messaging; + +import javax.json.JsonObject; + +/** + * Interface for a messaging envelope containing metadata and a payload. + */ +public interface Envelope { + + Metadata metadata(); + + JsonObject payload(); +} diff --git a/messaging/messaging-core/src/main/java/uk/gov/justice/services/messaging/JsonObjectMetadata.java b/messaging/messaging-core/src/main/java/uk/gov/justice/services/messaging/JsonObjectMetadata.java new file mode 100644 index 000000000..fda7badd6 --- /dev/null +++ b/messaging/messaging-core/src/main/java/uk/gov/justice/services/messaging/JsonObjectMetadata.java @@ -0,0 +1,93 @@ +package uk.gov.justice.services.messaging; + +import javax.json.JsonObject; +import javax.json.JsonString; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +import static uk.gov.justice.services.messaging.JsonObjects.getJsonString; +import static uk.gov.justice.services.messaging.JsonObjects.getLong; +import static uk.gov.justice.services.messaging.JsonObjects.getString; +import static uk.gov.justice.services.messaging.JsonObjects.getUUID; +import static uk.gov.justice.services.messaging.JsonObjects.getUUIDs; + +/** + * Implementation of metadata that uses a JsonObject internally to store the metadata. + */ +public class JsonObjectMetadata implements Metadata { + + private final JsonObject metadata; + + private JsonObjectMetadata(final JsonObject metadata) { + this.metadata = metadata; + } + + @Override + public UUID id() { + return getUUID(metadata, ID) + .orElseThrow(() -> new IllegalStateException("Missing id field")); + } + + @Override + public String name() { + return getString(metadata, NAME) + .orElseThrow(() -> new IllegalStateException("Missing name field")); + } + + @Override + public Optional clientCorrelationId() { + return getString(metadata, CLIENT_CORRELATION); + } + + @Override + public List causation() { + return getUUIDs(metadata, CAUSATION); + } + + @Override + public Optional userId() { + return getString(metadata, USER_ID); + } + + @Override + public Optional sessionId() { + return getString(metadata, SESSION_ID); + } + + @Override + public Optional streamId() { + return getUUID(metadata, STREAM_ID); + } + + @Override + public Optional version() { + return getLong(metadata, VERSION); + } + + @Override + public JsonObject asJsonObject() { + return metadata; + } + + /** + * Instantiate a {@link JsonObjectMetadata} object from a {@link JsonObject}. + * + * @param jsonObject the {@link JsonObject} to build the metadata from + * @return the {@link JsonObjectMetadata} + */ + public static Metadata metadataFrom(final JsonObject jsonObject) { + + JsonString id = getJsonString(jsonObject, Metadata.ID) + .orElseThrow(() -> new IllegalArgumentException("Missing id field")); + UUID.fromString(id.getString()); + + JsonString name = getJsonString(jsonObject, Metadata.NAME) + .orElseThrow(() -> new IllegalArgumentException("Missing name field")); + if (name.getString().isEmpty()) { + throw new IllegalArgumentException("Name field cannot be empty"); + } + + return new JsonObjectMetadata(jsonObject); + } +} diff --git a/messaging/messaging-core/src/main/java/uk/gov/justice/services/messaging/JsonObjects.java b/messaging/messaging-core/src/main/java/uk/gov/justice/services/messaging/JsonObjects.java new file mode 100644 index 000000000..95bed4f67 --- /dev/null +++ b/messaging/messaging-core/src/main/java/uk/gov/justice/services/messaging/JsonObjects.java @@ -0,0 +1,231 @@ +package uk.gov.justice.services.messaging; + +import com.google.common.collect.ImmutableList; + +import javax.json.JsonArray; +import javax.json.JsonNumber; +import javax.json.JsonObject; +import javax.json.JsonString; +import javax.json.JsonValue; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.UUID; +import java.util.function.BiFunction; +import java.util.function.Function; +import java.util.stream.Collectors; + +import static javax.json.JsonValue.ValueType; + +/** + * Collection of static utility methods for getting deep values from a {@link JsonObject}. + */ +public final class JsonObjects { + + /** + * Private constructor to prevent misuse of utility class. + */ + private JsonObjects() { + } + + /** + * Returns the (possibly nested) array value to which the specified name is mapped, if it exists. + * + * @param object the JsonObject from which to retrieve the value + * @param names the field name path whose associated value is to be returned + * @return the array value to which the specified name is mapped + * @throws IllegalStateException if the value is not assignable to JsonArray type + */ + public static Optional getJsonArray(final JsonObject object, final String... names) { + return getJsonValue(object, ValueType.ARRAY, JsonObject::getJsonArray, names); + } + + /** + * Returns the (possibly nested) object value to which the specified name is mapped, if it exists. + * + * @param object the JsonObject from which to retrieve the value + * @param names the field name path whose associated value is to be returned + * @return the object value to which the specified name is mapped, or + * {@code null} if this object contains no mapping for the name + * @throws IllegalStateException if the value is not assignable to JsonObject type + */ + public static Optional getJsonObject(final JsonObject object, final String... names) { + return getJsonValue(object, ValueType.OBJECT, JsonObject::getJsonObject, names); + } + + /** + * Returns the (possibly nested) number value to which the specified name is mapped, if it exists. + * + * @param object the JsonObject from which to retrieve the value + * @param names the field name path whose associated value is to be returned + * @return the number value to which the specified name is mapped + * @throws IllegalStateException if the value is not assignable to JsonNumber type + */ + public static Optional getJsonNumber(final JsonObject object, final String... names) { + return getJsonValue(object, ValueType.NUMBER, JsonObject::getJsonNumber, names); + } + + /** + * Returns the (possibly nested) string value to which the specified name is mapped, if it exists. + * + * @param object the JsonObject from which to retrieve the value + * @param names the field name path whose associated value is to be returned + * @return the string value to which the specified name is mapped + * @throws IllegalStateException if the value is not assignable to JsonString type + */ + public static Optional getJsonString(final JsonObject object, final String... names) { + return getJsonValue(object, ValueType.STRING, JsonObject::getJsonString, names); + } + + /** + * Returns a (possibly nested) value converted into a specified JsonValue type. + * + * @param object the JsonObject from which to retrieve the value + * @param valueType the type of JsonValue we need to return + * @param function the function to use to get the correct type of JsonValue from the JsonObject + * @param names the field name path whose associated value is to be returned + * @param the type of JsonValue that will be returned + * @return an optional value found at the specified location in the JsonObject + */ + private static Optional getJsonValue(final JsonObject object, + final ValueType valueType, + final BiFunction function, + final String... names) { + checkArguments(object, names); + if (names.length == 1) { + if (!object.containsKey(names[0])) { + return Optional.empty(); + } + ValueType actualValueType = object.get(names[0]).getValueType(); + + if (ValueType.NULL.equals(actualValueType)) { + return Optional.empty(); + } + + if (!valueType.equals(actualValueType)) { + throw new IllegalStateException(String.format("Field %s is not a %s", names[0], valueType.toString())); + } + + return Optional.of(function.apply(object, names[0])); + } else { + return getJsonObject(object, names[0]) + .flatMap(subObject -> getJsonValue(subObject, valueType, function, Arrays.copyOfRange(names, 1, names.length))); + } + } + + /** + * A convenience method for {@code getJsonString(name).get().getString()}. + * + * @param object the JsonObject from which to retrieve the value + * @param names whose associated value is to be returned as String + * @return the String value to which the specified name is mapped + * @throws IllegalStateException if the value is not assignable to JsonString type + */ + public static Optional getString(final JsonObject object, final String... names) { + return getJsonValue(object, ValueType.STRING, JsonObject::getJsonString, names) + .map(JsonString::getString); + } + + /** + * A convenience method for {@code UUID.fromString(getJsonString(name).get().getString())}. + * + * @param object the JsonObject from which to retrieve the value + * @param names whose associated value is to be returned as String + * @return the String value to which the specified name is mapped + * @throws IllegalStateException if the value is not assignable to JsonString type + * @throws IllegalArgumentException if the value is not assignable to a UUID + */ + public static Optional getUUID(final JsonObject object, final String... names) { + return getString(object, names) + .map(string -> { + try { + return UUID.fromString(string); + } catch (IllegalArgumentException ex) { + throw new IllegalStateException(String.format("Retrieved string '%s' is not a UUID", string), ex); + } + }); + } + + /** + * A convenience method for {@code JsonNumber.longValue}. + * + * @param object the JsonObject from which to retrieve the value + * @param names whose associated value is to be returned as Long + * @return the Long value to which the specified name is mapped + * @throws IllegalStateException if the value is not assignable to JsonNumber type + * @throws IllegalArgumentException if the value is not assignable to a Long + */ + public static Optional getLong(final JsonObject object, final String... names) { + return getJsonValue(object, ValueType.NUMBER, JsonObject::getJsonNumber, names) + .map(JsonNumber::longValue); + } + + /** + * A convenience method for getting a JsonArray as a List of a specific JsonValue type. + * + * @param object the JsonObject from which to retrieve the value + * @param names whose associated value is to be returned + * @param clazz the type of JsonValue that the returned list will contain + * @return the Long value to which the specified name is mapped + * @throws IllegalStateException if the value is not assignable to the specified type + * @throws IllegalArgumentException if the value is not assignable to a Long + */ + public static Optional> getList(final JsonObject object, final Class clazz, final String... names) { + return getJsonValue(object, ValueType.ARRAY, JsonObject::getJsonArray, names) + .map(jsonArray -> jsonArray.getValuesAs(clazz)) + .map(ImmutableList::copyOf); + } + + /** + * A convenience method for getting a list of a specific type. + * + * @param object the JsonObject from which to retrieve the value + * @param jsonClazz the JsonValue type that the value is stored as + * @param converter a function that can convert from the JsonValue class to the required type + * @param names whose associated value is to be returned + * @param the type of items in the return list + * @param the JsonValue type that the value is stored as + * @return an optional list of values found + */ + public static Optional> getList(final JsonObject object, + final Class jsonClazz, + final Function converter, + final String... names) { + return getList(object, jsonClazz, names) + .map(list -> list.stream() + .map(converter) + .collect(Collectors.toList())) + .map(ImmutableList::copyOf); + } + + /** + * Get a list of UUIDs from a JsonObject. + * + * @param object object the JsonObject from which to retrieve the list + * @param names the field name path whose associated value is to be returned + * @return the list of UUIDs or an empty list if none were found + */ + public static List getUUIDs(final JsonObject object, final String... names) { + return getList(object, JsonString.class, jsonString -> UUID.fromString(jsonString.getString()), names) + .orElse(Collections.emptyList()); + } + + /** + * Assert that the provided arguments are valid. The object must not be null, and the next field name must be non-empty. + * + * @param object the JsonObject from which to retrieve the value + * @param names the field names + */ + private static void checkArguments(final JsonObject object, final String... names) { + if (object == null) { + throw new IllegalArgumentException("Json object cannot be null"); + } + if (names.length == 0) { + throw new IllegalArgumentException("At least one level of field name must be provided"); + } + if (names[0] == null || names[0].isEmpty()) { + throw new IllegalArgumentException("Field name cannot be null or empty"); + } + } +} diff --git a/messaging/messaging-core/src/main/java/uk/gov/justice/services/messaging/Metadata.java b/messaging/messaging-core/src/main/java/uk/gov/justice/services/messaging/Metadata.java new file mode 100644 index 000000000..b729972eb --- /dev/null +++ b/messaging/messaging-core/src/main/java/uk/gov/justice/services/messaging/Metadata.java @@ -0,0 +1,87 @@ +package uk.gov.justice.services.messaging; + +import javax.json.JsonObject; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +/** + * Interface for an envelope's metadata. + */ +public interface Metadata { + + String ID = "id"; + String NAME = "name"; + String[] CLIENT_CORRELATION = new String[]{"correlation", "client"}; + String CONTEXT = "context"; + String[] USER_ID = new String[]{CONTEXT, "user"}; + String[] SESSION_ID = new String[]{CONTEXT, "session"}; + String STREAM = "stream"; + String[] STREAM_ID = new String[]{STREAM, "id"}; + String[] VERSION = new String[]{STREAM, "version"}; + String CAUSATION = "causation"; + + /** + * A system generated unique id used to definitively identify the payload within the system. + * + * @return the id + */ + UUID id(); + + /** + * Get the logical type name of the message payload. + * + * @return the name + */ + String name(); + + /** + * Get the correlation id supplied by the client, if one was specified + * + * @return the client's correlation id + */ + Optional clientCorrelationId(); + + /** + * Get a list of ids that indicate the sequence of commands or events that resulting in this message + * + * @return the causation list + */ + List causation(); + + /** + * Get the id of the user that initiated this message if one was specified. + * + * @return the user id + */ + Optional userId(); + + /** + * Get the id of the user's session that initiated this message if one was specified. + * + * @return the session id + */ + Optional sessionId(); + + /** + * Get the UUID of the stream this message belongs to, if one is specified. + * + * @return the optional UUID + */ + Optional streamId(); + + /** + * Get the sequence id (or version) that indicates where in the stream this message is positioned, if one is specified. + * + * @return the optional sequence id + */ + Optional version(); + + /** + * Return the whole metadata as a JsonObject. + * + * @return the metadata + */ + JsonObject asJsonObject(); + +} diff --git a/messaging/messaging-core/src/test/java/uk/gov/justice/services/messaging/DefaultEnvelopeTest.java b/messaging/messaging-core/src/test/java/uk/gov/justice/services/messaging/DefaultEnvelopeTest.java new file mode 100644 index 000000000..aaad12543 --- /dev/null +++ b/messaging/messaging-core/src/test/java/uk/gov/justice/services/messaging/DefaultEnvelopeTest.java @@ -0,0 +1,43 @@ +package uk.gov.justice.services.messaging; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; + +import javax.json.JsonObject; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.junit.Assert.assertThat; +import static uk.gov.justice.services.messaging.DefaultEnvelope.envelopeFrom; + +/** + * Unit tests for the {@link DefaultEnvelope} class. + */ +@RunWith(MockitoJUnitRunner.class) +public class DefaultEnvelopeTest { + + @Mock + private Metadata metadata; + + @Mock + private JsonObject payload; + + private Envelope envelope; + + @Before + public void setup() { + envelope = envelopeFrom(metadata, payload); + } + + @Test + public void shouldReturnMetadata() throws Exception { + assertThat(envelope.metadata(), equalTo(metadata)); + } + + @Test + public void shouldReturnPayload() throws Exception { + assertThat(envelope.payload(), equalTo(payload)); + } +} diff --git a/messaging/messaging-core/src/test/java/uk/gov/justice/services/messaging/JsonObjectMetadataTest.java b/messaging/messaging-core/src/test/java/uk/gov/justice/services/messaging/JsonObjectMetadataTest.java new file mode 100644 index 000000000..8b127d09f --- /dev/null +++ b/messaging/messaging-core/src/test/java/uk/gov/justice/services/messaging/JsonObjectMetadataTest.java @@ -0,0 +1,165 @@ +package uk.gov.justice.services.messaging; + +import com.google.common.collect.ImmutableList; +import org.junit.Before; +import org.junit.Test; + +import javax.json.Json; +import javax.json.JsonObject; +import java.util.UUID; + +import static javax.json.JsonValue.NULL; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; +import static uk.gov.justice.services.messaging.JsonObjectMetadata.CAUSATION; +import static uk.gov.justice.services.messaging.JsonObjectMetadata.CLIENT_CORRELATION; +import static uk.gov.justice.services.messaging.JsonObjectMetadata.CONTEXT; +import static uk.gov.justice.services.messaging.JsonObjectMetadata.ID; +import static uk.gov.justice.services.messaging.JsonObjectMetadata.NAME; +import static uk.gov.justice.services.messaging.JsonObjectMetadata.SESSION_ID; +import static uk.gov.justice.services.messaging.JsonObjectMetadata.STREAM; +import static uk.gov.justice.services.messaging.JsonObjectMetadata.STREAM_ID; +import static uk.gov.justice.services.messaging.JsonObjectMetadata.USER_ID; +import static uk.gov.justice.services.messaging.JsonObjectMetadata.VERSION; +import static uk.gov.justice.services.messaging.JsonObjectMetadata.metadataFrom; + +/** + * Unit tests for the {@link JsonObjectMetadata} class. + */ +public class JsonObjectMetadataTest { + + private static final String UUID_ID = "d04885b4-9652-4c2a-87c6-299bda0a87d4"; + private static final String UUID_CLIENT_CORRELATION = "8d67ed44-ecfb-43ce-867c-53077abf97a6"; + private static final String UUID_CAUSATION = "49ef76bc-df4f-4b91-8ca7-21972c30ee4c"; + private static final String UUID_USER_ID = "182a8f83-faa0-46d6-96d0-96999f05e3a2"; + private static final String UUID_SESSION_ID = "f0132298-7b79-4397-bab6-f2f5e27915f0"; + private static final String UUID_STREAM_ID = "f29e0415-3a3b-48d8-b301-d34faa58662a"; + private static final String MESSAGE_NAME = "logical.message.name"; + private static final Long STREAM_VERSION = 99L; + + private JsonObject jsonObject; + private Metadata metadata; + + @Before + public void setup() { + jsonObject = Json.createObjectBuilder() + .add(ID, UUID_ID) + .add(NAME, MESSAGE_NAME) + .add(CLIENT_CORRELATION[0], Json.createObjectBuilder() + .add(CLIENT_CORRELATION[1], UUID_CLIENT_CORRELATION) + ) + .add(CAUSATION, Json.createArrayBuilder() + .add(UUID_CAUSATION) + ) + .add(CONTEXT, Json.createObjectBuilder() + .add(USER_ID[1], UUID_USER_ID) + .add(SESSION_ID[1], UUID_SESSION_ID) + ) + .add(STREAM, Json.createObjectBuilder() + .add(STREAM_ID[1], UUID_STREAM_ID) + .add(VERSION[1], STREAM_VERSION) + ) + .build(); + metadata = metadataFrom(jsonObject); + } + + @Test + public void shouldReturnId() throws Exception { + assertThat(metadata.id(), equalTo(UUID.fromString(UUID_ID))); + } + + @Test + public void shouldReturnName() throws Exception { + assertThat(metadata.name(), equalTo(MESSAGE_NAME)); + } + + @Test + public void shouldReturnClientCorrelationId() throws Exception { + assertThat(metadata.clientCorrelationId().isPresent(), is(true)); + assertThat(metadata.clientCorrelationId().get(), equalTo(UUID_CLIENT_CORRELATION)); + } + + @Test + public void shouldReturnCausation() throws Exception { + assertThat(metadata.causation(), equalTo(ImmutableList.of(UUID.fromString(UUID_CAUSATION)))); + } + + @Test + public void shouldReturnUserId() throws Exception { + assertThat(metadata.userId().isPresent(), is(true)); + assertThat(metadata.userId().get(), equalTo(UUID_USER_ID)); + } + + @Test + public void shouldReturnSessionId() throws Exception { + assertThat(metadata.sessionId().isPresent(), is(true)); + assertThat(metadata.sessionId().get(), equalTo(UUID_SESSION_ID)); + } + + @Test + public void shouldReturnStreamId() throws Exception { + assertThat(metadata.streamId().isPresent(), is(true)); + assertThat(metadata.streamId().get(), equalTo(UUID.fromString(UUID_STREAM_ID))); + } + + @Test + public void shouldReturnStreamVersion() throws Exception { + assertThat(metadata.version().isPresent(), is(true)); + assertThat(metadata.version().get(), equalTo(STREAM_VERSION)); + } + + @Test + public void shouldReturnJsonObject() throws Exception { + assertThat(metadata.asJsonObject(), equalTo(jsonObject)); + } + + @Test(expected = IllegalArgumentException.class) + public void shouldThrowExceptionIfIdIsMissing() throws Exception { + metadataFrom(Json.createObjectBuilder() + .build() + ); + } + + @Test(expected = IllegalArgumentException.class) + public void shouldThrowExceptionIfIdIsNotUUID() throws Exception { + metadataFrom(Json.createObjectBuilder() + .add(ID, "blah") + .build() + ); + } + + @Test(expected = IllegalArgumentException.class) + public void shouldThrowExceptionIfIdIsNull() throws Exception { + metadataFrom(Json.createObjectBuilder() + .add(ID, NULL) + .build() + ); + } + + @Test(expected = IllegalArgumentException.class) + public void shouldThrowExceptionIfNameIsMissing() throws Exception { + metadataFrom(Json.createObjectBuilder() + .add(ID, UUID_ID) + .build() + ); + } + + @Test(expected = IllegalArgumentException.class) + public void shouldThrowExceptionIfNameIsEmpty() throws Exception { + metadataFrom(Json.createObjectBuilder() + .add(ID, UUID_ID) + .add(NAME, "") + .build() + ); + } + + @Test(expected = IllegalArgumentException.class) + public void shouldThrowExceptionIfNameIsNull() throws Exception { + metadataFrom(Json.createObjectBuilder() + .add(ID, UUID_ID) + .add(NAME, NULL) + .build() + ); + } +} diff --git a/messaging/messaging-core/src/test/java/uk/gov/justice/services/messaging/JsonObjectsTest.java b/messaging/messaging-core/src/test/java/uk/gov/justice/services/messaging/JsonObjectsTest.java new file mode 100644 index 000000000..c9101ffab --- /dev/null +++ b/messaging/messaging-core/src/test/java/uk/gov/justice/services/messaging/JsonObjectsTest.java @@ -0,0 +1,242 @@ +package uk.gov.justice.services.messaging; + +import com.google.common.collect.ImmutableList; +import org.junit.Test; + +import javax.json.Json; +import javax.json.JsonArray; +import javax.json.JsonNumber; +import javax.json.JsonObject; +import javax.json.JsonString; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +import static javax.json.JsonValue.NULL; +import static net.trajano.commons.testing.UtilityClassTestUtil.assertUtilityClassWellDefined; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.hasSize; + +/** + * Unit tests for the {@link JsonObjects} class. + */ +public class JsonObjectsTest { + + public static final String UUID_A = "da45e8f6-d945-4f09-a115-1139a9dbb754"; + public static final String UUID_B = "d04885b4-9652-4c2a-87c6-299bda0a87d4"; + + @Test + public void shouldBeWellDefinedUtilityClass() { + assertUtilityClassWellDefined(JsonObjects.class); + } + + @Test + public void shouldReturnJsonArray() { + JsonArray array = Json.createArrayBuilder() + .addNull() + .build(); + JsonObject object = Json.createObjectBuilder() + .add("name", array) + .build(); + Optional jsonArray = JsonObjects.getJsonArray(object, "name"); + + assertThat(jsonArray.isPresent(), is(true)); + assertThat(jsonArray.get(), equalTo(array)); + } + + @Test + public void shouldReturnJsonObject() { + JsonObject subObject = Json.createObjectBuilder() + .add("name2", "cheese") + .build(); + JsonObject object = Json.createObjectBuilder() + .add("name", subObject) + .build(); + Optional jsonObject = JsonObjects.getJsonObject(object, "name"); + + assertThat(jsonObject.isPresent(), is(true)); + assertThat(jsonObject.get(), equalTo(subObject)); + } + + @Test + public void shouldReturnJsonNumber() { + JsonObject object = Json.createObjectBuilder() + .add("name", 99L) + .build(); + Optional jsonNumber = JsonObjects.getJsonNumber(object, "name"); + + assertThat(jsonNumber.isPresent(), is(true)); + assertThat(jsonNumber.get().longValue(), equalTo(99L)); + } + + @Test + public void shouldReturnJsonString() { + JsonObject object = Json.createObjectBuilder() + .add("name", "test") + .build(); + Optional jsonString = JsonObjects.getJsonString(object, "name"); + + assertThat(jsonString.isPresent(), is(true)); + assertThat(jsonString.get().getString(), equalTo("test")); + } + + @Test + public void shouldReturnJsonStringForNestedField() { + JsonObject object = Json.createObjectBuilder() + .add("name", Json.createObjectBuilder() + .add("name2", "test") + .build()) + .build(); + Optional jsonString = JsonObjects.getJsonString(object, "name", "name2"); + + assertThat(jsonString.isPresent(), is(true)); + assertThat(jsonString.get().getString(), equalTo("test")); + } + + @Test + public void shouldReturnString() { + JsonObject object = Json.createObjectBuilder() + .add("name", "test") + .build(); + Optional string = JsonObjects.getString(object, "name"); + + assertThat(string.isPresent(), is(true)); + assertThat(string.get(), equalTo("test")); + } + + @Test(expected = IllegalStateException.class) + public void shouldThrowExceptionForNonString() { + JsonObject object = Json.createObjectBuilder() + .add("name", 99L) + .build(); + JsonObjects.getString(object, "name"); + } + + @Test + public void shouldReturnUUID() { + final String stringValue = "6c84963d-47a1-4d57-a706-09bea3fa84a5"; + JsonObject object = Json.createObjectBuilder() + .add("name", stringValue) + .build(); + Optional uuid = JsonObjects.getUUID(object, "name"); + + assertThat(uuid.isPresent(), is(true)); + assertThat(uuid.get(), equalTo(UUID.fromString(stringValue))); + } + + @Test(expected = IllegalStateException.class) + public void shouldThrowExceptionForNonUUID() { + JsonObject object = Json.createObjectBuilder() + .add("name", "blah") + .build(); + JsonObjects.getUUID(object, "name"); + } + + @Test + public void shouldReturnLong() { + JsonObject object = Json.createObjectBuilder() + .add("name", 99L) + .build(); + Optional string = JsonObjects.getLong(object, "name"); + + assertThat(string.isPresent(), is(true)); + assertThat(string.get(), equalTo(99L)); + } + + @Test(expected = IllegalStateException.class) + public void shouldThrowExceptionForNonLong() { + JsonObject object = Json.createObjectBuilder() + .add("name", "blah") + .build(); + JsonObjects.getLong(object, "name"); + } + + @Test + public void shouldReturnListOfJsonStrings() { + JsonArray array = Json.createArrayBuilder() + .add("test1") + .add("test2") + .build(); + JsonObject object = Json.createObjectBuilder() + .add("name", array) + .build(); + Optional> jsonStrings = JsonObjects.getList(object, JsonString.class, "name"); + + assertThat(jsonStrings.isPresent(), is(true)); + assertThat(jsonStrings.get(), hasSize(2)); + assertThat(jsonStrings.get().get(0).getString(), equalTo("test1")); + assertThat(jsonStrings.get().get(1).getString(), equalTo("test2")); + } + + @Test + public void shouldReturnListOfStrings() { + JsonArray array = Json.createArrayBuilder() + .add("test1") + .add("test2") + .build(); + JsonObject object = Json.createObjectBuilder() + .add("name", array) + .build(); + Optional> strings = JsonObjects.getList(object, JsonString.class, JsonString::getString, "name"); + + assertThat(strings.isPresent(), is(true)); + assertThat(strings.get(), equalTo(ImmutableList.of("test1", "test2"))); + } + + @Test + public void shouldReturnListOfUUIDs() { + JsonArray array = Json.createArrayBuilder() + .add(UUID_A) + .add(UUID_B) + .build(); + JsonObject object = Json.createObjectBuilder() + .add("name", array) + .build(); + List uuids = JsonObjects.getUUIDs(object, "name"); + + assertThat(uuids, equalTo(ImmutableList.of(UUID.fromString(UUID_A), UUID.fromString(UUID_B)))); + } + + @Test(expected = IllegalArgumentException.class) + public void shouldThrowExceptionForNullObject() { + JsonObjects.getString(null, "name"); + } + + @Test(expected = IllegalArgumentException.class) + public void shouldThrowExceptionForNullFieldName() { + JsonObject object = Json.createObjectBuilder() + .add("name", 99L) + .build(); + JsonObjects.getString(object, (String) null); + } + + @Test(expected = IllegalArgumentException.class) + public void shouldThrowExceptionForEmptyVarArgs() { + JsonObject object = Json.createObjectBuilder() + .add("name", 99L) + .build(); + JsonObjects.getString(object); + } + + @Test + public void shouldReturnEmptyIfFieldIsUnknown() { + JsonObject object = Json.createObjectBuilder() + .add("name", "test") + .build(); + Optional string = JsonObjects.getString(object, "name2"); + + assertThat(string.isPresent(), is(false)); + } + + @Test + public void shouldReturnEmptyIfFieldValueIsNull() { + JsonObject object = Json.createObjectBuilder() + .add("name", NULL) + .build(); + Optional string = JsonObjects.getString(object, "name"); + + assertThat(string.isPresent(), is(false)); + } +} diff --git a/messaging/pom.xml b/messaging/pom.xml new file mode 100644 index 000000000..5de8ebc0c --- /dev/null +++ b/messaging/pom.xml @@ -0,0 +1,18 @@ + + + + microservice-framework + uk.gov.justice.services + 0.1.0-SNAPSHOT + + 4.0.0 + + messaging + pom + + messaging-core + + + diff --git a/pom.xml b/pom.xml index e14d8c4dd..3eb72ec9f 100644 --- a/pom.xml +++ b/pom.xml @@ -22,6 +22,10 @@ scm:git:git@github.com:CJSCommonPlatform/microservice_framework.git + + messaging + + @@ -29,6 +33,7 @@ common-bom 1.6.0-SNAPSHOT import + pom