Initial OpenEcomp A&AI Rest Client commit. 77/3977/1
authorShwetank Dave <shwetank.dave@amdocs.com>
Mon, 8 May 2017 19:36:26 +0000 (15:36 -0400)
committerShwetank Dave <shwetank.dave@amdocs.com>
Mon, 8 May 2017 19:37:11 +0000 (15:37 -0400)
Change-Id: Ic6949778061bdf141431c4b14ea2417da6aa1e57
Signed-off-by: Shwetank Dave <shwetank.dave@amdocs.com>
15 files changed:
.gitignore [new file with mode: 0644]
.gitreview [new file with mode: 0644]
License.txt [new file with mode: 0644]
README.md [new file with mode: 0644]
pom.xml [new file with mode: 0644]
src/main/java/org/openecomp/restclient/client/Headers.java [new file with mode: 0644]
src/main/java/org/openecomp/restclient/client/OperationResult.java [new file with mode: 0644]
src/main/java/org/openecomp/restclient/client/RestClient.java [new file with mode: 0644]
src/main/java/org/openecomp/restclient/logging/RestClientMsgs.java [new file with mode: 0644]
src/main/java/org/openecomp/restclient/rest/HttpUtil.java [new file with mode: 0644]
src/main/java/org/openecomp/restclient/rest/RestClientBuilder.java [new file with mode: 0644]
src/main/resources/logging/RESTClientMsgs.properties [new file with mode: 0644]
src/test/java/org/openecomp/restclient/client/RESTClientTest.java [new file with mode: 0644]
src/test/java/org/openecomp/restclient/rest/RestClientBuilderTest.java [new file with mode: 0644]
version.properties [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..e2b927a
--- /dev/null
@@ -0,0 +1,6 @@
+.classpath
+.project
+.settings/
+target/
+logs/
+debug-logs/
diff --git a/.gitreview b/.gitreview
new file mode 100644 (file)
index 0000000..449c2df
--- /dev/null
@@ -0,0 +1,4 @@
+[gerrit]
+host=gerrit.onap.org
+port=29418
+project=aai/rest-client.git
diff --git a/License.txt b/License.txt
new file mode 100644 (file)
index 0000000..8a1e022
--- /dev/null
@@ -0,0 +1,22 @@
+============LICENSE_START=======================================================
+RestClient
+================================================================================
+Copyright © 2017 AT&T Intellectual Property.
+Copyright © 2017 Amdocs
+All rights reserved.
+================================================================================
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+============LICENSE_END=========================================================
+
+ECOMP and OpenECOMP are trademarks
+and service marks of AT&T Intellectual Property.
diff --git a/README.md b/README.md
new file mode 100644 (file)
index 0000000..d17a448
--- /dev/null
+++ b/README.md
@@ -0,0 +1,60 @@
+# Common REST Client Library
+
+This library provides a single client implementation to be used by micro services for communicating via its REST API.
+
+---
+
+## Usage
+In order to make the _REST Client_ library available to your microservice, include the following dependency in your service's pom.xml:
+
+    <!-- Common REST Client Library -->
+    <dependency>
+        <groupId>org.openecomp.aai</groupId>
+        <artifactId>rest-client</artifactId>
+        <version>1.0.0-SNAPSHOT</version>
+    </dependency>
+    
+## Code Examples
+
+### Creating and Configuring a Client Instance
+In order to start talking to a service, you need to create a client instance and configure it.  The _RestClient_ uses a fluent interface which allows it to be both instantiated and configured as in the following example:
+
+    // Create an instance of the Rest Client and configure it.
+    RestClient myClient = new RestClient()
+        .validateServerHostname(false)
+        .validateServerCertChain(true)
+        .clientCertFile("certificate_filename")
+        .trustStroe("trust_store_filename")
+        .connectTimeoutMs(1000)
+        .readTimeoutMs(1000)
+        
+Note, that all of the above configuration parameters are optional and will be set to default values if they are not specified.
+
+### Querying The A&AI
+Once your service has a client instance, it can query the _Active & Available Inventory_ by specifying an HTTP endpoint, headers, and the expected response format:
+
+       MultivaluedMap<String, String> headers = new MultivaluedMapImpl();
+       headers.put("Accept", Arrays.asList(new String[]{"application/json"}));
+       headers.put("X-FromAppId", Arrays.asList(new String[]{"APP-ID"}));
+       headers.put("X-TransactionId", Arrays.asList(new String[]{UUID.randomUUID().toString()}));
+
+    OperationResult result = myClient.queryActiveInventory("http://some/endpoint", headers, RestClient.RESPONSE_MIME_TYPE.JSON);
+    
+    // You can also specify number of re-tries:
+    int retries = 3
+    OperationResult result = myClient.queryActiveInventory("http://some/endpoint", headers, RestClient.RESPONSE_MIME_TYPE.JSON, retries);
+
+         
+The result of the query is returned as an _OperationResult_ object, which can be unpacked in the following manner:
+
+The standard HTTP result code received back from the _A&AI_ is accessible as follows:
+
+    int resultCode = getResultCode()
+
+The actual result payload is accessible in the following manner:
+
+    String resultPayload = result.getResult()
+
+Finally, in the event of a failure, a failure cause message will be populated and can be accessed as follows:
+
+    String failureCause = result.getFailureCause() 
diff --git a/pom.xml b/pom.xml
new file mode 100644 (file)
index 0000000..8d98e17
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,167 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+       <modelVersion>4.0.0</modelVersion>
+        
+       <groupId>org.openecomp.aai</groupId>
+       <artifactId>rest-client</artifactId>
+       <version>1.0.0-SNAPSHOT</version>
+       <name>REST Client</name>
+        
+       <properties>
+              <checkstyle.config.location>google_checks.xml</checkstyle.config.location>
+              <nexusproxy>https://nexus.onap.org</nexusproxy>
+              <!-- Sonar Properties -->
+              <sonar.language>java</sonar.language>
+              <sonar.java.coveragePlugin>jacoco</sonar.java.coveragePlugin>
+              <sonar.surefire.reportsPath>${project.build.directory}/surefire-reports</sonar.surefire.reportsPath>
+              <sonar.jacoco.reportPath>${project.build.directory}/coverage-reports/jacoco.exec</sonar.jacoco.reportPath>
+              <sonar.jacoco.reportMissing.force.zero>false</sonar.jacoco.reportMissing.force.zero>
+              <sonar.projectVersion>${project.version}</sonar.projectVersion>
+        </properties>
+
+       <dependencies>
+               <dependency>
+                       <groupId>com.sun.jersey</groupId>
+                       <artifactId>jersey-client</artifactId>
+                       <version>1.18</version>
+               </dependency>
+
+               <dependency>
+                       <groupId>org.openecomp.aai.logging-service</groupId>
+                       <artifactId>common-logging</artifactId>
+                       <version>1.0.0</version>
+               </dependency>
+
+               <dependency>
+                       <groupId>com.sun.jersey.jersey-test-framework</groupId>
+                       <artifactId>jersey-test-framework-grizzly2</artifactId>
+                       <version>1.18</version>
+                       <scope>test</scope>
+               </dependency>
+
+       </dependencies>
+
+       <build>
+               <pluginManagement>
+                       <plugins>
+                               <plugin>
+                                       <groupId>org.apache.maven.plugins</groupId>
+                                       <artifactId>maven-compiler-plugin</artifactId>
+                                       <configuration>
+                                               <source>1.8</source>
+                                               <target>1.8</target>
+                                       </configuration>
+                               </plugin>
+                               <plugin>
+                                       <artifactId>maven-release-plugin</artifactId>
+                                       <version>2.4.2</version>
+                                       <dependencies>
+                                               <dependency>
+                                                       <groupId>org.apache.maven.scm</groupId>
+                                                       <artifactId>maven-scm-provider-gitexe</artifactId>
+                                                       <version>1.8.1</version>
+                                               </dependency>
+                                       </dependencies>
+                               </plugin>
+
+                               <!-- Checkstyle plugin - used to report on compliance with -->
+                               <!-- the Google style guide. -->
+                               <plugin>
+                                       <groupId>org.apache.maven.plugins</groupId>
+                                       <artifactId>maven-site-plugin</artifactId>
+                                       <version>3.3</version>
+                                       <configuration>
+                                               <reportPlugins>
+                                                       <plugin>
+                                                               <groupId>org.apache.maven.plugins</groupId>
+                                                               <artifactId>maven-checkstyle-plugin</artifactId>
+                                                               <version>2.17</version>
+                                                               <reportSets>
+                                                                       <reportSet>
+                                                                               <reports>
+                                                                                       <report>checkstyle</report>
+                                                                               </reports>
+                                                                       </reportSet>
+                                                               </reportSets>
+                                                       </plugin>
+                                               </reportPlugins>
+                                       </configuration>
+                               </plugin>
+                       </plugins>
+               </pluginManagement>
+               <plugins>
+                       <!-- license plugin -->
+                       <plugin>
+                               <groupId>com.mycila</groupId>
+                               <artifactId>license-maven-plugin</artifactId>
+                               <version>3.0</version>
+                               <configuration>
+                                       <header>License.txt</header>
+                                       <includes>
+                                               <include>src/main/java/**</include>
+                                       </includes>
+                               </configuration>
+                               <executions>
+                                       <execution>
+                                               <goals>
+                                                       <goal>format</goal>
+                                               </goals>
+                                               <phase>process-sources</phase>
+                                       </execution>
+                               </executions>
+                       </plugin>
+
+                        <plugin>
+                              <groupId>org.sonatype.plugins</groupId>
+                              <artifactId>nexus-staging-maven-plugin</artifactId>
+                              <version>1.6.7</version>
+                              <extensions>true</extensions>
+                              <configuration>
+                                    <nexusUrl>${nexusproxy}</nexusUrl>
+                                    <stagingProfileId>176c31dfe190a</stagingProfileId>
+                                    <serverId>ecomp-staging</serverId>
+                              </configuration>
+                        </plugin>
+
+                        <plugin>
+                              <groupId>org.codehaus.mojo</groupId>
+                              <artifactId>sonar-maven-plugin</artifactId>
+                              <version>3.2</version>
+                        </plugin>
+                        <plugin>
+                              <groupId>org.jacoco</groupId>
+                              <artifactId>jacoco-maven-plugin</artifactId>
+                              <version>0.7.7.201606060606</version>
+                              <configuration>
+                                    <dumpOnExit>true</dumpOnExit>
+                              </configuration>
+                              <executions>
+                                    <execution>
+                                          <id>jacoco-initialize-unit-tests</id>
+                                          <goals>
+                                                <goal>prepare-agent</goal>
+                                          </goals>
+                                          <configuration>
+                                                <destFile>${project.build.directory}/coverage-reports/jacoco.exec</destFile>
+                                                <!-- <append>true</append> -->
+                                          </configuration>
+                                    </execution>
+                              </executions>
+                        </plugin>
+               </plugins>
+       </build>
+
+        <distributionManagement>
+              <repository>
+                    <id>ecomp-releases</id>
+                    <name>ECOMP Release Repository</name>
+                    <url>${nexusproxy}/content/repositories/releases/</url>
+              </repository>
+              <snapshotRepository>
+                    <id>ecomp-snapshots</id>
+                    <name>ECOMP Snapshot Repository</name>
+                    <url>${nexusproxy}/content/repositories/snapshots/</url>
+              </snapshotRepository>
+        </distributionManagement>
+        
+</project>
diff --git a/src/main/java/org/openecomp/restclient/client/Headers.java b/src/main/java/org/openecomp/restclient/client/Headers.java
new file mode 100644 (file)
index 0000000..aac817e
--- /dev/null
@@ -0,0 +1,37 @@
+/**\r
+ * ============LICENSE_START=======================================================\r
+ * RestClient\r
+ * ================================================================================\r
+ * Copyright © 2017 AT&T Intellectual Property.\r
+ * Copyright © 2017 Amdocs\r
+ * All rights reserved.\r
+ * ================================================================================\r
+ * Licensed under the Apache License, Version 2.0 (the "License");\r
+ * you may not use this file except in compliance with the License.\r
+ * You may obtain a copy of the License at\r
+ *\r
+ *    http://www.apache.org/licenses/LICENSE-2.0\r
+ *\r
+ * Unless required by applicable law or agreed to in writing, software\r
+ * distributed under the License is distributed on an "AS IS" BASIS,\r
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ * See the License for the specific language governing permissions and\r
+ * limitations under the License.\r
+ * ============LICENSE_END=========================================================\r
+ *\r
+ * ECOMP and OpenECOMP are trademarks\r
+ * and service marks of AT&T Intellectual Property.\r
+ */\r
+package org.openecomp.restclient.client;\r
+\r
+public final class Headers {\r
+\r
+  public static final String FROM_APP_ID = "X-FromAppId";\r
+  public static final String TRANSACTION_ID = "X-TransactionId";\r
+  public static final String RESOURCE_VERSION = "resourceVersion";\r
+  public static final String ETAG = "ETag";\r
+  public static final String IF_MATCH = "If-Match";\r
+  public static final String IF_NONE_MATCH = "If-None-Match";\r
+  public static final String ACCEPT = "Accept";\r
+\r
+}\r
diff --git a/src/main/java/org/openecomp/restclient/client/OperationResult.java b/src/main/java/org/openecomp/restclient/client/OperationResult.java
new file mode 100644 (file)
index 0000000..c9d0f9c
--- /dev/null
@@ -0,0 +1,83 @@
+/**\r
+ * ============LICENSE_START=======================================================\r
+ * RestClient\r
+ * ================================================================================\r
+ * Copyright © 2017 AT&T Intellectual Property.\r
+ * Copyright © 2017 Amdocs\r
+ * All rights reserved.\r
+ * ================================================================================\r
+ * Licensed under the Apache License, Version 2.0 (the "License");\r
+ * you may not use this file except in compliance with the License.\r
+ * You may obtain a copy of the License at\r
+ *\r
+ *    http://www.apache.org/licenses/LICENSE-2.0\r
+ *\r
+ * Unless required by applicable law or agreed to in writing, software\r
+ * distributed under the License is distributed on an "AS IS" BASIS,\r
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ * See the License for the specific language governing permissions and\r
+ * limitations under the License.\r
+ * ============LICENSE_END=========================================================\r
+ *\r
+ * ECOMP and OpenECOMP are trademarks\r
+ * and service marks of AT&T Intellectual Property.\r
+ */\r
+package org.openecomp.restclient.client;\r
+\r
+import javax.ws.rs.core.MultivaluedMap;\r
+\r
+public class OperationResult {\r
+\r
+  private String result;\r
+  private String failureCause;\r
+  private int resultCode;\r
+  private MultivaluedMap<String, String> headers;\r
+\r
+  /**\r
+   * Get the HTTP headers of the response.\r
+   *\r
+   * @return the HTTP headers of the response.\r
+   */\r
+  public MultivaluedMap<String, String> getHeaders() {\r
+    return headers;\r
+  }\r
+\r
+  public void setHeaders(MultivaluedMap<String, String> headers) {\r
+    this.headers = headers;\r
+  }\r
+\r
+\r
+  public String getResult() {\r
+    return result;\r
+  }\r
+\r
+  public void setResult(String result) {\r
+    this.result = result;\r
+  }\r
+\r
+  public int getResultCode() {\r
+    return resultCode;\r
+  }\r
+\r
+  public String getFailureCause() {\r
+    return failureCause;\r
+  }\r
+\r
+  public void setFailureCause(String failureCause) {\r
+    this.failureCause = failureCause;\r
+  }\r
+\r
+  public void setResultCode(int resultCode) {\r
+    this.resultCode = resultCode;\r
+  }\r
+\r
+  public OperationResult() {\r
+    super();\r
+  }\r
+\r
+  @Override\r
+  public String toString() {\r
+    return "OperationResult [result=" + result + ", resultCode=" + resultCode + "]";\r
+  }\r
+\r
+}\r
diff --git a/src/main/java/org/openecomp/restclient/client/RestClient.java b/src/main/java/org/openecomp/restclient/client/RestClient.java
new file mode 100644 (file)
index 0000000..900c4e0
--- /dev/null
@@ -0,0 +1,686 @@
+/**
+ * ============LICENSE_START=======================================================
+ * RestClient
+ * ================================================================================
+ * Copyright © 2017 AT&T Intellectual Property.
+ * Copyright © 2017 Amdocs
+ * All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ *
+ * ECOMP and OpenECOMP are trademarks
+ * and service marks of AT&T Intellectual Property.
+ */
+package org.openecomp.restclient.client;
+
+import com.sun.jersey.api.client.Client;
+import com.sun.jersey.api.client.ClientResponse;
+import com.sun.jersey.api.client.WebResource;
+import com.sun.jersey.api.client.WebResource.Builder;
+import com.sun.jersey.core.util.MultivaluedMapImpl;
+
+import org.openecomp.cl.api.LogFields;
+import org.openecomp.cl.api.LogLine;
+import org.openecomp.cl.api.Logger;
+import org.openecomp.cl.eelf.LoggerFactory;
+import org.openecomp.cl.mdc.MdcContext;
+import org.openecomp.cl.mdc.MdcOverride;
+import org.openecomp.restclient.logging.RestClientMsgs;
+import org.openecomp.restclient.rest.RestClientBuilder;
+
+import java.io.ByteArrayOutputStream;
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.UUID;
+
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.Response;
+
+
+/**
+ * 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;
+
+  /**
+   * The low level instance of the REST client that will be used to communicate with the endpoint.
+   */
+  private Client restClient = null;
+
+  /** 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();
+
+  /**
+   * 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;
+  }
+
+
+  /**
+   * 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) {
+    logger.debug("Set client certificate password = " + 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;
+  }
+
+  /**
+   * 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<String, List<String>> 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, url, Integer.toString(retryCount + 1));
+
+      // Submit our query to the AAI.
+      result = processRequest(operation, url, payload, headers, contentType, responseType);
+
+      // If the submission was successful then we're done.
+      if (Integer.toString(result.getResultCode()).charAt(0) == '2') {
+        logger.info(RestClientMsgs.HTTP_REQUEST_TIME_WITH_RETRIES,
+            Long.toString(System.currentTimeMillis() - startTimeInMs), url,
+            Integer.toString(retryCount));
+        return result;
+      }
+
+      // Our submission was unsuccessful...
+      try {
+        // Sleep between re-tries to be nice to the target system.
+        Thread.sleep(500);
+
+      } 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.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<String, List<String>> 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<String, List<String>> 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<String, List<String>> headers,
+      MediaType contentType, MediaType responseType) {
+    return processRequest(postOp, url, payload, headers, contentType, 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<String, List<String>> 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<String, List<String>> 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<String, List<String>> 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<String, String> 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<String, List<String>> 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<String, List<String>> header : headers.entrySet()) {
+        builder.header(header.getKey(), header.getValue());
+      }
+    }
+
+    return builder;
+  }
+
+  private void debugRequest(String url, String payload, Map<String, List<String>> 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<String, List<String>> 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<String, String> 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<String, List<String>> 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.
+   */
+  private synchronized Client getClient() throws Exception {
+
+    if (restClient == null) {
+
+      if (logger.isDebugEnabled()) {
+        logger.debug("Instantiating REST client with following parameters:");
+        logger.debug(
+            "  validate server hostname          = " + clientBuilder.isValidateServerHostname());
+        logger.debug(
+            "  validate server certificate chain = " + clientBuilder.isValidateServerCertChain());
+        logger.debug(
+            "  client certificate filename       = " + clientBuilder.getClientCertFileName());
+        logger.debug(
+            "  client certificate password       = " + clientBuilder.getClientCertPassword());
+        logger.debug(
+            "  trust store filename              = " + clientBuilder.getTruststoreFilename());
+        logger.debug("  connection timeout                = "
+            + clientBuilder.getConnectTimeoutInMs() + " ms");
+        logger.debug(
+            "  read timeout                      = " + clientBuilder.getReadTimeoutInMs() + " ms");
+      }
+
+      restClient = clientBuilder.getClient();
+    }
+
+    return restClient;
+  }
+
+
+  /**
+   * 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();
+    String payload = response.getEntity(String.class);
+
+    opResult.setResultCode(statusCode);
+
+    if ((statusCode < 200) || (statusCode > 299)) {
+      opResult.setFailureCause(payload);
+    } else {
+      opResult.setResult(payload);
+    }
+
+    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;
+    }
+  }
+
+  /**
+   * 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;
+    }
+  }
+}
diff --git a/src/main/java/org/openecomp/restclient/logging/RestClientMsgs.java b/src/main/java/org/openecomp/restclient/logging/RestClientMsgs.java
new file mode 100644 (file)
index 0000000..0b59139
--- /dev/null
@@ -0,0 +1,114 @@
+/**\r
+ * ============LICENSE_START=======================================================\r
+ * RestClient\r
+ * ================================================================================\r
+ * Copyright © 2017 AT&T Intellectual Property.\r
+ * Copyright © 2017 Amdocs\r
+ * All rights reserved.\r
+ * ================================================================================\r
+ * Licensed under the Apache License, Version 2.0 (the "License");\r
+ * you may not use this file except in compliance with the License.\r
+ * You may obtain a copy of the License at\r
+ *\r
+ *    http://www.apache.org/licenses/LICENSE-2.0\r
+ *\r
+ * Unless required by applicable law or agreed to in writing, software\r
+ * distributed under the License is distributed on an "AS IS" BASIS,\r
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ * See the License for the specific language governing permissions and\r
+ * limitations under the License.\r
+ * ============LICENSE_END=========================================================\r
+ *\r
+ * ECOMP and OpenECOMP are trademarks\r
+ * and service marks of AT&T Intellectual Property.\r
+ */\r
+package org.openecomp.restclient.logging;\r
+\r
+import com.att.eelf.i18n.EELFResourceManager;\r
+import org.openecomp.cl.eelf.LogMessageEnum;\r
+\r
+public enum RestClientMsgs implements LogMessageEnum {\r
+\r
+  /**\r
+   * Arguments: \r
+   *    {0} = HTTP operation \r
+   *    {1} = URL\r
+   */\r
+  HTTP_REQUEST,\r
+\r
+  /**\r
+   * Arguments: \r
+   *    {0} = HTTP operation \r
+   *    {1} = URL \r
+   *    {2} = Attempt count.\r
+   */\r
+  HTTP_REQUEST_WITH_RETRIES,\r
+\r
+  /**\r
+   * Arguments: \r
+   *    {0} = HTTP operation \r
+   *    {1} - URL \r
+   *    {2} - Operation time in ms.\r
+   */\r
+  HTTP_REQUEST_TIME,\r
+\r
+  /**\r
+   * Arguments: \r
+   *    {0} = HTTP operation \r
+   *    {1} - URL \r
+   *    {2} - Operation time in ms. \r
+   *    {3} - Retry count.\r
+   */\r
+  HTTP_REQUEST_TIME_WITH_RETRIES,\r
+\r
+  /**\r
+   * Arguments: \r
+   *    {0} = HTTP operation \r
+   *    {1} - URL \r
+   *    {2} - Error message.\r
+   */\r
+  HTTP_REQUEST_INTERRUPTED,\r
+\r
+  /**\r
+   * Arguments: \r
+   *    {0} = HTTP operation \r
+   *    {1} - URL \r
+   *    {2} - Error message.\r
+   */\r
+  HTTP_REQUEST_ERROR,\r
+\r
+  /**\r
+   * . Arguments: \r
+   *    {0} = Target URL\r
+   */\r
+  HEALTH_CHECK_ATTEMPT,\r
+\r
+  /**\r
+   * . Arguments: \r
+   *    {0} = Target URL\r
+   */\r
+  HEALTH_CHECK_SUCCESS,\r
+\r
+  /**\r
+   * . Arguments: \r
+   *    {0} = Target URL \r
+   *    {1} = failure cause\r
+   */\r
+  HEALTH_CHECK_FAILURE,\r
+\r
+\r
+  /**\r
+   * . Arguments: \r
+   *    {0} = URL \r
+   *    {1} - Response code\r
+   */\r
+  HTTP_RESPONSE;\r
+\r
+\r
+  /**\r
+   * Static initializer to ensure the resource bundles for this class are loaded...\r
+   */\r
+  static {\r
+    EELFResourceManager.loadMessageBundle("logging/RESTClientMsgs");\r
+  }\r
+}\r
diff --git a/src/main/java/org/openecomp/restclient/rest/HttpUtil.java b/src/main/java/org/openecomp/restclient/rest/HttpUtil.java
new file mode 100644 (file)
index 0000000..89af684
--- /dev/null
@@ -0,0 +1,115 @@
+/**\r
+ * ============LICENSE_START=======================================================\r
+ * RestClient\r
+ * ================================================================================\r
+ * Copyright © 2017 AT&T Intellectual Property.\r
+ * Copyright © 2017 Amdocs\r
+ * All rights reserved.\r
+ * ================================================================================\r
+ * Licensed under the Apache License, Version 2.0 (the "License");\r
+ * you may not use this file except in compliance with the License.\r
+ * You may obtain a copy of the License at\r
+ *\r
+ *    http://www.apache.org/licenses/LICENSE-2.0\r
+ *\r
+ * Unless required by applicable law or agreed to in writing, software\r
+ * distributed under the License is distributed on an "AS IS" BASIS,\r
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ * See the License for the specific language governing permissions and\r
+ * limitations under the License.\r
+ * ============LICENSE_END=========================================================\r
+ *\r
+ * ECOMP and OpenECOMP are trademarks\r
+ * and service marks of AT&T Intellectual Property.\r
+ */\r
+package org.openecomp.restclient.rest;\r
+\r
+public class HttpUtil {\r
+\r
+  /**\r
+   * Determines if the provided HTTP response is present in the provided list of acceptable response\r
+   * codes.\r
+   *\r
+   * @param response the http response we got from our request\r
+   * @param list the list of acceptable response codes\r
+   * @return true if the http response is in the provided list\r
+   */\r
+  public static boolean isHttpResponseInList(int response, int... list) {\r
+    for (int checkCode : list) {\r
+      if (checkCode == response) {\r
+        return true;\r
+      }\r
+    }\r
+    return false;\r
+  }\r
+\r
+  /**\r
+   * Determines if the provided http response is of the information class.\r
+   *\r
+   * @param response the http response we got from our request\r
+   * @return true if the response is of the informational class and false otherwise\r
+   */\r
+  public static boolean isHttpResponseClassInformational(int response) {\r
+    return isExpectedHttpResponseClass(response, '1');\r
+  }\r
+\r
+  /**\r
+   * Determines if the provided http response is of the success class.\r
+   *\r
+   * @param response the http response we got from our request\r
+   * @return true if the response is of the success class and false otherwise\r
+   */\r
+  public static boolean isHttpResponseClassSuccess(int response) {\r
+    return isExpectedHttpResponseClass(response, '2');\r
+  }\r
+\r
+  /**\r
+   * Determines if the provided http response is of the redirection class.\r
+   *\r
+   * @param response the http response we got from our request\r
+   * @return true if the response is of the redirection class and false otherwise\r
+   */\r
+  public static boolean isHttpResponseClassRedirection(int response) {\r
+    return isExpectedHttpResponseClass(response, '3');\r
+  }\r
+\r
+  /**\r
+   * Determines if the provided http response is of the client error class.\r
+   *\r
+   * @param response the http response we got from our request\r
+   * @return true if the response is of the client error class and false otherwise\r
+   */\r
+  public static boolean isHttpResponseClassClientError(int response) {\r
+    return isExpectedHttpResponseClass(response, '4');\r
+  }\r
+\r
+  /**\r
+   * Determines if the provided http response is of the server error class.\r
+   *\r
+   * @param response the http response we got from our request\r
+   * @return true if the response is of the server error class and false otherwise\r
+   */\r
+  public static boolean isHttpResponseClassServerError(int response) {\r
+    return isExpectedHttpResponseClass(response, '5');\r
+  }\r
+\r
+  /**\r
+   * Helper method to determine if we have received the response class we are expecting.\r
+   *\r
+   * @param response the http response we got from our request\r
+   * @param expectedClass the expected http response class ie: 1, 2, 3, 4, 5 which maps to 1xx, 2xx,\r
+   *        3xx, 4xx, 5xx respectively\r
+   * @return true if the response if of our expected class and false if not\r
+   */\r
+  private static boolean isExpectedHttpResponseClass(int response, char expectedClass) {\r
+    if (response < 100 || response >= 600) {\r
+      return false;\r
+    }\r
+\r
+    if (Integer.toString(response).charAt(0) == expectedClass) {\r
+      return true;\r
+    }\r
+\r
+    return false;\r
+  }\r
+}\r
diff --git a/src/main/java/org/openecomp/restclient/rest/RestClientBuilder.java b/src/main/java/org/openecomp/restclient/rest/RestClientBuilder.java
new file mode 100644 (file)
index 0000000..3d546fe
--- /dev/null
@@ -0,0 +1,229 @@
+/**\r
+ * ============LICENSE_START=======================================================\r
+ * RestClient\r
+ * ================================================================================\r
+ * Copyright © 2017 AT&T Intellectual Property.\r
+ * Copyright © 2017 Amdocs\r
+ * All rights reserved.\r
+ * ================================================================================\r
+ * Licensed under the Apache License, Version 2.0 (the "License");\r
+ * you may not use this file except in compliance with the License.\r
+ * You may obtain a copy of the License at\r
+ *\r
+ *    http://www.apache.org/licenses/LICENSE-2.0\r
+ *\r
+ * Unless required by applicable law or agreed to in writing, software\r
+ * distributed under the License is distributed on an "AS IS" BASIS,\r
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ * See the License for the specific language governing permissions and\r
+ * limitations under the License.\r
+ * ============LICENSE_END=========================================================\r
+ *\r
+ * ECOMP and OpenECOMP are trademarks\r
+ * and service marks of AT&T Intellectual Property.\r
+ */\r
+package org.openecomp.restclient.rest;\r
+\r
+import com.sun.jersey.api.client.Client;\r
+import com.sun.jersey.api.client.config.ClientConfig;\r
+import com.sun.jersey.api.client.config.DefaultClientConfig;\r
+import com.sun.jersey.client.urlconnection.HTTPSProperties;\r
+\r
+import java.io.FileInputStream;\r
+import java.security.KeyStore;\r
+import java.security.cert.X509Certificate;\r
+\r
+import javax.net.ssl.HostnameVerifier;\r
+import javax.net.ssl.KeyManagerFactory;\r
+import javax.net.ssl.SSLContext;\r
+import javax.net.ssl.SSLSession;\r
+import javax.net.ssl.TrustManager;\r
+import javax.net.ssl.X509TrustManager;\r
+\r
+/**\r
+ * This is a generic REST Client builder with flexible security validation. Sometimes it's nice to\r
+ * be able to disable server chain cert validation and hostname validation to work-around lab\r
+ * issues, but at the same time be able to provide complete validation with client cert + hostname +\r
+ * server cert chain validation.  \r
+ * I used the ModelLoader REST client as a base and merged in the TSUI client I wrote which also\r
+ * validates the server hostname and server certificate chain.\r
+ * \r
+ * @author DAVEA\r
+ *\r
+ */\r
+public class RestClientBuilder {\r
+\r
+  public static final boolean DEFAULT_VALIDATE_SERVER_HOST = false;\r
+  public static final boolean DEFAULT_VALIDATE_CERT_CHAIN = false;\r
+  public static final String DEFAULT_CLIENT_CERT_FILENAME = null;\r
+  public static final String DEFAULT_CERT_PASSWORD = null;\r
+  public static final String DEFAULT_TRUST_STORE_FILENAME = null;\r
+  public static final int DEFAULT_CONNECT_TIMEOUT_MS = 60000;\r
+  public static final int DEFAULT_READ_TIMEOUT_MS = 60000;\r
+\r
+  private static final String SSL_PROTOCOL = "TLS";\r
+  private static final String KEYSTORE_ALGORITHM = "SunX509";\r
+  private static final String KEYSTORE_TYPE = "PKCS12";\r
+\r
+  /*\r
+   * TODO: implement fluent interface?\r
+   */\r
+\r
+  private boolean validateServerHostname;\r
+  private boolean validateServerCertChain;\r
+  private String clientCertFileName;\r
+  private String clientCertPassword;\r
+  private String truststoreFilename;\r
+  private int connectTimeoutInMs;\r
+  private int readTimeoutInMs;\r
+\r
+  /**\r
+   * Rest Client Builder.\r
+   */\r
+  public RestClientBuilder() {\r
+    validateServerHostname = DEFAULT_VALIDATE_SERVER_HOST;\r
+    validateServerCertChain = DEFAULT_VALIDATE_CERT_CHAIN;\r
+    clientCertFileName = DEFAULT_CLIENT_CERT_FILENAME;\r
+    clientCertPassword = DEFAULT_CERT_PASSWORD;\r
+    truststoreFilename = DEFAULT_TRUST_STORE_FILENAME;\r
+    connectTimeoutInMs = DEFAULT_CONNECT_TIMEOUT_MS;\r
+    readTimeoutInMs = DEFAULT_READ_TIMEOUT_MS;\r
+  }\r
+\r
+  public boolean isValidateServerHostname() {\r
+    return validateServerHostname;\r
+  }\r
+\r
+  public void setValidateServerHostname(boolean validateServerHostname) {\r
+    this.validateServerHostname = validateServerHostname;\r
+  }\r
+\r
+  public boolean isValidateServerCertChain() {\r
+    return validateServerCertChain;\r
+  }\r
+\r
+  public void setValidateServerCertChain(boolean validateServerCertChain) {\r
+    this.validateServerCertChain = validateServerCertChain;\r
+  }\r
+\r
+  public String getClientCertFileName() {\r
+    return clientCertFileName;\r
+  }\r
+\r
+  public void setClientCertFileName(String clientCertFileName) {\r
+    this.clientCertFileName = clientCertFileName;\r
+  }\r
+\r
+  public String getClientCertPassword() {\r
+    return clientCertPassword;\r
+  }\r
+\r
+  public void setClientCertPassword(String clientCertPassword) {\r
+    this.clientCertPassword = clientCertPassword;\r
+  }\r
+\r
+  public String getTruststoreFilename() {\r
+    return truststoreFilename;\r
+  }\r
+\r
+  public void setTruststoreFilename(String truststoreFilename) {\r
+    this.truststoreFilename = truststoreFilename;\r
+  }\r
+\r
+  public int getConnectTimeoutInMs() {\r
+    return connectTimeoutInMs;\r
+  }\r
+\r
+  public void setConnectTimeoutInMs(int connectTimeoutInMs) {\r
+    this.connectTimeoutInMs = connectTimeoutInMs;\r
+  }\r
+\r
+  public int getReadTimeoutInMs() {\r
+    return readTimeoutInMs;\r
+  }\r
+\r
+  public void setReadTimeoutInMs(int readTimeoutInMs) {\r
+    this.readTimeoutInMs = readTimeoutInMs;\r
+  }\r
+\r
+  /**\r
+   * Returns Client.\r
+   */\r
+  public Client getClient() throws Exception {\r
+\r
+    ClientConfig clientConfig = new DefaultClientConfig();\r
+\r
+    // Check to see if we need to perform proper validation of\r
+    // the certificate chains.\r
+    TrustManager[] trustAllCerts = null;\r
+    if (validateServerCertChain) {\r
+      if (truststoreFilename != null) {\r
+        System.setProperty("javax.net.ssl.trustStore", truststoreFilename);\r
+      } else {\r
+        throw new IllegalArgumentException("Trust store filename must be set!");\r
+      }\r
+\r
+    } else {\r
+\r
+      // We aren't validating certificates, so create a trust manager that does\r
+      // not validate certificate chains.\r
+      trustAllCerts = new TrustManager[] {new X509TrustManager() {\r
+        public X509Certificate[] getAcceptedIssuers() {\r
+          return null;\r
+        }\r
+\r
+        public void checkClientTrusted(X509Certificate[] certs, String authType) {}\r
+\r
+        public void checkServerTrusted(X509Certificate[] certs, String authType) {}\r
+      }};\r
+    }\r
+\r
+    // Set up the SSL context, keystore, etc. to use for our connection\r
+    // to the AAI.\r
+    SSLContext ctx = SSLContext.getInstance(SSL_PROTOCOL);\r
+    KeyManagerFactory kmf = KeyManagerFactory.getInstance(KEYSTORE_ALGORITHM);\r
+    KeyStore ks = KeyStore.getInstance(KEYSTORE_TYPE);\r
+\r
+    char[] pwd = null;\r
+    if (clientCertPassword != null) {\r
+      pwd = clientCertPassword.toCharArray();\r
+    }\r
+\r
+    if (clientCertFileName != null) {\r
+      FileInputStream fin = new FileInputStream(clientCertFileName);\r
+\r
+      // Load the keystore and initialize the key manager factory.\r
+      ks.load(fin, pwd);\r
+      kmf.init(ks, pwd);\r
+\r
+      ctx.init(kmf.getKeyManagers(), trustAllCerts, null);\r
+    } else {\r
+      ctx.init(null, trustAllCerts, null);\r
+    }\r
+\r
+\r
+    // Are we performing validation of the server host name?\r
+    if (validateServerHostname) {\r
+      clientConfig.getProperties().put(HTTPSProperties.PROPERTY_HTTPS_PROPERTIES,\r
+          new HTTPSProperties(null, ctx));\r
+\r
+    } else {\r
+      clientConfig.getProperties().put(HTTPSProperties.PROPERTY_HTTPS_PROPERTIES,\r
+          new HTTPSProperties(new HostnameVerifier() {\r
+            @Override\r
+            public boolean verify(String str, SSLSession sslSession) {\r
+              return true;\r
+            }\r
+          }, ctx));\r
+    }\r
+\r
+    // Finally, create and initialize our client...\r
+    Client client = null;\r
+    client = Client.create(clientConfig);\r
+    client.setConnectTimeout(connectTimeoutInMs);\r
+    client.setReadTimeout(readTimeoutInMs);\r
+\r
+    // ...and return it to the caller.\r
+    return client;\r
+  }\r
+}\r
diff --git a/src/main/resources/logging/RESTClientMsgs.properties b/src/main/resources/logging/RESTClientMsgs.properties
new file mode 100644 (file)
index 0000000..9df0764
--- /dev/null
@@ -0,0 +1,57 @@
+#Resource key=Error Code|Message text|Resolution text |Description text
+#######
+#Newlines can be utilized to add some clarity ensuring continuing line
+#has atleast one leading space
+#ResourceKey=\
+#             ERR0000E\
+#             Sample error msg txt\
+#             Sample resolution msg\
+#             Sample description txt
+#
+######
+#Error code classification category
+#000    Info/Debug
+#100    Permission errors
+#200    Availability errors/Timeouts
+#300    Data errors
+#400    Schema Interface type/validation errors
+#500    Business process errors
+#900    Unknown errors
+#
+########################################################################
+
+HTTP_REQUEST=\
+    AC0001I|\
+    {0} request at url = {1}
+
+HTTP_REQUEST_WITH_RETRIES=\
+    AC0002I|\
+    {0} request at url = {1} attempt number = {2}
+
+HTTP_REQUEST_TIME=\
+    AC0003I|\
+    {0} request operation time = {1} ms for link = {2}
+
+HTTP_RESPONSE=\
+    AC0004I|\
+    request at url = {0} resulted in http response: {1}
+    
+HEALTH_CHECK_ATTEMPT=\
+    AC0005I|\
+    Attempting to connect to {0} at {1}
+
+HEALTH_CHECK_SUCCESS=\
+    AC0006I|\
+    Successfully established connection to {0} at {1}
+
+HTTP_REQUEST_INTERRUPTED=\
+    AC2001E|\
+    {0} request interrupted while sleeping at url = {1} with cause = {2}
+
+HTTP_REQUEST_ERROR=\
+    AC2002E|\
+    Error during {0} operation to endpoint at url = {1} with error = {2}
+
+HEALTH_CHECK_FAILURE=\
+    AC2003E|\
+    Failed to establish connection to {0} at {1}. Cause {2}
diff --git a/src/test/java/org/openecomp/restclient/client/RESTClientTest.java b/src/test/java/org/openecomp/restclient/client/RESTClientTest.java
new file mode 100644 (file)
index 0000000..b049c38
--- /dev/null
@@ -0,0 +1,391 @@
+package org.openecomp.restclient.client;\r
+\r
+import org.junit.Before;\r
+import org.junit.Test;\r
+import org.openecomp.restclient.client.OperationResult;\r
+import org.openecomp.restclient.client.RestClient;\r
+import org.openecomp.restclient.rest.RestClientBuilder;\r
+\r
+import static org.junit.Assert.assertTrue;\r
+import static org.junit.Assert.assertNull;\r
+import static org.junit.Assert.assertEquals;\r
+\r
+import javax.ws.rs.GET;\r
+import javax.ws.rs.Path;\r
+import javax.ws.rs.core.MediaType;\r
+\r
+import com.sun.jersey.test.framework.AppDescriptor;\r
+import com.sun.jersey.test.framework.JerseyTest;\r
+import com.sun.jersey.test.framework.WebAppDescriptor;\r
+\r
+import com.sun.jersey.api.client.Client;\r
+\r
+\r
+/**\r
+ * This suite of tests is intended to exercise the behaviour of the {@link RestClient}.\r
+ */\r
+public class RESTClientTest extends JerseyTest {\r
+\r
+  private static final String GOOD_AAI_ENDPOINT = "testaai/good";\r
+  private static final String FAIL_ALWAYS_AAI_ENDPOINT = "testaai/failalways";\r
+  private static final String FAIL_THEN_SUCCEED_ENDPOINT = "testaai/failthensucceed";\r
+  private static final String INVALID_AAI_ENDPOINT = "testaai/bad";\r
+\r
+  private static final String AAI_GET_REPLY_PAYLOAD = "Reply from AAI";\r
+\r
+  private static final int SUCCESS_RESULT_CODE = 200;\r
+  private static final int INVALID_END_POINT_RESULT_CODE = 404;\r
+  private static final int INTERNAL_ERR_RESULT_CODE = 500;\r
+  private static final int TIMEOUT_RESULT_CODE = 504;\r
+\r
+\r
+  /**\r
+   * Creates a new instance of the {@link RESTClientTest} test suite.\r
+   */\r
+  public RESTClientTest() throws Exception {\r
+\r
+    // Tell our in memory container to look here for resource endpoints.\r
+    super("org.openecomp.restclient.client");\r
+  }\r
+\r
+\r
+  @Override\r
+  protected AppDescriptor configure() {\r
+    return new WebAppDescriptor.Builder().build();\r
+  }\r
+\r
+\r
+  /**\r
+   * Perform common initialization actions that need to run before every unit test.\r
+   */\r
+  @Before\r
+  public void setup() {\r
+\r
+    // Initialize our test endpoints to make sure that all of their\r
+    // counters have valid starting values\r
+    AAI_FailAlways_Stub.initialize();\r
+    AAI_FailThenSucceed_Stub.initialize();\r
+  }\r
+\r
+\r
+  /**\r
+   * This test validates that all of the {@link RestClient}'s configurable parameters can be set via\r
+   * its fluent interface and that those values are successfully passed down to the underlying\r
+   * {@link RestClientBuilder} instance.\r
+   */\r
+  @Test\r
+  public void configureAAIClientTest() {\r
+\r
+    final boolean VALIDATE_SERVER = true;\r
+    final boolean VALIDATE_CERT_CHAIN = true;\r
+    final String CLIENT_CERT_FILE = "myCertFile";\r
+    final String CLIENT_CERT_PASSWORD = "My voice is my password";\r
+    final String TRUST_STORE = "myTrustStore";\r
+    final int CONNECT_TIMEOUT = 5000;\r
+    final int READ_TIMEOUT = 5000;\r
+\r
+    // Create an instance of our test version of the REST client builder.\r
+    TestRestClientBuilder clientBuilder = new TestRestClientBuilder();\r
+\r
+    // Now, create a new instance of the {@link AAIClient} and configure\r
+    // its parameters.\r
+    RestClient testClient =\r
+        new RestClient(clientBuilder).validateServerHostname(true).validateServerCertChain(true)\r
+            .clientCertFile("myCertFile").clientCertPassword("My voice is my password")\r
+            .trustStore("myTrustStore").connectTimeoutMs(5000).readTimeoutMs(5000);\r
+\r
+    // Validate that the parameters of the test REST client builder that\r
+    // we passed to the AAI client have been set according to what we\r
+    // passed in when we instantiated the AAI client.\r
+    assertEquals("Unexpected 'validate server host name' value", VALIDATE_SERVER,\r
+        clientBuilder.isValidateServerHostname());\r
+    assertEquals("Unexpected 'validate certificat chain' value", VALIDATE_CERT_CHAIN,\r
+        clientBuilder.isValidateServerCertChain());\r
+    assertTrue("Unexpected client certificate filename",\r
+        CLIENT_CERT_FILE.equals(clientBuilder.getClientCertFileName()));\r
+    assertTrue("Unexpected client certificate password",\r
+        CLIENT_CERT_PASSWORD.equals(clientBuilder.getClientCertPassword()));\r
+    assertTrue("Unexpected trust store filename",\r
+        TRUST_STORE.equals(clientBuilder.getTruststoreFilename()));\r
+    assertEquals("Unexpected connection timeout value", CONNECT_TIMEOUT,\r
+        clientBuilder.getConnectTimeoutInMs());\r
+    assertEquals("Unexpected read timeout value", READ_TIMEOUT, clientBuilder.getReadTimeoutInMs());\r
+  }\r
+\r
+\r
+  /**\r
+   * This test validates that the {@link RestClient} can submit a GET request to a valid REST\r
+   * endpoint and receive a valid response.\r
+   */\r
+  @Test\r
+  public void queryAAI_SuccessTest() {\r
+\r
+    // Create an instance of the AAIClient that uses our test version of\r
+    // the REST client builder.\r
+    RestClient testClient = new RestClient(new TestRestClientBuilder());\r
+\r
+    // Query our stubbed out AAI with a URL that we expecte to get a successful\r
+    // reply from.\r
+    OperationResult or =\r
+        testClient.get(getBaseURI() + GOOD_AAI_ENDPOINT, null, MediaType.APPLICATION_JSON_TYPE);\r
+\r
+    // Validate that a successful query returns a result code of 200.\r
+    assertEquals("Unexpected result code", SUCCESS_RESULT_CODE, or.getResultCode());\r
+\r
+    // Validate that no error cause gets set on a successful query.\r
+    assertNull("Operation result failure code should not be set for successful GET",\r
+        or.getFailureCause());\r
+\r
+    // Validate that our query returned the expected payload from our dummy\r
+    // AAI.\r
+    assertTrue("Incorrect payload returned from AAI query",\r
+        AAI_GET_REPLY_PAYLOAD.equals(or.getResult()));\r
+  }\r
+\r
+\r
+  /**\r
+   * This test validates that the {@link RestClient} behaves as expected when query requests are\r
+   * unsuccessful.\r
+   * <p>\r
+   * Specifically, the following scenarios are covered:<br>\r
+   * 1) Submitting a GET request to an invalid REST endpoint 2) Submitting a GET request to a valid\r
+   * endpoint which throws an error rather than replying successfully.\r
+   * <p>\r
+   * Note that this test exercises the 'single attempt' variant of the query method.\r
+   */\r
+  @Test\r
+  public void queryAAI_FailureTest() {\r
+\r
+    // Create an instance of the AAIClient that uses our test version of\r
+    // the REST client builder.\r
+    RestClient testClient = new RestClient(new TestRestClientBuilder());\r
+\r
+    // Query our stubbed out AAI with a URL that we expecte to get a successful\r
+    // reply from.\r
+    OperationResult or =\r
+        testClient.get(getBaseURI() + INVALID_AAI_ENDPOINT, null, MediaType.APPLICATION_JSON_TYPE);\r
+\r
+    // Validate that an attempt to query a non-existing endpoint results in\r
+    // a 404 error.\r
+    assertEquals("Unexpected result code", INVALID_END_POINT_RESULT_CODE, or.getResultCode());\r
+\r
+    // Validate that no payload was set since the query failed.\r
+    assertNull("Payload should not be set on 404 error", or.getResult());\r
+\r
+    // Now, submit a query request to the stubbed AAI.\r
+    or = testClient.get(getBaseURI() + FAIL_ALWAYS_AAI_ENDPOINT, null,\r
+        MediaType.APPLICATION_JSON_TYPE);\r
+\r
+    // Validate that a query to a avalid returns a result code of 500.\r
+    assertEquals("Unexpected result code", INTERNAL_ERR_RESULT_CODE, or.getResultCode());\r
+  }\r
+\r
+\r
+  /**\r
+   * This test validates the behaviour of querying the AAI with a number of retries requested in the\r
+   * case where we never get a successful reply.\r
+   */\r
+  @Test\r
+  public void queryAAIWithRetries_TimeoutTest() {\r
+\r
+    int NUM_RETRIES = 3;\r
+\r
+\r
+    // Create an instance of the AAIClient that uses our test version of\r
+    // the REST client builder.\r
+    RestClient testClient = new RestClient(new TestRestClientBuilder());\r
+\r
+    // Initialize our test endpoint to make sure that all of its\r
+    // counters have valid starting values\r
+    // AAI_FailAlways_Stub.initialize();\r
+\r
+    // Perform a query against the stubbed AAI, specifying a number of times\r
+    // to retry in the event of an error.\r
+    OperationResult or = testClient.get(getBaseURI() + FAIL_ALWAYS_AAI_ENDPOINT, null,\r
+        MediaType.APPLICATION_JSON_TYPE, NUM_RETRIES);\r
+\r
+    // Validate that failing for all of our retry attempts results in a\r
+    // 504 error.\r
+    assertEquals("Unexpected result code", TIMEOUT_RESULT_CODE, or.getResultCode());\r
+\r
+    // Validate that our stubbed AAI actually received the expected number\r
+    // of retried requests.\r
+    assertEquals("Unexpected number of retries", NUM_RETRIES, AAI_FailAlways_Stub.getCount);\r
+  }\r
+\r
+\r
+  /**\r
+   * This test validates the behaviour of querying the AAI with a number of retries requested in the\r
+   * case where our query initially fails but then succeeds on one of the subsequent retries.\r
+   */\r
+  @Test\r
+  public void queryAAIWithRetries_FailThenSucceedTest() {\r
+\r
+    int num_retries = AAI_FailThenSucceed_Stub.MAX_FAILURES + 2;\r
+\r
+    // Create an instance of the AAIClient that uses our test version of\r
+    // the REST client builder.\r
+    RestClient testClient = new RestClient(new TestRestClientBuilder());\r
+\r
+    // Initialize our test endpoint to make sure that all of its\r
+    // counters have valid starting values.\r
+    // AAI_FailThenSucceed_Stub.initialize();\r
+\r
+    // Perform a query against the stubbed AAI, specifying a number of times\r
+    // to retry in the event of an error.\r
+    OperationResult or = testClient.get(getBaseURI() + FAIL_THEN_SUCCEED_ENDPOINT, null,\r
+        MediaType.APPLICATION_JSON_TYPE, num_retries);\r
+\r
+    // Validate that after failing a few attempts we finally got back a\r
+    // success code.\r
+    assertEquals("Unexpected result code", SUCCESS_RESULT_CODE, or.getResultCode());\r
+\r
+    // Validate that our stubbed AAI actually received the expected number\r
+    // of retried requests.\r
+    assertEquals("Unexpected number of retries", AAI_FailThenSucceed_Stub.MAX_FAILURES + 1,\r
+        AAI_FailThenSucceed_Stub.getCount);\r
+  }\r
+\r
+\r
+  /**\r
+   * This class provides a simple in-memory REST end point to stand in for a real AAI.\r
+   * <p>\r
+   * This endpoint always returns a valid reply to a GET request and is used for success path\r
+   * testing.\r
+   */\r
+  @Path(GOOD_AAI_ENDPOINT)\r
+  public static class AAI_Success_Stub {\r
+\r
+    /**\r
+     * This is the end point for GET requests. It just returns a simple, pre-canned response\r
+     * payload.\r
+     * \r
+     * @return - A pre-canned response.\r
+     */\r
+    @GET\r
+    public String getEndpoint() {\r
+      return AAI_GET_REPLY_PAYLOAD;\r
+    }\r
+  }\r
+\r
+\r
+  /**\r
+   * This class provides a simple in-memory REST end point to stand in for a real AAI.\r
+   * <p>\r
+   * This endpoint always returns throws an error instead of responding successfully and is used for\r
+   * certain failure path tests.\r
+   */\r
+  @Path(FAIL_ALWAYS_AAI_ENDPOINT)\r
+  public static class AAI_FailAlways_Stub {\r
+\r
+    /**\r
+     * Maintains a running count of the number of GET requests that have been received.\r
+     */\r
+    public static int getCount;\r
+\r
+\r
+    /**\r
+     * Resets all of the endpoints counters.\r
+     */\r
+    public static void initialize() {\r
+      getCount = 0;\r
+    }\r
+\r
+\r
+    /**\r
+     * This is the end point for GET requests. It just throws an error instead of returning a valid\r
+     * response.\r
+     * \r
+     * @return - NONE. We actually throw an exception intentionally instead of returning.\r
+     */\r
+    @GET\r
+    public String getEndpoint() {\r
+\r
+      // Keep track of the number of get requests that we have received\r
+      // so that this value can be used for validation purposes later.\r
+      getCount++;\r
+\r
+      // Always just throw an error instead of replying successfully.\r
+      throw new UnsupportedOperationException("Intentional Failure");\r
+    }\r
+  }\r
+\r
+\r
+  /**\r
+   * This class provides a simple in-memory REST end point to stand in for a real AAI.\r
+   * <p>\r
+   * This end point will throw errors instead of responding for a certain number of requests, after\r
+   * which it will return a valid, pre-canned response.\r
+   * \r
+   * @return - A pre-canned response.\r
+   */\r
+  @Path(FAIL_THEN_SUCCEED_ENDPOINT)\r
+  public static class AAI_FailThenSucceed_Stub {\r
+\r
+    /**\r
+     * The number of requests for which we should throw errors before responding successfully.\r
+     */\r
+    public static int MAX_FAILURES = 2;\r
+\r
+    /**\r
+     * Maintains a running count of the number of GET requests that have been received.\r
+     */\r
+    public static int getCount;\r
+\r
+    /**\r
+     * Maintains a running count of the number of requests which we have failed, so that we will\r
+     * know when to stop failing and return a valid response.\r
+     */\r
+    private static int failCount;\r
+\r
+\r
+    /**\r
+     * Resets all of the endpoints counters.\r
+     */\r
+    public static void initialize() {\r
+      getCount = 0;\r
+      failCount = 0;\r
+    }\r
+\r
+\r
+    /**\r
+     * This is the end point for GET requests. It will throw errors for a certain number of requests\r
+     * and then return a valid response.\r
+     * \r
+     * @return - A pre-canned response string.\r
+     */\r
+    @GET\r
+    public String getEndpoint() {\r
+\r
+      // Keep track of the number of get requests that we have received\r
+      // so that this value can be used for validation purposes later.\r
+      getCount++;\r
+\r
+      // We only want to fail a set number of times, so check now to\r
+      // see what we should do.\r
+      if (failCount < MAX_FAILURES) {\r
+        failCount++;\r
+        throw new UnsupportedOperationException("Intentional Failure");\r
+\r
+      } else {\r
+        // We've failed as often as we need to. Time to reply\r
+        // successfully.\r
+        failCount = 0;\r
+        return AAI_GET_REPLY_PAYLOAD;\r
+      }\r
+    }\r
+  }\r
+\r
+\r
+  /**\r
+   * This class overrides the behaviour of the {@link RestClientBuilder} used by the\r
+   * {@link RestClient} to just return the in memory client provided by the JerseyTest framework.\r
+   */\r
+  private class TestRestClientBuilder extends RestClientBuilder {\r
+\r
+    @Override\r
+    public Client getClient() throws Exception {\r
+      return client();\r
+    }\r
+  }\r
+}\r
diff --git a/src/test/java/org/openecomp/restclient/rest/RestClientBuilderTest.java b/src/test/java/org/openecomp/restclient/rest/RestClientBuilderTest.java
new file mode 100644 (file)
index 0000000..93e5520
--- /dev/null
@@ -0,0 +1,166 @@
+package org.openecomp.restclient.rest;\r
+\r
+import java.util.Map;\r
+\r
+import org.junit.Before;\r
+import org.junit.Test;\r
+import org.openecomp.restclient.rest.RestClientBuilder;\r
+\r
+import static org.junit.Assert.*;\r
+\r
+import com.sun.jersey.api.client.Client;\r
+\r
+import com.sun.jersey.client.urlconnection.HTTPSProperties;\r
+\r
+\r
+/**\r
+ * This suite of tests is intended to exercise the functionality of the generice REST client\r
+ * builder.\r
+ */\r
+public class RestClientBuilderTest {\r
+\r
+  /**\r
+   * This test validates that we can enable and disable certificate chain verification and that the\r
+   * associated parameters are correctly set.\r
+   */\r
+  @Test\r
+  public void certificateChainVerificationTest() throws Exception {\r
+\r
+    final String TRUST_STORE_FILENAME = "myTrustStore";\r
+\r
+\r
+    // Instantiate a RestClientBuilder with default parameters and\r
+    // get a client instance.\r
+    RestClientBuilder builder = new RestClientBuilder();\r
+    Client client = builder.getClient();\r
+\r
+    // Validate that, by default, no trust store has been set.\r
+    assertNull("Trust store filename should not be set for default builder",\r
+        System.getProperty("javax.net.ssl.trustStore"));\r
+\r
+    // Now, enable certificate chain verification, but don't specify\r
+    // a trust store filename.\r
+    builder.setValidateServerCertChain(true);\r
+\r
+    // Now, get a new client instance. We expect the builder to complain\r
+    // because there is no trust store filename.\r
+    try {\r
+      Client client2 = builder.getClient();\r
+      fail("Expected exception due to no trust store filename.");\r
+\r
+    } catch (IllegalArgumentException e) {\r
+      assertTrue(e.getMessage().contains("Trust store filename must be set"));\r
+    }\r
+\r
+    // Now, set a value for the trust store filename and try again to\r
+    // get a client instance. This time it should succeed and we should\r
+    // see that our trust name filename was set.\r
+    builder.setTruststoreFilename(TRUST_STORE_FILENAME);\r
+    Client client3 = builder.getClient();\r
+\r
+    // Validate that the trust store filename was set.\r
+    assertNotNull("Expected trust store filename to be set",\r
+        System.getProperty("javax.net.ssl.trustStore"));\r
+\r
+    // Validate that the filename is set to the value we specified.\r
+    assertTrue(\r
+        "Unexpected trust store filename value " + System.getProperty("javax.net.ssl.trustStore"),\r
+        System.getProperty("javax.net.ssl.trustStore").equals(TRUST_STORE_FILENAME));\r
+  }\r
+\r
+\r
+  /**\r
+   * This test validates that we can set timeout values in our client builder and that those values\r
+   * are reflected in the client produced by the builder.\r
+   */\r
+  @Test\r
+  public void timeoutValuesTest() throws Exception {\r
+\r
+    // Instantiate a RestClientBuilder with default parameters.\r
+    RestClientBuilder builder = new RestClientBuilder();\r
+\r
+    // Now, get a client instance and retrieve the client properties.\r
+    Client client = builder.getClient();\r
+\r
+    Map<String, Object> props = client.getProperties();\r
+\r
+    // Validate that the connection and read timeouts are set to the\r
+    // default values.\r
+    assertEquals("Unexpected connect timeout parameter",\r
+        props.get("com.sun.jersey.client.property.connectTimeout"),\r
+        RestClientBuilder.DEFAULT_CONNECT_TIMEOUT_MS);\r
+    assertEquals("Unexpected read timeout parameter",\r
+        props.get("com.sun.jersey.client.property.readTimeout"),\r
+        RestClientBuilder.DEFAULT_READ_TIMEOUT_MS);\r
+\r
+    // Now, change the timeouts in the builder to non-default values.\r
+    builder.setConnectTimeoutInMs(RestClientBuilder.DEFAULT_CONNECT_TIMEOUT_MS + 100);\r
+    builder.setReadTimeoutInMs(RestClientBuilder.DEFAULT_READ_TIMEOUT_MS + 100);\r
+\r
+    // Retrieve a new client instance and get the client properties.\r
+    Client client2 = builder.getClient();\r
+    props = client2.getProperties();\r
+\r
+    // Validate that the connection and read timeouts are set to the\r
+    // new values.\r
+    assertEquals("Unexpected connect timeout parameter",\r
+        props.get("com.sun.jersey.client.property.connectTimeout"),\r
+        RestClientBuilder.DEFAULT_CONNECT_TIMEOUT_MS + 100);\r
+    assertEquals("Unexpected read timeout parameter",\r
+        props.get("com.sun.jersey.client.property.readTimeout"),\r
+        RestClientBuilder.DEFAULT_READ_TIMEOUT_MS + 100);\r
+  }\r
+\r
+\r
+  /**\r
+   * This test validates that we can enable and disable host name verification in the clients\r
+   * produced by our builder.\r
+   */\r
+  @Test\r
+  public void hostNameVerifierTest() throws Exception {\r
+\r
+    // Instantiate a RestClientBuilder with default parameters.\r
+    RestClientBuilder builder = new RestClientBuilder();\r
+\r
+    // Now, get a client instance.\r
+    Client client1 = builder.getClient();\r
+\r
+    // Retrieve the client's HTTPS properties.\r
+    HTTPSProperties httpProps = getHTTPSProperties(client1);\r
+\r
+    // By default, hostname verification should be disabled, which means\r
+    // that our builder will have injected its own {@link HostnameVerifier}\r
+    // which just always returns true.\r
+    assertNotNull(httpProps.getHostnameVerifier());\r
+\r
+    // Verify that the host name verifier returns true regardless of what\r
+    // hostname we pass in.\r
+    assertTrue("Default hostname verifier should always return true",\r
+        httpProps.getHostnameVerifier().verify("not_a_valid_hostname", null));\r
+\r
+\r
+    // Now, enable hostname verification for our client builder, and\r
+    // get a new client.\r
+    builder.setValidateServerHostname(true);\r
+    Client client2 = builder.getClient();\r
+\r
+    // Retrieve the client's HTTPS properties.\r
+    httpProps = getHTTPSProperties(client2);\r
+\r
+    // Verify that with hostname verification enabled, our builder did not\r
+    // insert its own stubbed verifier.\r
+    assertNull(httpProps.getHostnameVerifier());\r
+  }\r
+\r
+\r
+  /**\r
+   * This is a convenience method which extracts the HTTPS properties from a supplied client.\r
+   *\r
+   * @parameter aClient - The client to retrieve the HTTPS properties from.\r
+   */\r
+  private HTTPSProperties getHTTPSProperties(Client aClient) {\r
+\r
+    Map<String, Object> props = aClient.getProperties();\r
+    return (HTTPSProperties) props.get(HTTPSProperties.PROPERTY_HTTPS_PROPERTIES);\r
+  }\r
+}\r
diff --git a/version.properties b/version.properties
new file mode 100644 (file)
index 0000000..2ccc7c2
--- /dev/null
@@ -0,0 +1,13 @@
+# Versioning variables
+# Note that these variables cannot be structured (e.g. : version.release or version.snapshot etc... )
+# because they are used in Jenkins, whose plug-in doesn't support
+
+major=0
+minor=9
+patch=0
+
+base_version=${major}.${minor}.${patch}
+
+# Release must be completed with git revision # in Jenkins
+release_version=${base_version}
+snapshot_version=${base_version}-SNAPSHOT
\ No newline at end of file