From 666b4d7a3b758ebec900f96a6aafe5c1a9ad2093 Mon Sep 17 00:00:00 2001 From: "Arul.Nambi" Date: Thu, 14 Sep 2017 13:44:07 -0400 Subject: Renaming openecomp to onap Issue-ID: AAI-208 Change-Id: I852fda30e0b44e73da8ae3e4748b49c663e544d0 Signed-off-by: Arul.Nambi --- .../org/onap/aai/restclient/client/Headers.java | 35 + .../aai/restclient/client/OperationResult.java | 156 ++++ .../org/onap/aai/restclient/client/RestClient.java | 857 +++++++++++++++++++++ .../restclient/enums/RestAuthenticationMode.java | 64 ++ .../aai/restclient/logging/RestClientMsgs.java | 117 +++ .../org/onap/aai/restclient/rest/HttpUtil.java | 95 +++ .../aai/restclient/rest/RestClientBuilder.java | 312 ++++++++ .../org/openecomp/restclient/client/Headers.java | 35 - .../restclient/client/OperationResult.java | 156 ---- .../openecomp/restclient/client/RestClient.java | 857 --------------------- .../restclient/enums/RestAuthenticationMode.java | 64 -- .../restclient/logging/RestClientMsgs.java | 117 --- .../org/openecomp/restclient/rest/HttpUtil.java | 95 --- .../restclient/rest/RestClientBuilder.java | 312 -------- .../aai/restclient/client/OperationResultTest.java | 106 +++ .../aai/restclient/client/RestfulClientTest.java | 340 ++++++++ .../enums/RestAuthenticationModeTest.java | 56 ++ .../org/onap/aai/restclient/rest/HttpUtilTest.java | 74 ++ .../aai/restclient/rest/RestClientBuilderTest.java | 273 +++++++ .../restclient/client/OperationResultTest.java | 105 --- .../restclient/client/RestfulClientTest.java | 338 -------- .../enums/RestAuthenticationModeTest.java | 55 -- .../openecomp/restclient/rest/HttpUtilTest.java | 73 -- .../restclient/rest/RestClientBuilderTest.java | 272 ------- 24 files changed, 2485 insertions(+), 2479 deletions(-) create mode 100644 src/main/java/org/onap/aai/restclient/client/Headers.java create mode 100644 src/main/java/org/onap/aai/restclient/client/OperationResult.java create mode 100644 src/main/java/org/onap/aai/restclient/client/RestClient.java create mode 100644 src/main/java/org/onap/aai/restclient/enums/RestAuthenticationMode.java create mode 100644 src/main/java/org/onap/aai/restclient/logging/RestClientMsgs.java create mode 100644 src/main/java/org/onap/aai/restclient/rest/HttpUtil.java create mode 100644 src/main/java/org/onap/aai/restclient/rest/RestClientBuilder.java delete mode 100644 src/main/java/org/openecomp/restclient/client/Headers.java delete mode 100644 src/main/java/org/openecomp/restclient/client/OperationResult.java delete mode 100644 src/main/java/org/openecomp/restclient/client/RestClient.java delete mode 100644 src/main/java/org/openecomp/restclient/enums/RestAuthenticationMode.java delete mode 100644 src/main/java/org/openecomp/restclient/logging/RestClientMsgs.java delete mode 100644 src/main/java/org/openecomp/restclient/rest/HttpUtil.java delete mode 100644 src/main/java/org/openecomp/restclient/rest/RestClientBuilder.java create mode 100644 src/test/java/org/onap/aai/restclient/client/OperationResultTest.java create mode 100644 src/test/java/org/onap/aai/restclient/client/RestfulClientTest.java create mode 100644 src/test/java/org/onap/aai/restclient/enums/RestAuthenticationModeTest.java create mode 100644 src/test/java/org/onap/aai/restclient/rest/HttpUtilTest.java create mode 100644 src/test/java/org/onap/aai/restclient/rest/RestClientBuilderTest.java delete mode 100644 src/test/java/org/openecomp/restclient/client/OperationResultTest.java delete mode 100644 src/test/java/org/openecomp/restclient/client/RestfulClientTest.java delete mode 100644 src/test/java/org/openecomp/restclient/enums/RestAuthenticationModeTest.java delete mode 100644 src/test/java/org/openecomp/restclient/rest/HttpUtilTest.java delete mode 100644 src/test/java/org/openecomp/restclient/rest/RestClientBuilderTest.java (limited to 'src') diff --git a/src/main/java/org/onap/aai/restclient/client/Headers.java b/src/main/java/org/onap/aai/restclient/client/Headers.java new file mode 100644 index 0000000..56903bc --- /dev/null +++ b/src/main/java/org/onap/aai/restclient/client/Headers.java @@ -0,0 +1,35 @@ +/** + * ============LICENSE_START======================================================= + * org.onap.aai + * ================================================================================ + * Copyright © 2017 AT&T Intellectual Property. All rights reserved. + * Copyright © 2017 Amdocs + * ================================================================================ + * 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 is a trademark and service mark of AT&T Intellectual Property. + */ +package org.onap.aai.restclient.client; + +public final class Headers { + + public static final String FROM_APP_ID = "X-FromAppId"; + public static final String TRANSACTION_ID = "X-TransactionId"; + public static final String RESOURCE_VERSION = "resourceVersion"; + public static final String ETAG = "ETag"; + 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/onap/aai/restclient/client/OperationResult.java b/src/main/java/org/onap/aai/restclient/client/OperationResult.java new file mode 100644 index 0000000..ad11863 --- /dev/null +++ b/src/main/java/org/onap/aai/restclient/client/OperationResult.java @@ -0,0 +1,156 @@ +/** + * ============LICENSE_START======================================================= + * org.onap.aai + * ================================================================================ + * Copyright © 2017 AT&T Intellectual Property. All rights reserved. + * Copyright © 2017 Amdocs + * ================================================================================ + * 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 is a trademark and service mark of AT&T Intellectual Property. + */ +package org.onap.aai.restclient.client; + +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 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. + * + * @return the HTTP headers of the response. + */ + public MultivaluedMap getHeaders() { + return responseHeaders; + } + + /** + * 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; + } + + public void setResult(String result) { + this.result = result; + } + + public int getResultCode() { + return resultCode; + } + + 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 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 + ", requestedLink=" + requestedLink + + ", failureCause=" + failureCause + ", resultCode=" + resultCode + ", numRetries=" + + numRetries + ", responseHeaders=" + responseHeaders + "]"; + } + +} diff --git a/src/main/java/org/onap/aai/restclient/client/RestClient.java b/src/main/java/org/onap/aai/restclient/client/RestClient.java new file mode 100644 index 0000000..02f12b1 --- /dev/null +++ b/src/main/java/org/onap/aai/restclient/client/RestClient.java @@ -0,0 +1,857 @@ +/** + * ============LICENSE_START======================================================= + * org.onap.aai + * ================================================================================ + * Copyright © 2017 AT&T Intellectual Property. All rights reserved. + * Copyright © 2017 Amdocs + * ================================================================================ + * 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 is a trademark and service mark of AT&T Intellectual Property. + */ +package org.onap.aai.restclient.client; + +import java.io.ByteArrayOutputStream; +import java.text.SimpleDateFormat; +import java.util.Arrays; +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.onap.aai.restclient.enums.RestAuthenticationMode; +import org.onap.aai.restclient.logging.RestClientMsgs; +import org.onap.aai.restclient.rest.RestClientBuilder; +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 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 { + + /** + * This is a generic builder that is used for constructing the REST client that we will use to + * communicate with the REST endpoint. + */ + private RestClientBuilder clientBuilder; + + 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"); + + /** Standard logger for producing metric statements. */ + private Logger metricsLogger = LoggerFactory.getInstance().getMetricsLogger("AAIRESTClient"); + + private SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX"); + + /** Reusable function call for GET REST operations. */ + private final RestOperation getOp = new GetRestOperation(); + + /** Reusable function call for PUT REST operations. */ + private final RestOperation putOp = new PutRestOperation(); + + /** Reusable function call for POST REST operations. */ + private final RestOperation postOp = new PostRestOperation(); + + /** 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(); + + } + + + /** + * Creates a new instance of the {@link RestClient} using the supplied {@link RestClientBuilder}. + * + * @param rcBuilder - The REST client builder that this instance of the {@link RestClient} should + * use. + */ + 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; + } + + + /** + * Sets the flag to indicate whether or not validation should be performed against the host name + * of the server we are trying to communicate with. + * + * @parameter validate - Set to true to enable validation, false to disable + * + * @return The AAIRESTClient instance. This is useful for chaining parameter assignments. + */ + public RestClient validateServerHostname(boolean validate) { + logger.debug("Set validate server hostname = " + validate); + clientBuilder.setValidateServerHostname(validate); + return this; + } + + + /** + * Sets the flag to indicate whether or not validation should be performed against the certificate + * chain. + * + * @parameter validate - Set to true to enable validation, false to disable. + * + * @return The AAIRESTClient instance. This is useful for chaining parameter assignments. + */ + public RestClient validateServerCertChain(boolean validate) { + logger.debug("Set validate server certificate chain = " + validate); + clientBuilder.setValidateServerCertChain(validate); + return this; + } + + + /** + * Assigns the client certificate file to use. + * + * @param filename - The name of the certificate file. + * + * @return The AAIRESTClient instance. This is useful for chaining parameter assignments. + */ + public RestClient clientCertFile(String filename) { + logger.debug("Set client certificate filename = " + filename); + clientBuilder.setClientCertFileName(filename); + return this; + } + + + /** + * Assigns the client certificate password to use. + * + * @param password - The certificate password. + * + * @return The AAIRESTClient instance. This is useful for chaining parameter assignments. + */ + public RestClient clientCertPassword(String password) { + clientBuilder.setClientCertPassword(password); + return this; + } + + + /** + * Assigns the name of the trust store file to use. + * + * @param filename - the name of the trust store file. + * + * @return The AAIRESTClient instance. This is useful for chaining parameter assignments. + */ + public RestClient trustStore(String filename) { + logger.debug("Set trust store filename = " + filename); + clientBuilder.setTruststoreFilename(filename); + return this; + } + + + /** + * Assigns the connection timeout (in ms) to use when connecting to the target server. + * + * @param timeout - The length of time to wait in ms before timing out. + * + * @return The AAIRESTClient instance. This is useful for chaining parameter assignments. + */ + public RestClient connectTimeoutMs(int timeout) { + logger.debug("Set connection timeout = " + timeout + " ms"); + clientBuilder.setConnectTimeoutInMs(timeout); + return this; + } + + + /** + * Assigns the read timeout (in ms) to use when communicating with the target server. + * + * @param timeout The read timeout in milliseconds. + * + * @return The AAIRESTClient instance. This is useful for chaining parameter assignments. + */ + public RestClient readTimeoutMs(int timeout) { + logger.debug("Set read timeout = " + timeout + " ms"); + clientBuilder.setReadTimeoutInMs(timeout); + 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. + * This variant of the method will perform a requested number of retries in the event that the + * first request is unsuccessful. + * + * @param operation - the REST operation type to send to the url + * @param url - The REST endpoint to submit the REST request to. + * @param payload - They payload to provide in the REST request, if applicable + * @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 REST request. + */ + protected OperationResult processRequest(RestOperation operation, String url, String payload, + Map> headers, MediaType contentType, MediaType responseType, + int numRetries) { + + + OperationResult result = null; + + long startTimeInMs = System.currentTimeMillis(); + for (int retryCount = 0; retryCount < numRetries; retryCount++) { + + 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 (!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(50); + + } catch (InterruptedException e) { + logger.error(RestClientMsgs.HTTP_REQUEST_INTERRUPTED, url, e.getLocalizedMessage()); + break; + } + } + + // 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."); + + return result; + } + + /** + * This method operates on a REST endpoint by submitting an HTTP operation request against the + * supplied URL. + * + * @param operation - the REST operation type to send to the url + * @param url - The REST endpoint to submit the REST request to. + * @param payload - They payload to provide in the REST request, if applicable + * @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 REST request. + */ + protected OperationResult processRequest(RestOperation operation, String url, String payload, + Map> headers, MediaType contentType, MediaType responseType) { + + ClientResponse clientResponse = null; + OperationResult operationResult = new OperationResult(); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + + String requestType = operation.getRequestType().name(); + + // Grab the current time so that we can log how long the + // query took once we are done. + long startTimeInMs = System.currentTimeMillis(); + MdcOverride override = new MdcOverride(); + override.addAttribute(MdcContext.MDC_START_TIME, formatter.format(startTimeInMs)); + + logger.info(RestClientMsgs.HTTP_REQUEST, requestType, url); + + try { + + // Get a REST client instance for our request. + Client client = getClient(); + + // Debug log the request + debugRequest(url, payload, headers, responseType); + + // Get a client request builder, and submit our GET request. + Builder builder = getClientBuilder(client, url, payload, headers, contentType, responseType); + clientResponse = operation.processOperation(builder); + + populateOperationResult(clientResponse, operationResult); + + // Debug log the response + debugResponse(operationResult, clientResponse.getHeaders()); + + } catch (Exception ex) { + + logger.error(RestClientMsgs.HTTP_REQUEST_ERROR, requestType, url, ex.getLocalizedMessage()); + operationResult.setResultCode(500); + operationResult.setFailureCause( + "Error during GET operation to AAI with message = " + ex.getLocalizedMessage()); + + } finally { + + if (logger.isDebugEnabled()) { + logger.debug(baos.toString()); + } + + // Not every valid response code is actually represented by the Response.Status + // object, so we need to guard against missing codes, otherwise we throw null + // pointer exceptions when we try to generate our metrics logs... + Response.Status responseStatus = + Response.Status.fromStatusCode(operationResult.getResultCode()); + String responseStatusCodeString = ""; + if (responseStatus != null) { + responseStatusCodeString = responseStatus.toString(); + } + + metricsLogger.info(RestClientMsgs.HTTP_REQUEST_TIME, + new LogFields().setField(LogLine.DefinedFields.STATUS_CODE, responseStatusCodeString) + .setField(LogLine.DefinedFields.RESPONSE_CODE, operationResult.getResultCode()) + .setField(LogLine.DefinedFields.RESPONSE_DESCRIPTION, operationResult.getResult()), + override, requestType, Long.toString(System.currentTimeMillis() - startTimeInMs), url); + logger.info(RestClientMsgs.HTTP_REQUEST_TIME, requestType, + Long.toString(System.currentTimeMillis() - startTimeInMs), url); + logger.info(RestClientMsgs.HTTP_RESPONSE, url, + operationResult.getResultCode() + " " + responseStatusCodeString); + } + + return operationResult; + } + + /** + * This method submits an HTTP PUT request against the supplied URL. + * + * @param url - The REST endpoint to submit the PUT 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 PUT request. + */ + public OperationResult put(String url, String payload, Map> headers, + MediaType contentType, MediaType responseType) { + return processRequest(putOp, url, payload, headers, contentType, responseType); + } + + /** + * This method submits an HTTP POST request against the supplied URL. + * + * @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 post(String url, String payload, Map> headers, + 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. + * + * @param url - The REST endpoint to submit the GET 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 GET request. + */ + public OperationResult get(String url, Map> headers, + MediaType responseType) { + return processRequest(getOp, url, null, headers, null, responseType); + } + + /** + * This method submits an HTTP GET request against the supplied URL. + * This variant of the method will perform a requested number of retries in the event that the + * first request is unsuccessful. + * + * @param url - The REST endpoint to submit the GET request to. + * @param headers - The headers that should be passed in the request + * @param responseType - The expected format of the response. + * @param numRetries - The number of times to try resubmitting the request in the event of a + * failure. + * + * @return The result of the GET request. + */ + public OperationResult get(String url, Map> headers, MediaType responseType, + int numRetries) { + return processRequest(getOp, url, null, headers, null, responseType, numRetries); + } + + /** + * This method submits an HTTP DELETE request against the supplied URL. + * + * @param url - The REST endpoint to submit the DELETE 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 DELETE request. + */ + public OperationResult delete(String url, Map> headers, + MediaType responseType) { + return processRequest(deleteOp, url, null, headers, null, responseType); + } + + /** + * This method does a health check ("ping") against the supplied URL. + * + * @param url - The REST endpoint to attempt a health check. + * @param srcAppName - The name of the application using this client. + * @param destAppName - The name of the destination app. + * + * @return A boolean value. True if connection attempt was successful, false otherwise. + * + */ + public boolean healthCheck(String url, String srcAppName, String destAppName) { + return healthCheck(url, srcAppName, destAppName, MediaType.TEXT_PLAIN_TYPE); + + } + + /** + * This method does a health check ("ping") against the supplied URL. + * + * @param url - The REST endpoint to attempt a health check. + * @param srcAppName - The name of the application using this client. + * @param destAppName - The name of the destination app. + * @param responseType - The response type. + * + * @return A boolean value. True if connection attempt was successful, false otherwise. + * + */ + public boolean healthCheck(String url, String srcAppName, String destAppName, + MediaType responseType) { + MultivaluedMap headers = new MultivaluedMapImpl(); + headers.put(Headers.FROM_APP_ID, Arrays.asList(new String[] {srcAppName})); + headers.put(Headers.TRANSACTION_ID, Arrays.asList(new String[] {UUID.randomUUID().toString()})); + + try { + logger.info(RestClientMsgs.HEALTH_CHECK_ATTEMPT, destAppName, url); + OperationResult result = get(url, headers, responseType); + + if (result != null && result.getFailureCause() == null) { + logger.info(RestClientMsgs.HEALTH_CHECK_SUCCESS, destAppName, url); + return true; + } else { + logger.error(RestClientMsgs.HEALTH_CHECK_FAILURE, destAppName, url, + result.getFailureCause()); + return false; + } + } catch (Exception e) { + logger.error(RestClientMsgs.HEALTH_CHECK_FAILURE, destAppName, url, e.getMessage()); + return false; + } + } + + /** + * This method constructs a client request builder that can be used for submitting REST requests + * to the supplied URL endpoint. + * + * @param client - The REST client we will be using to talk to the server. + * @param url - The URL endpoint that our request will be submitted to. + * @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 A client request builder. + */ + private Builder getClientBuilder(Client client, String url, String payload, + Map> headers, MediaType contentType, MediaType responseType) { + + WebResource resource = client.resource(url); + Builder builder = null; + + builder = resource.accept(responseType); + + if (contentType != null) { + builder.type(contentType); + } + + if (payload != null) { + builder.entity(payload); + } + + if (headers != null) { + 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; + } + + private void debugRequest(String url, String payload, Map> headers, + MediaType responseType) { + if (logger.isDebugEnabled()) { + StringBuilder debugRequest = new StringBuilder("REQUEST:\n"); + debugRequest.append("URL: ").append(url).append("\n"); + debugRequest.append("Payload: ").append(payload).append("\n"); + debugRequest.append("Response Type: ").append(responseType).append("\n"); + if (headers != null) { + debugRequest.append("Headers: "); + for (Entry> header : headers.entrySet()) { + debugRequest.append("\n\t").append(header.getKey()).append(":"); + for (String headerEntry : header.getValue()) { + debugRequest.append("\"").append(headerEntry).append("\" "); + } + } + } + logger.debug(debugRequest.toString()); + } + } + + private void debugResponse(OperationResult operationResult, + MultivaluedMap headers) { + if (logger.isDebugEnabled()) { + StringBuilder debugResponse = new StringBuilder("RESPONSE:\n"); + debugResponse.append("Result: ").append(operationResult.getResultCode()).append("\n"); + debugResponse.append("Failure Cause: ").append(operationResult.getFailureCause()) + .append("\n"); + debugResponse.append("Payload: ").append(operationResult.getResult()).append("\n"); + if (headers != null) { + debugResponse.append("Headers: "); + for (Entry> header : headers.entrySet()) { + debugResponse.append("\n\t").append(header.getKey()).append(":"); + for (String headerEntry : header.getValue()) { + debugResponse.append("\"").append(headerEntry).append("\" "); + } + } + } + logger.debug(debugResponse.toString()); + } + } + + /** + * 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. + */ + 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()); + } + + return clientInstance.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; + + } + + + /** + * This method populates the fields of an {@link OperationResult} instance based on the contents + * of a {@link ClientResponse} received in response to a REST request. + */ + private void populateOperationResult(ClientResponse response, OperationResult opResult) { + + // If we got back a NULL response, then just produce a generic + // error code and result indicating this. + if (response == null) { + opResult.setResultCode(500); + opResult.setFailureCause("Client response was null"); + return; + } + + int statusCode = response.getStatus(); + opResult.setResultCode(statusCode); + + if (opResult.wasSuccessful()) { + if (statusCode != Response.Status.NO_CONTENT.getStatusCode()) { + opResult.setResult(response.getEntity(String.class)); + } + } else { + opResult.setFailureCause(response.getEntity(String.class)); + } + + opResult.setHeaders(response.getHeaders()); + } + + private class GetRestOperation implements RestOperation { + public ClientResponse processOperation(Builder builder) { + return builder.get(ClientResponse.class); + } + + public RequestType getRequestType() { + return RequestType.GET; + } + } + + private class PutRestOperation implements RestOperation { + public ClientResponse processOperation(Builder builder) { + return builder.put(ClientResponse.class); + } + + public RequestType getRequestType() { + return RequestType.PUT; + } + } + + private class PostRestOperation implements RestOperation { + public ClientResponse processOperation(Builder builder) { + return builder.post(ClientResponse.class); + } + + public RequestType getRequestType() { + return RequestType.POST; + } + } + + private class DeleteRestOperation implements RestOperation { + public ClientResponse processOperation(Builder builder) { + return builder.delete(ClientResponse.class); + } + + public RequestType getRequestType() { + 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. + */ + private interface RestOperation { + + /** + * Method used to wrap the functionality of making a REST call out to the endpoint. + * + * @param builder the Jersey builder used to make the request + * @return the response from the REST endpoint + */ + public ClientResponse processOperation(Builder builder); + + /** + * Returns the REST request type. + */ + public RequestType getRequestType(); + + /** + * The supported REST request types. + */ + public enum RequestType { + 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/onap/aai/restclient/enums/RestAuthenticationMode.java b/src/main/java/org/onap/aai/restclient/enums/RestAuthenticationMode.java new file mode 100644 index 0000000..6bff196 --- /dev/null +++ b/src/main/java/org/onap/aai/restclient/enums/RestAuthenticationMode.java @@ -0,0 +1,64 @@ +/** + * ============LICENSE_START======================================================= + * org.onap.aai + * ================================================================================ + * Copyright © 2017 AT&T Intellectual Property. All rights reserved. + * Copyright © 2017 Amdocs + * ================================================================================ + * 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 is a trademark and service mark of AT&T Intellectual Property. + */ +package org.onap.aai.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/onap/aai/restclient/logging/RestClientMsgs.java b/src/main/java/org/onap/aai/restclient/logging/RestClientMsgs.java new file mode 100644 index 0000000..09af107 --- /dev/null +++ b/src/main/java/org/onap/aai/restclient/logging/RestClientMsgs.java @@ -0,0 +1,117 @@ +/** + * ============LICENSE_START======================================================= + * org.onap.aai + * ================================================================================ + * Copyright © 2017 AT&T Intellectual Property. All rights reserved. + * Copyright © 2017 Amdocs + * ================================================================================ + * 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 is a trademark and service mark of AT&T Intellectual Property. + */ +package org.onap.aai.restclient.logging; + +import com.att.eelf.i18n.EELFResourceManager; +import org.openecomp.cl.eelf.LogMessageEnum; + +public enum RestClientMsgs implements LogMessageEnum { + + /** + * Arguments: + * {0} = HTTP operation + * {1} = URL + */ + HTTP_REQUEST, + + /** + * Arguments: + * {0} = HTTP operation + * {1} = URL + * {2} = Attempt count. + */ + HTTP_REQUEST_WITH_RETRIES, + + /** + * Arguments: + * {0} = HTTP operation + * {1} - URL + * {2} - Operation time in ms. + */ + HTTP_REQUEST_TIME, + + /** + * Arguments: + * {0} = HTTP operation + * {1} - URL + * {2} - Operation time in ms. + * {3} - Retry count. + */ + HTTP_REQUEST_TIME_WITH_RETRIES, + + /** + * Arguments: + * {0} = HTTP operation + * {1} - URL + * {2} - Error message. + */ + HTTP_REQUEST_INTERRUPTED, + + /** + * Arguments: + * {0} = HTTP operation + * {1} - URL + * {2} - Error message. + */ + HTTP_REQUEST_ERROR, + + /** + * . Arguments: + * {0} = Target URL + */ + HEALTH_CHECK_ATTEMPT, + + /** + * . Arguments: + * {0} = Target URL + */ + HEALTH_CHECK_SUCCESS, + + /** + * . Arguments: + * {0} = Target URL + * {1} = failure cause + */ + HEALTH_CHECK_FAILURE, + + + /** + * . Arguments: + * {0} = URL + * {1} - Response code + */ + HTTP_RESPONSE, + + /** + * . Arguments: + * {0} = failure cause + */ + CLIENT_INITIALIZATION_FAILURE; + + /** + * Static initializer to ensure the resource bundles for this class are loaded... + */ + static { + EELFResourceManager.loadMessageBundle("logging/RESTClientMsgs"); + } +} diff --git a/src/main/java/org/onap/aai/restclient/rest/HttpUtil.java b/src/main/java/org/onap/aai/restclient/rest/HttpUtil.java new file mode 100644 index 0000000..0677a16 --- /dev/null +++ b/src/main/java/org/onap/aai/restclient/rest/HttpUtil.java @@ -0,0 +1,95 @@ +/** + * ============LICENSE_START======================================================= + * org.onap.aai + * ================================================================================ + * Copyright © 2017 AT&T Intellectual Property. All rights reserved. + * Copyright © 2017 Amdocs + * ================================================================================ + * 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 is a trademark and service mark of AT&T Intellectual Property. + */ +package org.onap.aai.restclient.rest; + +public class HttpUtil { + + /** + * Determines if the provided HTTP response is present in the provided list of acceptable response + * codes. + * + * @param response the http response we got from our request + * @param list the list of acceptable response codes + * @return true if the http response is in the provided list + */ + public static boolean isHttpResponseInList(int response, int... list) { + for (int checkCode : list) { + if (checkCode == response) { + return true; + } + } + return false; + } + + /** + * Determines if the provided http response is of the information class. + * + * @param response the http response we got from our request + * @return true if the response is of the informational class and false otherwise + */ + public static boolean isHttpResponseClassInformational(int response) { + return ( response >= 100 && response <= 199); + } + + /** + * Determines if the provided http response is of the success class. + * + * @param response the http response we got from our request + * @return true if the response is of the success class and false otherwise + */ + public static boolean isHttpResponseClassSuccess(int response) { + return ( response >= 200 && response <= 299); + + } + + /** + * Determines if the provided http response is of the redirection class. + * + * @param response the http response we got from our request + * @return true if the response is of the redirection class and false otherwise + */ + public static boolean isHttpResponseClassRedirection(int response) { + return ( response >= 300 && response <= 399); + } + + /** + * Determines if the provided http response is of the client error class. + * + * @param response the http response we got from our request + * @return true if the response is of the client error class and false otherwise + */ + public static boolean isHttpResponseClassClientError(int response) { + return ( response >= 400 && response <= 499); + } + + /** + * Determines if the provided http response is of the server error class. + * + * @param response the http response we got from our request + * @return true if the response is of the server error class and false otherwise + */ + public static boolean isHttpResponseClassServerError(int response) { + return ( response >= 500 && response <= 599); + } + +} diff --git a/src/main/java/org/onap/aai/restclient/rest/RestClientBuilder.java b/src/main/java/org/onap/aai/restclient/rest/RestClientBuilder.java new file mode 100644 index 0000000..f446f27 --- /dev/null +++ b/src/main/java/org/onap/aai/restclient/rest/RestClientBuilder.java @@ -0,0 +1,312 @@ +/** + * ============LICENSE_START======================================================= + * org.onap.aai + * ================================================================================ + * Copyright © 2017 AT&T Intellectual Property. All rights reserved. + * Copyright © 2017 Amdocs + * ================================================================================ + * 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 is a trademark and service mark of AT&T Intellectual Property. + */ +package org.onap.aai.restclient.rest; + +import java.io.FileInputStream; +import java.security.KeyStore; +import java.security.cert.X509Certificate; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSession; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; + +import org.onap.aai.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. + */ +public class RestClientBuilder { + + public static final boolean DEFAULT_VALIDATE_SERVER_HOST = false; + public static final boolean DEFAULT_VALIDATE_CERT_CHAIN = false; + public static final String DEFAULT_CLIENT_CERT_FILENAME = null; + public static final String DEFAULT_CERT_PASSWORD = null; + 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.SSL_CERT; + 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"; + private static final String TRUST_STORE_PROPERTY = "javax.net.ssl.trustStore"; + + private boolean validateServerHostname; + private boolean validateServerCertChain; + private String clientCertFileName; + private String clientCertPassword; + private String truststoreFilename; + private int connectTimeoutInMs; + private int readTimeoutInMs; + private RestAuthenticationMode authenticationMode; + private String basicAuthUsername; + private String basicAuthPassword; + + /** + * Rest Client Builder. + */ + public RestClientBuilder() { + validateServerHostname = DEFAULT_VALIDATE_SERVER_HOST; + validateServerCertChain = DEFAULT_VALIDATE_CERT_CHAIN; + clientCertFileName = DEFAULT_CLIENT_CERT_FILENAME; + clientCertPassword = DEFAULT_CERT_PASSWORD; + truststoreFilename = DEFAULT_TRUST_STORE_FILENAME; + connectTimeoutInMs = DEFAULT_CONNECT_TIMEOUT_MS; + readTimeoutInMs = DEFAULT_READ_TIMEOUT_MS; + authenticationMode = DEFAULT_AUTH_MODE; + basicAuthUsername = DEFAULT_BASIC_AUTH_USERNAME; + basicAuthPassword = DEFAULT_BASIC_AUTH_PASSWORD; + } + + public boolean isValidateServerHostname() { + return validateServerHostname; + } + + public void setValidateServerHostname(boolean validateServerHostname) { + this.validateServerHostname = validateServerHostname; + } + + public boolean isValidateServerCertChain() { + return validateServerCertChain; + } + + public void setValidateServerCertChain(boolean validateServerCertChain) { + this.validateServerCertChain = validateServerCertChain; + } + + public String getClientCertFileName() { + return clientCertFileName; + } + + public void setClientCertFileName(String clientCertFileName) { + this.clientCertFileName = clientCertFileName; + } + + public String getClientCertPassword() { + return clientCertPassword; + } + + public void setClientCertPassword(String clientCertPassword) { + this.clientCertPassword = clientCertPassword; + } + + public String getTruststoreFilename() { + return truststoreFilename; + } + + public void setTruststoreFilename(String truststoreFilename) { + this.truststoreFilename = truststoreFilename; + } + + public int getConnectTimeoutInMs() { + return connectTimeoutInMs; + } + + public void setConnectTimeoutInMs(int connectTimeoutInMs) { + this.connectTimeoutInMs = connectTimeoutInMs; + } + + public int getReadTimeoutInMs() { + return readTimeoutInMs; + } + + public void setReadTimeoutInMs(int readTimeoutInMs) { + 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 configured for SSL + */ + public Client getClient() throws Exception { + + 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; + if (validateServerCertChain) { + if (truststoreFilename != null) { + System.setProperty(TRUST_STORE_PROPERTY, truststoreFilename); + } else { + throw new IllegalArgumentException("Trust store filename must be set!"); + } + + } else { + + // We aren't validating certificates, so create a trust manager that does + // not validate certificate chains. + trustAllCerts = new TrustManager[] {new X509TrustManager() { + public X509Certificate[] getAcceptedIssuers() { + return null; + } + + public void checkClientTrusted(X509Certificate[] certs, String authType) {} + + public void checkServerTrusted(X509Certificate[] certs, String authType) {} + }}; + } + + // Set up the SSL context, keystore, etc. to use for our connection + // to the AAI. + SSLContext ctx = SSLContext.getInstance(SSL_PROTOCOL); + KeyManagerFactory kmf = KeyManagerFactory.getInstance(KEYSTORE_ALGORITHM); + KeyStore ks = KeyStore.getInstance(KEYSTORE_TYPE); + + char[] pwd = null; + if (clientCertPassword != null) { + pwd = clientCertPassword.toCharArray(); + } + + if (clientCertFileName != null) { + FileInputStream fin = new FileInputStream(clientCertFileName); + + // Load the keystore and initialize the key manager factory. + ks.load(fin, pwd); + kmf.init(ks, pwd); + + ctx.init(kmf.getKeyManagers(), trustAllCerts, null); + } else { + ctx.init(null, trustAllCerts, null); + } + + // Are we performing validation of the server host name? + if (validateServerHostname) { + clientConfig.getProperties().put(HTTPSProperties.PROPERTY_HTTPS_PROPERTIES, + new HTTPSProperties(null, ctx)); + + } else { + clientConfig.getProperties().put(HTTPSProperties.PROPERTY_HTTPS_PROPERTIES, + new HTTPSProperties(new HostnameVerifier() { + @Override + public boolean verify(String str, SSLSession sslSession) { + return true; + } + }, 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; + client = Client.create(clientConfig); + client.setConnectTimeout(connectTimeoutInMs); + client.setReadTimeout(readTimeoutInMs); + + // ...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/java/org/openecomp/restclient/client/Headers.java b/src/main/java/org/openecomp/restclient/client/Headers.java deleted file mode 100644 index 988a068..0000000 --- a/src/main/java/org/openecomp/restclient/client/Headers.java +++ /dev/null @@ -1,35 +0,0 @@ -/** - * ============LICENSE_START======================================================= - * org.onap.aai - * ================================================================================ - * Copyright © 2017 AT&T Intellectual Property. All rights reserved. - * Copyright © 2017 Amdocs - * ================================================================================ - * 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 is a trademark and service mark of AT&T Intellectual Property. - */ -package org.openecomp.restclient.client; - -public final class Headers { - - public static final String FROM_APP_ID = "X-FromAppId"; - public static final String TRANSACTION_ID = "X-TransactionId"; - public static final String RESOURCE_VERSION = "resourceVersion"; - public static final String ETAG = "ETag"; - 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 deleted file mode 100644 index 2c2cfe2..0000000 --- a/src/main/java/org/openecomp/restclient/client/OperationResult.java +++ /dev/null @@ -1,156 +0,0 @@ -/** - * ============LICENSE_START======================================================= - * org.onap.aai - * ================================================================================ - * Copyright © 2017 AT&T Intellectual Property. All rights reserved. - * Copyright © 2017 Amdocs - * ================================================================================ - * 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 is a trademark and service mark of AT&T Intellectual Property. - */ -package org.openecomp.restclient.client; - -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 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. - * - * @return the HTTP headers of the response. - */ - public MultivaluedMap getHeaders() { - return responseHeaders; - } - - /** - * 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; - } - - public void setResult(String result) { - this.result = result; - } - - public int getResultCode() { - return resultCode; - } - - 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 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 + ", 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 deleted file mode 100644 index 2912909..0000000 --- a/src/main/java/org/openecomp/restclient/client/RestClient.java +++ /dev/null @@ -1,857 +0,0 @@ -/** - * ============LICENSE_START======================================================= - * org.onap.aai - * ================================================================================ - * Copyright © 2017 AT&T Intellectual Property. All rights reserved. - * Copyright © 2017 Amdocs - * ================================================================================ - * 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 is a trademark and service mark of AT&T Intellectual Property. - */ -package org.openecomp.restclient.client; - -import java.io.ByteArrayOutputStream; -import java.text.SimpleDateFormat; -import java.util.Arrays; -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 { - - /** - * This is a generic builder that is used for constructing the REST client that we will use to - * communicate with the REST endpoint. - */ - private RestClientBuilder clientBuilder; - - 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"); - - /** Standard logger for producing metric statements. */ - private Logger metricsLogger = LoggerFactory.getInstance().getMetricsLogger("AAIRESTClient"); - - private SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX"); - - /** Reusable function call for GET REST operations. */ - private final RestOperation getOp = new GetRestOperation(); - - /** Reusable function call for PUT REST operations. */ - private final RestOperation putOp = new PutRestOperation(); - - /** Reusable function call for POST REST operations. */ - private final RestOperation postOp = new PostRestOperation(); - - /** 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(); - - } - - - /** - * Creates a new instance of the {@link RestClient} using the supplied {@link RestClientBuilder}. - * - * @param rcBuilder - The REST client builder that this instance of the {@link RestClient} should - * use. - */ - 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; - } - - - /** - * Sets the flag to indicate whether or not validation should be performed against the host name - * of the server we are trying to communicate with. - * - * @parameter validate - Set to true to enable validation, false to disable - * - * @return The AAIRESTClient instance. This is useful for chaining parameter assignments. - */ - public RestClient validateServerHostname(boolean validate) { - logger.debug("Set validate server hostname = " + validate); - clientBuilder.setValidateServerHostname(validate); - return this; - } - - - /** - * Sets the flag to indicate whether or not validation should be performed against the certificate - * chain. - * - * @parameter validate - Set to true to enable validation, false to disable. - * - * @return The AAIRESTClient instance. This is useful for chaining parameter assignments. - */ - public RestClient validateServerCertChain(boolean validate) { - logger.debug("Set validate server certificate chain = " + validate); - clientBuilder.setValidateServerCertChain(validate); - return this; - } - - - /** - * Assigns the client certificate file to use. - * - * @param filename - The name of the certificate file. - * - * @return The AAIRESTClient instance. This is useful for chaining parameter assignments. - */ - public RestClient clientCertFile(String filename) { - logger.debug("Set client certificate filename = " + filename); - clientBuilder.setClientCertFileName(filename); - return this; - } - - - /** - * Assigns the client certificate password to use. - * - * @param password - The certificate password. - * - * @return The AAIRESTClient instance. This is useful for chaining parameter assignments. - */ - public RestClient clientCertPassword(String password) { - clientBuilder.setClientCertPassword(password); - return this; - } - - - /** - * Assigns the name of the trust store file to use. - * - * @param filename - the name of the trust store file. - * - * @return The AAIRESTClient instance. This is useful for chaining parameter assignments. - */ - public RestClient trustStore(String filename) { - logger.debug("Set trust store filename = " + filename); - clientBuilder.setTruststoreFilename(filename); - return this; - } - - - /** - * Assigns the connection timeout (in ms) to use when connecting to the target server. - * - * @param timeout - The length of time to wait in ms before timing out. - * - * @return The AAIRESTClient instance. This is useful for chaining parameter assignments. - */ - public RestClient connectTimeoutMs(int timeout) { - logger.debug("Set connection timeout = " + timeout + " ms"); - clientBuilder.setConnectTimeoutInMs(timeout); - return this; - } - - - /** - * Assigns the read timeout (in ms) to use when communicating with the target server. - * - * @param timeout The read timeout in milliseconds. - * - * @return The AAIRESTClient instance. This is useful for chaining parameter assignments. - */ - public RestClient readTimeoutMs(int timeout) { - logger.debug("Set read timeout = " + timeout + " ms"); - clientBuilder.setReadTimeoutInMs(timeout); - 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. - * This variant of the method will perform a requested number of retries in the event that the - * first request is unsuccessful. - * - * @param operation - the REST operation type to send to the url - * @param url - The REST endpoint to submit the REST request to. - * @param payload - They payload to provide in the REST request, if applicable - * @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 REST request. - */ - protected OperationResult processRequest(RestOperation operation, String url, String payload, - Map> headers, MediaType contentType, MediaType responseType, - int numRetries) { - - - OperationResult result = null; - - long startTimeInMs = System.currentTimeMillis(); - for (int retryCount = 0; retryCount < numRetries; retryCount++) { - - 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 (!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(50); - - } catch (InterruptedException e) { - logger.error(RestClientMsgs.HTTP_REQUEST_INTERRUPTED, url, e.getLocalizedMessage()); - break; - } - } - - // 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."); - - return result; - } - - /** - * This method operates on a REST endpoint by submitting an HTTP operation request against the - * supplied URL. - * - * @param operation - the REST operation type to send to the url - * @param url - The REST endpoint to submit the REST request to. - * @param payload - They payload to provide in the REST request, if applicable - * @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 REST request. - */ - protected OperationResult processRequest(RestOperation operation, String url, String payload, - Map> headers, MediaType contentType, MediaType responseType) { - - ClientResponse clientResponse = null; - OperationResult operationResult = new OperationResult(); - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - - String requestType = operation.getRequestType().name(); - - // Grab the current time so that we can log how long the - // query took once we are done. - long startTimeInMs = System.currentTimeMillis(); - MdcOverride override = new MdcOverride(); - override.addAttribute(MdcContext.MDC_START_TIME, formatter.format(startTimeInMs)); - - logger.info(RestClientMsgs.HTTP_REQUEST, requestType, url); - - try { - - // Get a REST client instance for our request. - Client client = getClient(); - - // Debug log the request - debugRequest(url, payload, headers, responseType); - - // Get a client request builder, and submit our GET request. - Builder builder = getClientBuilder(client, url, payload, headers, contentType, responseType); - clientResponse = operation.processOperation(builder); - - populateOperationResult(clientResponse, operationResult); - - // Debug log the response - debugResponse(operationResult, clientResponse.getHeaders()); - - } catch (Exception ex) { - - logger.error(RestClientMsgs.HTTP_REQUEST_ERROR, requestType, url, ex.getLocalizedMessage()); - operationResult.setResultCode(500); - operationResult.setFailureCause( - "Error during GET operation to AAI with message = " + ex.getLocalizedMessage()); - - } finally { - - if (logger.isDebugEnabled()) { - logger.debug(baos.toString()); - } - - // Not every valid response code is actually represented by the Response.Status - // object, so we need to guard against missing codes, otherwise we throw null - // pointer exceptions when we try to generate our metrics logs... - Response.Status responseStatus = - Response.Status.fromStatusCode(operationResult.getResultCode()); - String responseStatusCodeString = ""; - if (responseStatus != null) { - responseStatusCodeString = responseStatus.toString(); - } - - metricsLogger.info(RestClientMsgs.HTTP_REQUEST_TIME, - new LogFields().setField(LogLine.DefinedFields.STATUS_CODE, responseStatusCodeString) - .setField(LogLine.DefinedFields.RESPONSE_CODE, operationResult.getResultCode()) - .setField(LogLine.DefinedFields.RESPONSE_DESCRIPTION, operationResult.getResult()), - override, requestType, Long.toString(System.currentTimeMillis() - startTimeInMs), url); - logger.info(RestClientMsgs.HTTP_REQUEST_TIME, requestType, - Long.toString(System.currentTimeMillis() - startTimeInMs), url); - logger.info(RestClientMsgs.HTTP_RESPONSE, url, - operationResult.getResultCode() + " " + responseStatusCodeString); - } - - return operationResult; - } - - /** - * This method submits an HTTP PUT request against the supplied URL. - * - * @param url - The REST endpoint to submit the PUT 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 PUT request. - */ - public OperationResult put(String url, String payload, Map> headers, - MediaType contentType, MediaType responseType) { - return processRequest(putOp, url, payload, headers, contentType, responseType); - } - - /** - * This method submits an HTTP POST request against the supplied URL. - * - * @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 post(String url, String payload, Map> headers, - 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. - * - * @param url - The REST endpoint to submit the GET 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 GET request. - */ - public OperationResult get(String url, Map> headers, - MediaType responseType) { - return processRequest(getOp, url, null, headers, null, responseType); - } - - /** - * This method submits an HTTP GET request against the supplied URL. - * This variant of the method will perform a requested number of retries in the event that the - * first request is unsuccessful. - * - * @param url - The REST endpoint to submit the GET request to. - * @param headers - The headers that should be passed in the request - * @param responseType - The expected format of the response. - * @param numRetries - The number of times to try resubmitting the request in the event of a - * failure. - * - * @return The result of the GET request. - */ - public OperationResult get(String url, Map> headers, MediaType responseType, - int numRetries) { - return processRequest(getOp, url, null, headers, null, responseType, numRetries); - } - - /** - * This method submits an HTTP DELETE request against the supplied URL. - * - * @param url - The REST endpoint to submit the DELETE 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 DELETE request. - */ - public OperationResult delete(String url, Map> headers, - MediaType responseType) { - return processRequest(deleteOp, url, null, headers, null, responseType); - } - - /** - * This method does a health check ("ping") against the supplied URL. - * - * @param url - The REST endpoint to attempt a health check. - * @param srcAppName - The name of the application using this client. - * @param destAppName - The name of the destination app. - * - * @return A boolean value. True if connection attempt was successful, false otherwise. - * - */ - public boolean healthCheck(String url, String srcAppName, String destAppName) { - return healthCheck(url, srcAppName, destAppName, MediaType.TEXT_PLAIN_TYPE); - - } - - /** - * This method does a health check ("ping") against the supplied URL. - * - * @param url - The REST endpoint to attempt a health check. - * @param srcAppName - The name of the application using this client. - * @param destAppName - The name of the destination app. - * @param responseType - The response type. - * - * @return A boolean value. True if connection attempt was successful, false otherwise. - * - */ - public boolean healthCheck(String url, String srcAppName, String destAppName, - MediaType responseType) { - MultivaluedMap headers = new MultivaluedMapImpl(); - headers.put(Headers.FROM_APP_ID, Arrays.asList(new String[] {srcAppName})); - headers.put(Headers.TRANSACTION_ID, Arrays.asList(new String[] {UUID.randomUUID().toString()})); - - try { - logger.info(RestClientMsgs.HEALTH_CHECK_ATTEMPT, destAppName, url); - OperationResult result = get(url, headers, responseType); - - if (result != null && result.getFailureCause() == null) { - logger.info(RestClientMsgs.HEALTH_CHECK_SUCCESS, destAppName, url); - return true; - } else { - logger.error(RestClientMsgs.HEALTH_CHECK_FAILURE, destAppName, url, - result.getFailureCause()); - return false; - } - } catch (Exception e) { - logger.error(RestClientMsgs.HEALTH_CHECK_FAILURE, destAppName, url, e.getMessage()); - return false; - } - } - - /** - * This method constructs a client request builder that can be used for submitting REST requests - * to the supplied URL endpoint. - * - * @param client - The REST client we will be using to talk to the server. - * @param url - The URL endpoint that our request will be submitted to. - * @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 A client request builder. - */ - private Builder getClientBuilder(Client client, String url, String payload, - Map> headers, MediaType contentType, MediaType responseType) { - - WebResource resource = client.resource(url); - Builder builder = null; - - builder = resource.accept(responseType); - - if (contentType != null) { - builder.type(contentType); - } - - if (payload != null) { - builder.entity(payload); - } - - if (headers != null) { - 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; - } - - private void debugRequest(String url, String payload, Map> headers, - MediaType responseType) { - if (logger.isDebugEnabled()) { - StringBuilder debugRequest = new StringBuilder("REQUEST:\n"); - debugRequest.append("URL: ").append(url).append("\n"); - debugRequest.append("Payload: ").append(payload).append("\n"); - debugRequest.append("Response Type: ").append(responseType).append("\n"); - if (headers != null) { - debugRequest.append("Headers: "); - for (Entry> header : headers.entrySet()) { - debugRequest.append("\n\t").append(header.getKey()).append(":"); - for (String headerEntry : header.getValue()) { - debugRequest.append("\"").append(headerEntry).append("\" "); - } - } - } - logger.debug(debugRequest.toString()); - } - } - - private void debugResponse(OperationResult operationResult, - MultivaluedMap headers) { - if (logger.isDebugEnabled()) { - StringBuilder debugResponse = new StringBuilder("RESPONSE:\n"); - debugResponse.append("Result: ").append(operationResult.getResultCode()).append("\n"); - debugResponse.append("Failure Cause: ").append(operationResult.getFailureCause()) - .append("\n"); - debugResponse.append("Payload: ").append(operationResult.getResult()).append("\n"); - if (headers != null) { - debugResponse.append("Headers: "); - for (Entry> header : headers.entrySet()) { - debugResponse.append("\n\t").append(header.getKey()).append(":"); - for (String headerEntry : header.getValue()) { - debugResponse.append("\"").append(headerEntry).append("\" "); - } - } - } - logger.debug(debugResponse.toString()); - } - } - - /** - * 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. - */ - 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()); - } - - return clientInstance.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; - - } - - - /** - * This method populates the fields of an {@link OperationResult} instance based on the contents - * of a {@link ClientResponse} received in response to a REST request. - */ - private void populateOperationResult(ClientResponse response, OperationResult opResult) { - - // If we got back a NULL response, then just produce a generic - // error code and result indicating this. - if (response == null) { - opResult.setResultCode(500); - opResult.setFailureCause("Client response was null"); - return; - } - - int statusCode = response.getStatus(); - opResult.setResultCode(statusCode); - - if (opResult.wasSuccessful()) { - if (statusCode != Response.Status.NO_CONTENT.getStatusCode()) { - opResult.setResult(response.getEntity(String.class)); - } - } else { - opResult.setFailureCause(response.getEntity(String.class)); - } - - opResult.setHeaders(response.getHeaders()); - } - - private class GetRestOperation implements RestOperation { - public ClientResponse processOperation(Builder builder) { - return builder.get(ClientResponse.class); - } - - public RequestType getRequestType() { - return RequestType.GET; - } - } - - private class PutRestOperation implements RestOperation { - public ClientResponse processOperation(Builder builder) { - return builder.put(ClientResponse.class); - } - - public RequestType getRequestType() { - return RequestType.PUT; - } - } - - private class PostRestOperation implements RestOperation { - public ClientResponse processOperation(Builder builder) { - return builder.post(ClientResponse.class); - } - - public RequestType getRequestType() { - return RequestType.POST; - } - } - - private class DeleteRestOperation implements RestOperation { - public ClientResponse processOperation(Builder builder) { - return builder.delete(ClientResponse.class); - } - - public RequestType getRequestType() { - 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. - */ - private interface RestOperation { - - /** - * Method used to wrap the functionality of making a REST call out to the endpoint. - * - * @param builder the Jersey builder used to make the request - * @return the response from the REST endpoint - */ - public ClientResponse processOperation(Builder builder); - - /** - * Returns the REST request type. - */ - public RequestType getRequestType(); - - /** - * The supported REST request types. - */ - public enum RequestType { - 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 deleted file mode 100644 index 3f86043..0000000 --- a/src/main/java/org/openecomp/restclient/enums/RestAuthenticationMode.java +++ /dev/null @@ -1,64 +0,0 @@ -/** - * ============LICENSE_START======================================================= - * org.onap.aai - * ================================================================================ - * Copyright © 2017 AT&T Intellectual Property. All rights reserved. - * Copyright © 2017 Amdocs - * ================================================================================ - * 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 is a trademark and service mark 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 deleted file mode 100644 index dabdc96..0000000 --- a/src/main/java/org/openecomp/restclient/logging/RestClientMsgs.java +++ /dev/null @@ -1,117 +0,0 @@ -/** - * ============LICENSE_START======================================================= - * org.onap.aai - * ================================================================================ - * Copyright © 2017 AT&T Intellectual Property. All rights reserved. - * Copyright © 2017 Amdocs - * ================================================================================ - * 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 is a trademark and service mark of AT&T Intellectual Property. - */ -package org.openecomp.restclient.logging; - -import com.att.eelf.i18n.EELFResourceManager; -import org.openecomp.cl.eelf.LogMessageEnum; - -public enum RestClientMsgs implements LogMessageEnum { - - /** - * Arguments: - * {0} = HTTP operation - * {1} = URL - */ - HTTP_REQUEST, - - /** - * Arguments: - * {0} = HTTP operation - * {1} = URL - * {2} = Attempt count. - */ - HTTP_REQUEST_WITH_RETRIES, - - /** - * Arguments: - * {0} = HTTP operation - * {1} - URL - * {2} - Operation time in ms. - */ - HTTP_REQUEST_TIME, - - /** - * Arguments: - * {0} = HTTP operation - * {1} - URL - * {2} - Operation time in ms. - * {3} - Retry count. - */ - HTTP_REQUEST_TIME_WITH_RETRIES, - - /** - * Arguments: - * {0} = HTTP operation - * {1} - URL - * {2} - Error message. - */ - HTTP_REQUEST_INTERRUPTED, - - /** - * Arguments: - * {0} = HTTP operation - * {1} - URL - * {2} - Error message. - */ - HTTP_REQUEST_ERROR, - - /** - * . Arguments: - * {0} = Target URL - */ - HEALTH_CHECK_ATTEMPT, - - /** - * . Arguments: - * {0} = Target URL - */ - HEALTH_CHECK_SUCCESS, - - /** - * . Arguments: - * {0} = Target URL - * {1} = failure cause - */ - HEALTH_CHECK_FAILURE, - - - /** - * . Arguments: - * {0} = URL - * {1} - Response code - */ - HTTP_RESPONSE, - - /** - * . Arguments: - * {0} = failure cause - */ - CLIENT_INITIALIZATION_FAILURE; - - /** - * Static initializer to ensure the resource bundles for this class are loaded... - */ - static { - EELFResourceManager.loadMessageBundle("logging/RESTClientMsgs"); - } -} diff --git a/src/main/java/org/openecomp/restclient/rest/HttpUtil.java b/src/main/java/org/openecomp/restclient/rest/HttpUtil.java deleted file mode 100644 index f0c2854..0000000 --- a/src/main/java/org/openecomp/restclient/rest/HttpUtil.java +++ /dev/null @@ -1,95 +0,0 @@ -/** - * ============LICENSE_START======================================================= - * org.onap.aai - * ================================================================================ - * Copyright © 2017 AT&T Intellectual Property. All rights reserved. - * Copyright © 2017 Amdocs - * ================================================================================ - * 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 is a trademark and service mark of AT&T Intellectual Property. - */ -package org.openecomp.restclient.rest; - -public class HttpUtil { - - /** - * Determines if the provided HTTP response is present in the provided list of acceptable response - * codes. - * - * @param response the http response we got from our request - * @param list the list of acceptable response codes - * @return true if the http response is in the provided list - */ - public static boolean isHttpResponseInList(int response, int... list) { - for (int checkCode : list) { - if (checkCode == response) { - return true; - } - } - return false; - } - - /** - * Determines if the provided http response is of the information class. - * - * @param response the http response we got from our request - * @return true if the response is of the informational class and false otherwise - */ - public static boolean isHttpResponseClassInformational(int response) { - return ( response >= 100 && response <= 199); - } - - /** - * Determines if the provided http response is of the success class. - * - * @param response the http response we got from our request - * @return true if the response is of the success class and false otherwise - */ - public static boolean isHttpResponseClassSuccess(int response) { - return ( response >= 200 && response <= 299); - - } - - /** - * Determines if the provided http response is of the redirection class. - * - * @param response the http response we got from our request - * @return true if the response is of the redirection class and false otherwise - */ - public static boolean isHttpResponseClassRedirection(int response) { - return ( response >= 300 && response <= 399); - } - - /** - * Determines if the provided http response is of the client error class. - * - * @param response the http response we got from our request - * @return true if the response is of the client error class and false otherwise - */ - public static boolean isHttpResponseClassClientError(int response) { - return ( response >= 400 && response <= 499); - } - - /** - * Determines if the provided http response is of the server error class. - * - * @param response the http response we got from our request - * @return true if the response is of the server error class and false otherwise - */ - public static boolean isHttpResponseClassServerError(int response) { - return ( response >= 500 && response <= 599); - } - -} diff --git a/src/main/java/org/openecomp/restclient/rest/RestClientBuilder.java b/src/main/java/org/openecomp/restclient/rest/RestClientBuilder.java deleted file mode 100644 index c7b1fd8..0000000 --- a/src/main/java/org/openecomp/restclient/rest/RestClientBuilder.java +++ /dev/null @@ -1,312 +0,0 @@ -/** - * ============LICENSE_START======================================================= - * org.onap.aai - * ================================================================================ - * Copyright © 2017 AT&T Intellectual Property. All rights reserved. - * Copyright © 2017 Amdocs - * ================================================================================ - * 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 is a trademark and service mark of AT&T Intellectual Property. - */ -package org.openecomp.restclient.rest; - -import java.io.FileInputStream; -import java.security.KeyStore; -import java.security.cert.X509Certificate; - -import javax.net.ssl.HostnameVerifier; -import javax.net.ssl.KeyManagerFactory; -import javax.net.ssl.SSLContext; -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. - */ -public class RestClientBuilder { - - public static final boolean DEFAULT_VALIDATE_SERVER_HOST = false; - public static final boolean DEFAULT_VALIDATE_CERT_CHAIN = false; - public static final String DEFAULT_CLIENT_CERT_FILENAME = null; - public static final String DEFAULT_CERT_PASSWORD = null; - 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.SSL_CERT; - 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"; - private static final String TRUST_STORE_PROPERTY = "javax.net.ssl.trustStore"; - - private boolean validateServerHostname; - private boolean validateServerCertChain; - private String clientCertFileName; - private String clientCertPassword; - private String truststoreFilename; - private int connectTimeoutInMs; - private int readTimeoutInMs; - private RestAuthenticationMode authenticationMode; - private String basicAuthUsername; - private String basicAuthPassword; - - /** - * Rest Client Builder. - */ - public RestClientBuilder() { - validateServerHostname = DEFAULT_VALIDATE_SERVER_HOST; - validateServerCertChain = DEFAULT_VALIDATE_CERT_CHAIN; - clientCertFileName = DEFAULT_CLIENT_CERT_FILENAME; - clientCertPassword = DEFAULT_CERT_PASSWORD; - truststoreFilename = DEFAULT_TRUST_STORE_FILENAME; - connectTimeoutInMs = DEFAULT_CONNECT_TIMEOUT_MS; - readTimeoutInMs = DEFAULT_READ_TIMEOUT_MS; - authenticationMode = DEFAULT_AUTH_MODE; - basicAuthUsername = DEFAULT_BASIC_AUTH_USERNAME; - basicAuthPassword = DEFAULT_BASIC_AUTH_PASSWORD; - } - - public boolean isValidateServerHostname() { - return validateServerHostname; - } - - public void setValidateServerHostname(boolean validateServerHostname) { - this.validateServerHostname = validateServerHostname; - } - - public boolean isValidateServerCertChain() { - return validateServerCertChain; - } - - public void setValidateServerCertChain(boolean validateServerCertChain) { - this.validateServerCertChain = validateServerCertChain; - } - - public String getClientCertFileName() { - return clientCertFileName; - } - - public void setClientCertFileName(String clientCertFileName) { - this.clientCertFileName = clientCertFileName; - } - - public String getClientCertPassword() { - return clientCertPassword; - } - - public void setClientCertPassword(String clientCertPassword) { - this.clientCertPassword = clientCertPassword; - } - - public String getTruststoreFilename() { - return truststoreFilename; - } - - public void setTruststoreFilename(String truststoreFilename) { - this.truststoreFilename = truststoreFilename; - } - - public int getConnectTimeoutInMs() { - return connectTimeoutInMs; - } - - public void setConnectTimeoutInMs(int connectTimeoutInMs) { - this.connectTimeoutInMs = connectTimeoutInMs; - } - - public int getReadTimeoutInMs() { - return readTimeoutInMs; - } - - public void setReadTimeoutInMs(int readTimeoutInMs) { - 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 configured for SSL - */ - public Client getClient() throws Exception { - - 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; - if (validateServerCertChain) { - if (truststoreFilename != null) { - System.setProperty(TRUST_STORE_PROPERTY, truststoreFilename); - } else { - throw new IllegalArgumentException("Trust store filename must be set!"); - } - - } else { - - // We aren't validating certificates, so create a trust manager that does - // not validate certificate chains. - trustAllCerts = new TrustManager[] {new X509TrustManager() { - public X509Certificate[] getAcceptedIssuers() { - return null; - } - - public void checkClientTrusted(X509Certificate[] certs, String authType) {} - - public void checkServerTrusted(X509Certificate[] certs, String authType) {} - }}; - } - - // Set up the SSL context, keystore, etc. to use for our connection - // to the AAI. - SSLContext ctx = SSLContext.getInstance(SSL_PROTOCOL); - KeyManagerFactory kmf = KeyManagerFactory.getInstance(KEYSTORE_ALGORITHM); - KeyStore ks = KeyStore.getInstance(KEYSTORE_TYPE); - - char[] pwd = null; - if (clientCertPassword != null) { - pwd = clientCertPassword.toCharArray(); - } - - if (clientCertFileName != null) { - FileInputStream fin = new FileInputStream(clientCertFileName); - - // Load the keystore and initialize the key manager factory. - ks.load(fin, pwd); - kmf.init(ks, pwd); - - ctx.init(kmf.getKeyManagers(), trustAllCerts, null); - } else { - ctx.init(null, trustAllCerts, null); - } - - // Are we performing validation of the server host name? - if (validateServerHostname) { - clientConfig.getProperties().put(HTTPSProperties.PROPERTY_HTTPS_PROPERTIES, - new HTTPSProperties(null, ctx)); - - } else { - clientConfig.getProperties().put(HTTPSProperties.PROPERTY_HTTPS_PROPERTIES, - new HTTPSProperties(new HostnameVerifier() { - @Override - public boolean verify(String str, SSLSession sslSession) { - return true; - } - }, 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; - client = Client.create(clientConfig); - client.setConnectTimeout(connectTimeoutInMs); - client.setReadTimeout(readTimeoutInMs); - - // ...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/test/java/org/onap/aai/restclient/client/OperationResultTest.java b/src/test/java/org/onap/aai/restclient/client/OperationResultTest.java new file mode 100644 index 0000000..6f18f9a --- /dev/null +++ b/src/test/java/org/onap/aai/restclient/client/OperationResultTest.java @@ -0,0 +1,106 @@ +/** + * ============LICENSE_START======================================================= + * org.onap.aai + * ================================================================================ + * Copyright © 2017 AT&T Intellectual Property. All rights reserved. + * Copyright © 2017 Amdocs + * ================================================================================ + * 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 is a trademark and service mark of AT&T Intellectual Property. + */ +package org.onap.aai.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 org.onap.aai.restclient.client.OperationResult; + +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/onap/aai/restclient/client/RestfulClientTest.java b/src/test/java/org/onap/aai/restclient/client/RestfulClientTest.java new file mode 100644 index 0000000..0e5c84e --- /dev/null +++ b/src/test/java/org/onap/aai/restclient/client/RestfulClientTest.java @@ -0,0 +1,340 @@ +/* + * ============LICENSE_START=========================================================================================== + * Copyright (c) 2017 AT&T Intellectual Property. + * Copyright (c) 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.onap.aai.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.Response; +import javax.ws.rs.core.Response.Status; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mockito; +import org.onap.aai.restclient.client.OperationResult; +import org.onap.aai.restclient.client.RestClient; +import org.onap.aai.restclient.enums.RestAuthenticationMode; +import org.onap.aai.restclient.rest.RestClientBuilder; + +import com.sun.jersey.api.client.Client; +import com.sun.jersey.api.client.ClientResponse; +import com.sun.jersey.api.client.UniformInterfaceException; +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 static final String TEST_URL = "http://localhost:9000/aai/v7"; + + private final MultivaluedMapImpl emptyMap = new MultivaluedMapImpl(); + + private RestClientBuilder mockClientBuilder; + private Client mockedClient; + private WebResource mockedWebResource; + private Builder mockedBuilder; + private ClientResponse mockedClientResponse; + + /** + * Test case initialization + * + * @throws Exception the exception + */ + @SuppressWarnings("unchecked") + @Before + public void init() throws Exception { + mockedClientResponse = Mockito.mock(ClientResponse.class); + setResponseStatus(Response.Status.OK); + Mockito.when(mockedClientResponse.getHeaders()).thenReturn(emptyMap); + Mockito.when(mockedClientResponse.getEntity(String.class)).thenReturn("hello"); + + mockedBuilder = Mockito.mock(Builder.class); + Mockito.when(mockedBuilder.get(Mockito.any(Class.class))).thenReturn(mockedClientResponse); + Mockito.when(mockedBuilder.post(Mockito.any(Class.class))).thenReturn(mockedClientResponse); + Mockito.when(mockedBuilder.put(Mockito.any(Class.class))).thenReturn(mockedClientResponse); + Mockito.when(mockedBuilder.delete(Mockito.any(Class.class))).thenReturn(mockedClientResponse); + Mockito.when(mockedBuilder.head()).thenReturn(mockedClientResponse); + + mockedWebResource = Mockito.mock(WebResource.class); + Mockito.when(mockedWebResource.accept(Mockito.anyVararg())).thenReturn(mockedBuilder); + + mockedClient = Mockito.mock(Client.class); + Mockito.when(mockedClient.resource(Mockito.anyString())).thenReturn(mockedWebResource); + + mockClientBuilder = Mockito.mock(RestClientBuilder.class); + Mockito.when(mockClientBuilder.getClient()).thenReturn(mockedClient); + } + + @Test + public void validateConstructors() { + RestClient restClient = new RestClient(); + assertNotNull(restClient); + restClient = new RestClient(mockClientBuilder); + assertNotNull(restClient); + } + + @Test + public void validateBasicClientConstruction() throws Exception { + Client client = new RestClient(mockClientBuilder).authenticationMode(RestAuthenticationMode.HTTP_NOAUTH) + .connectTimeoutMs(1000).readTimeoutMs(500).getClient(); + assertNotNull(client); + } + + @Test + public void validateClientWithSslBasicAuthConstruction() throws Exception { + Client client = new RestClient(mockClientBuilder).authenticationMode(RestAuthenticationMode.SSL_BASIC) + .connectTimeoutMs(1000).readTimeoutMs(500).basicAuthPassword("password").basicAuthUsername("username") + .getClient(); + assertNotNull(client); + } + + @Test + public void validateClientWithSslCertConstruction() throws Exception { + // This test covers the standard SSL settings, i.e. no validation + assertNotNull(buildClient()); + + RestClient restClient = new RestClient(mockClientBuilder); + + // Test with validation enabled + Client client = restClient.authenticationMode(RestAuthenticationMode.SSL_CERT).connectTimeoutMs(1000) + .readTimeoutMs(500).clientCertFile("cert").clientCertPassword("password").validateServerCertChain(true) + .validateServerHostname(true).getClient(); + assertNotNull(client); + + // Test with a trust store + 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 { + RestClient restClient = buildClient(); + + OperationResult result = restClient.put(TEST_URL, "", emptyMap, MediaType.APPLICATION_JSON_TYPE, + MediaType.APPLICATION_JSON_TYPE); + + assertEquals(Response.Status.OK.getStatusCode(), result.getResultCode()); + assertNotNull(result.getResult()); + assertNull(result.getFailureCause()); + + // Repeat the PUT operation, this time with a return code of 204 + setResponseToNoContent(); + result = restClient.put(TEST_URL, "", emptyMap, MediaType.APPLICATION_JSON_TYPE, + MediaType.APPLICATION_JSON_TYPE); + + assertEquals(Response.Status.NO_CONTENT.getStatusCode(), result.getResultCode()); + assertNull(result.getResult()); + assertNull(result.getFailureCause()); + } + + @Test + public void validateSuccessfulPost() throws Exception { + RestClient restClient = buildClient(); + + OperationResult result = restClient.post(TEST_URL, "", emptyMap, MediaType.APPLICATION_JSON_TYPE, + MediaType.APPLICATION_JSON_TYPE); + + assertEquals(Response.Status.OK.getStatusCode(), result.getResultCode()); + assertNotNull(result.getResult()); + assertNull(result.getFailureCause()); + + // Repeat the POST operation, this time with a return code of 204 + setResponseToNoContent(); + result = restClient.post(TEST_URL, "", emptyMap, MediaType.APPLICATION_JSON_TYPE, + MediaType.APPLICATION_JSON_TYPE); + + assertEquals(Response.Status.NO_CONTENT.getStatusCode(), result.getResultCode()); + assertNull(result.getResult()); + assertNull(result.getFailureCause()); + } + + @Test + public void validateSuccessfulGet() throws Exception { + OperationResult result = buildClient().get(TEST_URL, emptyMap, MediaType.APPLICATION_JSON_TYPE); + + assertEquals(Response.Status.OK.getStatusCode(), result.getResultCode()); + assertNotNull(result.getResult()); + assertNull(result.getFailureCause()); + } + + @Test + public void validateSuccessfulGetWithBasicAuth() throws Exception { + RestClient restClient = new RestClient(mockClientBuilder).authenticationMode(RestAuthenticationMode.SSL_BASIC) + .connectTimeoutMs(1000).readTimeoutMs(500).basicAuthUsername("username").basicAuthUsername("password"); + + OperationResult result = restClient.get(TEST_URL, emptyMap, MediaType.APPLICATION_JSON_TYPE); + + assertEquals(Response.Status.OK.getStatusCode(), result.getResultCode()); + assertNotNull(result.getResult()); + assertNull(result.getFailureCause()); + } + + @Test + public void validateResourceNotFoundGet() throws Exception { + setResponseStatus(Response.Status.NOT_FOUND); + Mockito.when(mockedClientResponse.getEntity(String.class)).thenReturn("RNF"); + + OperationResult result = buildClient().get(TEST_URL, emptyMap, MediaType.APPLICATION_JSON_TYPE); + + assertEquals(Response.Status.NOT_FOUND.getStatusCode(), result.getResultCode()); + assertNull(result.getResult()); + assertNotNull(result.getFailureCause()); + } + + @Test + public void validateHealthCheck() throws Exception { + boolean targetServiceHealthy = + buildClient().healthCheck("http://localhost:9000/aai/util/echo", "startSerice", "targetService"); + + assertEquals(true, targetServiceHealthy); + } + + @Test + public void validateHealthCheckFailureWith403() throws Exception { + Mockito.when(mockedClientResponse.getStatus()).thenReturn(Response.Status.FORBIDDEN.getStatusCode()); + + boolean targetServiceHealthy = + buildClient().healthCheck("http://localhost:9000/aai/util/echo", "startSerice", "targetService"); + + assertEquals(false, targetServiceHealthy); + } + + @Test + public void validateHealthCheckFailureWithThrownException() throws Exception { + Mockito.when(mockedBuilder.get(Mockito.any(Class.class))).thenThrow(new IllegalArgumentException("error")); + + boolean targetServiceHealthy = + buildClient().healthCheck("http://localhost:9000/aai/util/echo", "startSerice", "targetService"); + + assertEquals(false, targetServiceHealthy); + } + + @Test + public void validateSuccessfulGetWithRetries() throws Exception { + Mockito.when(mockedClientResponse.getStatus()).thenReturn(408).thenReturn(Response.Status.OK.getStatusCode()); + Mockito.when(mockedClientResponse.getEntity(String.class)).thenReturn("error").thenReturn("ok"); + + OperationResult result = buildClient().get(TEST_URL, emptyMap, MediaType.APPLICATION_JSON_TYPE, 3); + + assertEquals(Response.Status.OK.getStatusCode(), result.getResultCode()); + assertNotNull(result.getResult()); + assertNull(result.getFailureCause()); + + } + + @Test + public void validateFailedGetWithRetriesCausedByResourceNotFound() throws Exception { + setResponseStatus(Response.Status.NOT_FOUND); + Mockito.when(mockedClientResponse.getEntity(String.class)).thenReturn("error").thenReturn("ok"); + + OperationResult result = buildClient().get(TEST_URL, emptyMap, MediaType.APPLICATION_JSON_TYPE, 3); + + assertEquals(Response.Status.NOT_FOUND.getStatusCode(), result.getResultCode()); + assertNull(result.getResult()); + assertNotNull(result.getFailureCause()); + + } + + @Test + public void validateFailedGetAfterMaxRetries() throws Exception { + setResponseStatus(Response.Status.INTERNAL_SERVER_ERROR); + Mockito.when(mockedClientResponse.getEntity(String.class)).thenReturn("error"); + + OperationResult result = buildClient().get(TEST_URL, emptyMap, MediaType.APPLICATION_JSON_TYPE, 3); + + assertEquals(504, result.getResultCode()); + assertNull(result.getResult()); + assertNotNull(result.getFailureCause()); + + } + + @Test + public void validateSuccessfulDelete() throws Exception { + RestClient restClient = buildClient(); + + OperationResult result = restClient.delete(TEST_URL, emptyMap, MediaType.APPLICATION_JSON_TYPE); + + assertEquals(Response.Status.OK.getStatusCode(), result.getResultCode()); + assertNotNull(result.getResult()); + assertNull(result.getFailureCause()); + + // Repeat the DELETE operation, this time with a return code of 204 + setResponseToNoContent(); + result = restClient.delete(TEST_URL, emptyMap, MediaType.APPLICATION_JSON_TYPE); + + assertEquals(Response.Status.NO_CONTENT.getStatusCode(), result.getResultCode()); + assertNull(result.getResult()); + assertNull(result.getFailureCause()); + } + + + @Test + public void validateSuccessfulHead() throws Exception { + OperationResult result = buildClient().head(TEST_URL, emptyMap, MediaType.APPLICATION_JSON_TYPE); + + assertEquals(Response.Status.OK.getStatusCode(), result.getResultCode()); + assertNotNull(result.getResult()); + assertNull(result.getFailureCause()); + + } + + @Test + public void validateSuccessfulPatch() throws Exception { + Mockito.when(mockedBuilder.header("X-HTTP-Method-Override", "PATCH")).thenReturn(mockedBuilder); + OperationResult result = buildClient().patch(TEST_URL, "", emptyMap, MediaType.APPLICATION_JSON_TYPE, + MediaType.APPLICATION_JSON_TYPE); + + assertEquals(Response.Status.OK.getStatusCode(), result.getResultCode()); + assertNotNull(result.getResult()); + assertNull(result.getFailureCause()); + + } + + /** + * Specify the status code of the response object returned by the mocked client + * + * @param status object storing the status code to mock in the ClientResponse + */ + private void setResponseStatus(Status status) { + Mockito.when(mockedClientResponse.getStatus()).thenReturn(status.getStatusCode()); + } + + /** + * Set the mocked client to return a response of "204 No Content" + */ + private void setResponseToNoContent() { + setResponseStatus(Response.Status.NO_CONTENT); + // The Jersey client throws an exception when getEntity() is called following a 204 response + UniformInterfaceException uniformInterfaceException = new UniformInterfaceException(mockedClientResponse); + Mockito.when(mockedClientResponse.getEntity(String.class)).thenThrow(uniformInterfaceException); + } + + /** + * @return a mocked Rest Client object using standard SSL settings + */ + private RestClient buildClient() { + return new RestClient(mockClientBuilder).authenticationMode(RestAuthenticationMode.SSL_CERT) + .connectTimeoutMs(1000).readTimeoutMs(500).clientCertFile("cert").clientCertPassword("password"); + } + +} diff --git a/src/test/java/org/onap/aai/restclient/enums/RestAuthenticationModeTest.java b/src/test/java/org/onap/aai/restclient/enums/RestAuthenticationModeTest.java new file mode 100644 index 0000000..c95431c --- /dev/null +++ b/src/test/java/org/onap/aai/restclient/enums/RestAuthenticationModeTest.java @@ -0,0 +1,56 @@ +/** + * ============LICENSE_START======================================================= + * org.onap.aai + * ================================================================================ + * Copyright © 2017 AT&T Intellectual Property. All rights reserved. + * Copyright © 2017 Amdocs + * ================================================================================ + * 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 is a trademark and service mark of AT&T Intellectual Property. + */ +package org.onap.aai.restclient.enums; + +import static org.junit.Assert.assertEquals; + +import org.junit.Before; +import org.junit.Test; +import org.onap.aai.restclient.enums.RestAuthenticationMode; + +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/onap/aai/restclient/rest/HttpUtilTest.java b/src/test/java/org/onap/aai/restclient/rest/HttpUtilTest.java new file mode 100644 index 0000000..7e9291c --- /dev/null +++ b/src/test/java/org/onap/aai/restclient/rest/HttpUtilTest.java @@ -0,0 +1,74 @@ +/** + * ============LICENSE_START======================================================= + * org.onap.aai + * ================================================================================ + * Copyright © 2017 AT&T Intellectual Property. All rights reserved. + * Copyright © 2017 Amdocs + * ================================================================================ + * 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 is a trademark and service mark of AT&T Intellectual Property. + */ +package org.onap.aai.restclient.rest; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.junit.Before; +import org.junit.Test; +import org.onap.aai.restclient.rest.HttpUtil; + +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/onap/aai/restclient/rest/RestClientBuilderTest.java b/src/test/java/org/onap/aai/restclient/rest/RestClientBuilderTest.java new file mode 100644 index 0000000..013f817 --- /dev/null +++ b/src/test/java/org/onap/aai/restclient/rest/RestClientBuilderTest.java @@ -0,0 +1,273 @@ +/** + * ============LICENSE_START======================================================= + * org.onap.aai + * ================================================================================ + * Copyright © 2017 AT&T Intellectual Property. All rights reserved. + * Copyright © 2017 Amdocs + * ================================================================================ + * 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 is a trademark and service mark of AT&T Intellectual Property. + */ +package org.onap.aai.restclient.rest; + +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.onap.aai.restclient.enums.RestAuthenticationMode; +import org.onap.aai.restclient.rest.RestClientBuilder; + +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. + */ +public class RestClientBuilderTest { + + /** + * 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 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")); + + } + + @Test + 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)); + } + + + @Test + 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"); + } + + } + + @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(); + + } + + +} diff --git a/src/test/java/org/openecomp/restclient/client/OperationResultTest.java b/src/test/java/org/openecomp/restclient/client/OperationResultTest.java deleted file mode 100644 index 77e6e29..0000000 --- a/src/test/java/org/openecomp/restclient/client/OperationResultTest.java +++ /dev/null @@ -1,105 +0,0 @@ -/** - * ============LICENSE_START======================================================= - * org.onap.aai - * ================================================================================ - * Copyright © 2017 AT&T Intellectual Property. All rights reserved. - * Copyright © 2017 Amdocs - * ================================================================================ - * 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 is a trademark and service mark of AT&T Intellectual Property. - */ -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/RestfulClientTest.java b/src/test/java/org/openecomp/restclient/client/RestfulClientTest.java deleted file mode 100644 index 784aa91..0000000 --- a/src/test/java/org/openecomp/restclient/client/RestfulClientTest.java +++ /dev/null @@ -1,338 +0,0 @@ -/* - * ============LICENSE_START=========================================================================================== - * Copyright (c) 2017 AT&T Intellectual Property. - * Copyright (c) 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.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.Response; -import javax.ws.rs.core.Response.Status; - -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.UniformInterfaceException; -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 static final String TEST_URL = "http://localhost:9000/aai/v7"; - - private final MultivaluedMapImpl emptyMap = new MultivaluedMapImpl(); - - private RestClientBuilder mockClientBuilder; - private Client mockedClient; - private WebResource mockedWebResource; - private Builder mockedBuilder; - private ClientResponse mockedClientResponse; - - /** - * Test case initialization - * - * @throws Exception the exception - */ - @SuppressWarnings("unchecked") - @Before - public void init() throws Exception { - mockedClientResponse = Mockito.mock(ClientResponse.class); - setResponseStatus(Response.Status.OK); - Mockito.when(mockedClientResponse.getHeaders()).thenReturn(emptyMap); - Mockito.when(mockedClientResponse.getEntity(String.class)).thenReturn("hello"); - - mockedBuilder = Mockito.mock(Builder.class); - Mockito.when(mockedBuilder.get(Mockito.any(Class.class))).thenReturn(mockedClientResponse); - Mockito.when(mockedBuilder.post(Mockito.any(Class.class))).thenReturn(mockedClientResponse); - Mockito.when(mockedBuilder.put(Mockito.any(Class.class))).thenReturn(mockedClientResponse); - Mockito.when(mockedBuilder.delete(Mockito.any(Class.class))).thenReturn(mockedClientResponse); - Mockito.when(mockedBuilder.head()).thenReturn(mockedClientResponse); - - mockedWebResource = Mockito.mock(WebResource.class); - Mockito.when(mockedWebResource.accept(Mockito.anyVararg())).thenReturn(mockedBuilder); - - mockedClient = Mockito.mock(Client.class); - Mockito.when(mockedClient.resource(Mockito.anyString())).thenReturn(mockedWebResource); - - mockClientBuilder = Mockito.mock(RestClientBuilder.class); - Mockito.when(mockClientBuilder.getClient()).thenReturn(mockedClient); - } - - @Test - public void validateConstructors() { - RestClient restClient = new RestClient(); - assertNotNull(restClient); - restClient = new RestClient(mockClientBuilder); - assertNotNull(restClient); - } - - @Test - public void validateBasicClientConstruction() throws Exception { - Client client = new RestClient(mockClientBuilder).authenticationMode(RestAuthenticationMode.HTTP_NOAUTH) - .connectTimeoutMs(1000).readTimeoutMs(500).getClient(); - assertNotNull(client); - } - - @Test - public void validateClientWithSslBasicAuthConstruction() throws Exception { - Client client = new RestClient(mockClientBuilder).authenticationMode(RestAuthenticationMode.SSL_BASIC) - .connectTimeoutMs(1000).readTimeoutMs(500).basicAuthPassword("password").basicAuthUsername("username") - .getClient(); - assertNotNull(client); - } - - @Test - public void validateClientWithSslCertConstruction() throws Exception { - // This test covers the standard SSL settings, i.e. no validation - assertNotNull(buildClient()); - - RestClient restClient = new RestClient(mockClientBuilder); - - // Test with validation enabled - Client client = restClient.authenticationMode(RestAuthenticationMode.SSL_CERT).connectTimeoutMs(1000) - .readTimeoutMs(500).clientCertFile("cert").clientCertPassword("password").validateServerCertChain(true) - .validateServerHostname(true).getClient(); - assertNotNull(client); - - // Test with a trust store - 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 { - RestClient restClient = buildClient(); - - OperationResult result = restClient.put(TEST_URL, "", emptyMap, MediaType.APPLICATION_JSON_TYPE, - MediaType.APPLICATION_JSON_TYPE); - - assertEquals(Response.Status.OK.getStatusCode(), result.getResultCode()); - assertNotNull(result.getResult()); - assertNull(result.getFailureCause()); - - // Repeat the PUT operation, this time with a return code of 204 - setResponseToNoContent(); - result = restClient.put(TEST_URL, "", emptyMap, MediaType.APPLICATION_JSON_TYPE, - MediaType.APPLICATION_JSON_TYPE); - - assertEquals(Response.Status.NO_CONTENT.getStatusCode(), result.getResultCode()); - assertNull(result.getResult()); - assertNull(result.getFailureCause()); - } - - @Test - public void validateSuccessfulPost() throws Exception { - RestClient restClient = buildClient(); - - OperationResult result = restClient.post(TEST_URL, "", emptyMap, MediaType.APPLICATION_JSON_TYPE, - MediaType.APPLICATION_JSON_TYPE); - - assertEquals(Response.Status.OK.getStatusCode(), result.getResultCode()); - assertNotNull(result.getResult()); - assertNull(result.getFailureCause()); - - // Repeat the POST operation, this time with a return code of 204 - setResponseToNoContent(); - result = restClient.post(TEST_URL, "", emptyMap, MediaType.APPLICATION_JSON_TYPE, - MediaType.APPLICATION_JSON_TYPE); - - assertEquals(Response.Status.NO_CONTENT.getStatusCode(), result.getResultCode()); - assertNull(result.getResult()); - assertNull(result.getFailureCause()); - } - - @Test - public void validateSuccessfulGet() throws Exception { - OperationResult result = buildClient().get(TEST_URL, emptyMap, MediaType.APPLICATION_JSON_TYPE); - - assertEquals(Response.Status.OK.getStatusCode(), result.getResultCode()); - assertNotNull(result.getResult()); - assertNull(result.getFailureCause()); - } - - @Test - public void validateSuccessfulGetWithBasicAuth() throws Exception { - RestClient restClient = new RestClient(mockClientBuilder).authenticationMode(RestAuthenticationMode.SSL_BASIC) - .connectTimeoutMs(1000).readTimeoutMs(500).basicAuthUsername("username").basicAuthUsername("password"); - - OperationResult result = restClient.get(TEST_URL, emptyMap, MediaType.APPLICATION_JSON_TYPE); - - assertEquals(Response.Status.OK.getStatusCode(), result.getResultCode()); - assertNotNull(result.getResult()); - assertNull(result.getFailureCause()); - } - - @Test - public void validateResourceNotFoundGet() throws Exception { - setResponseStatus(Response.Status.NOT_FOUND); - Mockito.when(mockedClientResponse.getEntity(String.class)).thenReturn("RNF"); - - OperationResult result = buildClient().get(TEST_URL, emptyMap, MediaType.APPLICATION_JSON_TYPE); - - assertEquals(Response.Status.NOT_FOUND.getStatusCode(), result.getResultCode()); - assertNull(result.getResult()); - assertNotNull(result.getFailureCause()); - } - - @Test - public void validateHealthCheck() throws Exception { - boolean targetServiceHealthy = - buildClient().healthCheck("http://localhost:9000/aai/util/echo", "startSerice", "targetService"); - - assertEquals(true, targetServiceHealthy); - } - - @Test - public void validateHealthCheckFailureWith403() throws Exception { - Mockito.when(mockedClientResponse.getStatus()).thenReturn(Response.Status.FORBIDDEN.getStatusCode()); - - boolean targetServiceHealthy = - buildClient().healthCheck("http://localhost:9000/aai/util/echo", "startSerice", "targetService"); - - assertEquals(false, targetServiceHealthy); - } - - @Test - public void validateHealthCheckFailureWithThrownException() throws Exception { - Mockito.when(mockedBuilder.get(Mockito.any(Class.class))).thenThrow(new IllegalArgumentException("error")); - - boolean targetServiceHealthy = - buildClient().healthCheck("http://localhost:9000/aai/util/echo", "startSerice", "targetService"); - - assertEquals(false, targetServiceHealthy); - } - - @Test - public void validateSuccessfulGetWithRetries() throws Exception { - Mockito.when(mockedClientResponse.getStatus()).thenReturn(408).thenReturn(Response.Status.OK.getStatusCode()); - Mockito.when(mockedClientResponse.getEntity(String.class)).thenReturn("error").thenReturn("ok"); - - OperationResult result = buildClient().get(TEST_URL, emptyMap, MediaType.APPLICATION_JSON_TYPE, 3); - - assertEquals(Response.Status.OK.getStatusCode(), result.getResultCode()); - assertNotNull(result.getResult()); - assertNull(result.getFailureCause()); - - } - - @Test - public void validateFailedGetWithRetriesCausedByResourceNotFound() throws Exception { - setResponseStatus(Response.Status.NOT_FOUND); - Mockito.when(mockedClientResponse.getEntity(String.class)).thenReturn("error").thenReturn("ok"); - - OperationResult result = buildClient().get(TEST_URL, emptyMap, MediaType.APPLICATION_JSON_TYPE, 3); - - assertEquals(Response.Status.NOT_FOUND.getStatusCode(), result.getResultCode()); - assertNull(result.getResult()); - assertNotNull(result.getFailureCause()); - - } - - @Test - public void validateFailedGetAfterMaxRetries() throws Exception { - setResponseStatus(Response.Status.INTERNAL_SERVER_ERROR); - Mockito.when(mockedClientResponse.getEntity(String.class)).thenReturn("error"); - - OperationResult result = buildClient().get(TEST_URL, emptyMap, MediaType.APPLICATION_JSON_TYPE, 3); - - assertEquals(504, result.getResultCode()); - assertNull(result.getResult()); - assertNotNull(result.getFailureCause()); - - } - - @Test - public void validateSuccessfulDelete() throws Exception { - RestClient restClient = buildClient(); - - OperationResult result = restClient.delete(TEST_URL, emptyMap, MediaType.APPLICATION_JSON_TYPE); - - assertEquals(Response.Status.OK.getStatusCode(), result.getResultCode()); - assertNotNull(result.getResult()); - assertNull(result.getFailureCause()); - - // Repeat the DELETE operation, this time with a return code of 204 - setResponseToNoContent(); - result = restClient.delete(TEST_URL, emptyMap, MediaType.APPLICATION_JSON_TYPE); - - assertEquals(Response.Status.NO_CONTENT.getStatusCode(), result.getResultCode()); - assertNull(result.getResult()); - assertNull(result.getFailureCause()); - } - - - @Test - public void validateSuccessfulHead() throws Exception { - OperationResult result = buildClient().head(TEST_URL, emptyMap, MediaType.APPLICATION_JSON_TYPE); - - assertEquals(Response.Status.OK.getStatusCode(), result.getResultCode()); - assertNotNull(result.getResult()); - assertNull(result.getFailureCause()); - - } - - @Test - public void validateSuccessfulPatch() throws Exception { - Mockito.when(mockedBuilder.header("X-HTTP-Method-Override", "PATCH")).thenReturn(mockedBuilder); - OperationResult result = buildClient().patch(TEST_URL, "", emptyMap, MediaType.APPLICATION_JSON_TYPE, - MediaType.APPLICATION_JSON_TYPE); - - assertEquals(Response.Status.OK.getStatusCode(), result.getResultCode()); - assertNotNull(result.getResult()); - assertNull(result.getFailureCause()); - - } - - /** - * Specify the status code of the response object returned by the mocked client - * - * @param status object storing the status code to mock in the ClientResponse - */ - private void setResponseStatus(Status status) { - Mockito.when(mockedClientResponse.getStatus()).thenReturn(status.getStatusCode()); - } - - /** - * Set the mocked client to return a response of "204 No Content" - */ - private void setResponseToNoContent() { - setResponseStatus(Response.Status.NO_CONTENT); - // The Jersey client throws an exception when getEntity() is called following a 204 response - UniformInterfaceException uniformInterfaceException = new UniformInterfaceException(mockedClientResponse); - Mockito.when(mockedClientResponse.getEntity(String.class)).thenThrow(uniformInterfaceException); - } - - /** - * @return a mocked Rest Client object using standard SSL settings - */ - private RestClient buildClient() { - return new RestClient(mockClientBuilder).authenticationMode(RestAuthenticationMode.SSL_CERT) - .connectTimeoutMs(1000).readTimeoutMs(500).clientCertFile("cert").clientCertPassword("password"); - } - -} diff --git a/src/test/java/org/openecomp/restclient/enums/RestAuthenticationModeTest.java b/src/test/java/org/openecomp/restclient/enums/RestAuthenticationModeTest.java deleted file mode 100644 index ebdddbf..0000000 --- a/src/test/java/org/openecomp/restclient/enums/RestAuthenticationModeTest.java +++ /dev/null @@ -1,55 +0,0 @@ -/** - * ============LICENSE_START======================================================= - * org.onap.aai - * ================================================================================ - * Copyright © 2017 AT&T Intellectual Property. All rights reserved. - * Copyright © 2017 Amdocs - * ================================================================================ - * 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 is a trademark and service mark of AT&T Intellectual Property. - */ -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 deleted file mode 100644 index 9eb0362..0000000 --- a/src/test/java/org/openecomp/restclient/rest/HttpUtilTest.java +++ /dev/null @@ -1,73 +0,0 @@ -/** - * ============LICENSE_START======================================================= - * org.onap.aai - * ================================================================================ - * Copyright © 2017 AT&T Intellectual Property. All rights reserved. - * Copyright © 2017 Amdocs - * ================================================================================ - * 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 is a trademark and service mark of AT&T Intellectual Property. - */ -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 deleted file mode 100644 index 1481c06..0000000 --- a/src/test/java/org/openecomp/restclient/rest/RestClientBuilderTest.java +++ /dev/null @@ -1,272 +0,0 @@ -/** - * ============LICENSE_START======================================================= - * org.onap.aai - * ================================================================================ - * Copyright © 2017 AT&T Intellectual Property. All rights reserved. - * Copyright © 2017 Amdocs - * ================================================================================ - * 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 is a trademark and service mark of AT&T Intellectual Property. - */ -package org.openecomp.restclient.rest; - -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.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. - */ -public class RestClientBuilderTest { - - /** - * 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 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")); - - } - - @Test - 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)); - } - - - @Test - 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"); - } - - } - - @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