summaryrefslogtreecommitdiffstats
path: root/openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-api
diff options
context:
space:
mode:
authorolegb <olegb@amdocs.com>2018-02-19 16:24:54 +0200
committerOren Kleks <orenkle@amdocs.com>2018-03-26 08:29:36 +0000
commitf2cee7829ae7d8fae58239dd0018b2aa790c0251 (patch)
treeef6ca1bd45d5364299020b6858f61584bfd59239 /openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-api
parent365d1c3f7db39eeec66437cf9f6c77bcce729819 (diff)
Added JAX-RS filters for logging
Change-Id: I57ab333278d4ea26530e916ef89670df8b51b555 Issue-ID: SDC-772 Signed-off-by: olegb <olegb@amdocs.com> Signed-off-by: vempo <vitaliy.emporopulo@amdocs.com>
Diffstat (limited to 'openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-api')
-rw-r--r--openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-api/pom.xml37
-rw-r--r--openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-api/src/main/java/org/openecomp/sdc/logging/LoggingConstants.java40
-rw-r--r--openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-api/src/main/java/org/openecomp/sdc/logging/api/ServiceBinder.java22
-rw-r--r--openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-api/src/main/java/org/openecomp/sdc/logging/servlet/HttpHeader.java55
-rw-r--r--openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-api/src/main/java/org/openecomp/sdc/logging/servlet/LoggingFilter.java136
-rw-r--r--openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-api/src/main/java/org/openecomp/sdc/logging/servlet/jaxrs/LoggingRequestFilter.java118
-rw-r--r--openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-api/src/main/java/org/openecomp/sdc/logging/servlet/jaxrs/LoggingResponseFilter.java147
-rw-r--r--openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-api/src/test/java/org/openecomp/sdc/logging/servlet/LoggingFilterTest.java199
-rw-r--r--openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-api/src/test/java/org/openecomp/sdc/logging/servlet/jaxrs/HttpHeaderTest.java98
-rw-r--r--openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-api/src/test/java/org/openecomp/sdc/logging/servlet/jaxrs/LoggingRequestFilterTest.java262
-rw-r--r--openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-api/src/test/java/org/openecomp/sdc/logging/servlet/jaxrs/LoggingResponseFilterTest.java234
11 files changed, 1335 insertions, 13 deletions
diff --git a/openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-api/pom.xml b/openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-api/pom.xml
index 927f45454f..754a103706 100644
--- a/openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-api/pom.xml
+++ b/openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-api/pom.xml
@@ -15,11 +15,48 @@
<dependencies>
<dependency>
+ <groupId>javax.ws.rs</groupId>
+ <artifactId>javax.ws.rs-api</artifactId>
+ <version>${ws.rs.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>javax.servlet</groupId>
+ <artifactId>servlet-api</artifactId>
+ <version>${servlet-api.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <!-- testing -->
+ <dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>${testng.version}</version>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>org.easymock</groupId>
+ <artifactId>easymock</artifactId>
+ <version>${easymock.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.powermock</groupId>
+ <artifactId>powermock-api-easymock</artifactId>
+ <version>${powermock.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.powermock</groupId>
+ <artifactId>powermock-core</artifactId>
+ <version>${powermock.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.powermock</groupId>
+ <artifactId>powermock-module-testng</artifactId>
+ <version>${powermock.version}</version>
+ <scope>test</scope>
+ </dependency>
</dependencies>
</project>
diff --git a/openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-api/src/main/java/org/openecomp/sdc/logging/LoggingConstants.java b/openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-api/src/main/java/org/openecomp/sdc/logging/LoggingConstants.java
new file mode 100644
index 0000000000..e8635b2418
--- /dev/null
+++ b/openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-api/src/main/java/org/openecomp/sdc/logging/LoggingConstants.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright © 2016-2018 European Support Limited
+ *
+ * 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.
+ */
+
+package org.openecomp.sdc.logging;
+
+/**
+ * Constants that must be visible across the logging API.
+ *
+ * @author evitaliy
+ * @since 18 Mar 2018
+ */
+public class LoggingConstants {
+
+ /**
+ * Default HTTP header for propagation of a request ID for distributed tracing.
+ */
+ public static final String DEFAULT_REQUEST_ID_HEADER = "X-ECOMP-RequestID";
+
+ /**
+ * Default HTTP header for exchanging a partner name between components.
+ */
+ public static final String DEFAULT_PARTNER_NAME_HEADER = "USER_ID";
+
+ private LoggingConstants() {
+ // prevent instantiation of the constants class
+ }
+}
diff --git a/openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-api/src/main/java/org/openecomp/sdc/logging/api/ServiceBinder.java b/openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-api/src/main/java/org/openecomp/sdc/logging/api/ServiceBinder.java
index e3c9ea02de..6706d6fa46 100644
--- a/openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-api/src/main/java/org/openecomp/sdc/logging/api/ServiceBinder.java
+++ b/openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-api/src/main/java/org/openecomp/sdc/logging/api/ServiceBinder.java
@@ -4,9 +4,9 @@
* 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.
@@ -16,25 +16,22 @@
package org.openecomp.sdc.logging.api;
-import org.openecomp.sdc.logging.spi.LoggerCreationService;
-import org.openecomp.sdc.logging.spi.LoggingContextService;
-import org.openecomp.sdc.logging.spi.LoggingServiceProvider;
-
import java.util.Iterator;
import java.util.Optional;
import java.util.ServiceLoader;
+import org.openecomp.sdc.logging.spi.LoggerCreationService;
+import org.openecomp.sdc.logging.spi.LoggingContextService;
+import org.openecomp.sdc.logging.spi.LoggingServiceProvider;
/**
* <p>Binds to a concrete implementation of logging services.</p>
- *
* <p>In order to use the factory, a particular (e.g. framework-specific) implementation of a service must be
* configured as described in
* <a href="http://docs.oracle.com/javase/8/docs/api/java/util/ServiceLoader.html">java.util.ServiceLoader</a>).</p>
*
* @author evitaliy
- * @since 13/09/2016.
- *
* @see ServiceLoader
+ * @since 13 Sep 2016
*/
// No advanced logging can be used here because we don't know
@@ -44,7 +41,7 @@ class ServiceBinder {
private static final LoggingServiceProvider PROVIDER = lookupProvider();
- private ServiceBinder () {
+ private ServiceBinder() {
// prevent instantiation
}
@@ -54,9 +51,8 @@ class ServiceBinder {
Iterator<LoggingServiceProvider> iterator = loader.iterator();
if (!iterator.hasNext()) {
- System.err.printf("[ERROR] No provider configured for logging services %s. " +
- "Default implementation will be used.\n",
- LoggingServiceProvider.class.getName());
+ System.err.printf("[ERROR] No provider configured for logging services %s. "
+ + "Default implementation will be used.\n", LoggingServiceProvider.class.getName());
return null;
}
diff --git a/openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-api/src/main/java/org/openecomp/sdc/logging/servlet/HttpHeader.java b/openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-api/src/main/java/org/openecomp/sdc/logging/servlet/HttpHeader.java
new file mode 100644
index 0000000000..4dcc197796
--- /dev/null
+++ b/openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-api/src/main/java/org/openecomp/sdc/logging/servlet/HttpHeader.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright © 2016-2018 European Support Limited
+ *
+ * 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.
+ */
+
+package org.openecomp.sdc.logging.servlet;
+
+import java.util.function.Function;
+
+/**
+ * Handles any of possible header names to read a value for that header. This is useful for backward compatibility, if
+ * multiple headers may have the same meaning. For instance, when requests come from multiple service, some using an old
+ * header and others using a new header to pass the same information.
+ *
+ * @author evitaliy
+ * @since 25 Mar 2018
+ */
+public class HttpHeader {
+
+ private final String[] keys;
+
+ public HttpHeader(String... keys) {
+ this.keys = keys;
+ }
+
+ /**
+ * Returns the value of any of the possible headers.
+ *
+ * @param reader function for reading an HTTP header.
+ * @return value or null if not found
+ */
+ public String getAny(Function<String, String> reader) {
+
+ for (String k : keys) {
+
+ String value = reader.apply(k);
+ if (value != null) {
+ return value;
+ }
+ }
+
+ return null;
+ }
+}
diff --git a/openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-api/src/main/java/org/openecomp/sdc/logging/servlet/LoggingFilter.java b/openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-api/src/main/java/org/openecomp/sdc/logging/servlet/LoggingFilter.java
new file mode 100644
index 0000000000..e53f28119c
--- /dev/null
+++ b/openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-api/src/main/java/org/openecomp/sdc/logging/servlet/LoggingFilter.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright © 2016-2018 European Support Limited
+ *
+ * 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.
+ */
+
+package org.openecomp.sdc.logging.servlet;
+
+import static org.openecomp.sdc.logging.LoggingConstants.DEFAULT_PARTNER_NAME_HEADER;
+import static org.openecomp.sdc.logging.LoggingConstants.DEFAULT_REQUEST_ID_HEADER;
+
+import java.io.IOException;
+import java.util.UUID;
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import org.openecomp.sdc.logging.api.ContextData;
+import org.openecomp.sdc.logging.api.Logger;
+import org.openecomp.sdc.logging.api.LoggerFactory;
+import org.openecomp.sdc.logging.api.LoggingContext;
+import org.openecomp.sdc.logging.servlet.jaxrs.LoggingRequestFilter;
+import org.openecomp.sdc.logging.servlet.jaxrs.LoggingResponseFilter;
+
+/**
+ * <p>Places logging context information. Must be configured as a servlet filter in <i>web.xml</i>. The behavior can be
+ * customized via init-params.</p>
+ * <p>Example:</p>
+ * <pre>
+ *
+ * &lt;filter&gt;
+ * &lt;filter-name&gt;LoggingServletFilter&lt;/filter-name&gt;
+ * &lt;filter-class&gt;org.openecomp.sdc.logging.servlet.LoggingFilter&lt;/filter-class&gt;
+ * &lt;init-param&gt;
+ * &lt;param-name&gt;requestIdHeaders&lt;/param-name&gt;
+ * &lt;param-value&gt;X-ONAP-RequestID&lt;/param-value&gt;
+ * &lt;/init-param&gt;
+ * &lt;init-param&gt;
+ * &lt;param-name&gt;partnerNameHeaders&lt;/param-name&gt;
+ * &lt;param-value&gt;USER_ID&lt;/param-value&gt;
+ * &lt;/init-param&gt;
+ * &lt;/filter&gt;
+ *
+ * &lt;filter-mapping&gt;
+ * &lt;filter-name&gt;LoggingServletFilter&lt;/filter-name&gt;
+ * &lt;url-pattern&gt;/*&lt;/url-pattern&gt;
+ * &lt;/filter-mapping&gt;
+ *
+ * </pre>
+ *
+ * @author evitaliy
+ * @since 25 Jul 2016
+ * @deprecated Kept for backward compatibility. For JAX-RS application, use
+ * {@link LoggingRequestFilter} and
+ * {@link LoggingResponseFilter} instead.
+ */
+@Deprecated
+public class LoggingFilter implements Filter {
+
+ static final String MULTI_VALUE_SEPARATOR = ",";
+
+ static final String REQUEST_ID_HEADERS_PARAM = "requestIdHeaders";
+ static final String PARTNER_NAME_HEADERS_PARAM = "partnerNameHeaders";
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(LoggingFilter.class);
+
+ private HttpHeader requestIdHeaders;
+ private HttpHeader partnerNameHeaders;
+
+ @Override
+ public void init(FilterConfig config) {
+ requestIdHeaders = getInitParam(config, REQUEST_ID_HEADERS_PARAM, DEFAULT_REQUEST_ID_HEADER);
+ partnerNameHeaders = getInitParam(config, PARTNER_NAME_HEADERS_PARAM, DEFAULT_PARTNER_NAME_HEADER);
+ }
+
+ @Override
+ public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
+ throws IOException, ServletException {
+
+ HttpServletRequest httpRequest = HttpServletRequest.class.cast(request);
+
+ try {
+
+ LoggingContext.clear();
+
+ ContextData.ContextDataBuilder contextData = ContextData.builder();
+
+ contextData.serviceName(httpRequest.getRequestURI());
+
+ String requestId = requestIdHeaders.getAny(httpRequest::getHeader);
+ contextData.requestId(requestId == null ? UUID.randomUUID().toString() : requestId);
+
+ String partner = partnerNameHeaders.getAny(httpRequest::getHeader);
+ if (partner != null) {
+ contextData.partnerName(partner);
+ }
+
+ LoggingContext.put(contextData.build());
+
+ chain.doFilter(request, response);
+
+ } finally {
+ LoggingContext.clear();
+ }
+ }
+
+ @Override
+ public void destroy() {
+ // forced by the interface - not implemented
+ }
+
+ private HttpHeader getInitParam(FilterConfig config, String paramName, String defaultValue) {
+
+ String value = config.getInitParameter(paramName);
+ LOGGER.debug("Logging filter configuration param '{}' value '{}'", paramName, value);
+
+ if (value == null) {
+ return new HttpHeader(defaultValue);
+ } else {
+ return new HttpHeader(value.split(MULTI_VALUE_SEPARATOR));
+ }
+ }
+}
diff --git a/openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-api/src/main/java/org/openecomp/sdc/logging/servlet/jaxrs/LoggingRequestFilter.java b/openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-api/src/main/java/org/openecomp/sdc/logging/servlet/jaxrs/LoggingRequestFilter.java
new file mode 100644
index 0000000000..06660f35f4
--- /dev/null
+++ b/openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-api/src/main/java/org/openecomp/sdc/logging/servlet/jaxrs/LoggingRequestFilter.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright © 2016-2018 European Support Limited
+ *
+ * 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.
+ */
+
+package org.openecomp.sdc.logging.servlet.jaxrs;
+
+import static org.openecomp.sdc.logging.LoggingConstants.DEFAULT_PARTNER_NAME_HEADER;
+import static org.openecomp.sdc.logging.LoggingConstants.DEFAULT_REQUEST_ID_HEADER;
+
+import java.util.UUID;
+import javax.ws.rs.container.ContainerRequestContext;
+import javax.ws.rs.container.ContainerRequestFilter;
+import javax.ws.rs.container.ResourceInfo;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.ext.Provider;
+import org.openecomp.sdc.logging.api.ContextData;
+import org.openecomp.sdc.logging.api.LoggingContext;
+import org.openecomp.sdc.logging.servlet.HttpHeader;
+
+/**
+ * <p>Takes care of logging initialization an HTTP request hits the application. This includes populating logging
+ * context and storing the request processing start time, so that it can be used for audit. The filter was built
+ * <b>works in tandem</b> with {@link LoggingResponseFilter} or a similar implementation.</p>
+ * <p>The filter requires a few HTTP header names to be configured. These HTTP headers are used for propagating logging
+ * and tracing information between ONAP components.</p>
+ * <p>Sample configuration for a Spring environment:</p>
+ * <pre>
+ * &lt;jaxrs:providers&gt;
+ * &lt;bean class="org.openecomp.sdc.logging.ws.rs.LoggingRequestFilter"&gt;
+ * &lt;property name="requestIdHeaders" value="X-ONAP-RequestID"/&gt;
+ * &lt;property name="partnerNameHeaders" value="X-ONAP-InstanceID"/&gt;
+ * &lt;/bean&gt;
+ * &lt;/jaxrs:providers&gt;
+ * </pre>
+ * <p>Keep in mind that the filters does nothing in case when a request cannot be mapped to a working JAX-RS resource
+ * (implementation). For instance, when the path is invalid (404), or there is no handler for a particular method (405).
+ * </p>
+ *
+ * @author evitaliy, katyr
+ * @since 29 Oct 17
+ *
+ * @see ContainerRequestFilter
+ */
+@Provider
+public class LoggingRequestFilter implements ContainerRequestFilter {
+
+ static final String MULTI_VALUE_SEPARATOR = ",";
+
+ static final String START_TIME_KEY = "audit.start.time";
+
+ private ResourceInfo resource;
+
+ private HttpHeader requestIdHeader = new HttpHeader(DEFAULT_REQUEST_ID_HEADER);
+ private HttpHeader partnerNameHeader = new HttpHeader(DEFAULT_PARTNER_NAME_HEADER);
+
+ /**
+ * Injection of a resource that matches the request from JAX-RS context.
+ *
+ * @param resource automatically injected by JAX-RS container
+ */
+ @Context
+ public void setResource(ResourceInfo resource) {
+ this.resource = resource;
+ }
+
+ /**
+ * Configuration parameter for request ID HTTP header.
+ */
+ public void setRequestIdHeaders(String requestIdHeaders) {
+ this.requestIdHeader = new HttpHeader(requestIdHeaders.split(MULTI_VALUE_SEPARATOR));
+ }
+
+ /**
+ * Configuration parameter for partner name HTTP header.
+ */
+ public void setPartnerNameHeaders(String partnerNameHeaders) {
+ this.partnerNameHeader = new HttpHeader(partnerNameHeaders.split(MULTI_VALUE_SEPARATOR));
+ }
+
+ @Override
+ public void filter(ContainerRequestContext containerRequestContext) {
+
+ if (resource == null) {
+ // JAX-RS could not find a mapping this response, probably due to HTTP 404 (not found),
+ // 405 (method not allowed), 415 (unsupported media type), etc. with a message in Web server log
+ return;
+ }
+
+ containerRequestContext.setProperty(START_TIME_KEY, System.currentTimeMillis());
+
+ LoggingContext.clear();
+
+ ContextData.ContextDataBuilder contextData = ContextData.builder();
+ contextData.serviceName(resource.getResourceClass().getName() + "." + resource.getResourceMethod().getName());
+
+ String partnerName = partnerNameHeader.getAny(containerRequestContext::getHeaderString);
+ if (partnerName != null) {
+ contextData.partnerName(partnerName);
+ }
+
+ String requestId = requestIdHeader.getAny(containerRequestContext::getHeaderString);
+ contextData.requestId(requestId == null ? UUID.randomUUID().toString() : requestId);
+
+ LoggingContext.put(contextData.build());
+ }
+}
diff --git a/openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-api/src/main/java/org/openecomp/sdc/logging/servlet/jaxrs/LoggingResponseFilter.java b/openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-api/src/main/java/org/openecomp/sdc/logging/servlet/jaxrs/LoggingResponseFilter.java
new file mode 100644
index 0000000000..fbe28a79eb
--- /dev/null
+++ b/openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-api/src/main/java/org/openecomp/sdc/logging/servlet/jaxrs/LoggingResponseFilter.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright © 2016-2018 European Support Limited
+ *
+ * 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.
+ */
+
+package org.openecomp.sdc.logging.servlet.jaxrs;
+
+import static org.openecomp.sdc.logging.api.StatusCode.COMPLETE;
+import static org.openecomp.sdc.logging.api.StatusCode.ERROR;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.ws.rs.container.ContainerRequestContext;
+import javax.ws.rs.container.ContainerResponseContext;
+import javax.ws.rs.container.ContainerResponseFilter;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.ext.Provider;
+import org.openecomp.sdc.logging.api.AuditData;
+import org.openecomp.sdc.logging.api.Logger;
+import org.openecomp.sdc.logging.api.LoggerFactory;
+import org.openecomp.sdc.logging.api.LoggingContext;
+import org.openecomp.sdc.logging.api.StatusCode;
+
+/**
+ * <p>Takes care of logging when an HTTP request leaves the application. This includes writing to audit and clearing
+ * logging context. This filter <b>only works properly in tandem</b> with {@link LoggingRequestFilter} or a similar
+ * implementation.</p>
+ * <p>Sample configuration for a Spring environment:</p>
+ * <pre>
+ * &lt;jaxrs:providers&gt;
+ * &lt;bean class="org.openecomp.sdc.logging.ws.rs.LoggingResponseFilter"/&gt;
+ * &lt;/jaxrs:providers&gt;
+ * </pre>
+ * <p><i>It is highly recommended to configure a custom JAX-RS exception mapper so that this filter will not be bypassed
+ * due to unhandled application or container exceptions.</i></p>
+ *
+ * @author evitaliy
+ * @since 29 Oct 17
+ *
+ * @see ContainerResponseFilter
+ */
+@Provider
+public class LoggingResponseFilter implements ContainerResponseFilter {
+
+ private final Logger logger = LoggerFactory.getLogger(this.getClass());
+
+ /**
+ * Tracks reporting configuration problems to the log. We want to report them only once, and not to write to log
+ * upon every request, as the configuration will not change in runtime.
+ */
+ private boolean reportBadConfiguration = true;
+
+ private HttpServletRequest httpRequest;
+
+ /**
+ * Injection of HTTP request object from JAX-RS context.
+ *
+ * @param httpRequest automatically injected by JAX-RS container
+ */
+ @Context
+ public void setHttpRequest(HttpServletRequest httpRequest) {
+ this.httpRequest = httpRequest;
+ }
+
+ @Override
+ public void filter(ContainerRequestContext containerRequestContext,
+ ContainerResponseContext containerResponseContext) {
+
+ try {
+ writeAudit(containerRequestContext, containerResponseContext);
+ } finally {
+ LoggingContext.clear();
+ }
+ }
+
+ private void writeAudit(ContainerRequestContext containerRequestContext,
+ ContainerResponseContext containerResponseContext) {
+
+ if (!logger.isAuditEnabled()) {
+ return;
+ }
+
+ long start = readStartTime(containerRequestContext);
+ long end = System.currentTimeMillis();
+
+ Response.StatusType statusInfo = containerResponseContext.getStatusInfo();
+ int responseCode = statusInfo.getStatusCode();
+ StatusCode statusCode = isSuccess(responseCode) ? COMPLETE : ERROR;
+
+ AuditData auditData = AuditData.builder().startTime(start).endTime(end).statusCode(statusCode)
+ .responseCode(Integer.toString(responseCode))
+ .responseDescription(statusInfo.getReasonPhrase())
+ .clientIpAddress(httpRequest.getRemoteAddr()).build();
+ logger.audit(auditData);
+ }
+
+ private boolean isSuccess(int responseCode) {
+ return responseCode > 199 && responseCode < 400;
+ }
+
+ private long readStartTime(ContainerRequestContext containerRequestContext) {
+
+ Object startTime = containerRequestContext.getProperty(LoggingRequestFilter.START_TIME_KEY);
+ if (startTime == null) {
+ return handleMissingStartTime();
+ }
+
+ return parseStartTime(startTime);
+ }
+
+ private long handleMissingStartTime() {
+ reportConfigProblem("{} key was not found in JAX-RS request context. "
+ + "Make sure you configured a request filter", LoggingRequestFilter.START_TIME_KEY);
+ return 0;
+ }
+
+ private long parseStartTime(Object startTime) {
+
+ try {
+ return Long.class.cast(startTime);
+ } catch (ClassCastException e) {
+ reportConfigProblem("{} key in JAX-RS request context contains an object of type '{}', but 'java.lang.Long'"
+ + " is expected", LoggingRequestFilter.START_TIME_KEY, startTime.getClass().getName());
+ return 0;
+ }
+ }
+
+ private void reportConfigProblem(String message, Object... arguments) {
+
+ if (reportBadConfiguration) {
+ reportBadConfiguration = false;
+ logger.error(message, arguments);
+ }
+ }
+}
+
diff --git a/openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-api/src/test/java/org/openecomp/sdc/logging/servlet/LoggingFilterTest.java b/openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-api/src/test/java/org/openecomp/sdc/logging/servlet/LoggingFilterTest.java
new file mode 100644
index 0000000000..535a50ca42
--- /dev/null
+++ b/openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-api/src/test/java/org/openecomp/sdc/logging/servlet/LoggingFilterTest.java
@@ -0,0 +1,199 @@
+/*
+ * Copyright © 2016-2018 European Support Limited
+ *
+ * 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.
+ */
+
+package org.openecomp.sdc.logging.servlet;
+
+import static org.easymock.EasyMock.anyObject;
+import static org.openecomp.sdc.logging.LoggingConstants.DEFAULT_PARTNER_NAME_HEADER;
+import static org.openecomp.sdc.logging.LoggingConstants.DEFAULT_REQUEST_ID_HEADER;
+import static org.openecomp.sdc.logging.servlet.LoggingFilter.PARTNER_NAME_HEADERS_PARAM;
+import static org.openecomp.sdc.logging.servlet.LoggingFilter.REQUEST_ID_HEADERS_PARAM;
+
+import java.io.IOException;
+import java.util.UUID;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.easymock.EasyMock;
+import org.openecomp.sdc.logging.api.ContextData;
+import org.openecomp.sdc.logging.api.LoggingContext;
+import org.powermock.api.easymock.PowerMock;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.modules.testng.PowerMockTestCase;
+import org.testng.ITestResult;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.Test;
+
+/**
+ * Unit-tests logging filter for initialization and data retrieval.
+ *
+ * @author evitaliy
+ * @since 17 Aug 2016
+ */
+@PrepareForTest(LoggingContext.class)
+public class LoggingFilterTest extends PowerMockTestCase {
+
+ private static final String RANDOM_REQUEST_URI = UUID.randomUUID().toString();
+ private static final String RANDOM_REQUEST_ID = UUID.randomUUID().toString();
+ private static final String RANDOM_PARTNER_NAME = UUID.randomUUID().toString();
+
+ /**
+ * Verify all mocks after each test.
+ */
+ @AfterMethod
+ public void verifyMocks(ITestResult result) {
+
+ try {
+ PowerMock.verifyAll();
+ } catch (AssertionError e) {
+ throw new AssertionError("Expectations failed in: " + result.getMethod().getMethodName(), e);
+ }
+ }
+
+
+ @Test
+ public void filterPopulatesValuesWhenNoInitParamsAndNoHeaders() throws Exception {
+
+ mockLoggingContext();
+ LoggingFilter loggingFilter = new LoggingFilter();
+ loggingFilter.init(mockFilterConfig(null, null));
+ loggingFilter.doFilter(new MockRequestBuilder().build(), mockResponse(), mockChain());
+ }
+
+ @Test
+ public void filterPopulatesValuesWhenNoInitParamsAndExistingHeaders() throws Exception {
+
+ mockLoggingContext();
+
+ LoggingFilter loggingFilter = new LoggingFilter();
+ loggingFilter.init(mockFilterConfig(null, null));
+
+ HttpServletRequest mockRequest = new MockRequestBuilder().partnerName(RANDOM_PARTNER_NAME)
+ .requestId(RANDOM_REQUEST_ID).build();
+ loggingFilter.doFilter(mockRequest, mockResponse(), mockChain());
+ }
+
+ @Test
+ public void filterPopulatesValuesWhenCustomInitParamsAndNoHeaders() throws Exception {
+
+ mockLoggingContext();
+
+ final String requestIdHeader = "x-request";
+ final String partnerNameHeader = "x-partner";
+
+ LoggingFilter loggingFilter = new LoggingFilter();
+ FilterConfig mockConfig = mockFilterConfig(requestIdHeader, partnerNameHeader);
+ loggingFilter.init(mockConfig);
+
+ HttpServletRequest mockRequest = new MockRequestBuilder().requestIdHeader(requestIdHeader)
+ .partnerNameHeader(partnerNameHeader).build();
+ loggingFilter.doFilter(mockRequest, mockResponse(), mockChain());
+ }
+
+ @Test
+ public void filterPopulatesValuesWhenCustomInitParamsAndExistingHeaders() throws Exception {
+
+ mockLoggingContext();
+
+ final String requestIdHeader = "x-request-id";
+ final String partnerNameHeader = "x-partner-name";
+
+ LoggingFilter loggingFilter = new LoggingFilter();
+ FilterConfig mockConfig = mockFilterConfig(requestIdHeader, partnerNameHeader);
+ loggingFilter.init(mockConfig);
+
+ HttpServletRequest mockRequest = new MockRequestBuilder()
+ .partnerNameHeader(partnerNameHeader).partnerName(RANDOM_PARTNER_NAME)
+ .requestIdHeader(requestIdHeader).requestId(RANDOM_REQUEST_ID).build();
+ loggingFilter.doFilter(mockRequest, mockResponse(), mockChain());
+ }
+
+ private FilterConfig mockFilterConfig(String requestIdHeader, String partnerNameHeader) {
+ FilterConfig config = EasyMock.mock(FilterConfig.class);
+ EasyMock.expect(config.getInitParameter(REQUEST_ID_HEADERS_PARAM)).andReturn(requestIdHeader);
+ EasyMock.expect(config.getInitParameter(PARTNER_NAME_HEADERS_PARAM)).andReturn(partnerNameHeader);
+ EasyMock.replay(config);
+ return config;
+ }
+
+ private FilterChain mockChain() throws IOException, ServletException {
+ FilterChain chain = EasyMock.mock(FilterChain.class);
+ chain.doFilter(anyObject(ServletRequest.class), anyObject(ServletResponse.class));
+ EasyMock.expectLastCall().once();
+ EasyMock.replay(chain);
+ return chain;
+ }
+
+ private ServletResponse mockResponse() {
+ HttpServletResponse servletResponse = EasyMock.mock(HttpServletResponse.class);
+ EasyMock.replay(servletResponse);
+ return servletResponse;
+ }
+
+ private void mockLoggingContext() {
+
+ PowerMock.mockStatic(LoggingContext.class);
+
+ LoggingContext.clear();
+ EasyMock.expectLastCall().times(2);
+
+ LoggingContext.put(anyObject(ContextData.class));
+ EasyMock.expectLastCall().once();
+
+ PowerMock.replay(LoggingContext.class);
+ }
+
+ private static class MockRequestBuilder {
+
+ private String requestIdHeader = DEFAULT_REQUEST_ID_HEADER;
+ private String partnerNameHeader = DEFAULT_PARTNER_NAME_HEADER;
+ private String requestId = null;
+ private String partnerName = null;
+
+ MockRequestBuilder requestIdHeader(String h) {
+ this.requestIdHeader = h;
+ return this;
+ }
+
+ MockRequestBuilder requestId(String id) {
+ this.requestId = id;
+ return this;
+ }
+
+ MockRequestBuilder partnerNameHeader(String h) {
+ this.partnerNameHeader = h;
+ return this;
+ }
+
+ MockRequestBuilder partnerName(String name) {
+ this.partnerName = name;
+ return this;
+ }
+
+ HttpServletRequest build() {
+ HttpServletRequest mockRequest = EasyMock.mock(HttpServletRequest.class);
+ EasyMock.expect(mockRequest.getRequestURI()).andReturn(RANDOM_REQUEST_URI);
+ EasyMock.expect(mockRequest.getHeader(requestIdHeader)).andReturn(requestId);
+ EasyMock.expect(mockRequest.getHeader(partnerNameHeader)).andReturn(partnerName);
+ EasyMock.replay(mockRequest);
+ return mockRequest;
+ }
+ }
+}
diff --git a/openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-api/src/test/java/org/openecomp/sdc/logging/servlet/jaxrs/HttpHeaderTest.java b/openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-api/src/test/java/org/openecomp/sdc/logging/servlet/jaxrs/HttpHeaderTest.java
new file mode 100644
index 0000000000..3376c922e4
--- /dev/null
+++ b/openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-api/src/test/java/org/openecomp/sdc/logging/servlet/jaxrs/HttpHeaderTest.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright © 2016-2018 European Support Limited
+ *
+ * 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.
+ */
+
+package org.openecomp.sdc.logging.servlet.jaxrs;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNull;
+
+import java.util.function.Function;
+import org.openecomp.sdc.logging.servlet.HttpHeader;
+import org.testng.annotations.Test;
+
+/**
+ * Unit tests mutliple-option headers.
+ *
+ * @author evitaliy
+ * @since 25 Mar 2018
+ */
+public class HttpHeaderTest {
+
+ @Test
+ public void valueReturnedWhenSinglePossibleHeader() {
+
+ final String key = "Head";
+ final String value = "1234";
+
+ Function<String, String> reader = createReader(key, value);
+ HttpHeader header = new HttpHeader(key);
+ assertEquals(header.getAny(reader), value);
+ }
+
+ @Test
+ public void nullReturnedWhenSingleNoMatchingHeader() {
+
+ final String key = "Head";
+
+ Function<String, String> reader = createReader(key, null);
+ HttpHeader header = new HttpHeader(key);
+ assertNull(header.getAny(reader));
+ }
+
+ @Test
+ public void nullReturnedWhenNoneHeaderMatches() {
+ Function<String, String> reader = createReader("None", "Value");
+ HttpHeader header = new HttpHeader("A", "B", "C");
+ assertNull(header.getAny(reader));
+ }
+
+ @Test
+ public void valueReturnedWhenLastHeaderMatches() {
+
+ final String lastKey = "Last";
+ final String value = "1234";
+
+ Function<String, String> reader = createReader(lastKey, value);
+ HttpHeader header = new HttpHeader("First", "Second", lastKey);
+ assertEquals(header.getAny(reader), value);
+ }
+
+ @Test
+ public void valueReturnedWhenFirstHeaderMatches() {
+
+ final String firstKey = "First";
+ final String value = "1234";
+
+ Function<String, String> reader = createReader(firstKey, value);
+ HttpHeader header = new HttpHeader(firstKey, "Second", "Third");
+ assertEquals(header.getAny(reader), value);
+ }
+
+ @Test
+ public void valueReturnedWhenMiddleHeaderMatches() {
+
+ final String middleKey = "Second";
+ final String value = "1234";
+
+ Function<String, String> reader = createReader(middleKey, value);
+ HttpHeader header = new HttpHeader("First", middleKey, "Third");
+ assertEquals(header.getAny(reader), value);
+ }
+
+ private Function<String, String> createReader(String key, String value) {
+ return h -> h.equals(key) ? value : null;
+ }
+} \ No newline at end of file
diff --git a/openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-api/src/test/java/org/openecomp/sdc/logging/servlet/jaxrs/LoggingRequestFilterTest.java b/openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-api/src/test/java/org/openecomp/sdc/logging/servlet/jaxrs/LoggingRequestFilterTest.java
new file mode 100644
index 0000000000..0e0be017df
--- /dev/null
+++ b/openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-api/src/test/java/org/openecomp/sdc/logging/servlet/jaxrs/LoggingRequestFilterTest.java
@@ -0,0 +1,262 @@
+/*
+ * Copyright © 2016-2018 European Support Limited
+ *
+ * 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.
+ */
+
+package org.openecomp.sdc.logging.servlet.jaxrs;
+
+import static org.easymock.EasyMock.anyObject;
+import static org.easymock.EasyMock.anyString;
+import static org.openecomp.sdc.logging.servlet.jaxrs.LoggingRequestFilter.START_TIME_KEY;
+
+import java.lang.reflect.Method;
+import java.util.UUID;
+import javax.ws.rs.container.ContainerRequestContext;
+import javax.ws.rs.container.ResourceInfo;
+import org.easymock.EasyMock;
+import org.openecomp.sdc.logging.LoggingConstants;
+import org.openecomp.sdc.logging.api.ContextData;
+import org.openecomp.sdc.logging.api.LoggingContext;
+import org.powermock.api.easymock.PowerMock;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.modules.testng.PowerMockTestCase;
+import org.testng.ITestResult;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.Test;
+
+/**
+ * Unit testing JAX-RS request filter.
+ *
+ * @author evitaliy
+ * @since 19 Mar 2018
+ */
+@PrepareForTest({LoggingContext.class, ContextData.class})
+public class LoggingRequestFilterTest extends PowerMockTestCase {
+
+ private static final Class RESOURCE_CLASS = MockResource.class;
+ private static final Method RESOURCE_METHOD = MockResource.class.getDeclaredMethods()[0];
+ private static final String RESOURCE_NAME = RESOURCE_CLASS.getName() + "." + RESOURCE_METHOD.getName();
+
+ private static final String RANDOM_REQUEST_ID = UUID.randomUUID().toString();
+ private static final String RANDOM_PARTNER_NAME = UUID.randomUUID().toString();
+
+ /**
+ * Verify all mocks after each test.
+ */
+ @AfterMethod
+ public void verifyMocks(ITestResult result) {
+
+ try {
+ PowerMock.verifyAll();
+ } catch (AssertionError e) {
+ throw new AssertionError("Expectations failed in: " + result.getMethod().getMethodName(), e);
+ }
+ }
+
+ @Test
+ public void notHandledWhenNoMatchingResource() {
+
+ PowerMock.mockStatic(LoggingContext.class);
+ PowerMock.replay(LoggingContext.class);
+
+ new LoggingRequestFilter().filter(mockEmptyContainerRequestContext());
+ }
+
+ @Test
+ public void serviceNamePopulatedWhenThereIsMatchingResource() {
+
+ mockContextDataBuilder(null, RESOURCE_NAME, null);
+ mockLoggingContext();
+
+ LoggingRequestFilter filter = new LoggingRequestFilter();
+ filter.setResource(mockResource());
+
+ filter.filter(mockContainerRequestContext(
+ new RequestIdHeader(null),
+ new PartnerHeader(null)));
+ }
+
+ @Test
+ public void partnerNamePopulatedWhenPresentInDefaultHeader() {
+
+ mockContextDataBuilder(null, RESOURCE_NAME, RANDOM_PARTNER_NAME);
+ mockLoggingContext();
+
+ LoggingRequestFilter filter = new LoggingRequestFilter();
+ filter.setResource(mockResource());
+
+ filter.filter(mockContainerRequestContext(
+ new RequestIdHeader(null),
+ new PartnerHeader(RANDOM_PARTNER_NAME)));
+ }
+
+ @Test
+ public void partnerNamePopulatedWhenPresentInCustomHeader() {
+
+ final String partnerHeader = "x-partner-header";
+ mockContextDataBuilder(null, RESOURCE_NAME, RANDOM_PARTNER_NAME);
+ mockLoggingContext();
+
+ LoggingRequestFilter filter = new LoggingRequestFilter();
+ filter.setResource(mockResource());
+ filter.setPartnerNameHeaders(partnerHeader);
+
+ filter.filter(mockContainerRequestContext(
+ new RequestIdHeader(null),
+ new PartnerHeader(partnerHeader, RANDOM_PARTNER_NAME)));
+ }
+
+ @Test
+ public void requestIdPopulatedWhenPresentInDefaultHeader() {
+
+ mockContextDataBuilder(RANDOM_REQUEST_ID, RESOURCE_NAME, null);
+ mockLoggingContext();
+
+ LoggingRequestFilter filter = new LoggingRequestFilter();
+ filter.setResource(mockResource());
+
+ filter.filter(mockContainerRequestContext(
+ new RequestIdHeader(RANDOM_REQUEST_ID),
+ new PartnerHeader(null)));
+ }
+
+ @Test
+ public void requestIdPopulatedWhenPresentInCustomHeader() {
+
+ final String requestIdHeader = "x-request-id";
+ mockContextDataBuilder(RANDOM_REQUEST_ID, RESOURCE_NAME, null);
+ mockLoggingContext();
+
+ LoggingRequestFilter filter = new LoggingRequestFilter();
+ filter.setResource(mockResource());
+ filter.setRequestIdHeaders(requestIdHeader);
+
+ filter.filter(mockContainerRequestContext(
+ new RequestIdHeader(requestIdHeader, RANDOM_REQUEST_ID),
+ new PartnerHeader(null)));
+ }
+
+ private ResourceInfo mockResource() {
+ ResourceInfo resource = EasyMock.mock(ResourceInfo.class);
+ //noinspection unchecked
+ EasyMock.expect(resource.getResourceClass()).andReturn(RESOURCE_CLASS);
+ EasyMock.expect(resource.getResourceMethod()).andReturn(RESOURCE_METHOD);
+ EasyMock.replay(resource);
+ return resource;
+ }
+
+ private ContainerRequestContext mockEmptyContainerRequestContext() {
+ ContainerRequestContext requestContext = EasyMock.mock(ContainerRequestContext.class);
+ EasyMock.replay(requestContext);
+ return requestContext;
+ }
+
+ private ContainerRequestContext mockContainerRequestContext(Header... headers) {
+
+ ContainerRequestContext requestContext = EasyMock.mock(ContainerRequestContext.class);
+
+ for (Header h : headers) {
+ EasyMock.expect(requestContext.getHeaderString(h.key)).andReturn(h.value);
+ }
+
+ requestContext.setProperty(EasyMock.eq(START_TIME_KEY), EasyMock.anyLong());
+ EasyMock.expectLastCall();
+
+ EasyMock.replay(requestContext);
+ return requestContext;
+ }
+
+ private void mockContextDataBuilder(String requestId, String serviceName, String partnerName) {
+
+ ContextData.ContextDataBuilder mockBuilder = EasyMock.mock(ContextData.ContextDataBuilder.class);
+
+ if (requestId != null) {
+ EasyMock.expect(mockBuilder.requestId(requestId)).andReturn(mockBuilder);
+ } else {
+ EasyMock.expect(mockBuilder.requestId(anyString())).andReturn(mockBuilder);
+ }
+
+ if (serviceName != null) {
+ EasyMock.expect(mockBuilder.serviceName(serviceName)).andReturn(mockBuilder);
+ }
+
+ if (partnerName != null) {
+ EasyMock.expect(mockBuilder.partnerName(partnerName)).andReturn(mockBuilder);
+ }
+
+ EasyMock.expect(mockBuilder.build()).andReturn(EasyMock.mock(ContextData.class));
+ EasyMock.replay(mockBuilder);
+
+ PowerMock.mockStatic(ContextData.class);
+
+ ContextData.builder();
+ PowerMock.expectLastCall().andReturn(mockBuilder);
+
+ PowerMock.replay(ContextData.class);
+ }
+
+ private void mockLoggingContext() {
+
+ PowerMock.mockStatic(LoggingContext.class);
+
+ LoggingContext.clear();
+ EasyMock.expectLastCall().once();
+
+ LoggingContext.put(anyObject(ContextData.class));
+ EasyMock.expectLastCall().once();
+
+ PowerMock.replay(LoggingContext.class);
+ }
+
+ private abstract static class Header {
+
+ private final String key;
+ private final String value;
+
+ private Header(String key, String value) {
+ this.key = key;
+ this.value = value;
+ }
+ }
+
+ private static class PartnerHeader extends Header {
+
+ private PartnerHeader(String value) {
+ super(LoggingConstants.DEFAULT_PARTNER_NAME_HEADER, value);
+ }
+
+ private PartnerHeader(String key, String value) {
+ super(key, value);
+ }
+ }
+
+ private static class RequestIdHeader extends Header {
+
+ private RequestIdHeader(String value) {
+ super(LoggingConstants.DEFAULT_REQUEST_ID_HEADER, value);
+ }
+
+ private RequestIdHeader(String key, String value) {
+ super(key, value);
+ }
+ }
+
+ private static class MockResource {
+
+ @SuppressWarnings("EmptyMethod")
+ void process() {
+ // no-op
+ }
+ }
+} \ No newline at end of file
diff --git a/openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-api/src/test/java/org/openecomp/sdc/logging/servlet/jaxrs/LoggingResponseFilterTest.java b/openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-api/src/test/java/org/openecomp/sdc/logging/servlet/jaxrs/LoggingResponseFilterTest.java
new file mode 100644
index 0000000000..37627d5d5a
--- /dev/null
+++ b/openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-api/src/test/java/org/openecomp/sdc/logging/servlet/jaxrs/LoggingResponseFilterTest.java
@@ -0,0 +1,234 @@
+/*
+ * Copyright © 2016-2018 European Support Limited
+ *
+ * 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.
+ */
+
+package org.openecomp.sdc.logging.servlet.jaxrs;
+
+import static org.easymock.EasyMock.anyObject;
+import static org.easymock.EasyMock.anyString;
+
+import java.util.Comparator;
+import java.util.Objects;
+import java.util.function.Consumer;
+import javax.servlet.http.HttpServletRequest;
+import javax.ws.rs.container.ContainerRequestContext;
+import javax.ws.rs.container.ContainerResponseContext;
+import javax.ws.rs.core.Response;
+import org.easymock.EasyMock;
+import org.easymock.LogicalOperator;
+import org.openecomp.sdc.logging.api.AuditData;
+import org.openecomp.sdc.logging.api.Logger;
+import org.openecomp.sdc.logging.api.LoggerFactory;
+import org.openecomp.sdc.logging.api.LoggingContext;
+import org.openecomp.sdc.logging.api.StatusCode;
+import org.powermock.api.easymock.PowerMock;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.modules.testng.PowerMockTestCase;
+import org.testng.ITestResult;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.Test;
+
+
+/**
+ * Unit testing JAX-RS response filter.
+ *
+ * @author evitaliy
+ * @since 19 Mar 2018
+ */
+@PrepareForTest({LoggingContext.class, LoggerFactory.class})
+public class LoggingResponseFilterTest extends PowerMockTestCase {
+
+ /**
+ * Verify all mocks after each test.
+ */
+ @AfterMethod
+ public void verifyMocks(ITestResult result) {
+
+ try {
+ PowerMock.verifyAll();
+ } catch (AssertionError e) {
+ throw new AssertionError("Expectations failed in: " + result.getMethod().getMethodName(), e);
+ }
+ }
+
+ @Test
+ public void noAuditWhenAuditDisabled() {
+ mockLogger(false, AuditData.builder().build());
+ mockLoggingContext();
+ new LoggingResponseFilter().filter(mockDisabledRequestContext(), mockDisabledResponseContext());
+ }
+
+ private void mockLogger(boolean enabled, AuditData auditData, Consumer<Logger>... additionalMockings) {
+
+ Logger logger = EasyMock.mock(Logger.class);
+
+ EasyMock.expect(logger.isAuditEnabled()).andReturn(enabled).atLeastOnce();
+
+ if (enabled) {
+ logger.audit(EasyMock.cmp(auditData, new AuditDataComparator(), LogicalOperator.EQUAL));
+ EasyMock.expectLastCall();
+ }
+
+ for (Consumer<Logger> mocking : additionalMockings) {
+ mocking.accept(logger);
+ }
+
+ EasyMock.replay(logger);
+
+ PowerMock.mockStatic(LoggerFactory.class);
+ LoggerFactory.getLogger(LoggingResponseFilter.class);
+ PowerMock.expectLastCall().andReturn(logger);
+ PowerMock.replay(LoggerFactory.class);
+ }
+
+ private void mockLoggingContext() {
+ PowerMock.mockStatic(LoggingContext.class);
+ LoggingContext.clear();
+ EasyMock.expectLastCall().once();
+ PowerMock.replay(LoggingContext.class);
+ }
+
+ private ContainerRequestContext mockDisabledRequestContext() {
+ ContainerRequestContext requestContext = EasyMock.mock(ContainerRequestContext.class);
+ EasyMock.replay(requestContext);
+ return requestContext;
+ }
+
+ private ContainerResponseContext mockDisabledResponseContext() {
+ ContainerResponseContext responseContext = EasyMock.mock(ContainerResponseContext.class);
+ EasyMock.replay(responseContext);
+ return responseContext;
+ }
+
+ @Test
+ public void startTimeReadWhenPresentInRequestContext() {
+
+ final String clientIp = "10.56.56.10";
+ final long startTime = 12345L;
+ final Response.Status ok = Response.Status.OK;
+
+ mockLogger(true, buildAuditData(startTime, clientIp, ok, StatusCode.COMPLETE));
+
+ mockLoggingContext();
+ LoggingResponseFilter filter = new LoggingResponseFilter();
+ filter.setHttpRequest(mockHttpRequest(clientIp));
+
+ filter.filter(mockRequestContext(startTime), mockResponseContext(ok));
+ }
+
+ private AuditData buildAuditData(long startTime, String clientIp, Response.Status responseStatus,
+ StatusCode status) {
+ return AuditData.builder().startTime(startTime).responseCode(Integer.toString(responseStatus.getStatusCode()))
+ .responseDescription(responseStatus.getReasonPhrase()).clientIpAddress(clientIp)
+ .statusCode(status).build();
+ }
+
+ private HttpServletRequest mockHttpRequest(String clientIp) {
+ HttpServletRequest servletRequest = EasyMock.mock(HttpServletRequest.class);
+ EasyMock.expect(servletRequest.getRemoteAddr()).andReturn(clientIp);
+ EasyMock.replay(servletRequest);
+ return servletRequest;
+ }
+
+ private ContainerRequestContext mockRequestContext(Object startTime) {
+ ContainerRequestContext requestContext = EasyMock.mock(ContainerRequestContext.class);
+ EasyMock.expect(requestContext.getProperty(LoggingRequestFilter.START_TIME_KEY)).andReturn(startTime);
+ EasyMock.replay(requestContext);
+ return requestContext;
+ }
+
+ private ContainerResponseContext mockResponseContext(Response.StatusType statusInfo) {
+ ContainerResponseContext responseContext = EasyMock.mock(ContainerResponseContext.class);
+ EasyMock.expect(responseContext.getStatusInfo()).andReturn(statusInfo);
+ EasyMock.replay(responseContext);
+ return responseContext;
+ }
+
+ @Test
+ public void startTimeZeroWhenNotPresentInRequestContext() {
+
+ final String clientIp = "10.56.56.12";
+ final Response.Status ok = Response.Status.OK;
+
+ AuditData expectedAuditData = buildAuditData(0, clientIp, ok, StatusCode.COMPLETE);
+
+ mockLogger(true, expectedAuditData, logger -> {
+ logger.error(anyString(), anyObject(Object[].class));
+ EasyMock.expectLastCall();
+ });
+
+ mockLoggingContext();
+ LoggingResponseFilter filter = new LoggingResponseFilter();
+ filter.setHttpRequest(mockHttpRequest(clientIp));
+
+ filter.filter(mockRequestContext(null), mockResponseContext(ok));
+ }
+
+ @Test
+ public void startTimeZeroWhenIncorrectObjectType() {
+
+ final String clientIp = "10.56.56.13";
+ final Response.Status accepted = Response.Status.ACCEPTED;
+
+ AuditData expectedAuditData = buildAuditData(0, clientIp, accepted, StatusCode.COMPLETE);
+
+ mockLogger(true, expectedAuditData, logger -> {
+ logger.error(anyString(), new Object[] {anyString(), anyString()});
+ EasyMock.expectLastCall();
+ });
+
+ mockLoggingContext();
+ LoggingResponseFilter filter = new LoggingResponseFilter();
+ filter.setHttpRequest(mockHttpRequest(clientIp));
+
+ filter.filter(mockRequestContext("string object"), mockResponseContext(accepted));
+ }
+
+ @Test
+ public void statusErrorWhenHttpResponseGreaterThan399() {
+
+ final Response.Status error = Response.Status.BAD_REQUEST;
+ final String clientIp = "10.56.56.13";
+ final long startTime = 88668603L;
+
+ AuditData expectedAuditData = buildAuditData(startTime, clientIp, error, StatusCode.ERROR);
+
+ mockLogger(true, expectedAuditData);
+
+ mockLoggingContext();
+ LoggingResponseFilter filter = new LoggingResponseFilter();
+ filter.setHttpRequest(mockHttpRequest(clientIp));
+
+ filter.filter(mockRequestContext(startTime), mockResponseContext(error));
+ }
+
+ private static class AuditDataComparator implements Comparator<AuditData> {
+
+ @Override
+ public int compare(AuditData one, AuditData two) {
+
+ // don't compare end time as it changes
+ if (Objects.equals(one.getClientIpAddress(), two.getClientIpAddress()) && Objects
+ .equals(one.getResponseCode(), two.getResponseCode()) && Objects
+ .equals(one.getResponseDescription(), one.getResponseDescription()) && one.getStartTime() == two
+ .getStartTime() && Objects.equals(one.getStatusCode(), two.getStatusCode())) {
+
+ return 0;
+ }
+
+ return -1;
+ }
+ }
+} \ No newline at end of file