From f709acf2e6fc372ed36e0d2612a0b25ff1d582de Mon Sep 17 00:00:00 2001 From: "dave.adams (da490c)" Date: Wed, 24 May 2017 17:08:10 -0400 Subject: Update rest-client with additional operations Change-Id: Ia19c5156d40c816dc1ee77cece92c43f40791c59 Signed-off-by: dave.adams (da490c) --- README.md | 25 +- pom.xml | 36 +- .../org/openecomp/restclient/client/Headers.java | 2 +- .../restclient/client/OperationResult.java | 91 ++- .../openecomp/restclient/client/RestClient.java | 281 ++++++-- .../restclient/enums/RestAuthenticationMode.java | 66 ++ .../restclient/logging/RestClientMsgs.java | 7 +- .../org/openecomp/restclient/rest/HttpUtil.java | 30 +- .../restclient/rest/RestClientBuilder.java | 120 +++- .../resources/logging/RESTClientMsgs.properties | 4 + .../restclient/client/OperationResultTest.java | 83 +++ .../restclient/client/RESTClientTest.java | 391 ----------- .../restclient/client/RestfulClientTest.java | 721 +++++++++++++++++++++ .../enums/RestAuthenticationModeTest.java | 33 + .../openecomp/restclient/rest/HttpUtilTest.java | 51 ++ .../restclient/rest/RestClientBuilderTest.java | 360 ++++++---- 16 files changed, 1660 insertions(+), 641 deletions(-) create mode 100644 src/main/java/org/openecomp/restclient/enums/RestAuthenticationMode.java create mode 100644 src/test/java/org/openecomp/restclient/client/OperationResultTest.java delete mode 100644 src/test/java/org/openecomp/restclient/client/RESTClientTest.java create mode 100644 src/test/java/org/openecomp/restclient/client/RestfulClientTest.java create mode 100644 src/test/java/org/openecomp/restclient/enums/RestAuthenticationModeTest.java create mode 100644 src/test/java/org/openecomp/restclient/rest/HttpUtilTest.java diff --git a/README.md b/README.md index d17a448..4151a47 100644 --- a/README.md +++ b/README.md @@ -11,16 +11,19 @@ In order to make the _REST Client_ library available to your microservice, inclu org.openecomp.aai rest-client - 1.0.0-SNAPSHOT + 1.1.0-SNAPSHOT ## Code Examples ### Creating and Configuring a Client Instance -In order to start talking to a service, you need to create a client instance and configure it. The _RestClient_ uses a fluent interface which allows it to be both instantiated and configured as in the following example: +In order to start talking to a service, you need to create a client instance and configure it. The _RestClient_ uses a fluent interface which allows it to be both instantiated and configured as in the following examples: + +i) A client using an SSL Client Certificate: // Create an instance of the Rest Client and configure it. RestClient myClient = new RestClient() + .authenticationMode(RestAuthenticationMode.SSL_CERT) .validateServerHostname(false) .validateServerCertChain(true) .clientCertFile("certificate_filename") @@ -28,6 +31,24 @@ In order to start talking to a service, you need to create a client instance and .connectTimeoutMs(1000) .readTimeoutMs(1000) +ii) A client using SSL Basic-Auth: + + // Create an instance of the Rest Client and configure it. + RestClient myClient = new RestClient() + .authenticationMode(RestAuthenticationMode.SSL_BASIC) + .basicAuthUsername("username") + .basicAuthPassword("password") + .connectTimeoutMs(1000) + .readTimeoutMs(1000) + +iii) A client using non-authentication HTTP: + + // Create an instance of the Rest Client and configure it. + RestClient myClient = new RestClient() + .authenticationMode(RestAuthenticationMode.HTTP_NOAUTH) + .connectTimeoutMs(1000) + .readTimeoutMs(1000) + Note, that all of the above configuration parameters are optional and will be set to default values if they are not specified. ### Querying The A&AI diff --git a/pom.xml b/pom.xml index 59374ae..45a5f3e 100644 --- a/pom.xml +++ b/pom.xml @@ -31,13 +31,41 @@ common-logging 1.0.0 - + - com.sun.jersey.jersey-test-framework - jersey-test-framework-grizzly2 - 1.18 + org.mockito + mockito-all + 1.10.19 test + + + org.powermock + powermock-module-junit4 + 1.6.2 + test + + + + org.powermock + powermock-api-mockito + 1.6.2 + test + + + + org.powermock + powermock-module-javaagent + 1.6.2 + test + + + + org.powermock + powermock-module-junit4-rule-agent + 1.6.2 + test + diff --git a/src/main/java/org/openecomp/restclient/client/Headers.java b/src/main/java/org/openecomp/restclient/client/Headers.java index aac817e..dbfb6bb 100644 --- a/src/main/java/org/openecomp/restclient/client/Headers.java +++ b/src/main/java/org/openecomp/restclient/client/Headers.java @@ -33,5 +33,5 @@ public final class Headers { public static final String IF_MATCH = "If-Match"; public static final String IF_NONE_MATCH = "If-None-Match"; public static final String ACCEPT = "Accept"; - + public static final String AUTHORIZATION = "Authorization"; } diff --git a/src/main/java/org/openecomp/restclient/client/OperationResult.java b/src/main/java/org/openecomp/restclient/client/OperationResult.java index c9d0f9c..1a301b9 100644 --- a/src/main/java/org/openecomp/restclient/client/OperationResult.java +++ b/src/main/java/org/openecomp/restclient/client/OperationResult.java @@ -28,10 +28,32 @@ import javax.ws.rs.core.MultivaluedMap; public class OperationResult { + private String requestedLink; private String result; private String failureCause; + private boolean fromCache; private int resultCode; - private MultivaluedMap headers; + private int numRetries; + private MultivaluedMap responseHeaders; + + + public OperationResult() { + super(); + this.numRetries = 0; + this.fromCache = false; + } + + /** + * Instantiates a new operation result. + * + * @param resultCode the result code + * @param result the result + */ + public OperationResult(int resultCode, String result) { + this(); + this.resultCode = resultCode; + this.result = result; + } /** * Get the HTTP headers of the response. @@ -39,13 +61,21 @@ public class OperationResult { * @return the HTTP headers of the response. */ public MultivaluedMap getHeaders() { - return headers; + return responseHeaders; } - public void setHeaders(MultivaluedMap headers) { - this.headers = headers; + /** + * Returns true if the HTTP Status Code 200 <= x <= 299 + * + * @return true, if successful + */ + public boolean wasSuccessful() { + return (resultCode > 199 && resultCode < 300); } + public void setHeaders(MultivaluedMap headers) { + this.responseHeaders = headers; + } public String getResult() { return result; @@ -62,22 +92,67 @@ public class OperationResult { public String getFailureCause() { return failureCause; } - + + /** + * Sets the result. + * + * @param resultCode the result code + * @param result the result + */ + public void setResult(int resultCode, String result) { + this.resultCode = resultCode; + this.result = result; + } + public void setFailureCause(String failureCause) { this.failureCause = failureCause; } + /** + * Sets the failure cause. + * + * @param resultCode the result code + * @param failureCause the result error + */ + public void setFailureCause(int resultCode, String failureCause) { + this.resultCode = resultCode; + this.failureCause = failureCause; + } + + public void setResultCode(int resultCode) { this.resultCode = resultCode; } - public OperationResult() { - super(); + public String getRequestedLink() { + return requestedLink; + } + + public void setRequestedLink(String requestedLink) { + this.requestedLink = requestedLink; + } + + public boolean isFromCache() { + return fromCache; + } + + public void setFromCache(boolean fromCache) { + this.fromCache = fromCache; + } + + public int getNumRetries() { + return numRetries; + } + + public void setNumRetries(int numRetries) { + this.numRetries = numRetries; } @Override public String toString() { - return "OperationResult [result=" + result + ", resultCode=" + resultCode + "]"; + return "OperationResult [result=" + result + ", requestedLink=" + requestedLink + + ", failureCause=" + failureCause + ", resultCode=" + resultCode + ", numRetries=" + + numRetries + ", responseHeaders=" + responseHeaders + "]"; } } diff --git a/src/main/java/org/openecomp/restclient/client/RestClient.java b/src/main/java/org/openecomp/restclient/client/RestClient.java index 900c4e0..02d19e6 100644 --- a/src/main/java/org/openecomp/restclient/client/RestClient.java +++ b/src/main/java/org/openecomp/restclient/client/RestClient.java @@ -24,21 +24,6 @@ */ package org.openecomp.restclient.client; -import com.sun.jersey.api.client.Client; -import com.sun.jersey.api.client.ClientResponse; -import com.sun.jersey.api.client.WebResource; -import com.sun.jersey.api.client.WebResource.Builder; -import com.sun.jersey.core.util.MultivaluedMapImpl; - -import org.openecomp.cl.api.LogFields; -import org.openecomp.cl.api.LogLine; -import org.openecomp.cl.api.Logger; -import org.openecomp.cl.eelf.LoggerFactory; -import org.openecomp.cl.mdc.MdcContext; -import org.openecomp.cl.mdc.MdcOverride; -import org.openecomp.restclient.logging.RestClientMsgs; -import org.openecomp.restclient.rest.RestClientBuilder; - import java.io.ByteArrayOutputStream; import java.text.SimpleDateFormat; import java.util.Arrays; @@ -46,16 +31,36 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.Response; +import org.openecomp.cl.api.LogFields; +import org.openecomp.cl.api.LogLine; +import org.openecomp.cl.api.Logger; +import org.openecomp.cl.eelf.LoggerFactory; +import org.openecomp.cl.mdc.MdcContext; +import org.openecomp.cl.mdc.MdcOverride; +import org.openecomp.restclient.enums.RestAuthenticationMode; +import org.openecomp.restclient.logging.RestClientMsgs; +import org.openecomp.restclient.rest.RestClientBuilder; + +import com.sun.jersey.api.client.Client; +import com.sun.jersey.api.client.ClientResponse; +import com.sun.jersey.api.client.WebResource; +import com.sun.jersey.api.client.WebResource.Builder; +import com.sun.jersey.core.util.MultivaluedMapImpl; + /** * This class provides a general client implementation that micro services can use for communicating * with the endpoints via their exposed REST interfaces. + * */ + public class RestClient { /** @@ -63,11 +68,9 @@ public class RestClient { * communicate with the REST endpoint. */ private RestClientBuilder clientBuilder; - - /** - * The low level instance of the REST client that will be used to communicate with the endpoint. - */ - private Client restClient = null; + + private final ConcurrentMap CLIENT_CACHE = new ConcurrentHashMap(); + private static final String REST_CLIENT_INSTANCE = "REST_CLIENT_INSTANCE"; /** Standard logger for producing log statements. */ private Logger logger = LoggerFactory.getInstance().getLogger("AAIRESTClient"); @@ -89,11 +92,20 @@ public class RestClient { /** Reusable function call for DELETE REST operations. */ private final RestOperation deleteOp = new DeleteRestOperation(); + /** Reusable function call for HEAD REST operations. */ + private final RestOperation headOp = new HeadRestOperation(); + + /** Reusable function call for PATCH REST operations. */ + private final RestOperation patchOp = new PatchRestOperation(); + + /** * Creates a new instance of the {@link RestClient}. */ public RestClient() { + clientBuilder = new RestClientBuilder(); + } @@ -106,6 +118,27 @@ public class RestClient { public RestClient(RestClientBuilder rcBuilder) { clientBuilder = rcBuilder; } + + public RestClient authenticationMode(RestAuthenticationMode mode) { + logger.debug("Set rest authentication mode= " + mode); + clientBuilder.setAuthenticationMode(mode); + return this; + } + + public RestClient basicAuthUsername(String username) { + logger.debug("Set SSL BasicAuth username = " + username); + clientBuilder.setBasicAuthUsername(username); + return this; + } + + public RestClient basicAuthPassword(String password) { + /* + * purposely not logging out the password, I guess we could obfuscate it if we really want to + * see it in the logs + */ + clientBuilder.setBasicAuthPassword(password); + return this; + } /** @@ -160,7 +193,6 @@ public class RestClient { * @return The AAIRESTClient instance. This is useful for chaining parameter assignments. */ public RestClient clientCertPassword(String password) { - logger.debug("Set client certificate password = " + password); clientBuilder.setClientCertPassword(password); return this; } @@ -207,6 +239,26 @@ public class RestClient { return this; } + private boolean shouldRetry(OperationResult operationResult) { + + if (operationResult == null) { + return true; + } + + int resultCode = operationResult.getResultCode(); + + if (resultCode == 200) { + return false; + } + + if (resultCode == 404) { + return false; + } + + return true; + + } + /** * This method operates on a REST endpoint by submitting an HTTP operation request against the * supplied URL. @@ -232,23 +284,29 @@ public class RestClient { long startTimeInMs = System.currentTimeMillis(); for (int retryCount = 0; retryCount < numRetries; retryCount++) { - logger.info(RestClientMsgs.HTTP_REQUEST_WITH_RETRIES, url, Integer.toString(retryCount + 1)); - + logger.info(RestClientMsgs.HTTP_REQUEST_WITH_RETRIES, operation.getRequestType().toString(), + url, Integer.toString(retryCount + 1)); + // Submit our query to the AAI. result = processRequest(operation, url, payload, headers, contentType, responseType); // If the submission was successful then we're done. - if (Integer.toString(result.getResultCode()).charAt(0) == '2') { - logger.info(RestClientMsgs.HTTP_REQUEST_TIME_WITH_RETRIES, - Long.toString(System.currentTimeMillis() - startTimeInMs), url, + + if (!shouldRetry(result)) { + + logger.info(RestClientMsgs.HTTP_REQUEST_TIME_WITH_RETRIES, operation.getRequestType().toString(),url, + Long.toString(System.currentTimeMillis() - startTimeInMs), Integer.toString(retryCount)); + + result.setNumRetries(retryCount); + return result; } // Our submission was unsuccessful... try { // Sleep between re-tries to be nice to the target system. - Thread.sleep(500); + Thread.sleep(50); } catch (InterruptedException e) { logger.error(RestClientMsgs.HTTP_REQUEST_INTERRUPTED, url, e.getLocalizedMessage()); @@ -257,9 +315,10 @@ public class RestClient { } // If we've gotten this far, then we failed all of our retries. + result.setNumRetries(numRetries); result.setResultCode(504); result.setFailureCause( - "Failed to get a successful result " + "after multiple retries to target server"); + "Failed to get a successful result after multiple retries to target server."); return result; } @@ -379,7 +438,39 @@ public class RestClient { MediaType contentType, MediaType responseType) { return processRequest(postOp, url, payload, headers, contentType, responseType); } + + /** + * This method submits an HTTP POST request against the supplied URL, and emulates a PATCH + * operation by setting a special header value + * + * @param url - The REST endpoint to submit the POST request to. + * @param payload - the payload to send to the supplied URL + * @param headers - The headers that should be passed in the request + * @param contentType - The content type of the payload + * @param responseType - The expected format of the response. + * + * @return The result of the POST request. + */ + public OperationResult patch(String url, String payload, Map> headers, + MediaType contentType, MediaType responseType) { + return processRequest(patchOp, url, payload, headers, contentType, responseType); + } + + /** + * This method submits an HTTP HEAD request against the supplied URL + * + * @param url - The REST endpoint to submit the POST request to. + * @param headers - The headers that should be passed in the request + * @param responseType - The expected format of the response. + * + * @return The result of the POST request. + */ + public OperationResult head(String url, Map> headers, + MediaType responseType) { + return processRequest(headOp, url, null, headers, null, responseType); + } + /** * This method submits an HTTP GET request against the supplied URL. * @@ -508,6 +599,12 @@ public class RestClient { for (Entry> header : headers.entrySet()) { builder.header(header.getKey(), header.getValue()); } + + if (clientBuilder.getAuthenticationMode() == RestAuthenticationMode.SSL_BASIC) { + builder = builder.header(Headers.AUTHORIZATION, + clientBuilder.getBasicAuthenticationCredentials()); + } + } return builder; @@ -554,39 +651,58 @@ public class RestClient { } } - /** * This method creates an instance of the low level REST client to use for communicating with the * AAI, if one has not already been created, otherwise it returns the already created instance. * * @return A {@link Client} instance. */ - private synchronized Client getClient() throws Exception { + protected Client getClient() throws Exception { + + /* + * Attempting a new way of doing non-blocking thread-safe lazy-initialization by using Java 1.8 + * computeIfAbsent functionality. A null value will not be stored, but once a valid mapping has + * been established, then the same value will be returned. + * + * One awkwardness of the computeIfAbsent is the lack of support for thrown exceptions, which + * required a bit of hoop jumping to preserve the original exception for the purpose of + * maintaining the pre-existing this API signature. + */ + + final InitializedClient clientInstance = + CLIENT_CACHE.computeIfAbsent(REST_CLIENT_INSTANCE, k -> loggedClientInitialization()); + + if (clientInstance.getCaughtException() != null) { + throw new InstantiationException(clientInstance.getCaughtException().getMessage()); + } - if (restClient == null) { + return clientInstance.getClient(); - if (logger.isDebugEnabled()) { - logger.debug("Instantiating REST client with following parameters:"); - logger.debug( - " validate server hostname = " + clientBuilder.isValidateServerHostname()); - logger.debug( - " validate server certificate chain = " + clientBuilder.isValidateServerCertChain()); - logger.debug( - " client certificate filename = " + clientBuilder.getClientCertFileName()); - logger.debug( - " client certificate password = " + clientBuilder.getClientCertPassword()); - logger.debug( - " trust store filename = " + clientBuilder.getTruststoreFilename()); - logger.debug(" connection timeout = " - + clientBuilder.getConnectTimeoutInMs() + " ms"); - logger.debug( - " read timeout = " + clientBuilder.getReadTimeoutInMs() + " ms"); - } + } - restClient = clientBuilder.getClient(); + /** + * This method will only be called if computerIfAbsent is true. The return value is null, then the result is not + * stored in the map. + * + * @return a new client instance or null + */ + private InitializedClient loggedClientInitialization() { + + if (logger.isDebugEnabled()) { + logger.debug("Instantiating REST client with following parameters:"); + logger.debug(clientBuilder.toString()); + } + + InitializedClient initClient = new InitializedClient(); + + try { + initClient.setClient(clientBuilder.getClient()); + } catch ( Throwable error ) { + initClient.setCaughtException(error); } + + return initClient; - return restClient; } @@ -609,10 +725,10 @@ public class RestClient { opResult.setResultCode(statusCode); - if ((statusCode < 200) || (statusCode > 299)) { - opResult.setFailureCause(payload); - } else { + if (opResult.wasSuccessful()) { opResult.setResult(payload); + } else { + opResult.setFailureCause(payload); } opResult.setHeaders(response.getHeaders()); @@ -657,6 +773,34 @@ public class RestClient { return RequestType.DELETE; } } + + private class HeadRestOperation implements RestOperation { + public ClientResponse processOperation(Builder builder) { + return builder.head(); + } + + public RequestType getRequestType() { + return RequestType.HEAD; + } + } + + private class PatchRestOperation implements RestOperation { + + /** + * Technically there is no standarized PATCH operation for the + * jersey client, but we can use the method-override approach + * instead. + */ + public ClientResponse processOperation(Builder builder) { + builder = builder.header("X-HTTP-Method-Override", "PATCH"); + return builder.post(ClientResponse.class); + } + + public RequestType getRequestType() { + return RequestType.PATCH; + } + } + /** * Interface used wrap a Jersey REST call using a functional interface. @@ -680,7 +824,36 @@ public class RestClient { * The supported REST request types. */ public enum RequestType { - GET, PUT, POST, DELETE; + GET, PUT, POST, DELETE, PATCH, HEAD + } + } + + /* + * An entity to encapsulate an expected result and a potential failure cause when returning from a + * functional interface during the computeIfAbsent call. + */ + private class InitializedClient { + private Client client; + private Throwable caughtException; + + public InitializedClient() { + client = null; + caughtException = null; + } + + public Client getClient() { + return client; + } + public void setClient(Client client) { + this.client = client; + } + public Throwable getCaughtException() { + return caughtException; + } + public void setCaughtException(Throwable caughtException) { + this.caughtException = caughtException; } + } + } diff --git a/src/main/java/org/openecomp/restclient/enums/RestAuthenticationMode.java b/src/main/java/org/openecomp/restclient/enums/RestAuthenticationMode.java new file mode 100644 index 0000000..5174b75 --- /dev/null +++ b/src/main/java/org/openecomp/restclient/enums/RestAuthenticationMode.java @@ -0,0 +1,66 @@ +/** + * ============LICENSE_START======================================================= + * RestClient + * ================================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ +package org.openecomp.restclient.enums; + +/** + * Authentication Modes: + *
  • HTTP_NOAUTH - intended to represent basic HTTP no authentication + *
  • SSL_BASIC - HTTP/S with username/password + *
  • SSL_CERT - HTTP/S with client cert + */ + +public enum RestAuthenticationMode { + HTTP_NOAUTH("HTTP_NO_AUTH"), SSL_BASIC("SSL_BASIC"), SSL_CERT("SSL_CERT"), UNKNOWN_MODE( + "UNKNOWN_MODE"); + + private String authenticationModeLabel; + + private RestAuthenticationMode(String authModelLabel) { + this.authenticationModeLabel = authModelLabel; + } + + public String getAuthenticationModeLabel() { + return authenticationModeLabel; + } + + public static RestAuthenticationMode getRestAuthenticationMode(String authenticationMode) { + + RestAuthenticationMode mappedMode = RestAuthenticationMode.UNKNOWN_MODE; + + if (authenticationMode == null) { + return mappedMode; + } + + try { + mappedMode = RestAuthenticationMode.valueOf(authenticationMode); + } catch (Exception exc) { + mappedMode = RestAuthenticationMode.UNKNOWN_MODE; + } + + return mappedMode; + + } + +} diff --git a/src/main/java/org/openecomp/restclient/logging/RestClientMsgs.java b/src/main/java/org/openecomp/restclient/logging/RestClientMsgs.java index 0b59139..49ac648 100644 --- a/src/main/java/org/openecomp/restclient/logging/RestClientMsgs.java +++ b/src/main/java/org/openecomp/restclient/logging/RestClientMsgs.java @@ -102,8 +102,13 @@ public enum RestClientMsgs implements LogMessageEnum { * {0} = URL * {1} - Response code */ - HTTP_RESPONSE; + HTTP_RESPONSE, + /** + * . Arguments: + * {0} = failure cause + */ + CLIENT_INITIALIZATION_FAILURE; /** * Static initializer to ensure the resource bundles for this class are loaded... diff --git a/src/main/java/org/openecomp/restclient/rest/HttpUtil.java b/src/main/java/org/openecomp/restclient/rest/HttpUtil.java index 89af684..4ea8928 100644 --- a/src/main/java/org/openecomp/restclient/rest/HttpUtil.java +++ b/src/main/java/org/openecomp/restclient/rest/HttpUtil.java @@ -50,7 +50,7 @@ public class HttpUtil { * @return true if the response is of the informational class and false otherwise */ public static boolean isHttpResponseClassInformational(int response) { - return isExpectedHttpResponseClass(response, '1'); + return ( response >= 100 && response <= 199); } /** @@ -60,7 +60,8 @@ public class HttpUtil { * @return true if the response is of the success class and false otherwise */ public static boolean isHttpResponseClassSuccess(int response) { - return isExpectedHttpResponseClass(response, '2'); + return ( response >= 200 && response <= 299); + } /** @@ -70,7 +71,7 @@ public class HttpUtil { * @return true if the response is of the redirection class and false otherwise */ public static boolean isHttpResponseClassRedirection(int response) { - return isExpectedHttpResponseClass(response, '3'); + return ( response >= 300 && response <= 399); } /** @@ -80,7 +81,7 @@ public class HttpUtil { * @return true if the response is of the client error class and false otherwise */ public static boolean isHttpResponseClassClientError(int response) { - return isExpectedHttpResponseClass(response, '4'); + return ( response >= 400 && response <= 499); } /** @@ -90,26 +91,7 @@ public class HttpUtil { * @return true if the response is of the server error class and false otherwise */ public static boolean isHttpResponseClassServerError(int response) { - return isExpectedHttpResponseClass(response, '5'); + return ( response >= 500 && response <= 599); } - /** - * Helper method to determine if we have received the response class we are expecting. - * - * @param response the http response we got from our request - * @param expectedClass the expected http response class ie: 1, 2, 3, 4, 5 which maps to 1xx, 2xx, - * 3xx, 4xx, 5xx respectively - * @return true if the response if of our expected class and false if not - */ - private static boolean isExpectedHttpResponseClass(int response, char expectedClass) { - if (response < 100 || response >= 600) { - return false; - } - - if (Integer.toString(response).charAt(0) == expectedClass) { - return true; - } - - return false; - } } diff --git a/src/main/java/org/openecomp/restclient/rest/RestClientBuilder.java b/src/main/java/org/openecomp/restclient/rest/RestClientBuilder.java index 3d546fe..0afd8a6 100644 --- a/src/main/java/org/openecomp/restclient/rest/RestClientBuilder.java +++ b/src/main/java/org/openecomp/restclient/rest/RestClientBuilder.java @@ -24,11 +24,6 @@ */ package org.openecomp.restclient.rest; -import com.sun.jersey.api.client.Client; -import com.sun.jersey.api.client.config.ClientConfig; -import com.sun.jersey.api.client.config.DefaultClientConfig; -import com.sun.jersey.client.urlconnection.HTTPSProperties; - import java.io.FileInputStream; import java.security.KeyStore; import java.security.cert.X509Certificate; @@ -40,16 +35,19 @@ import javax.net.ssl.SSLSession; import javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; +import org.openecomp.restclient.enums.RestAuthenticationMode; + +import com.sun.jersey.api.client.Client; +import com.sun.jersey.api.client.config.ClientConfig; +import com.sun.jersey.api.client.config.DefaultClientConfig; +import com.sun.jersey.client.urlconnection.HTTPSProperties; + /** * This is a generic REST Client builder with flexible security validation. Sometimes it's nice to * be able to disable server chain cert validation and hostname validation to work-around lab * issues, but at the same time be able to provide complete validation with client cert + hostname + - * server cert chain validation. - * I used the ModelLoader REST client as a base and merged in the TSUI client I wrote which also - * validates the server hostname and server certificate chain. - * - * @author DAVEA - * + * server cert chain validation. I used the ModelLoader REST client as a base and merged in the TSUI + * client I wrote which also validates the server hostname and server certificate chain. */ public class RestClientBuilder { @@ -60,15 +58,14 @@ public class RestClientBuilder { public static final String DEFAULT_TRUST_STORE_FILENAME = null; public static final int DEFAULT_CONNECT_TIMEOUT_MS = 60000; public static final int DEFAULT_READ_TIMEOUT_MS = 60000; + public static final RestAuthenticationMode DEFAULT_AUTH_MODE = RestAuthenticationMode.HTTP_NOAUTH; + public static final String DEFAULT_BASIC_AUTH_USERNAME = ""; + public static final String DEFAULT_BASIC_AUTH_PASSWORD = ""; private static final String SSL_PROTOCOL = "TLS"; private static final String KEYSTORE_ALGORITHM = "SunX509"; private static final String KEYSTORE_TYPE = "PKCS12"; - /* - * TODO: implement fluent interface? - */ - private boolean validateServerHostname; private boolean validateServerCertChain; private String clientCertFileName; @@ -76,6 +73,9 @@ public class RestClientBuilder { private String truststoreFilename; private int connectTimeoutInMs; private int readTimeoutInMs; + private RestAuthenticationMode authenticationMode; + private String basicAuthUsername; + private String basicAuthPassword; /** * Rest Client Builder. @@ -88,6 +88,9 @@ public class RestClientBuilder { truststoreFilename = DEFAULT_TRUST_STORE_FILENAME; connectTimeoutInMs = DEFAULT_CONNECT_TIMEOUT_MS; readTimeoutInMs = DEFAULT_READ_TIMEOUT_MS; + authenticationMode = RestAuthenticationMode.HTTP_NOAUTH; + basicAuthUsername = DEFAULT_BASIC_AUTH_USERNAME; + basicAuthPassword = DEFAULT_BASIC_AUTH_PASSWORD; } public boolean isValidateServerHostname() { @@ -146,13 +149,50 @@ public class RestClientBuilder { this.readTimeoutInMs = readTimeoutInMs; } + + + public RestAuthenticationMode getAuthenticationMode() { + return authenticationMode; + } + + public void setAuthenticationMode(RestAuthenticationMode authenticationMode) { + this.authenticationMode = authenticationMode; + } + + public String getBasicAuthUsername() { + return basicAuthUsername; + } + + public void setBasicAuthUsername(String basicAuthUsername) { + this.basicAuthUsername = basicAuthUsername; + } + + public String getBasicAuthPassword() { + return basicAuthPassword; + } + + public void setBasicAuthPassword(String basicAuthPassword) { + this.basicAuthPassword = basicAuthPassword; + } + /** - * Returns Client. + * Returns Client configured for SSL */ public Client getClient() throws Exception { - ClientConfig clientConfig = new DefaultClientConfig(); + switch (authenticationMode) { + case SSL_BASIC: + case SSL_CERT: + return getClient(true); + default: + // return basic non-authenticating HTTP client + return getClient(false); + } + + } + + protected void setupSecureSocketLayerClientConfig(ClientConfig clientConfig) throws Exception { // Check to see if we need to perform proper validation of // the certificate chains. TrustManager[] trustAllCerts = null; @@ -201,7 +241,6 @@ public class RestClientBuilder { ctx.init(null, trustAllCerts, null); } - // Are we performing validation of the server host name? if (validateServerHostname) { clientConfig.getProperties().put(HTTPSProperties.PROPERTY_HTTPS_PROPERTIES, @@ -216,6 +255,21 @@ public class RestClientBuilder { } }, ctx)); } + } + + + /** + * Returns client instance + * + * @param useSsl - used to configure the client with an ssl-context or just plain http + */ + protected Client getClient(boolean useSsl) throws Exception { + + ClientConfig clientConfig = new DefaultClientConfig(); + + if (useSsl) { + setupSecureSocketLayerClientConfig(clientConfig); + } // Finally, create and initialize our client... Client client = null; @@ -226,4 +280,34 @@ public class RestClientBuilder { // ...and return it to the caller. return client; } + + public String getBasicAuthenticationCredentials() { + + String usernameAndPassword = getBasicAuthUsername() + ":" + getBasicAuthPassword(); + return "Basic " + java.util.Base64.getEncoder().encodeToString(usernameAndPassword.getBytes()); + } + + /* + * Added a little bit of logic to obfuscate passwords that could be logged out + * (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return "RestClientBuilder [validateServerHostname=" + validateServerHostname + + ", validateServerCertChain=" + validateServerCertChain + ", " + + (clientCertFileName != null ? "clientCertFileName=" + clientCertFileName + ", " : "") + + (clientCertPassword != null + ? "clientCertPassword=" + + java.util.Base64.getEncoder().encodeToString(clientCertPassword.getBytes()) + ", " + : "") + + (truststoreFilename != null ? "truststoreFilename=" + truststoreFilename + ", " : "") + + "connectTimeoutInMs=" + connectTimeoutInMs + ", readTimeoutInMs=" + readTimeoutInMs + ", " + + (authenticationMode != null ? "authenticationMode=" + authenticationMode + ", " : "") + + (basicAuthUsername != null ? "basicAuthUsername=" + basicAuthUsername + ", " : "") + + (basicAuthPassword != null ? "basicAuthPassword=" + + java.util.Base64.getEncoder().encodeToString(basicAuthPassword.getBytes()) : "") + + "]"; + } + } diff --git a/src/main/resources/logging/RESTClientMsgs.properties b/src/main/resources/logging/RESTClientMsgs.properties index 9df0764..75d2b8d 100644 --- a/src/main/resources/logging/RESTClientMsgs.properties +++ b/src/main/resources/logging/RESTClientMsgs.properties @@ -55,3 +55,7 @@ HTTP_REQUEST_ERROR=\ HEALTH_CHECK_FAILURE=\ AC2003E|\ Failed to establish connection to {0} at {1}. Cause {2} + +CLIENT_INITIALIZATION_FAILURE=\ + AC2004E|\ + Failure to initialize rest client. Cause {0} diff --git a/src/test/java/org/openecomp/restclient/client/OperationResultTest.java b/src/test/java/org/openecomp/restclient/client/OperationResultTest.java new file mode 100644 index 0000000..df85877 --- /dev/null +++ b/src/test/java/org/openecomp/restclient/client/OperationResultTest.java @@ -0,0 +1,83 @@ +package org.openecomp.restclient.client; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import javax.ws.rs.core.MultivaluedMap; + +import org.junit.Before; +import org.junit.Test; + +import com.sun.jersey.core.util.MultivaluedMapImpl; + +public class OperationResultTest { + + /** + * Test case initialization + * + * @throws Exception the exception + */ + @Before + public void init() throws Exception { + } + + @Test + public void validateConstruction() { + + OperationResult opResult = new OperationResult(); + assertEquals(opResult.getNumRetries(),0); + assertFalse(opResult.isFromCache()); + assertFalse(opResult.wasSuccessful()); + opResult.setResultCode(612); + assertFalse(opResult.wasSuccessful()); + assertNull(opResult.getHeaders()); + + opResult = new OperationResult(204,"no content found"); + assertEquals(opResult.getResultCode(),204); + assertEquals(opResult.getResult(),"no content found"); + assertTrue(opResult.wasSuccessful()); + + MultivaluedMap multiMap = new MultivaluedMapImpl(); + multiMap.add("p1","v1"); + multiMap.add("p2","v2"); + opResult.setHeaders(multiMap); + assertNotNull(opResult.getHeaders()); + assertEquals(opResult.getHeaders().size(), 2); + + } + + @Test + public void validateAccesors() { + + OperationResult opResult = new OperationResult(); + + opResult.setFailureCause("failure"); + opResult.setFromCache(false); + opResult.setNumRetries(101); + opResult.setRequestedLink("http://localhost:1234"); + opResult.setResult("result"); + opResult.setResultCode(555); + + assertEquals(opResult.getFailureCause(), "failure"); + assertFalse(opResult.isFromCache()); + assertEquals(opResult.getNumRetries(),101); + assertEquals(opResult.getRequestedLink(),"http://localhost:1234"); + assertEquals(opResult.getResult(), "result"); + assertEquals(opResult.getResultCode(),555); + + opResult.setResult(212, "mostly successful"); + assertEquals(opResult.getResultCode(),212); + assertEquals(opResult.getResult(), "mostly successful"); + + assertTrue(opResult.toString().contains("OperationResult")); + + opResult.setFailureCause(511, "things melting"); + assertEquals(opResult.getResultCode(),511); + assertEquals(opResult.getFailureCause(), "things melting"); + + } + +} diff --git a/src/test/java/org/openecomp/restclient/client/RESTClientTest.java b/src/test/java/org/openecomp/restclient/client/RESTClientTest.java deleted file mode 100644 index b049c38..0000000 --- a/src/test/java/org/openecomp/restclient/client/RESTClientTest.java +++ /dev/null @@ -1,391 +0,0 @@ -package org.openecomp.restclient.client; - -import org.junit.Before; -import org.junit.Test; -import org.openecomp.restclient.client.OperationResult; -import org.openecomp.restclient.client.RestClient; -import org.openecomp.restclient.rest.RestClientBuilder; - -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertEquals; - -import javax.ws.rs.GET; -import javax.ws.rs.Path; -import javax.ws.rs.core.MediaType; - -import com.sun.jersey.test.framework.AppDescriptor; -import com.sun.jersey.test.framework.JerseyTest; -import com.sun.jersey.test.framework.WebAppDescriptor; - -import com.sun.jersey.api.client.Client; - - -/** - * This suite of tests is intended to exercise the behaviour of the {@link RestClient}. - */ -public class RESTClientTest extends JerseyTest { - - private static final String GOOD_AAI_ENDPOINT = "testaai/good"; - private static final String FAIL_ALWAYS_AAI_ENDPOINT = "testaai/failalways"; - private static final String FAIL_THEN_SUCCEED_ENDPOINT = "testaai/failthensucceed"; - private static final String INVALID_AAI_ENDPOINT = "testaai/bad"; - - private static final String AAI_GET_REPLY_PAYLOAD = "Reply from AAI"; - - private static final int SUCCESS_RESULT_CODE = 200; - private static final int INVALID_END_POINT_RESULT_CODE = 404; - private static final int INTERNAL_ERR_RESULT_CODE = 500; - private static final int TIMEOUT_RESULT_CODE = 504; - - - /** - * Creates a new instance of the {@link RESTClientTest} test suite. - */ - public RESTClientTest() throws Exception { - - // Tell our in memory container to look here for resource endpoints. - super("org.openecomp.restclient.client"); - } - - - @Override - protected AppDescriptor configure() { - return new WebAppDescriptor.Builder().build(); - } - - - /** - * Perform common initialization actions that need to run before every unit test. - */ - @Before - public void setup() { - - // Initialize our test endpoints to make sure that all of their - // counters have valid starting values - AAI_FailAlways_Stub.initialize(); - AAI_FailThenSucceed_Stub.initialize(); - } - - - /** - * This test validates that all of the {@link RestClient}'s configurable parameters can be set via - * its fluent interface and that those values are successfully passed down to the underlying - * {@link RestClientBuilder} instance. - */ - @Test - public void configureAAIClientTest() { - - final boolean VALIDATE_SERVER = true; - final boolean VALIDATE_CERT_CHAIN = true; - final String CLIENT_CERT_FILE = "myCertFile"; - final String CLIENT_CERT_PASSWORD = "My voice is my password"; - final String TRUST_STORE = "myTrustStore"; - final int CONNECT_TIMEOUT = 5000; - final int READ_TIMEOUT = 5000; - - // Create an instance of our test version of the REST client builder. - TestRestClientBuilder clientBuilder = new TestRestClientBuilder(); - - // Now, create a new instance of the {@link AAIClient} and configure - // its parameters. - RestClient testClient = - new RestClient(clientBuilder).validateServerHostname(true).validateServerCertChain(true) - .clientCertFile("myCertFile").clientCertPassword("My voice is my password") - .trustStore("myTrustStore").connectTimeoutMs(5000).readTimeoutMs(5000); - - // Validate that the parameters of the test REST client builder that - // we passed to the AAI client have been set according to what we - // passed in when we instantiated the AAI client. - assertEquals("Unexpected 'validate server host name' value", VALIDATE_SERVER, - clientBuilder.isValidateServerHostname()); - assertEquals("Unexpected 'validate certificat chain' value", VALIDATE_CERT_CHAIN, - clientBuilder.isValidateServerCertChain()); - assertTrue("Unexpected client certificate filename", - CLIENT_CERT_FILE.equals(clientBuilder.getClientCertFileName())); - assertTrue("Unexpected client certificate password", - CLIENT_CERT_PASSWORD.equals(clientBuilder.getClientCertPassword())); - assertTrue("Unexpected trust store filename", - TRUST_STORE.equals(clientBuilder.getTruststoreFilename())); - assertEquals("Unexpected connection timeout value", CONNECT_TIMEOUT, - clientBuilder.getConnectTimeoutInMs()); - assertEquals("Unexpected read timeout value", READ_TIMEOUT, clientBuilder.getReadTimeoutInMs()); - } - - - /** - * This test validates that the {@link RestClient} can submit a GET request to a valid REST - * endpoint and receive a valid response. - */ - @Test - public void queryAAI_SuccessTest() { - - // Create an instance of the AAIClient that uses our test version of - // the REST client builder. - RestClient testClient = new RestClient(new TestRestClientBuilder()); - - // Query our stubbed out AAI with a URL that we expecte to get a successful - // reply from. - OperationResult or = - testClient.get(getBaseURI() + GOOD_AAI_ENDPOINT, null, MediaType.APPLICATION_JSON_TYPE); - - // Validate that a successful query returns a result code of 200. - assertEquals("Unexpected result code", SUCCESS_RESULT_CODE, or.getResultCode()); - - // Validate that no error cause gets set on a successful query. - assertNull("Operation result failure code should not be set for successful GET", - or.getFailureCause()); - - // Validate that our query returned the expected payload from our dummy - // AAI. - assertTrue("Incorrect payload returned from AAI query", - AAI_GET_REPLY_PAYLOAD.equals(or.getResult())); - } - - - /** - * This test validates that the {@link RestClient} behaves as expected when query requests are - * unsuccessful. - *

    - * Specifically, the following scenarios are covered:
    - * 1) Submitting a GET request to an invalid REST endpoint 2) Submitting a GET request to a valid - * endpoint which throws an error rather than replying successfully. - *

    - * Note that this test exercises the 'single attempt' variant of the query method. - */ - @Test - public void queryAAI_FailureTest() { - - // Create an instance of the AAIClient that uses our test version of - // the REST client builder. - RestClient testClient = new RestClient(new TestRestClientBuilder()); - - // Query our stubbed out AAI with a URL that we expecte to get a successful - // reply from. - OperationResult or = - testClient.get(getBaseURI() + INVALID_AAI_ENDPOINT, null, MediaType.APPLICATION_JSON_TYPE); - - // Validate that an attempt to query a non-existing endpoint results in - // a 404 error. - assertEquals("Unexpected result code", INVALID_END_POINT_RESULT_CODE, or.getResultCode()); - - // Validate that no payload was set since the query failed. - assertNull("Payload should not be set on 404 error", or.getResult()); - - // Now, submit a query request to the stubbed AAI. - or = testClient.get(getBaseURI() + FAIL_ALWAYS_AAI_ENDPOINT, null, - MediaType.APPLICATION_JSON_TYPE); - - // Validate that a query to a avalid returns a result code of 500. - assertEquals("Unexpected result code", INTERNAL_ERR_RESULT_CODE, or.getResultCode()); - } - - - /** - * This test validates the behaviour of querying the AAI with a number of retries requested in the - * case where we never get a successful reply. - */ - @Test - public void queryAAIWithRetries_TimeoutTest() { - - int NUM_RETRIES = 3; - - - // Create an instance of the AAIClient that uses our test version of - // the REST client builder. - RestClient testClient = new RestClient(new TestRestClientBuilder()); - - // Initialize our test endpoint to make sure that all of its - // counters have valid starting values - // AAI_FailAlways_Stub.initialize(); - - // Perform a query against the stubbed AAI, specifying a number of times - // to retry in the event of an error. - OperationResult or = testClient.get(getBaseURI() + FAIL_ALWAYS_AAI_ENDPOINT, null, - MediaType.APPLICATION_JSON_TYPE, NUM_RETRIES); - - // Validate that failing for all of our retry attempts results in a - // 504 error. - assertEquals("Unexpected result code", TIMEOUT_RESULT_CODE, or.getResultCode()); - - // Validate that our stubbed AAI actually received the expected number - // of retried requests. - assertEquals("Unexpected number of retries", NUM_RETRIES, AAI_FailAlways_Stub.getCount); - } - - - /** - * This test validates the behaviour of querying the AAI with a number of retries requested in the - * case where our query initially fails but then succeeds on one of the subsequent retries. - */ - @Test - public void queryAAIWithRetries_FailThenSucceedTest() { - - int num_retries = AAI_FailThenSucceed_Stub.MAX_FAILURES + 2; - - // Create an instance of the AAIClient that uses our test version of - // the REST client builder. - RestClient testClient = new RestClient(new TestRestClientBuilder()); - - // Initialize our test endpoint to make sure that all of its - // counters have valid starting values. - // AAI_FailThenSucceed_Stub.initialize(); - - // Perform a query against the stubbed AAI, specifying a number of times - // to retry in the event of an error. - OperationResult or = testClient.get(getBaseURI() + FAIL_THEN_SUCCEED_ENDPOINT, null, - MediaType.APPLICATION_JSON_TYPE, num_retries); - - // Validate that after failing a few attempts we finally got back a - // success code. - assertEquals("Unexpected result code", SUCCESS_RESULT_CODE, or.getResultCode()); - - // Validate that our stubbed AAI actually received the expected number - // of retried requests. - assertEquals("Unexpected number of retries", AAI_FailThenSucceed_Stub.MAX_FAILURES + 1, - AAI_FailThenSucceed_Stub.getCount); - } - - - /** - * This class provides a simple in-memory REST end point to stand in for a real AAI. - *

    - * This endpoint always returns a valid reply to a GET request and is used for success path - * testing. - */ - @Path(GOOD_AAI_ENDPOINT) - public static class AAI_Success_Stub { - - /** - * This is the end point for GET requests. It just returns a simple, pre-canned response - * payload. - * - * @return - A pre-canned response. - */ - @GET - public String getEndpoint() { - return AAI_GET_REPLY_PAYLOAD; - } - } - - - /** - * This class provides a simple in-memory REST end point to stand in for a real AAI. - *

    - * This endpoint always returns throws an error instead of responding successfully and is used for - * certain failure path tests. - */ - @Path(FAIL_ALWAYS_AAI_ENDPOINT) - public static class AAI_FailAlways_Stub { - - /** - * Maintains a running count of the number of GET requests that have been received. - */ - public static int getCount; - - - /** - * Resets all of the endpoints counters. - */ - public static void initialize() { - getCount = 0; - } - - - /** - * This is the end point for GET requests. It just throws an error instead of returning a valid - * response. - * - * @return - NONE. We actually throw an exception intentionally instead of returning. - */ - @GET - public String getEndpoint() { - - // Keep track of the number of get requests that we have received - // so that this value can be used for validation purposes later. - getCount++; - - // Always just throw an error instead of replying successfully. - throw new UnsupportedOperationException("Intentional Failure"); - } - } - - - /** - * This class provides a simple in-memory REST end point to stand in for a real AAI. - *

    - * This end point will throw errors instead of responding for a certain number of requests, after - * which it will return a valid, pre-canned response. - * - * @return - A pre-canned response. - */ - @Path(FAIL_THEN_SUCCEED_ENDPOINT) - public static class AAI_FailThenSucceed_Stub { - - /** - * The number of requests for which we should throw errors before responding successfully. - */ - public static int MAX_FAILURES = 2; - - /** - * Maintains a running count of the number of GET requests that have been received. - */ - public static int getCount; - - /** - * Maintains a running count of the number of requests which we have failed, so that we will - * know when to stop failing and return a valid response. - */ - private static int failCount; - - - /** - * Resets all of the endpoints counters. - */ - public static void initialize() { - getCount = 0; - failCount = 0; - } - - - /** - * This is the end point for GET requests. It will throw errors for a certain number of requests - * and then return a valid response. - * - * @return - A pre-canned response string. - */ - @GET - public String getEndpoint() { - - // Keep track of the number of get requests that we have received - // so that this value can be used for validation purposes later. - getCount++; - - // We only want to fail a set number of times, so check now to - // see what we should do. - if (failCount < MAX_FAILURES) { - failCount++; - throw new UnsupportedOperationException("Intentional Failure"); - - } else { - // We've failed as often as we need to. Time to reply - // successfully. - failCount = 0; - return AAI_GET_REPLY_PAYLOAD; - } - } - } - - - /** - * This class overrides the behaviour of the {@link RestClientBuilder} used by the - * {@link RestClient} to just return the in memory client provided by the JerseyTest framework. - */ - private class TestRestClientBuilder extends RestClientBuilder { - - @Override - public Client getClient() throws Exception { - return client(); - } - } -} diff --git a/src/test/java/org/openecomp/restclient/client/RestfulClientTest.java b/src/test/java/org/openecomp/restclient/client/RestfulClientTest.java new file mode 100644 index 0000000..c116482 --- /dev/null +++ b/src/test/java/org/openecomp/restclient/client/RestfulClientTest.java @@ -0,0 +1,721 @@ +package org.openecomp.restclient.client; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.MultivaluedMap; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mockito; +import org.openecomp.restclient.enums.RestAuthenticationMode; +import org.openecomp.restclient.rest.RestClientBuilder; + +import com.sun.jersey.api.client.Client; +import com.sun.jersey.api.client.ClientResponse; +import com.sun.jersey.api.client.WebResource; +import com.sun.jersey.api.client.WebResource.Builder; +import com.sun.jersey.core.util.MultivaluedMapImpl; + +public class RestfulClientTest { + + private RestClientBuilder mockClientBuilder; + private Client mockedClient; + + /** + * Test case initialization + * + * @throws Exception the exception + */ + @Before + public void init() throws Exception { + mockClientBuilder = Mockito.mock( RestClientBuilder.class ); + mockedClient = Mockito.mock( Client.class ); + } + + @Test + public void validateConstructors() { + + RestClient restClient = new RestClient(); + assertNotNull(restClient); + + restClient = null; + restClient = new RestClient( mockClientBuilder ); + assertNotNull(restClient); + + } + + @Test + public void validateBasicClientConstruction() throws Exception { + + Mockito.when( mockClientBuilder.getClient() ).thenReturn(mockedClient); + + RestClient restClient = new RestClient( mockClientBuilder ); + assertNotNull(restClient); + + Client client = restClient.authenticationMode(RestAuthenticationMode.HTTP_NOAUTH) + .connectTimeoutMs(1000).readTimeoutMs(500).getClient(); + + assertNotNull(client); + + } + + @Test + public void validateClientWithSslBasicAuthConstruction() throws Exception { + + Mockito.when( mockClientBuilder.getClient() ).thenReturn(mockedClient); + + RestClient restClient = new RestClient( mockClientBuilder ); + assertNotNull(restClient); + + Client client = restClient.authenticationMode(RestAuthenticationMode.SSL_BASIC) + .connectTimeoutMs(1000).readTimeoutMs(500).basicAuthPassword("password") + .basicAuthUsername("username").getClient(); + + assertNotNull(client); + + } + + @Test + public void validateClientWithSslCertConstruction() throws Exception { + + Mockito.when( mockClientBuilder.getClient() ).thenReturn(mockedClient); + + RestClient restClient = new RestClient( mockClientBuilder ); + assertNotNull(restClient); + + Client client = + restClient.authenticationMode(RestAuthenticationMode.SSL_CERT).connectTimeoutMs(1000) + .readTimeoutMs(500).clientCertFile("cert").clientCertPassword("password").getClient(); + + assertNotNull(client); + + client = null; + client = restClient.authenticationMode(RestAuthenticationMode.SSL_CERT).connectTimeoutMs(1000) + .readTimeoutMs(500).clientCertFile("cert").clientCertPassword("password") + .validateServerCertChain(true).validateServerHostname(true).getClient(); + + assertNotNull(client); + + client = null; + client = restClient.authenticationMode(RestAuthenticationMode.SSL_CERT).connectTimeoutMs(1000) + .readTimeoutMs(500).clientCertFile("cert").clientCertPassword("password") + .trustStore("truststore").getClient(); + + assertNotNull(client); + + } + + @Test + public void validateSuccessfulPut() throws Exception { + + Mockito.when( mockClientBuilder.getClient() ).thenReturn(mockedClient); + + WebResource mockedWebResource = Mockito.mock(WebResource.class); + Builder mockedBuilder = Mockito.mock(Builder.class); + ClientResponse mockedClientResponse = Mockito.mock(ClientResponse.class); + + Mockito.when( mockedClient.resource(Mockito.anyString())).thenReturn( mockedWebResource ); + Mockito.when(mockedWebResource.accept(Mockito.anyVararg())).thenReturn( mockedBuilder ); + Mockito.when(mockedBuilder.put(Mockito.any(Class.class))).thenReturn(mockedClientResponse); + + /* + * Finally the elements we want to validate + */ + + Mockito.when(mockedClientResponse.getStatus()).thenReturn(200); + Mockito.when(mockedClientResponse.getEntity(String.class)).thenReturn("hello"); + Mockito.when(mockedClientResponse.getHeaders()).thenReturn(new MultivaluedMapImpl()); + + RestClient restClient = new RestClient( mockClientBuilder ); + + assertNotNull(restClient); + + restClient = + restClient.authenticationMode(RestAuthenticationMode.SSL_CERT).connectTimeoutMs(1000) + .readTimeoutMs(500).clientCertFile("cert").clientCertPassword("password"); + + assertNotNull(restClient); + + MultivaluedMap headers = new MultivaluedMapImpl(); + + OperationResult result = restClient.put("http://localhost:9000/aai/v7", "", headers, MediaType.APPLICATION_JSON_TYPE, + MediaType.APPLICATION_JSON_TYPE); + + assertEquals(200, result.getResultCode()); + assertNotNull(result.getResult()); + assertNull(result.getFailureCause()); + + } + + @Test + public void validateSuccessfulPost() throws Exception { + + Mockito.when( mockClientBuilder.getClient() ).thenReturn(mockedClient); + + WebResource mockedWebResource = Mockito.mock(WebResource.class); + Builder mockedBuilder = Mockito.mock(Builder.class); + ClientResponse mockedClientResponse = Mockito.mock(ClientResponse.class); + + Mockito.when( mockedClient.resource(Mockito.anyString())).thenReturn( mockedWebResource ); + Mockito.when(mockedWebResource.accept(Mockito.anyVararg())).thenReturn( mockedBuilder ); + Mockito.when(mockedBuilder.post(Mockito.any(Class.class))).thenReturn(mockedClientResponse); + + /* + * Finally the elements we want to validate + */ + + Mockito.when(mockedClientResponse.getStatus()).thenReturn(200); + Mockito.when(mockedClientResponse.getEntity(String.class)).thenReturn("hello"); + Mockito.when(mockedClientResponse.getHeaders()).thenReturn(new MultivaluedMapImpl()); + + RestClient restClient = new RestClient( mockClientBuilder ); + + assertNotNull(restClient); + + restClient = + restClient.authenticationMode(RestAuthenticationMode.SSL_CERT).connectTimeoutMs(1000) + .readTimeoutMs(500).clientCertFile("cert").clientCertPassword("password"); + + assertNotNull(restClient); + + MultivaluedMap headers = new MultivaluedMapImpl(); + + OperationResult result = restClient.post("http://localhost:9000/aai/v7", "", headers, MediaType.APPLICATION_JSON_TYPE, + MediaType.APPLICATION_JSON_TYPE); + + assertEquals(200, result.getResultCode()); + assertNotNull(result.getResult()); + assertNull(result.getFailureCause()); + + } + + @Test + public void validateSuccessfulGet() throws Exception { + + Mockito.when( mockClientBuilder.getClient() ).thenReturn(mockedClient); + + WebResource mockedWebResource = Mockito.mock(WebResource.class); + Builder mockedBuilder = Mockito.mock(Builder.class); + ClientResponse mockedClientResponse = Mockito.mock(ClientResponse.class); + + Mockito.when( mockedClient.resource(Mockito.anyString())).thenReturn( mockedWebResource ); + Mockito.when(mockedWebResource.accept(Mockito.anyVararg())).thenReturn( mockedBuilder ); + Mockito.when(mockedBuilder.get(Mockito.any(Class.class))).thenReturn(mockedClientResponse); + + /* + * Finally the elements we want to validate + */ + + Mockito.when(mockedClientResponse.getStatus()).thenReturn(200); + Mockito.when(mockedClientResponse.getEntity(String.class)).thenReturn("hello"); + Mockito.when(mockedClientResponse.getHeaders()).thenReturn(new MultivaluedMapImpl()); + + RestClient restClient = new RestClient( mockClientBuilder ); + + assertNotNull(restClient); + + restClient = + restClient.authenticationMode(RestAuthenticationMode.SSL_CERT).connectTimeoutMs(1000) + .readTimeoutMs(500).clientCertFile("cert").clientCertPassword("password"); + + assertNotNull(restClient); + + MultivaluedMap headers = new MultivaluedMapImpl(); + + OperationResult result = + restClient.get("http://localhost:9000/aai/v7", headers, MediaType.APPLICATION_JSON_TYPE); + + assertEquals(200, result.getResultCode()); + assertNotNull(result.getResult()); + assertNull(result.getFailureCause()); + + } + + @Test + public void validateSuccessfulGetWithBasicAuth() throws Exception { + + Mockito.when( mockClientBuilder.getClient() ).thenReturn(mockedClient); + + WebResource mockedWebResource = Mockito.mock(WebResource.class); + Builder mockedBuilder = Mockito.mock(Builder.class); + ClientResponse mockedClientResponse = Mockito.mock(ClientResponse.class); + + Mockito.when( mockedClient.resource(Mockito.anyString())).thenReturn( mockedWebResource ); + Mockito.when(mockedWebResource.accept(Mockito.anyVararg())).thenReturn( mockedBuilder ); + Mockito.when(mockedBuilder.get(Mockito.any(Class.class))).thenReturn(mockedClientResponse); + + /* + * Finally the elements we want to validate + */ + + Mockito.when(mockedClientResponse.getStatus()).thenReturn(200); + Mockito.when(mockedClientResponse.getEntity(String.class)).thenReturn("hello"); + Mockito.when(mockedClientResponse.getHeaders()).thenReturn(new MultivaluedMapImpl()); + + RestClient restClient = new RestClient( mockClientBuilder ); + + assertNotNull(restClient); + + restClient = + restClient.authenticationMode(RestAuthenticationMode.SSL_BASIC).connectTimeoutMs(1000) + .readTimeoutMs(500).basicAuthUsername("username").basicAuthUsername("password"); + + assertNotNull(restClient); + + MultivaluedMap headers = new MultivaluedMapImpl(); + + OperationResult result = + restClient.get("http://localhost:9000/aai/v7", headers, MediaType.APPLICATION_JSON_TYPE); + + assertEquals(200, result.getResultCode()); + assertNotNull(result.getResult()); + assertNull(result.getFailureCause()); + + } + + @Test + public void validateResourceNotFoundGet() throws Exception { + + Mockito.when( mockClientBuilder.getClient() ).thenReturn(mockedClient); + + WebResource mockedWebResource = Mockito.mock(WebResource.class); + Builder mockedBuilder = Mockito.mock(Builder.class); + ClientResponse mockedClientResponse = Mockito.mock(ClientResponse.class); + + Mockito.when( mockedClient.resource(Mockito.anyString())).thenReturn( mockedWebResource ); + Mockito.when(mockedWebResource.accept(Mockito.anyVararg())).thenReturn( mockedBuilder ); + Mockito.when(mockedBuilder.get(Mockito.any(Class.class))).thenReturn(mockedClientResponse); + + /* + * Finally the elements we want to validate + */ + + Mockito.when(mockedClientResponse.getStatus()).thenReturn(404); + Mockito.when(mockedClientResponse.getEntity(String.class)).thenReturn("RNF"); + Mockito.when(mockedClientResponse.getHeaders()).thenReturn(new MultivaluedMapImpl()); + + RestClient restClient = new RestClient( mockClientBuilder ); + + assertNotNull(restClient); + + restClient = + restClient.authenticationMode(RestAuthenticationMode.SSL_CERT).connectTimeoutMs(1000) + .readTimeoutMs(500).clientCertFile("cert").clientCertPassword("password"); + + assertNotNull(restClient); + + MultivaluedMap headers = new MultivaluedMapImpl(); + + OperationResult result = + restClient.get("http://localhost:9000/aai/v7", headers, MediaType.APPLICATION_JSON_TYPE); + + assertEquals(404, result.getResultCode()); + assertNull(result.getResult()); + assertNotNull(result.getFailureCause()); + + } + + @Test + public void validateHealthCheck() throws Exception { + + Mockito.when( mockClientBuilder.getClient() ).thenReturn(mockedClient); + + WebResource mockedWebResource = Mockito.mock(WebResource.class); + Builder mockedBuilder = Mockito.mock(Builder.class); + ClientResponse mockedClientResponse = Mockito.mock(ClientResponse.class); + + Mockito.when( mockedClient.resource(Mockito.anyString())).thenReturn( mockedWebResource ); + Mockito.when(mockedWebResource.accept(Mockito.anyVararg())).thenReturn( mockedBuilder ); + Mockito.when(mockedBuilder.get(Mockito.any(Class.class))).thenReturn(mockedClientResponse); + + /* + * Finally the elements we want to validate + */ + + Mockito.when(mockedClientResponse.getStatus()).thenReturn(200); + Mockito.when(mockedClientResponse.getEntity(String.class)).thenReturn("hello"); + Mockito.when(mockedClientResponse.getHeaders()).thenReturn(new MultivaluedMapImpl()); + + RestClient restClient = new RestClient( mockClientBuilder ); + + assertNotNull(restClient); + + restClient = + restClient.authenticationMode(RestAuthenticationMode.SSL_CERT).connectTimeoutMs(1000) + .readTimeoutMs(500).clientCertFile("cert").clientCertPassword("password"); + + assertNotNull(restClient); + + MultivaluedMap headers = new MultivaluedMapImpl(); + + boolean targetServiceHealthy = + restClient.healthCheck("http://localhost:9000/aai/util/echo", "startSerice", "targetService"); + + assertEquals(true, targetServiceHealthy); + + } + + @Test + public void validateHealthCheckFailureWith403() throws Exception { + + Mockito.when( mockClientBuilder.getClient() ).thenReturn(mockedClient); + + WebResource mockedWebResource = Mockito.mock(WebResource.class); + Builder mockedBuilder = Mockito.mock(Builder.class); + ClientResponse mockedClientResponse = Mockito.mock(ClientResponse.class); + + Mockito.when( mockedClient.resource(Mockito.anyString())).thenReturn( mockedWebResource ); + Mockito.when(mockedWebResource.accept(Mockito.anyVararg())).thenReturn( mockedBuilder ); + Mockito.when(mockedBuilder.get(Mockito.any(Class.class))).thenReturn(mockedClientResponse); + + /* + * Finally the elements we want to validate + */ + + Mockito.when(mockedClientResponse.getStatus()).thenReturn(403); + Mockito.when(mockedClientResponse.getEntity(String.class)).thenReturn("hello"); + Mockito.when(mockedClientResponse.getHeaders()).thenReturn(new MultivaluedMapImpl()); + + RestClient restClient = new RestClient( mockClientBuilder ); + + assertNotNull(restClient); + + restClient = + restClient.authenticationMode(RestAuthenticationMode.SSL_CERT).connectTimeoutMs(1000) + .readTimeoutMs(500).clientCertFile("cert").clientCertPassword("password"); + + assertNotNull(restClient); + + MultivaluedMap headers = new MultivaluedMapImpl(); + + boolean targetServiceHealthy = + restClient.healthCheck("http://localhost:9000/aai/util/echo", "startSerice", "targetService"); + + assertEquals(false, targetServiceHealthy); + + } + + @Test + public void validateHealthCheckFailureWithThrownException() throws Exception { + + Mockito.when( mockClientBuilder.getClient() ).thenReturn(mockedClient); + + WebResource mockedWebResource = Mockito.mock(WebResource.class); + Builder mockedBuilder = Mockito.mock(Builder.class); + ClientResponse mockedClientResponse = Mockito.mock(ClientResponse.class); + + Mockito.when( mockedClient.resource(Mockito.anyString())).thenReturn( mockedWebResource ); + Mockito.when(mockedWebResource.accept(Mockito.anyVararg())).thenReturn( mockedBuilder ); + Mockito.when(mockedBuilder.get(Mockito.any(Class.class))).thenThrow(new IllegalArgumentException("error")); + + /* + * Finally the elements we want to validate + */ + +/* Mockito.when(mockedClientResponse.getStatus()).thenReturn(403); + Mockito.when(mockedClientResponse.getEntity(String.class)).thenReturn("hello"); + Mockito.when(mockedClientResponse.getHeaders()).thenReturn(new MultivaluedMapImpl());*/ + + RestClient restClient = new RestClient( mockClientBuilder ); + + assertNotNull(restClient); + + restClient = + restClient.authenticationMode(RestAuthenticationMode.SSL_CERT).connectTimeoutMs(1000) + .readTimeoutMs(500).clientCertFile("cert").clientCertPassword("password"); + + assertNotNull(restClient); + + MultivaluedMap headers = new MultivaluedMapImpl(); + + boolean targetServiceHealthy = + restClient.healthCheck("http://localhost:9000/aai/util/echo", "startSerice", "targetService"); + + assertEquals(false, targetServiceHealthy); + + } + @Test + public void validateSuccessfulGetWithRetries() throws Exception { + + RestClientBuilder myClientBuilder = Mockito.mock(RestClientBuilder.class); + Client myClient = Mockito.mock(Client.class); + + Mockito.when(myClientBuilder.getClient()).thenReturn(myClient).thenReturn(myClient); + + WebResource mockedWebResource = Mockito.mock(WebResource.class); + Builder mockedBuilder = Mockito.mock(Builder.class); + ClientResponse mockedClientResponse = Mockito.mock(ClientResponse.class); + + Mockito.when( myClient.resource(Mockito.anyString())).thenReturn( mockedWebResource ); + Mockito.when(mockedWebResource.accept(Mockito.anyVararg())).thenReturn( mockedBuilder ); + Mockito.when(mockedBuilder.get(Mockito.any(Class.class))).thenReturn(mockedClientResponse); + + + /* + * Finally the elements we want to validate + */ + + Mockito.when(mockedClientResponse.getStatus()).thenReturn(408).thenReturn(200); + + Mockito.when(mockedClientResponse.getEntity(String.class)).thenReturn("error").thenReturn("ok"); + + MultivaluedMap emptyHeaderMap = new MultivaluedMapImpl(); + + // Mockito is smart, the last recorded thenReturn is repeated successively + // for all subsequent calls to the method. + Mockito.when(mockedClientResponse.getHeaders()).thenReturn(emptyHeaderMap); + + RestClient restClient = new RestClient( myClientBuilder ); + + assertNotNull(restClient); + + restClient = + restClient.authenticationMode(RestAuthenticationMode.SSL_CERT).connectTimeoutMs(1000) + .readTimeoutMs(500).clientCertFile("cert").clientCertPassword("password"); + + assertNotNull(restClient); + + MultivaluedMap headers = new MultivaluedMapImpl(); + + OperationResult result = + restClient.get("http://localhost:9000/aai/v7", headers, MediaType.APPLICATION_JSON_TYPE, 3); + + assertEquals(200, result.getResultCode()); + assertNotNull(result.getResult()); + assertNull(result.getFailureCause()); + + } + + + @Test + public void validateFailedGetWithRetriesCausedByResourceNotFound() throws Exception { + + RestClientBuilder myClientBuilder = Mockito.mock(RestClientBuilder.class); + Client myClient = Mockito.mock(Client.class); + + Mockito.when(myClientBuilder.getClient()).thenReturn(myClient).thenReturn(myClient); + + WebResource mockedWebResource = Mockito.mock(WebResource.class); + Builder mockedBuilder = Mockito.mock(Builder.class); + ClientResponse mockedClientResponse = Mockito.mock(ClientResponse.class); + + Mockito.when( myClient.resource(Mockito.anyString())).thenReturn( mockedWebResource ); + Mockito.when(mockedWebResource.accept(Mockito.anyVararg())).thenReturn( mockedBuilder ); + Mockito.when(mockedBuilder.get(Mockito.any(Class.class))).thenReturn(mockedClientResponse); + + + /* + * Finally the elements we want to validate + */ + + Mockito.when(mockedClientResponse.getStatus()).thenReturn(404); + + Mockito.when(mockedClientResponse.getEntity(String.class)).thenReturn("error").thenReturn("ok"); + + MultivaluedMap emptyHeaderMap = new MultivaluedMapImpl(); + + // Mockito is smart, the last recorded thenReturn is repeated successively + // for all subsequent calls to the method. + Mockito.when(mockedClientResponse.getHeaders()).thenReturn(emptyHeaderMap); + + RestClient restClient = new RestClient( myClientBuilder ); + + assertNotNull(restClient); + + restClient = + restClient.authenticationMode(RestAuthenticationMode.SSL_CERT).connectTimeoutMs(1000) + .readTimeoutMs(500).clientCertFile("cert").clientCertPassword("password"); + + assertNotNull(restClient); + + MultivaluedMap headers = new MultivaluedMapImpl(); + + OperationResult result = + restClient.get("http://localhost:9000/aai/v7", headers, MediaType.APPLICATION_JSON_TYPE, 3); + + assertEquals(404, result.getResultCode()); + assertNull(result.getResult()); + assertNotNull(result.getFailureCause()); + + } + + @Test + public void validateFailedGetAfterMaxRetries() throws Exception { + + Mockito.when( mockClientBuilder.getClient() ).thenReturn(mockedClient); + + WebResource mockedWebResource = Mockito.mock(WebResource.class); + Builder mockedBuilder = Mockito.mock(Builder.class); + ClientResponse mockedClientResponse = Mockito.mock(ClientResponse.class); + + Mockito.when( mockedClient.resource(Mockito.anyString())).thenReturn( mockedWebResource ); + Mockito.when(mockedWebResource.accept(Mockito.anyVararg())).thenReturn( mockedBuilder ); + + Mockito.when(mockedBuilder.get(Mockito.any(Class.class))).thenReturn(null); + + /* + * Finally the elements we want to validate + */ + + Mockito.when(mockedClientResponse.getStatus()).thenReturn(500).thenReturn(500).thenReturn(500); + + Mockito.when(mockedClientResponse.getEntity(String.class)).thenReturn("error") + .thenReturn("error").thenReturn("error"); + + MultivaluedMap emptyHeaderMap = new MultivaluedMapImpl(); + + Mockito.when(mockedClientResponse.getHeaders()).thenReturn(emptyHeaderMap) + .thenReturn(emptyHeaderMap).thenReturn(emptyHeaderMap); + + RestClient restClient = new RestClient( mockClientBuilder ); + + assertNotNull(restClient); + + restClient = + restClient.authenticationMode(RestAuthenticationMode.SSL_CERT).connectTimeoutMs(1000) + .readTimeoutMs(500).clientCertFile("cert").clientCertPassword("password"); + + assertNotNull(restClient); + + MultivaluedMap headers = new MultivaluedMapImpl(); + + OperationResult result = + restClient.get("http://localhost:9000/aai/v7", headers, MediaType.APPLICATION_JSON_TYPE, 3); + + + assertEquals(504, result.getResultCode()); + assertNull(result.getResult()); + assertNotNull(result.getFailureCause()); + + } + + @Test + public void validateSuccessfulDelete() throws Exception { + + Mockito.when( mockClientBuilder.getClient() ).thenReturn(mockedClient); + + WebResource mockedWebResource = Mockito.mock(WebResource.class); + Builder mockedBuilder = Mockito.mock(Builder.class); + ClientResponse mockedClientResponse = Mockito.mock(ClientResponse.class); + + Mockito.when( mockedClient.resource(Mockito.anyString())).thenReturn( mockedWebResource ); + Mockito.when(mockedWebResource.accept(Mockito.anyVararg())).thenReturn( mockedBuilder ); + Mockito.when(mockedBuilder.delete(Mockito.any(Class.class))).thenReturn(mockedClientResponse); + + /* + * Finally the elements we want to validate + */ + + Mockito.when(mockedClientResponse.getStatus()).thenReturn(200); + Mockito.when(mockedClientResponse.getEntity(String.class)).thenReturn("hello"); + Mockito.when(mockedClientResponse.getHeaders()).thenReturn(new MultivaluedMapImpl()); + + RestClient restClient = new RestClient( mockClientBuilder ); + + assertNotNull(restClient); + + restClient = + restClient.authenticationMode(RestAuthenticationMode.SSL_CERT).connectTimeoutMs(1000) + .readTimeoutMs(500).clientCertFile("cert").clientCertPassword("password"); + + assertNotNull(restClient); + + MultivaluedMap headers = new MultivaluedMapImpl(); + + OperationResult result = restClient.delete("http://localhost:9000/aai/v7", headers, + MediaType.APPLICATION_JSON_TYPE); + + assertEquals(200, result.getResultCode()); + assertNotNull(result.getResult()); + assertNull(result.getFailureCause()); + + } + + @Test + public void validateSuccessfulHead() throws Exception { + + Mockito.when( mockClientBuilder.getClient() ).thenReturn(mockedClient); + + WebResource mockedWebResource = Mockito.mock(WebResource.class); + Builder mockedBuilder = Mockito.mock(Builder.class); + ClientResponse mockedClientResponse = Mockito.mock(ClientResponse.class); + + Mockito.when( mockedClient.resource(Mockito.anyString())).thenReturn( mockedWebResource ); + Mockito.when(mockedWebResource.accept(Mockito.anyVararg())).thenReturn( mockedBuilder ); + Mockito.when(mockedBuilder.head()).thenReturn(mockedClientResponse); + + /* + * Finally the elements we want to validate + */ + + Mockito.when(mockedClientResponse.getStatus()).thenReturn(200); + Mockito.when(mockedClientResponse.getEntity(String.class)).thenReturn("hello"); + Mockito.when(mockedClientResponse.getHeaders()).thenReturn(new MultivaluedMapImpl()); + + RestClient restClient = new RestClient( mockClientBuilder ); + + assertNotNull(restClient); + + restClient = + restClient.authenticationMode(RestAuthenticationMode.SSL_CERT).connectTimeoutMs(1000) + .readTimeoutMs(500).clientCertFile("cert").clientCertPassword("password"); + + assertNotNull(restClient); + + MultivaluedMap headers = new MultivaluedMapImpl(); + + OperationResult result = + restClient.head("http://localhost:9000/aai/v7", headers, MediaType.APPLICATION_JSON_TYPE); + + assertEquals(200, result.getResultCode()); + assertNotNull(result.getResult()); + assertNull(result.getFailureCause()); + + } + + @Test + public void validateSuccessfulPatch() throws Exception { + + Mockito.when( mockClientBuilder.getClient() ).thenReturn(mockedClient); + + WebResource mockedWebResource = Mockito.mock(WebResource.class); + Builder mockedBuilder = Mockito.mock(Builder.class); + ClientResponse mockedClientResponse = Mockito.mock(ClientResponse.class); + + Mockito.when( mockedClient.resource(Mockito.anyString())).thenReturn( mockedWebResource ); + Mockito.when(mockedWebResource.accept(Mockito.anyVararg())).thenReturn( mockedBuilder ); + Mockito.when(mockedBuilder.post(Mockito.any(Class.class))).thenReturn(mockedClientResponse); + Mockito.when(mockedBuilder.header("X-HTTP-Method-Override", "PATCH")).thenReturn(mockedBuilder); + /* + * Finally the elements we want to validate + */ + + Mockito.when(mockedClientResponse.getStatus()).thenReturn(200); + Mockito.when(mockedClientResponse.getEntity(String.class)).thenReturn("hello"); + Mockito.when(mockedClientResponse.getHeaders()).thenReturn(new MultivaluedMapImpl()); + + RestClient restClient = new RestClient( mockClientBuilder ); + + assertNotNull(restClient); + + restClient = + restClient.authenticationMode(RestAuthenticationMode.SSL_CERT).connectTimeoutMs(1000) + .readTimeoutMs(500).clientCertFile("cert").clientCertPassword("password"); + + assertNotNull(restClient); + + MultivaluedMap headers = new MultivaluedMapImpl(); + + OperationResult result = restClient.patch("http://localhost:9000/aai/v7", "", headers, MediaType.APPLICATION_JSON_TYPE, + MediaType.APPLICATION_JSON_TYPE); + + assertEquals(200, result.getResultCode()); + assertNotNull(result.getResult()); + assertNull(result.getFailureCause()); + + } + +} diff --git a/src/test/java/org/openecomp/restclient/enums/RestAuthenticationModeTest.java b/src/test/java/org/openecomp/restclient/enums/RestAuthenticationModeTest.java new file mode 100644 index 0000000..f4b280b --- /dev/null +++ b/src/test/java/org/openecomp/restclient/enums/RestAuthenticationModeTest.java @@ -0,0 +1,33 @@ +package org.openecomp.restclient.enums; + +import static org.junit.Assert.assertEquals; + +import org.junit.Before; +import org.junit.Test; + +public class RestAuthenticationModeTest { + + /** + * Test case initialization + * + * @throws Exception the exception + */ + @Before + public void init() throws Exception { + } + + @Test + public void validateEnumMappings() { + + assertEquals(RestAuthenticationMode.getRestAuthenticationMode(null), RestAuthenticationMode.UNKNOWN_MODE); + assertEquals(RestAuthenticationMode.getRestAuthenticationMode("OAuth"), RestAuthenticationMode.UNKNOWN_MODE); + assertEquals(RestAuthenticationMode.getRestAuthenticationMode("SSL_BASIC"), RestAuthenticationMode.SSL_BASIC); + assertEquals(RestAuthenticationMode.getRestAuthenticationMode("SSL_CERT"), RestAuthenticationMode.SSL_CERT); + assertEquals(RestAuthenticationMode.getRestAuthenticationMode("HTTP_NOAUTH"), RestAuthenticationMode.HTTP_NOAUTH); + + assertEquals(RestAuthenticationMode.SSL_BASIC.getAuthenticationModeLabel(),"SSL_BASIC"); + + + } + +} diff --git a/src/test/java/org/openecomp/restclient/rest/HttpUtilTest.java b/src/test/java/org/openecomp/restclient/rest/HttpUtilTest.java new file mode 100644 index 0000000..d1d88ea --- /dev/null +++ b/src/test/java/org/openecomp/restclient/rest/HttpUtilTest.java @@ -0,0 +1,51 @@ +package org.openecomp.restclient.rest; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.junit.Before; +import org.junit.Test; + +public class HttpUtilTest { + + /** + * Test case initialization + * + * @throws Exception the exception + */ + @Before + public void init() throws Exception { + } + + @Test + public void validateAccesors() { + + assertFalse(HttpUtil.isHttpResponseClassInformational(-1)); + assertFalse(HttpUtil.isHttpResponseClassInformational(99)); + assertTrue(HttpUtil.isHttpResponseClassInformational(183)); + assertFalse(HttpUtil.isHttpResponseClassInformational(200)); + + assertFalse(HttpUtil.isHttpResponseClassSuccess(199)); + assertTrue(HttpUtil.isHttpResponseClassSuccess(202)); + assertFalse(HttpUtil.isHttpResponseClassSuccess(300)); + + assertFalse(HttpUtil.isHttpResponseClassRedirection(299)); + assertTrue(HttpUtil.isHttpResponseClassRedirection(307)); + assertFalse(HttpUtil.isHttpResponseClassRedirection(401)); + + assertFalse(HttpUtil.isHttpResponseClassClientError(399)); + assertTrue(HttpUtil.isHttpResponseClassClientError(404)); + assertFalse(HttpUtil.isHttpResponseClassClientError(555)); + + assertFalse(HttpUtil.isHttpResponseClassServerError(499)); + assertTrue(HttpUtil.isHttpResponseClassServerError(504)); + assertFalse(HttpUtil.isHttpResponseClassServerError(662)); + + int[] successCodes = { 201, 202, 205, 299 }; + + assertTrue(HttpUtil.isHttpResponseInList(201, successCodes)); + assertFalse(HttpUtil.isHttpResponseInList(301, successCodes)); + + } + +} diff --git a/src/test/java/org/openecomp/restclient/rest/RestClientBuilderTest.java b/src/test/java/org/openecomp/restclient/rest/RestClientBuilderTest.java index 93e5520..e299e36 100644 --- a/src/test/java/org/openecomp/restclient/rest/RestClientBuilderTest.java +++ b/src/test/java/org/openecomp/restclient/rest/RestClientBuilderTest.java @@ -1,18 +1,18 @@ package org.openecomp.restclient.rest; -import java.util.Map; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import org.junit.Before; import org.junit.Test; -import org.openecomp.restclient.rest.RestClientBuilder; - -import static org.junit.Assert.*; +import org.openecomp.restclient.enums.RestAuthenticationMode; import com.sun.jersey.api.client.Client; - import com.sun.jersey.client.urlconnection.HTTPSProperties; - /** * This suite of tests is intended to exercise the functionality of the generice REST client * builder. @@ -20,147 +20,231 @@ import com.sun.jersey.client.urlconnection.HTTPSProperties; public class RestClientBuilderTest { /** - * This test validates that we can enable and disable certificate chain verification and that the - * associated parameters are correctly set. + * Test case initialization + * + * @throws Exception the exception */ + @Before + public void init() throws Exception { + } + + private String generateAuthorizationHeaderValue(String username, String password) { + String usernameAndPassword = username + ":" + password; + return "Basic " + java.util.Base64.getEncoder().encodeToString(usernameAndPassword.getBytes()); + } + @Test - public void certificateChainVerificationTest() throws Exception { - - final String TRUST_STORE_FILENAME = "myTrustStore"; - - - // Instantiate a RestClientBuilder with default parameters and - // get a client instance. - RestClientBuilder builder = new RestClientBuilder(); - Client client = builder.getClient(); - - // Validate that, by default, no trust store has been set. - assertNull("Trust store filename should not be set for default builder", - System.getProperty("javax.net.ssl.trustStore")); - - // Now, enable certificate chain verification, but don't specify - // a trust store filename. - builder.setValidateServerCertChain(true); - - // Now, get a new client instance. We expect the builder to complain - // because there is no trust store filename. - try { - Client client2 = builder.getClient(); - fail("Expected exception due to no trust store filename."); - - } catch (IllegalArgumentException e) { - assertTrue(e.getMessage().contains("Trust store filename must be set")); - } + public void validateAccesors() { + + RestClientBuilder restClientBuilder = new RestClientBuilder(); + + // test defaults + assertEquals(restClientBuilder.isValidateServerHostname(), RestClientBuilder.DEFAULT_VALIDATE_SERVER_HOST); + assertEquals(restClientBuilder.isValidateServerCertChain(), RestClientBuilder.DEFAULT_VALIDATE_CERT_CHAIN); + assertEquals(restClientBuilder.getClientCertFileName(), RestClientBuilder.DEFAULT_CLIENT_CERT_FILENAME); + assertEquals(restClientBuilder.getClientCertPassword(), RestClientBuilder.DEFAULT_CERT_PASSWORD); + assertEquals(restClientBuilder.getTruststoreFilename(), RestClientBuilder.DEFAULT_TRUST_STORE_FILENAME); + assertEquals(restClientBuilder.getConnectTimeoutInMs(), RestClientBuilder.DEFAULT_CONNECT_TIMEOUT_MS); + assertEquals(restClientBuilder.getReadTimeoutInMs(), RestClientBuilder.DEFAULT_READ_TIMEOUT_MS); + assertEquals(restClientBuilder.getAuthenticationMode(), RestClientBuilder.DEFAULT_AUTH_MODE); + assertEquals(restClientBuilder.getBasicAuthUsername(), RestClientBuilder.DEFAULT_BASIC_AUTH_USERNAME); + assertEquals(restClientBuilder.getBasicAuthPassword(), RestClientBuilder.DEFAULT_BASIC_AUTH_PASSWORD); + + restClientBuilder.setAuthenticationMode(RestAuthenticationMode.UNKNOWN_MODE); + restClientBuilder.setBasicAuthPassword("password"); + restClientBuilder.setBasicAuthUsername("username"); + restClientBuilder.setClientCertFileName("filename"); + restClientBuilder.setClientCertPassword("password"); + restClientBuilder.setConnectTimeoutInMs(12345); + restClientBuilder.setReadTimeoutInMs(54321); + restClientBuilder.setTruststoreFilename("truststore"); + restClientBuilder.setValidateServerCertChain(true); + restClientBuilder.setValidateServerHostname(true); + + assertEquals(restClientBuilder.isValidateServerHostname(), true); + assertEquals(restClientBuilder.isValidateServerCertChain(), true); + assertEquals(restClientBuilder.getClientCertFileName(), "filename"); + assertEquals(restClientBuilder.getClientCertPassword(), "password"); + assertEquals(restClientBuilder.getTruststoreFilename(), "truststore"); + assertEquals(restClientBuilder.getConnectTimeoutInMs(), 12345); + assertEquals(restClientBuilder.getReadTimeoutInMs(), 54321); + assertEquals(restClientBuilder.getAuthenticationMode(), RestAuthenticationMode.UNKNOWN_MODE); + assertEquals(restClientBuilder.getBasicAuthUsername(), "username"); + assertEquals(restClientBuilder.getBasicAuthPassword(), "password"); + + assertEquals(restClientBuilder.getBasicAuthenticationCredentials(), + generateAuthorizationHeaderValue("username", "password")); + + assertTrue(restClientBuilder.toString().contains("RestClientBuilder")); - // Now, set a value for the trust store filename and try again to - // get a client instance. This time it should succeed and we should - // see that our trust name filename was set. - builder.setTruststoreFilename(TRUST_STORE_FILENAME); - Client client3 = builder.getClient(); - - // Validate that the trust store filename was set. - assertNotNull("Expected trust store filename to be set", - System.getProperty("javax.net.ssl.trustStore")); - - // Validate that the filename is set to the value we specified. - assertTrue( - "Unexpected trust store filename value " + System.getProperty("javax.net.ssl.trustStore"), - System.getProperty("javax.net.ssl.trustStore").equals(TRUST_STORE_FILENAME)); } - - - /** - * This test validates that we can set timeout values in our client builder and that those values - * are reflected in the client produced by the builder. - */ + @Test - public void timeoutValuesTest() throws Exception { - - // Instantiate a RestClientBuilder with default parameters. - RestClientBuilder builder = new RestClientBuilder(); - - // Now, get a client instance and retrieve the client properties. - Client client = builder.getClient(); - - Map props = client.getProperties(); - - // Validate that the connection and read timeouts are set to the - // default values. - assertEquals("Unexpected connect timeout parameter", - props.get("com.sun.jersey.client.property.connectTimeout"), - RestClientBuilder.DEFAULT_CONNECT_TIMEOUT_MS); - assertEquals("Unexpected read timeout parameter", - props.get("com.sun.jersey.client.property.readTimeout"), - RestClientBuilder.DEFAULT_READ_TIMEOUT_MS); - - // Now, change the timeouts in the builder to non-default values. - builder.setConnectTimeoutInMs(RestClientBuilder.DEFAULT_CONNECT_TIMEOUT_MS + 100); - builder.setReadTimeoutInMs(RestClientBuilder.DEFAULT_READ_TIMEOUT_MS + 100); - - // Retrieve a new client instance and get the client properties. - Client client2 = builder.getClient(); - props = client2.getProperties(); - - // Validate that the connection and read timeouts are set to the - // new values. - assertEquals("Unexpected connect timeout parameter", - props.get("com.sun.jersey.client.property.connectTimeout"), - RestClientBuilder.DEFAULT_CONNECT_TIMEOUT_MS + 100); - assertEquals("Unexpected read timeout parameter", - props.get("com.sun.jersey.client.property.readTimeout"), - RestClientBuilder.DEFAULT_READ_TIMEOUT_MS + 100); + public void validateNoAuthClientCreation() throws Exception { + + RestClientBuilder restClientBuilder = new RestClientBuilder(); + + restClientBuilder.setAuthenticationMode(RestAuthenticationMode.HTTP_NOAUTH); + restClientBuilder.setConnectTimeoutInMs(12345); + restClientBuilder.setReadTimeoutInMs(54321); + + Client client = restClientBuilder.getClient(); + assertNotNull(client); + assertNull(client.getProperties().get(HTTPSProperties.PROPERTY_HTTPS_PROPERTIES)); } - - - /** - * This test validates that we can enable and disable host name verification in the clients - * produced by our builder. - */ + + @Test - public void hostNameVerifierTest() throws Exception { - - // Instantiate a RestClientBuilder with default parameters. - RestClientBuilder builder = new RestClientBuilder(); - - // Now, get a client instance. - Client client1 = builder.getClient(); - - // Retrieve the client's HTTPS properties. - HTTPSProperties httpProps = getHTTPSProperties(client1); - - // By default, hostname verification should be disabled, which means - // that our builder will have injected its own {@link HostnameVerifier} - // which just always returns true. - assertNotNull(httpProps.getHostnameVerifier()); - - // Verify that the host name verifier returns true regardless of what - // hostname we pass in. - assertTrue("Default hostname verifier should always return true", - httpProps.getHostnameVerifier().verify("not_a_valid_hostname", null)); - - - // Now, enable hostname verification for our client builder, and - // get a new client. - builder.setValidateServerHostname(true); - Client client2 = builder.getClient(); - - // Retrieve the client's HTTPS properties. - httpProps = getHTTPSProperties(client2); - - // Verify that with hostname verification enabled, our builder did not - // insert its own stubbed verifier. - assertNull(httpProps.getHostnameVerifier()); + public void validateUnknownModeCreateNoAuthClient() throws Exception { + + RestClientBuilder restClientBuilder = new RestClientBuilder(); + + restClientBuilder.setAuthenticationMode(RestAuthenticationMode.UNKNOWN_MODE); + restClientBuilder.setConnectTimeoutInMs(12345); + restClientBuilder.setReadTimeoutInMs(54321); + + Client client = restClientBuilder.getClient(); + assertNotNull(client); + assertNull(client.getProperties().get(HTTPSProperties.PROPERTY_HTTPS_PROPERTIES)); } + @Test + public void validateBasicAuthSslClient() throws Exception { + + RestClientBuilder restClientBuilder = new RestClientBuilder(); + + restClientBuilder.setAuthenticationMode(RestAuthenticationMode.SSL_BASIC); + restClientBuilder.setConnectTimeoutInMs(12345); + restClientBuilder.setReadTimeoutInMs(54321); + restClientBuilder.setBasicAuthUsername("username"); + restClientBuilder.setBasicAuthPassword("password"); + + Client client = restClientBuilder.getClient(); + + Object sslPropertiesObj = client.getProperties().get(HTTPSProperties.PROPERTY_HTTPS_PROPERTIES); + HTTPSProperties sslProps = null; + if ( sslPropertiesObj instanceof HTTPSProperties ) { + sslProps = (HTTPSProperties)sslPropertiesObj; + assertNotNull(sslProps.getHostnameVerifier()); + } else { + fail("Unexpected value for https properties object"); + } + + } - /** - * This is a convenience method which extracts the HTTPS properties from a supplied client. - * - * @parameter aClient - The client to retrieve the HTTPS properties from. - */ - private HTTPSProperties getHTTPSProperties(Client aClient) { - - Map props = aClient.getProperties(); - return (HTTPSProperties) props.get(HTTPSProperties.PROPERTY_HTTPS_PROPERTIES); + @Test + public void validateSslCertClient_noHostOrCertChainValidation() throws Exception { + + RestClientBuilder restClientBuilder = new RestClientBuilder(); + + restClientBuilder.setAuthenticationMode(RestAuthenticationMode.SSL_CERT); + restClientBuilder.setConnectTimeoutInMs(12345); + restClientBuilder.setReadTimeoutInMs(54321); + restClientBuilder.setValidateServerCertChain(false); + restClientBuilder.setValidateServerHostname(false); + + Client client = restClientBuilder.getClient(); + + Object sslPropertiesObj = client.getProperties().get(HTTPSProperties.PROPERTY_HTTPS_PROPERTIES); + HTTPSProperties sslProps = null; + if ( sslPropertiesObj instanceof HTTPSProperties ) { + sslProps = (HTTPSProperties)sslPropertiesObj; + assertNotNull(sslProps.getHostnameVerifier()); + } else { + fail("Unexpected value for https properties object"); + } } + + @Test + public void validateSslCertClient_hostOnlyValidation() throws Exception { + + RestClientBuilder restClientBuilder = new RestClientBuilder(); + + restClientBuilder.setAuthenticationMode(RestAuthenticationMode.SSL_CERT); + restClientBuilder.setConnectTimeoutInMs(12345); + restClientBuilder.setReadTimeoutInMs(54321); + restClientBuilder.setValidateServerCertChain(false); + restClientBuilder.setValidateServerHostname(true); + + Client client = restClientBuilder.getClient(); + + Object sslPropertiesObj = client.getProperties().get(HTTPSProperties.PROPERTY_HTTPS_PROPERTIES); + HTTPSProperties sslProps = null; + if ( sslPropertiesObj instanceof HTTPSProperties ) { + sslProps = (HTTPSProperties)sslPropertiesObj; + assertNull(sslProps.getHostnameVerifier()); + } else { + fail("Unexpected value for https properties object"); + } + } + + @Test + public void validateSslCertClient_certChainOnlyValidation() throws Exception { + + RestClientBuilder restClientBuilder = new RestClientBuilder(); + + restClientBuilder.setAuthenticationMode(RestAuthenticationMode.SSL_CERT); + restClientBuilder.setConnectTimeoutInMs(12345); + restClientBuilder.setReadTimeoutInMs(54321); + restClientBuilder.setValidateServerCertChain(true); + restClientBuilder.setValidateServerHostname(false); + restClientBuilder.setTruststoreFilename("truststore"); + restClientBuilder.setClientCertPassword(null); + + Client client = restClientBuilder.getClient(); + + Object sslPropertiesObj = client.getProperties().get(HTTPSProperties.PROPERTY_HTTPS_PROPERTIES); + HTTPSProperties sslProps = null; + if ( sslPropertiesObj instanceof HTTPSProperties ) { + sslProps = (HTTPSProperties)sslPropertiesObj; + assertNotNull(sslProps.getHostnameVerifier()); + } else { + fail("Unexpected value for https properties object"); + } + } + + @Test + public void validateSslCertClient_withHostAndCertChainValidation() throws Exception { + + RestClientBuilder restClientBuilder = new RestClientBuilder(); + + restClientBuilder.setAuthenticationMode(RestAuthenticationMode.SSL_CERT); + restClientBuilder.setConnectTimeoutInMs(12345); + restClientBuilder.setReadTimeoutInMs(54321); + restClientBuilder.setValidateServerCertChain(true); + restClientBuilder.setValidateServerHostname(true); + restClientBuilder.setClientCertPassword("password"); + restClientBuilder.setTruststoreFilename("truststore"); + + Client client = restClientBuilder.getClient(); + + Object sslPropertiesObj = client.getProperties().get(HTTPSProperties.PROPERTY_HTTPS_PROPERTIES); + HTTPSProperties sslProps = null; + if ( sslPropertiesObj instanceof HTTPSProperties ) { + sslProps = (HTTPSProperties)sslPropertiesObj; + assertNull(sslProps.getHostnameVerifier()); + } else { + fail("Unexpected value for https properties object"); + } } + + @Test (expected=IllegalArgumentException.class) + public void validateSslCertClient_illegalArgumentExceptionWhenTruststoreIsNull() throws Exception { + + RestClientBuilder restClientBuilder = new RestClientBuilder(); + + restClientBuilder.setAuthenticationMode(RestAuthenticationMode.SSL_CERT); + restClientBuilder.setConnectTimeoutInMs(12345); + restClientBuilder.setReadTimeoutInMs(54321); + restClientBuilder.setValidateServerCertChain(true); + restClientBuilder.setValidateServerHostname(true); + restClientBuilder.setTruststoreFilename(null); + + /* + * Creating the client in this scenario will cause an IllegalArgumentException caused by the + * truststore being null + */ + Client client = restClientBuilder.getClient(); + } + + } -- cgit 1.2.3-korg