diff --git a/clients/rest-client-core/src/main/java/uk/gov/justice/services/clients/core/EndpointDefinition.java b/clients/rest-client-core/src/main/java/uk/gov/justice/services/clients/core/EndpointDefinition.java index a70542ff9..8b4c7f9a1 100644 --- a/clients/rest-client-core/src/main/java/uk/gov/justice/services/clients/core/EndpointDefinition.java +++ b/clients/rest-client-core/src/main/java/uk/gov/justice/services/clients/core/EndpointDefinition.java @@ -7,7 +7,7 @@ */ public class EndpointDefinition { - private final String baseURi; + private final String baseUri; private final String path; private final Set pathParams; private final Set queryParams; @@ -22,7 +22,7 @@ public class EndpointDefinition { */ public EndpointDefinition(final String baseUri, final String path, final Set pathParams, final Set queryParams) { - this.baseURi = baseUri; + this.baseUri = baseUri; this.path = path; this.pathParams = pathParams; this.queryParams = queryParams; @@ -33,8 +33,8 @@ public EndpointDefinition(final String baseUri, final String path, final Set String.format("Sending GET request to %s using message: %s", target.getUri().toString(), toEnvelopeTraceString(envelope))); + + final Response response = builder.get(); + + trace(LOGGER, () -> String.format("Sent GET request %s and received: %s", envelope.metadata().id().toString(), toResponseTrace(response))); + + final int status = response.getStatus(); + if (status == NOT_FOUND.getStatusCode()) { + return enveloper.withMetadataFrom(envelope, envelope.metadata().name()).apply(null); + } else if (status != OK.getStatusCode()) { + throw new RuntimeException(format("GET request %s failed; expected 200 but got %s with reason \"%s\"", + envelope.metadata().id().toString(), status, + response.getStatusInfo().getReasonPhrase())); + } + + final JsonObject responseAsJsonObject = stringToJsonObjectConverter.convert(response.readEntity(String.class)); + + return jsonObjectEnvelopeConverter.asEnvelope(addMetadataIfMissing(responseAsJsonObject, envelope.metadata(), response.getHeaderString(CPPID))); + } + + /** + * Make a POST request using the envelope provided to a specified endpoint. + * + * @param definition the endpoint definition + * @param envelope the envelope containing the payload and/or parameters to pass in the request + */ + public void post(final EndpointDefinition definition, final JsonEnvelope envelope) { + final WebTarget target = createWebTarget(definition, envelope); + + final Builder builder = target.request(format(MEDIA_TYPE_PATTERN, envelope.metadata().name())); + + trace(LOGGER, () -> String.format("Sending POST request to %s using message: %s", target.getUri().toString(), toEnvelopeTraceString(envelope))); + + final JsonObject requestBody = stripParamsFromPayload(definition, envelope); + final Response response = builder.post(entity(requestBody.toString(), format(MEDIA_TYPE_PATTERN, envelope.metadata().name()))); + + trace(LOGGER, () -> String.format("Sent POST request %s and received: %s", envelope.metadata().id().toString(), toResponseTrace(response))); + + final int status = response.getStatus(); + if (status != ACCEPTED.getStatusCode()) { + throw new RuntimeException(format("POST request %s failed; expected 202 response but got %s with reason \"%s\"", + envelope.metadata().id().toString(), status, + response.getStatusInfo().getReasonPhrase())); + } + } + + private WebTarget createWebTarget(final EndpointDefinition definition, final JsonEnvelope envelope) { final JsonObject payload = envelope.payloadAsJsonObject(); final Client client = ClientBuilder.newClient(); WebTarget target = client - .target(definition.getBaseURi()) + .target(definition.getBaseUri()) .path(definition.getPath()); for (String pathParam : definition.getPathParams()) { @@ -76,28 +134,16 @@ public JsonEnvelope request(final EndpointDefinition definition, final JsonEnvel target = target.queryParam(paramName, payload.getString(paramName)); } } + return target; + } - final Invocation.Builder builder = target.request(format(MEDIA_TYPE_PATTERN, envelope.metadata().name())); - - final WebTarget finalTarget = target; - trace(LOGGER, () -> String.format("Sending REST request to %s using message: %s", finalTarget.getUri().toString(), - toEnvelopeTraceString(envelope))); - - final Response response = builder.get(); - - trace(LOGGER, () -> String.format("REST response for %s received: %s", envelope.metadata().id().toString(), toResponseTrace(response))); - - final int status = response.getStatus(); - if (status == NOT_FOUND.getStatusCode()) { - return enveloper.withMetadataFrom(envelope, envelope.metadata().name()).apply(null); - } else if (status != OK.getStatusCode()) { - throw new RuntimeException(format("Request Failed with code %s and reason \"%s\"", status, - response.getStatusInfo().getReasonPhrase())); - } - - final JsonObject responseAsJsonObject = stringToJsonObjectConverter.convert(response.readEntity(String.class)); - - return jsonObjectEnvelopeConverter.asEnvelope(addMetadataIfMissing(responseAsJsonObject, envelope.metadata(), response.getHeaderString(CPPID))); + private JsonObject stripParamsFromPayload(final EndpointDefinition definition, final JsonEnvelope envelope) { + final JsonObject payload = envelope.payloadAsJsonObject(); + final Set pathParams = definition.getPathParams(); + final Set queryParams = definition.getQueryParams().stream() + .map(QueryParam::getName) + .collect(toSet()); + return createObjectBuilderWithFilter(payload, (fieldName) -> !pathParams.contains(fieldName) && !queryParams.contains(fieldName)).build(); } private JsonObject addMetadataIfMissing(final JsonObject responseAsJsonObject, final Metadata requestMetadata, final String cppId) { @@ -105,7 +151,7 @@ private JsonObject addMetadataIfMissing(final JsonObject responseAsJsonObject, f return responseAsJsonObject; } - final JsonObject metadata = JsonObjects.createObjectBuilderWithFilter(requestMetadata.asJsonObject(), x -> !ID.equals(x)) + final JsonObject metadata = createObjectBuilderWithFilter(requestMetadata.asJsonObject(), x -> !ID.equals(x)) .add(ID, cppId) .build(); diff --git a/clients/rest-client-core/src/test/java/uk/gov/justice/services/clients/core/RestClientProcessorIT.java b/clients/rest-client-core/src/test/java/uk/gov/justice/services/clients/core/RestClientProcessorIT.java index 443d89437..dc38d00a3 100644 --- a/clients/rest-client-core/src/test/java/uk/gov/justice/services/clients/core/RestClientProcessorIT.java +++ b/clients/rest-client-core/src/test/java/uk/gov/justice/services/clients/core/RestClientProcessorIT.java @@ -1,13 +1,21 @@ package uk.gov.justice.services.clients.core; import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.equalToJson; import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.post; +import static com.github.tomakehurst.wiremock.client.WireMock.postRequestedFor; import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; +import static com.github.tomakehurst.wiremock.client.WireMock.verify; import static java.lang.String.format; import static java.util.Collections.emptySet; import static javax.json.JsonValue.NULL; +import static javax.ws.rs.core.HttpHeaders.ACCEPT; +import static javax.ws.rs.core.HttpHeaders.CONTENT_TYPE; +import static javax.ws.rs.core.Response.Status.ACCEPTED; +import static javax.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.notNullValue; import static org.hamcrest.MatcherAssert.assertThat; @@ -37,11 +45,14 @@ public class RestClientProcessorIT { private static final String REQUEST_PARAM_A_PARAM_B_FILE_NAME = "request-envelope-a-b"; private static final String REQUEST_PARAM_A_PARAM_C_FILE_NAME = "request-envelope-a-c"; - private static final String RESPONSE_WITh_METADATA_FILE_NAME = "response-with-metadata"; + private static final String RESPONSE_WITH_METADATA_FILE_NAME = "response-with-metadata"; private static final String RESPONSE_WITHOUT_METADATA_FILE_NAME = "response-without-metadata"; + private static final String POST_REQUEST_WITH_METADATA_FILE_NAME = "post-request-with-metadata"; + private static final String POST_REQUEST_BODY_ONLY_FILE_NAME = "post-request-body-only"; private static final String BASE_URI = "http://localhost:8089"; - private static final String CONTEXT_QUERY_MY_QUERY = "context.query.myquery"; + private static final String QUERY_NAME = "context.query.myquery"; + private static final String COMMAND_NAME = "context.my-command"; private static final String PAYLOAD_ID_NAME = "payloadId"; private static final String PAYLOAD_ID_VALUE = "c3f7182b-bd20-4678-ba8b-e7e5ea8629c3"; private static final String PAYLOAD_VERSION_NAME = "payloadVersion"; @@ -70,13 +81,13 @@ public void setup() throws IOException { } @Test - public void shouldCallRestServiceWithNoParameters() throws Exception { + public void shouldDoGetWithNoParameters() throws Exception { final String path = "/my/resource"; - final String mimetype = format("application/vnd.%s+json", CONTEXT_QUERY_MY_QUERY); + final String mimetype = format("application/vnd.%s+json", QUERY_NAME); stubFor(get(urlEqualTo(path)) - .withHeader("Accept", WireMock.equalTo(mimetype)) + .withHeader(ACCEPT, WireMock.equalTo(mimetype)) .willReturn(aResponse() .withStatus(200) .withHeader("Content-Type", mimetype) @@ -84,18 +95,18 @@ public void shouldCallRestServiceWithNoParameters() throws Exception { EndpointDefinition endpointDefinition = new EndpointDefinition(BASE_URI, path, emptySet(), emptySet()); - validateResponse(restClientProcessor.request(endpointDefinition, requestEnvelopeParamAParamB()), envelopeWithMetadataAsJson); + validateResponse(restClientProcessor.get(endpointDefinition, requestEnvelopeParamAParamB()), envelopeWithMetadataAsJson); } @Test - public void shouldCallRestServiceWithPathParameters() throws Exception { + public void shouldDoGetWithPathParameters() throws Exception { final String path = "/my/resource/{paramA}/{paramB}"; - final String mimetype = format("application/vnd.%s+json", CONTEXT_QUERY_MY_QUERY); + final String mimetype = format("application/vnd.%s+json", QUERY_NAME); stubFor(get(urlEqualTo("/my/resource/valueA/valueB")) - .withHeader("Accept", WireMock.equalTo(mimetype)) + .withHeader(ACCEPT, WireMock.equalTo(mimetype)) .willReturn(aResponse() .withStatus(200) .withHeader("Content-Type", mimetype) @@ -103,17 +114,39 @@ public void shouldCallRestServiceWithPathParameters() throws Exception { EndpointDefinition endpointDefinition = new EndpointDefinition(BASE_URI, path, ImmutableSet.of("paramA", "paramB"), emptySet()); - validateResponse(restClientProcessor.request(endpointDefinition, requestEnvelopeParamAParamB()), envelopeWithMetadataAsJson); + validateResponse(restClientProcessor.get(endpointDefinition, requestEnvelopeParamAParamB()), envelopeWithMetadataAsJson); + } + + @Test + public void shouldDoPostWithPathParameters() throws Exception { + + final String path = "/my/resource/{paramA}/{paramB}"; + final String mimetype = format("application/vnd.%s+json", COMMAND_NAME); + final String bodyWithoutParams = jsonFromFile(POST_REQUEST_BODY_ONLY_FILE_NAME); + + stubFor(post(urlEqualTo("/my/resource/valueA/valueB")) + .withHeader(CONTENT_TYPE, WireMock.equalTo(mimetype)) + .withRequestBody(equalToJson(bodyWithoutParams)) + .willReturn(aResponse() + .withStatus(ACCEPTED.getStatusCode()))); + + EndpointDefinition endpointDefinition = new EndpointDefinition(BASE_URI, path, ImmutableSet.of("paramA", "paramB"), emptySet()); + + restClientProcessor.post(endpointDefinition, postRequestEnvelope()); + + verify(postRequestedFor(urlEqualTo("/my/resource/valueA/valueB")) + .withHeader(CONTENT_TYPE, WireMock.equalTo(mimetype)) + .withRequestBody(equalToJson(bodyWithoutParams))); } @Test public void shouldHandleRemoteResponseWithMetadata() throws Exception { final String path = "/my/resource"; - final String mimetype = format("application/vnd.%s+json", CONTEXT_QUERY_MY_QUERY); + final String mimetype = format("application/vnd.%s+json", QUERY_NAME); stubFor(get(urlPathEqualTo(path)) - .withHeader("Accept", WireMock.equalTo(mimetype)) + .withHeader(ACCEPT, WireMock.equalTo(mimetype)) .withQueryParam("paramA", WireMock.equalTo("valueA")) .withQueryParam("paramC", WireMock.equalTo("valueC")) .willReturn(aResponse() @@ -125,17 +158,17 @@ public void shouldHandleRemoteResponseWithMetadata() throws Exception { EndpointDefinition endpointDefinition = new EndpointDefinition(BASE_URI, path, emptySet(), queryParams); - validateResponse(restClientProcessor.request(endpointDefinition, requestEnvelopeParamAParamC()), envelopeWithMetadataAsJson); + validateResponse(restClientProcessor.get(endpointDefinition, requestEnvelopeParamAParamC()), envelopeWithMetadataAsJson); } @Test public void shouldHandleRemoteResponseWithoutMetadata() throws Exception { final String path = "/my/resource"; - final String mimetype = format("application/vnd.%s+json", CONTEXT_QUERY_MY_QUERY); + final String mimetype = format("application/vnd.%s+json", QUERY_NAME); stubFor(get(urlPathEqualTo(path)) - .withHeader("Accept", WireMock.equalTo(mimetype)) + .withHeader(ACCEPT, WireMock.equalTo(mimetype)) .withQueryParam("paramA", WireMock.equalTo("valueA")) .withQueryParam("paramC", WireMock.equalTo("valueC")) .willReturn(aResponse() @@ -148,17 +181,17 @@ public void shouldHandleRemoteResponseWithoutMetadata() throws Exception { EndpointDefinition endpointDefinition = new EndpointDefinition(BASE_URI, path, emptySet(), queryParams); - validateResponse(restClientProcessor.request(endpointDefinition, requestEnvelopeParamAParamC()), envelopeWithMetadataAsJson); + validateResponse(restClientProcessor.get(endpointDefinition, requestEnvelopeParamAParamC()), envelopeWithMetadataAsJson); } @Test(expected = IllegalStateException.class) public void shouldThrowExceptionOnQueryParamMissing() throws Exception { final String path = "/my/resource"; - final String mimetype = format("application/vnd.%s+json", CONTEXT_QUERY_MY_QUERY); + final String mimetype = format("application/vnd.%s+json", QUERY_NAME); stubFor(get(urlPathEqualTo(path)) - .withHeader("Accept", WireMock.equalTo(mimetype)) + .withHeader(ACCEPT, WireMock.equalTo(mimetype)) .withQueryParam("paramA", WireMock.equalTo("valueA")) .withQueryParam("paramC", WireMock.equalTo("valueC")) .willReturn(aResponse() @@ -170,17 +203,17 @@ public void shouldThrowExceptionOnQueryParamMissing() throws Exception { EndpointDefinition endpointDefinition = new EndpointDefinition(BASE_URI, path, emptySet(), queryParams); - restClientProcessor.request(endpointDefinition, requestEnvelopeParamAParamB()); + restClientProcessor.get(endpointDefinition, requestEnvelopeParamAParamB()); } @Test public void shouldReturnJsonNullPayloadFor404ResponseCode() throws Exception { final String path = "/my/resource"; - final String mimetype = format("application/vnd.%s+json", CONTEXT_QUERY_MY_QUERY); + final String mimetype = format("application/vnd.%s+json", QUERY_NAME); stubFor(get(urlEqualTo(path)) - .withHeader("Accept", WireMock.equalTo(mimetype)) + .withHeader(ACCEPT, WireMock.equalTo(mimetype)) .willReturn(aResponse() .withStatus(404) .withHeader("Content-Type", mimetype) @@ -190,7 +223,7 @@ public void shouldReturnJsonNullPayloadFor404ResponseCode() throws Exception { EndpointDefinition endpointDefinition = new EndpointDefinition(BASE_URI, path, emptySet(), emptySet()); - JsonEnvelope response = restClientProcessor.request(endpointDefinition, requestEnvelopeParamAParamB()); + JsonEnvelope response = restClientProcessor.get(endpointDefinition, requestEnvelopeParamAParamB()); assertThat(response, notNullValue()); assertThat(response.payload(), equalTo(NULL)); @@ -200,10 +233,10 @@ public void shouldReturnJsonNullPayloadFor404ResponseCode() throws Exception { public void shouldThrowExceptionFor500ResponseCode() throws Exception { final String path = "/my/resource"; - final String mimetype = format("application/vnd.%s+json", CONTEXT_QUERY_MY_QUERY); + final String mimetype = format("application/vnd.%s+json", QUERY_NAME); stubFor(get(urlEqualTo(path)) - .withHeader("Accept", WireMock.equalTo(mimetype)) + .withHeader(ACCEPT, WireMock.equalTo(mimetype)) .willReturn(aResponse() .withStatus(500) .withHeader("Content-Type", mimetype) @@ -211,7 +244,25 @@ public void shouldThrowExceptionFor500ResponseCode() throws Exception { EndpointDefinition endpointDefinition = new EndpointDefinition(BASE_URI, path, emptySet(), emptySet()); - restClientProcessor.request(endpointDefinition, requestEnvelopeParamAParamB()); + restClientProcessor.get(endpointDefinition, requestEnvelopeParamAParamB()); + } + + @Test(expected = RuntimeException.class) + public void shouldThrowExceptionFor500ResponseCodeToPost() throws Exception { + + final String path = "/my/resource/{paramA}/{paramB}"; + final String mimetype = format("application/vnd.%s+json", COMMAND_NAME); + final String bodyWithoutParams = jsonFromFile(POST_REQUEST_BODY_ONLY_FILE_NAME); + + stubFor(post(urlEqualTo("/my/resource/valueA/valueB")) + .withHeader(CONTENT_TYPE, WireMock.equalTo(mimetype)) + .withRequestBody(equalToJson(bodyWithoutParams)) + .willReturn(aResponse() + .withStatus(INTERNAL_SERVER_ERROR.getStatusCode()))); + + EndpointDefinition endpointDefinition = new EndpointDefinition(BASE_URI, path, ImmutableSet.of("paramA", "paramB"), emptySet()); + + restClientProcessor.post(endpointDefinition, postRequestEnvelope()); } private String jsonFromFile(String jsonFileName) throws IOException { @@ -227,13 +278,17 @@ private JsonEnvelope requestEnvelopeParamAParamC() throws IOException { } private String responseWithMetadata() throws IOException { - return jsonFromFile(RESPONSE_WITh_METADATA_FILE_NAME); + return jsonFromFile(RESPONSE_WITH_METADATA_FILE_NAME); + } + + private JsonEnvelope postRequestEnvelope() throws IOException { + return new JsonObjectEnvelopeConverter().asEnvelope(new StringToJsonObjectConverter().convert(jsonFromFile(POST_REQUEST_WITH_METADATA_FILE_NAME))); } private void validateResponse(JsonEnvelope response, String expectedResponseJson) { assertThat(response.metadata(), notNullValue()); assertThat(response.metadata().id().toString(), equalTo(METADATA_ID_VALUE)); - assertThat(response.metadata().name(), equalTo(CONTEXT_QUERY_MY_QUERY)); + assertThat(response.metadata().name(), equalTo(QUERY_NAME)); JSONAssert.assertEquals(expectedResponseJson, new JsonObjectEnvelopeConverter().fromEnvelope(response).toString(), false); diff --git a/clients/rest-client-core/src/test/resources/json/post-request-body-only.json b/clients/rest-client-core/src/test/resources/json/post-request-body-only.json new file mode 100644 index 000000000..7a4a508a5 --- /dev/null +++ b/clients/rest-client-core/src/test/resources/json/post-request-body-only.json @@ -0,0 +1,3 @@ +{ + "bodyParam": "myBodyParam" +} diff --git a/clients/rest-client-core/src/test/resources/json/post-request-with-metadata.json b/clients/rest-client-core/src/test/resources/json/post-request-with-metadata.json new file mode 100644 index 000000000..20d7b1426 --- /dev/null +++ b/clients/rest-client-core/src/test/resources/json/post-request-with-metadata.json @@ -0,0 +1,9 @@ +{ + "_metadata": { + "id": "861c9430-7bc6-4bf0-b549-6534394b8d65", + "name": "context.my-command" + }, + "paramA": "valueA", + "paramB": "valueB", + "bodyParam": "myBodyParam" +} diff --git a/clients/rest-client-core/src/test/resources/json/response-with-metadata.json b/clients/rest-client-core/src/test/resources/json/response-with-metadata.json index 748ab2942..1566860bd 100644 --- a/clients/rest-client-core/src/test/resources/json/response-with-metadata.json +++ b/clients/rest-client-core/src/test/resources/json/response-with-metadata.json @@ -17,4 +17,4 @@ "payloadId": "c3f7182b-bd20-4678-ba8b-e7e5ea8629c3", "payloadVersion": 0, "payloadName": "Name of the Payload" -} \ No newline at end of file +} diff --git a/clients/rest-client-generator/pom.xml b/clients/rest-client-generator/pom.xml index 9abb2328d..e0ef0ce06 100644 --- a/clients/rest-client-generator/pom.xml +++ b/clients/rest-client-generator/pom.xml @@ -106,16 +106,37 @@ ${project.build.directory}/generated-test-sources uk.gov.justice.api - - QUERY_API - + generate-for-query-api generate generate-test-sources + + + **/*query-controller.raml + + + QUERY_API + + + + + generate-for-event-processor + + generate + + generate-test-sources + + + **/*command-api.raml + + + EVENT_PROCESSOR + + diff --git a/clients/rest-client-generator/src/main/java/uk/gov/justice/services/clients/rest/generator/RestClientGenerator.java b/clients/rest-client-generator/src/main/java/uk/gov/justice/services/clients/rest/generator/RestClientGenerator.java index 1c29488e3..e4e784a07 100644 --- a/clients/rest-client-generator/src/main/java/uk/gov/justice/services/clients/rest/generator/RestClientGenerator.java +++ b/clients/rest-client-generator/src/main/java/uk/gov/justice/services/clients/rest/generator/RestClientGenerator.java @@ -194,11 +194,16 @@ private MethodSpec methodOf(Resource resource, Action action, MimeType mimeType) action.getQueryParameters().forEach((name, queryParameter) -> addQueryParam(builder, queryParameter, name)); builder.addStatement("final $T def = new $T(BASE_URI, path, pathParams, queryParams)", EndpointDefinition.class, EndpointDefinition.class); - if (action.getType().equals(ActionType.GET)) { - builder.returns(methodReturnType); - builder.addStatement("return $L.request(def, envelope)", REST_CLIENT_PROCESSOR); - } else { - builder.addStatement("$L.request(def, envelope)", REST_CLIENT_PROCESSOR); + switch (action.getType()) { + case GET: + builder.returns(methodReturnType); + builder.addStatement("return $L.get(def, envelope)", REST_CLIENT_PROCESSOR); + break; + case POST: + builder.addStatement("$L.post(def, envelope)", REST_CLIENT_PROCESSOR); + break; + default: + throw new IllegalArgumentException(format("Action %s not supported in REST client generator", action.getType().toString())); } return builder.build(); diff --git a/clients/rest-client-generator/src/test/java/uk/gov/justice/api/RemoteExampleEventProcessorIT.java b/clients/rest-client-generator/src/test/java/uk/gov/justice/api/RemoteExampleEventProcessorIT.java new file mode 100644 index 000000000..9008d111f --- /dev/null +++ b/clients/rest-client-generator/src/test/java/uk/gov/justice/api/RemoteExampleEventProcessorIT.java @@ -0,0 +1,148 @@ +package uk.gov.justice.api; + +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; +import static com.github.tomakehurst.wiremock.client.WireMock.equalToJson; +import static com.github.tomakehurst.wiremock.client.WireMock.post; +import static com.github.tomakehurst.wiremock.client.WireMock.postRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; +import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; +import static com.github.tomakehurst.wiremock.client.WireMock.verify; +import static java.lang.String.format; +import static javax.json.Json.createObjectBuilder; +import static javax.ws.rs.core.HttpHeaders.CONTENT_TYPE; +import static javax.ws.rs.core.Response.Status.ACCEPTED; +import static uk.gov.justice.services.core.annotation.Component.EVENT_PROCESSOR; +import static uk.gov.justice.services.messaging.DefaultJsonEnvelope.envelopeFrom; +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.metadataFrom; + +import uk.gov.justice.services.clients.core.RestClientHelper; +import uk.gov.justice.services.clients.core.RestClientProcessor; +import uk.gov.justice.services.common.converter.ObjectToJsonValueConverter; +import uk.gov.justice.services.common.converter.StringToJsonObjectConverter; +import uk.gov.justice.services.core.annotation.ServiceComponent; +import uk.gov.justice.services.core.dispatcher.DispatcherProducer; +import uk.gov.justice.services.core.enveloper.Enveloper; +import uk.gov.justice.services.core.jms.JmsDestinations; +import uk.gov.justice.services.core.jms.JmsSenderFactory; +import uk.gov.justice.services.core.sender.ComponentDestination; +import uk.gov.justice.services.core.sender.Sender; +import uk.gov.justice.services.core.sender.SenderProducer; +import uk.gov.justice.services.messaging.JsonEnvelope; +import uk.gov.justice.services.messaging.JsonObjectEnvelopeConverter; +import uk.gov.justice.services.messaging.jms.EnvelopeConverter; +import uk.gov.justice.services.messaging.jms.JmsEnvelopeSender; + +import java.util.Properties; +import java.util.UUID; + +import javax.inject.Inject; +import javax.json.JsonObject; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.github.tomakehurst.wiremock.junit.WireMockRule; +import org.apache.openejb.OpenEjbContainer; +import org.apache.openejb.jee.Application; +import org.apache.openejb.jee.WebApp; +import org.apache.openejb.junit.ApplicationComposer; +import org.apache.openejb.testing.Classes; +import org.apache.openejb.testing.Configuration; +import org.apache.openejb.testing.Module; +import org.apache.openejb.testng.PropertiesBuilder; +import org.apache.openejb.util.NetworkUtil; +import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(ApplicationComposer.class) +@ServiceComponent(EVENT_PROCESSOR) +public class RemoteExampleEventProcessorIT { + + private static final String BASE_PATH = "/rest-client-generator/command/api/rest/example"; + private static final UUID QUERY_ID = UUID.randomUUID(); + private static final UUID USER_ID = UUID.randomUUID(); + private static final String USER_NAME = "John Smith"; + + private static int port = -1; + + @Rule + public WireMockRule wireMockRule = new WireMockRule(8080); + + @Inject + Sender sender; + + @BeforeClass + public static void beforeClass() { + port = NetworkUtil.getNextAvailablePort(); + } + + @Configuration + public Properties properties() { + return new PropertiesBuilder() + .p("httpejbd.port", Integer.toString(port)) + .p(OpenEjbContainer.OPENEJB_EMBEDDED_REMOTABLE, "true") + .build(); + } + + @Module + @Classes(cdi = true, value = { + RemoteExampleCommandApi.class, + RestClientProcessor.class, + RestClientHelper.class, + DispatcherProducer.class, + + // TODO: Remove the next 6 classes when sender is migrated fully to dispatcher system + SenderProducer.class, + JmsSenderFactory.class, + ComponentDestination.class, + JmsEnvelopeSender.class, + JmsDestinations.class, + EnvelopeConverter.class, + + StringToJsonObjectConverter.class, + JsonObjectEnvelopeConverter.class, + ObjectToJsonValueConverter.class, + ObjectMapper.class, + Enveloper.class + }) + public WebApp war() { + return new WebApp() + .contextRoot("rest-client-generator") + .addServlet("TestApp", Application.class.getName()); + } + + @Test + public void shouldSendCommandToRemoteService() { + + final String name = "people.command.update-user"; + final JsonObject metadata = createObjectBuilder() + .add(NAME, name) + .add(ID, QUERY_ID.toString()) + .build(); + final JsonObject payload = createObjectBuilder() + .add("userId", USER_ID.toString()) + .add("userName", USER_NAME) + .build(); + final String bodyPayload = createObjectBuilder().add("userName", USER_NAME).build().toString(); + + final JsonEnvelope command = envelopeFrom(metadataFrom(metadata), payload); + + final String path = format("/users/%s", USER_ID.toString()); + final String mimeType = format("application/vnd.%s+json", name); + + stubFor(post(urlEqualTo(BASE_PATH + path)) + .withRequestBody(equalToJson(bodyPayload)) + .willReturn(aResponse() + .withStatus(ACCEPTED.getStatusCode()))); + + sender.send(command); + + verify(postRequestedFor(urlEqualTo(BASE_PATH + path)) + .withHeader(CONTENT_TYPE, equalTo(mimeType)) + .withRequestBody(equalToJson(bodyPayload)) + ); + } +} diff --git a/clients/rest-client-generator/src/test/java/uk/gov/justice/api/RemoteExampleQueryApiIT.java b/clients/rest-client-generator/src/test/java/uk/gov/justice/api/RemoteExampleQueryApiIT.java index 602d3e830..ca048bc79 100644 --- a/clients/rest-client-generator/src/test/java/uk/gov/justice/api/RemoteExampleQueryApiIT.java +++ b/clients/rest-client-generator/src/test/java/uk/gov/justice/api/RemoteExampleQueryApiIT.java @@ -54,7 +54,7 @@ @ServiceComponent(QUERY_API) public class RemoteExampleQueryApiIT { - private static final String BASE_PATH = "/rest-adapter-generator/query/api/rest/example"; + private static final String BASE_PATH = "/rest-client-generator/query/controller/rest/example"; private static final String METADATA = "_metadata"; private static final JsonObject RESPONSE = Json.createObjectBuilder() .add(METADATA, Json.createObjectBuilder() @@ -94,7 +94,7 @@ public Properties properties() { @Module @Classes(cdi = true, value = { - RemoteExampleQueryApi.class, + RemoteExampleQueryController.class, RestClientProcessor.class, RestClientHelper.class, DispatcherProducer.class, diff --git a/clients/rest-client-generator/src/test/java/uk/gov/justice/services/clients/rest/generator/RestClientGenerator_MethodBodyTest.java b/clients/rest-client-generator/src/test/java/uk/gov/justice/services/clients/rest/generator/RestClientGenerator_MethodBodyTest.java index b23db3639..3d580f3ab 100644 --- a/clients/rest-client-generator/src/test/java/uk/gov/justice/services/clients/rest/generator/RestClientGenerator_MethodBodyTest.java +++ b/clients/rest-client-generator/src/test/java/uk/gov/justice/services/clients/rest/generator/RestClientGenerator_MethodBodyTest.java @@ -79,7 +79,7 @@ public void shouldCallRestClientWithEndpointDefinitionContainingBaseUri() throws Class clazz = compiler.compiledClassOf(BASE_PACKAGE, "RemoteService1QueryApi"); invokeFirstMethod(clazz); - assertThat(capturedEndpointDefinition().getBaseURi(), is("http://localhost:8080/contextabc/query/api/rest/service1")); + assertThat(capturedEndpointDefinition().getBaseUri(), is("http://localhost:8080/contextabc/query/api/rest/service1")); } @Test @@ -155,7 +155,26 @@ public void shouldPassEnvelopeToRestClient() throws Exception { JsonEnvelope envelope = DefaultJsonEnvelope.envelopeFrom(null, null); method.invoke(remoteClient, envelope); - verify(restClientProcessor).request(any(EndpointDefinition.class), same(envelope)); + verify(restClientProcessor).get(any(EndpointDefinition.class), same(envelope)); + } + + @Test + public void shouldPassPostEnvelopeToRestClient() throws Exception { + + restClientGenerator.run( + restRamlWithQueryApiDefaults() + .withDefaultPostResource() + .build(), + configurationWithBasePackage(BASE_PACKAGE, outputFolder, NOT_USED_GENERATOR_PROPERTIES)); + + Class clazz = compiler.compiledClassOf(BASE_PACKAGE, "RemoteServiceQueryApi"); + Object remoteClient = instanceOf(clazz); + Method method = firstMethodOf(clazz); + + JsonEnvelope envelope = DefaultJsonEnvelope.envelopeFrom(null, null); + method.invoke(remoteClient, envelope); + + verify(restClientProcessor).post(any(EndpointDefinition.class), same(envelope)); } private void invokeFirstMethod(final Class clazz) throws InstantiationException, IllegalAccessException, InvocationTargetException { @@ -167,7 +186,7 @@ private void invokeFirstMethod(final Class clazz) throws InstantiationExcepti private EndpointDefinition capturedEndpointDefinition() { ArgumentCaptor endpointDefCaptor = ArgumentCaptor.forClass(EndpointDefinition.class); - verify(restClientProcessor).request(endpointDefCaptor.capture(), any(JsonEnvelope.class)); + verify(restClientProcessor).get(endpointDefCaptor.capture(), any(JsonEnvelope.class)); return endpointDefCaptor.getValue(); } diff --git a/clients/rest-client-generator/src/test/resources/raml/example-command-api.raml b/clients/rest-client-generator/src/test/resources/raml/example-command-api.raml new file mode 100644 index 000000000..a4e5687a0 --- /dev/null +++ b/clients/rest-client-generator/src/test/resources/raml/example-command-api.raml @@ -0,0 +1,9 @@ +#%RAML 0.8 +title: Example Service +baseUri: http://localhost:8080/rest-client-generator/command/api/rest/example + +/users/{userId}: + post: + body: + application/vnd.people.command.update-user+json: + diff --git a/clients/rest-client-generator/src/test/resources/raml/example.raml b/clients/rest-client-generator/src/test/resources/raml/example-query-controller.raml similarity index 83% rename from clients/rest-client-generator/src/test/resources/raml/example.raml rename to clients/rest-client-generator/src/test/resources/raml/example-query-controller.raml index 452bd66d5..17abdfdd9 100644 --- a/clients/rest-client-generator/src/test/resources/raml/example.raml +++ b/clients/rest-client-generator/src/test/resources/raml/example-query-controller.raml @@ -1,6 +1,6 @@ #%RAML 0.8 title: Example Service -baseUri: http://localhost:8080/rest-adapter-generator/query/api/rest/example +baseUri: http://localhost:8080/rest-client-generator/query/controller/rest/example /users/{userId}: get: diff --git a/core/src/main/java/uk/gov/justice/services/core/dispatcher/DispatcherProducer.java b/core/src/main/java/uk/gov/justice/services/core/dispatcher/DispatcherProducer.java index 49a58900a..d04737844 100644 --- a/core/src/main/java/uk/gov/justice/services/core/dispatcher/DispatcherProducer.java +++ b/core/src/main/java/uk/gov/justice/services/core/dispatcher/DispatcherProducer.java @@ -8,6 +8,7 @@ import uk.gov.justice.services.core.annotation.ServiceComponent; import uk.gov.justice.services.core.annotation.ServiceComponentLocation; import uk.gov.justice.services.core.extension.ServiceComponentFoundEvent; +import uk.gov.justice.services.core.sender.Sender; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -59,6 +60,10 @@ public AsynchronousDispatcher produceAsynchronousDispatcher(final InjectionPoint return dispatcherFor(injectionPoint)::asynchronousDispatch; } + public Sender produceSender(final InjectionPoint injectionPoint) { + return dispatcherFor(injectionPoint)::asynchronousDispatch; + } + /** * Produces the correct implementation of a synchronous dispatcher depending on the {@link * Adapter} annotation at the injection point. diff --git a/core/src/main/java/uk/gov/justice/services/core/sender/SenderProducer.java b/core/src/main/java/uk/gov/justice/services/core/sender/SenderProducer.java index d93e477fa..406df8b3c 100644 --- a/core/src/main/java/uk/gov/justice/services/core/sender/SenderProducer.java +++ b/core/src/main/java/uk/gov/justice/services/core/sender/SenderProducer.java @@ -1,10 +1,12 @@ package uk.gov.justice.services.core.sender; +import static uk.gov.justice.services.core.annotation.Component.EVENT_PROCESSOR; import static uk.gov.justice.services.core.annotation.Component.componentFrom; import uk.gov.justice.services.core.annotation.Component; import uk.gov.justice.services.core.annotation.ServiceComponent; import uk.gov.justice.services.core.annotation.exception.MissingAnnotationException; +import uk.gov.justice.services.core.dispatcher.DispatcherProducer; import uk.gov.justice.services.core.jms.JmsSenderFactory; import java.util.Map; @@ -27,6 +29,9 @@ public class SenderProducer { @Inject ComponentDestination componentDestination; + @Inject + DispatcherProducer dispatcherProducer; + private Map senderMap; public SenderProducer() { @@ -44,13 +49,17 @@ public Sender produce(final InjectionPoint injectionPoint) { final Class targetClass = injectionPoint.getMember().getDeclaringClass(); if (targetClass.isAnnotationPresent(ServiceComponent.class)) { - return getSender(componentFrom(targetClass)); + return getSender(componentFrom(targetClass), injectionPoint); } else { throw new MissingAnnotationException("InjectionPoint class must be annotated with " + ServiceComponent.class); } } - private Sender getSender(final Component component) { + private Sender getSender(final Component component, final InjectionPoint injectionPoint) { + if (component == EVENT_PROCESSOR) { + return dispatcherProducer.produceSender(injectionPoint); + } + return senderMap.computeIfAbsent(component, c -> jmsSenderFactory.createJmsSender(componentDestination.getDefault(c))); } diff --git a/core/src/test/java/uk/gov/justice/services/core/dispatcher/DispatcherProducerTest.java b/core/src/test/java/uk/gov/justice/services/core/dispatcher/DispatcherProducerTest.java index 526bf3884..b7f2b8c99 100644 --- a/core/src/test/java/uk/gov/justice/services/core/dispatcher/DispatcherProducerTest.java +++ b/core/src/test/java/uk/gov/justice/services/core/dispatcher/DispatcherProducerTest.java @@ -20,6 +20,7 @@ import uk.gov.justice.services.core.annotation.ServiceComponent; import uk.gov.justice.services.core.extension.ServiceComponentFoundEvent; import uk.gov.justice.services.core.handler.exception.MissingHandlerException; +import uk.gov.justice.services.core.sender.Sender; import uk.gov.justice.services.messaging.JsonEnvelope; import uk.gov.justice.services.messaging.Metadata; @@ -149,6 +150,12 @@ public void shouldReturnDispatcher() throws Exception { assertThat(dispatcher, notNullValue()); } + @Test + public void shouldReturnSender() throws Exception { + Sender sender = dispatcherProducer.produceSender(commandControllerInjectionPoint1); + assertThat(sender, notNullValue()); + } + @Test(expected = IllegalStateException.class) public void shouldThrowExceptionWithNoAdaptor() throws Exception { doReturn(Object.class).when(commandControllerMember1).getDeclaringClass(); diff --git a/core/src/test/java/uk/gov/justice/services/core/sender/SenderProducerTest.java b/core/src/test/java/uk/gov/justice/services/core/sender/SenderProducerTest.java index 13315f936..822ea676d 100644 --- a/core/src/test/java/uk/gov/justice/services/core/sender/SenderProducerTest.java +++ b/core/src/test/java/uk/gov/justice/services/core/sender/SenderProducerTest.java @@ -12,9 +12,11 @@ import static uk.gov.justice.services.core.annotation.Component.COMMAND_API; import static uk.gov.justice.services.core.annotation.Component.COMMAND_CONTROLLER; import static uk.gov.justice.services.core.annotation.Component.COMMAND_HANDLER; +import static uk.gov.justice.services.core.annotation.Component.EVENT_PROCESSOR; import uk.gov.justice.services.core.annotation.ServiceComponent; import uk.gov.justice.services.core.annotation.exception.MissingAnnotationException; +import uk.gov.justice.services.core.dispatcher.DispatcherProducer; import uk.gov.justice.services.core.jms.JmsSender; import uk.gov.justice.services.core.jms.JmsSenderFactory; @@ -43,6 +45,9 @@ public class SenderProducerTest { @Mock private InjectionPoint commandHandlerInjectionPoint; + @Mock + private InjectionPoint eventProcessorInjectionPoint; + @Mock private InjectionPoint invalidInjectionPoint; @@ -58,6 +63,9 @@ public class SenderProducerTest { @Mock private Member commandHandlerMember; + @Mock + private Member eventProcessorMember; + @Mock private Member invalidMember; @@ -73,6 +81,9 @@ public class SenderProducerTest { @Mock private JmsSender commandHandlerJmsSender; + @Mock + private DispatcherProducer dispatcherProducer; + private SenderProducer senderProducer; @Before @@ -127,6 +138,16 @@ public void shouldReturnADifferentSender() throws Exception { verify(jmsSenderFactory, times(1)).createJmsSender(COMMAND_HANDLER); } + @Test + public void shouldReturnRestSenderForEventProcessor() throws Exception { + mockInjectionPoint(eventProcessorInjectionPoint, eventProcessorMember, TestEventProcessor.class); + senderProducer.dispatcherProducer = dispatcherProducer; + + senderProducer.produce(eventProcessorInjectionPoint); + + verify(dispatcherProducer, times(1)).produceSender(eventProcessorInjectionPoint); + } + @Test(expected = MissingAnnotationException.class) public void shouldThrowExceptionWithInvalidHandler() throws Exception { mockInjectionPoint(invalidInjectionPoint, invalidMember, TestInvalidHandler.class); @@ -162,6 +183,10 @@ public static class TestCommandController2 { public static class TestCommandHandler { } + @ServiceComponent(EVENT_PROCESSOR) + public static class TestEventProcessor { + } + public static class TestInvalidHandler { } } \ No newline at end of file