Added dispatch method with ServiceRequestEnvelope
diff --git a/serviceConfigDevLocal_MASTER.yml b/serviceConfigDevLocal_MASTER.yml index 59921dc..e27524c 100644 --- a/serviceConfigDevLocal_MASTER.yml +++ b/serviceConfigDevLocal_MASTER.yml
@@ -1,4 +1,4 @@ -servicesDistributionFileName: servicesDistributionDevLocal.json +servicesDistributionFileName: servicesDistributionDevServer.json logging: @@ -12,7 +12,7 @@ archivedFileCount: 5 timeZone: UTC loggers: - pta: DEBUG + pta: INFO org.eclipse.jetty.servlets: DEBUG server:
diff --git a/src/main/java/pta/de/api/ServiceRequestEnvelope.java b/src/main/java/pta/de/api/ServiceRequestEnvelope.java new file mode 100644 index 0000000..fb5ce7a --- /dev/null +++ b/src/main/java/pta/de/api/ServiceRequestEnvelope.java
@@ -0,0 +1,102 @@ +package pta.de.api; + +import com.fasterxml.jackson.annotation.JsonProperty; +import org.glassfish.jersey.internal.util.Base64; + +public class ServiceRequestEnvelope { + public static class HttpHeader { + private String attribute; + private String value; + + public String getAttribute() { + return attribute; + } + + public void setAttribute(String attribute) { + this.attribute = attribute; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + } + + private String serviceName; + private boolean isHttps; + private String method; + private String uriFragment; + private String payload; + private HttpHeader[] headers; + + @JsonProperty + public String getServiceName() { + return serviceName; + } + + @JsonProperty + public void setServiceName(String serviceName) { + this.serviceName = serviceName; + } + + @JsonProperty + public boolean isHttps() { + return isHttps; + } + + @JsonProperty + public void setHttps(boolean https) { + isHttps = https; + } + + @JsonProperty + public String getMethod() { + return method; + } + + @JsonProperty + public void setMethod(String method) { + this.method = method; + } + + @JsonProperty + public String getUriFragment() { + return uriFragment; + } + + @JsonProperty + public void setUriFragment(String uriFragment) { + this.uriFragment = uriFragment; + } + + @JsonProperty + public String getPayload() { + return payload; + } + + @JsonProperty + public void setPayload(String payload) { + this.payload = payload; + } + + @JsonProperty + public HttpHeader[] getHeaders() { + return headers; + } + + @JsonProperty + public void setHeaders(HttpHeader[] headers) { + this.headers = headers; + } + + public String getPayloadDecode() { + return Base64.decodeAsString(payload); + } + + public void setPayloadEncode(String payload) { + this.payload = Base64.encodeAsString(payload); + } +}
diff --git a/src/main/java/pta/de/core/communication/RestServiceWrapper.java b/src/main/java/pta/de/core/communication/RestServiceWrapper.java index dbfdbb8..1a5eead 100644 --- a/src/main/java/pta/de/core/communication/RestServiceWrapper.java +++ b/src/main/java/pta/de/core/communication/RestServiceWrapper.java
@@ -3,8 +3,7 @@ import org.apache.http.HttpResponse; import org.apache.http.HttpStatus; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.client.methods.HttpPost; +import org.apache.http.client.methods.*; import org.apache.http.conn.ssl.SSLConnectionSocketFactory; import org.apache.http.conn.ssl.TrustSelfSignedStrategy; import org.apache.http.entity.StringEntity; @@ -17,17 +16,51 @@ import pta.de.core.common.Globals; import pta.de.core.exceptions.HttpStatusException; +import javax.ws.rs.core.Response; import java.io.IOException; import java.nio.charset.StandardCharsets; +import java.util.List; public class RestServiceWrapper { + public static class HttpHeader { + private String attribute; + private String value; + + public HttpHeader(String attribute, String value) { + this.attribute = attribute; + this.value = value; + } + + public String getAttribute() { + return attribute; + } + + public void setAttribute(String attribute) { + this.attribute = attribute; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + } + private static final Logger LOGGER = Logger.getLogger(RestServiceWrapper.class.getName()); private boolean useHttps; + public enum HttpMethod {GET, POST, PUT, DELETE} + public RestServiceWrapper(boolean https) { this.useHttps = https; } + public static HttpHeader createHeader(String attribute, String value) { + return new HttpHeader(attribute, value); + } + public String performGetRequest(String restFunctionWithParams) throws HttpStatusException { LOGGER.debug("CompleteUrl: " + restFunctionWithParams); // create HTTP Client @@ -43,7 +76,7 @@ response = httpClient.execute(getRequest); } catch (IOException e) { - String errtext = "Communication to <" + restFunctionWithParams + "> failed!"; + String errtext = "Get communication to <" + restFunctionWithParams + "> failed."; LOGGER.warn(errtext, e); throw new HttpStatusException(HttpStatus.SC_SERVICE_UNAVAILABLE); } @@ -70,13 +103,69 @@ try { response = httpClient.execute(postRequest); } catch (IOException e) { - String errtext = "Communication to <" + restFunctionWithParams + "> failed!"; + String errtext = "Post communication to <" + restFunctionWithParams + "> failed!"; LOGGER.warn(errtext, e); throw new HttpStatusException(HttpStatus.SC_SERVICE_UNAVAILABLE); } return createJson(response); } + public Response performHttpRequest(HttpMethod method, String restFunctionWithParams, + List<HttpHeader> headerList, String data) throws HttpStatusException { + + // create HTTP Client + CloseableHttpClient httpClient = createHttpsClient(); + + HttpRequestBase request = createRequest(method, restFunctionWithParams, data); + for (HttpHeader header : headerList) { + request.addHeader(header.attribute, header.value); + } + + HttpResponse response; + // Execute request an catch response + try { + response = httpClient.execute(request); + } catch (IOException e) { + String errtext = "Communication to <" + restFunctionWithParams + "> failed..."; + LOGGER.warn(errtext, e); + throw new HttpStatusException(HttpStatus.SC_SERVICE_UNAVAILABLE); + } + + String ent = createJson(response); + + Response.ResponseBuilder rb = Response.status(response.getStatusLine().getStatusCode()); + if (ent != null) { + rb.entity(ent); + } + return rb.build(); + } + + private HttpRequestBase createRequest(HttpMethod method, String restFunctionWithParams, String data) throws HttpStatusException { + HttpRequestBase ret; + switch (method) { + case GET: + ret = new HttpGet(restFunctionWithParams); + break; + case POST: + ret = setPayload(new HttpPost(restFunctionWithParams), data); + break; + case PUT: + ret = setPayload(new HttpPut(restFunctionWithParams), data); + break; + case DELETE: + ret = new HttpDelete(restFunctionWithParams); + break; + default: + throw new HttpStatusException(HttpStatus.SC_METHOD_NOT_ALLOWED); + } + return ret; + } + + private HttpRequestBase setPayload(HttpEntityEnclosingRequestBase req, String data) { + req.setEntity(new StringEntity(data, StandardCharsets.UTF_8)); + return req; + } + private CloseableHttpClient createHttpsClient() throws HttpStatusException { if (useHttps) { try {
diff --git a/src/main/java/pta/de/core/controller/DispatchController.java b/src/main/java/pta/de/core/controller/DispatchController.java new file mode 100644 index 0000000..36329a3 --- /dev/null +++ b/src/main/java/pta/de/core/controller/DispatchController.java
@@ -0,0 +1,64 @@ +package pta.de.core.controller; + +import com.google.common.collect.Lists; +import org.apache.log4j.Logger; +import org.eclipse.jetty.http.HttpStatus; +import pta.de.api.ServiceDistributionCluster; +import pta.de.api.ServiceDistributionCluster.ServiceDistribution; +import pta.de.api.ServiceRequestEnvelope; +import pta.de.core.communication.RestServiceWrapper; +import pta.de.core.exceptions.HttpStatusException; + +import javax.ws.rs.core.Response; +import java.util.List; + +import static java.util.stream.Collectors.toList; + +public class DispatchController { + private static Logger logger = Logger.getLogger(DispatchController.class); + + public Response dispatch(String clustername, ServiceRequestEnvelope envelope) throws HttpStatusException { + RestServiceWrapper rsWrap = createRestServiceWrapper(envelope.isHttps()); + List<RestServiceWrapper.HttpHeader> transformedList = Lists.newArrayList(envelope.getHeaders()).stream() + .map(header -> + RestServiceWrapper.createHeader(header.getAttribute(), header.getValue())).collect(toList()); + + String url = locateBaseUrl(ServicesConfigCache.getInstance().getCache(), + clustername, envelope.getServiceName()) + "/" + + envelope.getUriFragment(); + + return rsWrap.performHttpRequest(resolveMethod(envelope.getMethod()), + url, transformedList, envelope.getPayloadDecode()); + } + + protected RestServiceWrapper createRestServiceWrapper(boolean useHttps) { + return new RestServiceWrapper(useHttps); + } + + private String locateBaseUrl(ServiceDistributionCluster[] cluster, String clustername, String servicename) throws HttpStatusException { + ServiceDistribution dist = new ServiceResolver(cluster) + .resolve(clustername, servicename); + if (dist == null) { + logger.error("Service [" + clustername + "]/[" + servicename + "] is not resolvable!"); + throw new HttpStatusException(HttpStatus.NOT_FOUND_404); + } + return dist.getProtocol() + "://" + dist.getHost() + ":" + dist.getPortApp() + dist.getUrlPath(); + } + + private RestServiceWrapper.HttpMethod resolveMethod(String method) throws HttpStatusException { + switch (method.toUpperCase()) { + case "GET": + return RestServiceWrapper.HttpMethod.GET; + case "POST": + return RestServiceWrapper.HttpMethod.POST; + case "PUT": + return RestServiceWrapper.HttpMethod.PUT; + case "DELETE": + return RestServiceWrapper.HttpMethod.DELETE; + default: + logger.error("Invalid Method: " + method); + throw new HttpStatusException(HttpStatus.METHOD_NOT_ALLOWED_405); + } + + } +}
diff --git a/src/main/java/pta/de/core/controller/ServiceResolver.java b/src/main/java/pta/de/core/controller/ServiceResolver.java new file mode 100644 index 0000000..fca1e45 --- /dev/null +++ b/src/main/java/pta/de/core/controller/ServiceResolver.java
@@ -0,0 +1,31 @@ +package pta.de.core.controller; + +import pta.de.api.ServiceDistributionCluster; +import pta.de.api.ServiceDistributionCluster.ServiceDistribution; + +import java.util.Arrays; +import java.util.Optional; + +public class ServiceResolver { + private final ServiceDistributionCluster[] clusters; + + public ServiceResolver(ServiceDistributionCluster[] clusters) { + this.clusters = clusters; + } + + public ServiceDistribution resolve(String clustername, String servicename) { + Optional<ServiceDistributionCluster> cluster + = Arrays.stream(clusters).filter( + x -> x.getClustername().equalsIgnoreCase(clustername)).findFirst(); + if (cluster.isPresent()) { + Optional<ServiceDistributionCluster.ServiceDistribution> dist = + Arrays.stream(cluster.get().getDistributions()) + .filter(x -> x.getName().equalsIgnoreCase(servicename)) + .findFirst(); + if (dist.isPresent()) { + return dist.get(); + } + } + return null; + } +}
diff --git a/src/main/java/pta/de/resources/MicsCentralResource.java b/src/main/java/pta/de/resources/MicsCentralResource.java index 15b4253..ee69d32 100644 --- a/src/main/java/pta/de/resources/MicsCentralResource.java +++ b/src/main/java/pta/de/resources/MicsCentralResource.java
@@ -3,13 +3,14 @@ import org.apache.log4j.Logger; import org.hibernate.validator.constraints.NotEmpty; +import pta.de.api.ServiceRequestEnvelope; +import pta.de.core.common.JsonGeneratorBase; import pta.de.core.controller.BackendController; import pta.de.core.controller.BaseWebService; +import pta.de.core.controller.DispatchController; +import pta.de.core.exceptions.HttpStatusException; -import javax.ws.rs.GET; -import javax.ws.rs.Path; -import javax.ws.rs.PathParam; -import javax.ws.rs.Produces; +import javax.ws.rs.*; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; @@ -35,6 +36,20 @@ return invokeRunnable(() -> new BackendController().getServiceHealthState(clustername, servicename)); } + @POST + @Path("/dispatch/{clustername}") + public Response dispatch(String envelope, + @PathParam("clustername") @NotEmpty String clustername) { + ServiceRequestEnvelope envObj = JsonGeneratorBase.getGson().fromJson(envelope, ServiceRequestEnvelope.class); + + try { + return new DispatchController().dispatch(clustername, envObj); + } catch (HttpStatusException hse) { + logger.error(hse); + return Response.status(hse.getHttpStatus()).build(); + } + } + @GET @Path("/versionInfo")
diff --git a/src/test/java/pta/de/api/ServiceRequestEnvelopeTest.java b/src/test/java/pta/de/api/ServiceRequestEnvelopeTest.java new file mode 100644 index 0000000..6feece9 --- /dev/null +++ b/src/test/java/pta/de/api/ServiceRequestEnvelopeTest.java
@@ -0,0 +1,43 @@ +package pta.de.api; + +import org.glassfish.jersey.internal.util.Base64; +import org.junit.Test; +import pta.de.api.ServiceRequestEnvelope.HttpHeader; + +import static junit.framework.TestCase.assertEquals; + +public class ServiceRequestEnvelopeTest { + + @Test + public void testPojo() { + final String payload = "Testme Accurate"; + final String payloadEncoded = Base64.encodeAsString(payload); + ServiceRequestEnvelope env = new ServiceRequestEnvelope(); + env.setHttps(true); + env.setMethod("POST"); + env.setPayloadEncode(payload); + env.setUriFragment("Fraggel"); + env.setServiceName("Salve"); + + HttpHeader[] headers = new HttpHeader[2]; + headers[0] = new HttpHeader(); + headers[0].setAttribute("1stAttr"); + headers[0].setValue("1stValue"); + headers[1] = new HttpHeader(); + headers[1].setAttribute("2ndAttr"); + headers[1].setValue("2ndValue"); + env.setHeaders(headers); + + assertEquals(true, env.isHttps()); + assertEquals("POST", env.getMethod()); + assertEquals(payload, env.getPayloadDecode()); + assertEquals(payloadEncoded, env.getPayload()); + env.setPayload(payload); + assertEquals(payload, env.getPayload()); + assertEquals("Fraggel", env.getUriFragment()); + assertEquals("Salve", env.getServiceName()); + assertEquals(2, env.getHeaders().length); + assertEquals("2ndAttr", env.getHeaders()[1].getAttribute()); + assertEquals("1stValue", env.getHeaders()[0].getValue()); + } +}
diff --git a/src/test/java/pta/de/core/controller/DispatchControllerTest.java b/src/test/java/pta/de/core/controller/DispatchControllerTest.java new file mode 100644 index 0000000..b061565 --- /dev/null +++ b/src/test/java/pta/de/core/controller/DispatchControllerTest.java
@@ -0,0 +1,102 @@ +package pta.de.core.controller; + +import org.easymock.EasyMock; +import org.eclipse.jetty.http.HttpStatus; +import org.junit.Before; +import org.junit.Test; +import org.powermock.reflect.Whitebox; +import pta.de.api.ServiceDistributionCluster; +import pta.de.api.ServiceRequestEnvelope; +import pta.de.core.common.JsonGeneratorBase; +import pta.de.core.common.util.ResourceLoaderBase; +import pta.de.core.communication.RestServiceWrapper; +import pta.de.core.exceptions.HttpStatusException; + +import javax.ws.rs.core.Response; + +import static org.easymock.EasyMock.anyObject; +import static org.easymock.EasyMock.anyString; +import static org.junit.Assert.assertEquals; + +public class DispatchControllerTest extends ResourceLoaderBase { + + @Before + public void init() { + String clusterString = this.loadStringFromResource("servicesDistributionTest.json"); + ServiceDistributionCluster[] cluster = JsonGeneratorBase.getGson() + .fromJson(clusterString, ServiceDistributionCluster[].class); + ServicesConfigCache cache = ServicesConfigCache.getInstance(); + Whitebox.setInternalState(cache, "cache", cluster); + + + } + + @Test + public void testDispatch_ok() throws HttpStatusException { + Response response = Response.ok().entity("{ret: '111'}").build(); + final RestServiceWrapper rester = EasyMock.createMock(RestServiceWrapper.class); + EasyMock.expect(rester.performHttpRequest(anyObject(), + anyString(), anyObject(), anyString())).andReturn(response); + EasyMock.replay(rester); + ServiceRequestEnvelope envelope = JsonGeneratorBase.getGson().fromJson( + loadStringFromResource("serviceRequestEnvelope.json"), ServiceRequestEnvelope.class); + + + DispatchController controller = new DispatchController() { + @Override + protected RestServiceWrapper createRestServiceWrapper(boolean b) { + return rester; + } + }; + + Response resp2 = controller.dispatch("elogbook.openK", envelope); + assertEquals(HttpStatus.OK_200, resp2.getStatus()); + + } + + @Test(expected = HttpStatusException.class) + public void testDispatch_InvCluster() throws HttpStatusException { + + Response response = Response.ok().entity("{ret: '111'}").build(); + final RestServiceWrapper rester = EasyMock.createMock(RestServiceWrapper.class); + EasyMock.expect(rester.performHttpRequest(anyObject(), + anyString(), anyObject(), anyString())).andReturn(response); + EasyMock.replay(rester); + ServiceRequestEnvelope envelope = JsonGeneratorBase.getGson().fromJson( + loadStringFromResource("serviceRequestEnvelope.json"), ServiceRequestEnvelope.class); + + + DispatchController controller = new DispatchController() { + @Override + protected RestServiceWrapper createRestServiceWrapper(boolean b) { + return rester; + } + }; + + try { + controller.dispatch("invalid", envelope); + } catch (HttpStatusException hse) { + assertEquals(HttpStatus.NOT_FOUND_404, hse.getHttpStatus()); + throw hse; + } + } + + @Test(expected = HttpStatusException.class) + public void testResolveMethod() throws Exception { + DispatchController ctrl = new DispatchController(); + assertEquals(RestServiceWrapper.HttpMethod.GET, Whitebox.invokeMethod(ctrl, "resolveMethod", "get")); + assertEquals(RestServiceWrapper.HttpMethod.POST, Whitebox.invokeMethod(ctrl, "resolveMethod", "Post")); + assertEquals(RestServiceWrapper.HttpMethod.DELETE, Whitebox.invokeMethod(ctrl, "resolveMethod", "DELETE")); + assertEquals(RestServiceWrapper.HttpMethod.PUT, Whitebox.invokeMethod(ctrl, "resolveMethod", "put")); + + String s; + try { + s = Whitebox.invokeMethod(ctrl, "resolveMethod", "invalid"); + } catch (HttpStatusException hse) { + // the correct test path goes in here!!!!!! + assertEquals(HttpStatus.METHOD_NOT_ALLOWED_405, hse.getHttpStatus()); + throw hse; + } + } + +}
diff --git a/src/test/resources/serviceRequestEnvelope.json b/src/test/resources/serviceRequestEnvelope.json new file mode 100644 index 0000000..9722f58 --- /dev/null +++ b/src/test/resources/serviceRequestEnvelope.json
@@ -0,0 +1,17 @@ +{ + "serviceName": "auth-n-auth.mics", + "isHttps": false, + "method": "GET", + "uriFragment": "versionInfo", + "payload": "eyAiYmFja2VuZFZlcnNpb24iOiAiMC4xLjMtU05BUFNIT1QifQ==", + "headers": [ + { + "attribute": "Content-Type", + "value": "application/json" + }, + { + "attribute": "accept", + "value": "application/json" + } + ] +} \ No newline at end of file