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