Added JAX-RS filters for logging 99/32099/16
authorolegb <olegb@amdocs.com>
Mon, 19 Feb 2018 14:24:54 +0000 (16:24 +0200)
committerOren Kleks <orenkle@amdocs.com>
Mon, 26 Mar 2018 08:29:36 +0000 (08:29 +0000)
Change-Id: I57ab333278d4ea26530e916ef89670df8b51b555
Issue-ID: SDC-772
Signed-off-by: olegb <olegb@amdocs.com>
Signed-off-by: vempo <vitaliy.emporopulo@amdocs.com>
18 files changed:
openecomp-be/api/openecomp-sdc-rest-webapp/onboarding-rest-war/src/main/webapp/WEB-INF/beans-services.xml
openecomp-be/api/openecomp-sdc-rest-webapp/onboarding-rest-war/src/main/webapp/WEB-INF/web.xml
openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-api/pom.xml
openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-api/src/main/java/org/openecomp/sdc/logging/LoggingConstants.java [new file with mode: 0644]
openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-api/src/main/java/org/openecomp/sdc/logging/api/ServiceBinder.java
openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-api/src/main/java/org/openecomp/sdc/logging/servlet/HttpHeader.java [new file with mode: 0644]
openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-api/src/main/java/org/openecomp/sdc/logging/servlet/LoggingFilter.java [new file with mode: 0644]
openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-api/src/main/java/org/openecomp/sdc/logging/servlet/jaxrs/LoggingRequestFilter.java [new file with mode: 0644]
openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-api/src/main/java/org/openecomp/sdc/logging/servlet/jaxrs/LoggingResponseFilter.java [new file with mode: 0644]
openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-api/src/test/java/org/openecomp/sdc/logging/servlet/LoggingFilterTest.java [new file with mode: 0644]
openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-api/src/test/java/org/openecomp/sdc/logging/servlet/jaxrs/HttpHeaderTest.java [new file with mode: 0644]
openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-api/src/test/java/org/openecomp/sdc/logging/servlet/jaxrs/LoggingRequestFilterTest.java [new file with mode: 0644]
openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-api/src/test/java/org/openecomp/sdc/logging/servlet/jaxrs/LoggingResponseFilterTest.java [new file with mode: 0644]
openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-core/pom.xml
openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-core/src/main/java/org/openecomp/sdc/logging/servlet/LoggingFilter.java [deleted file]
openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-core/src/test/java/org/openecomp/sdc/logging/aspects/MetricsAspectTest.java
openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-core/src/test/java/org/openecomp/sdc/logging/servlet/LoggingFilterTest.java [deleted file]
openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-core/src/test/java/org/openecomp/sdc/logging/slf4j/SLF4JLoggerWrapperTest.java

index 7710032..589406d 100644 (file)
             <ref bean="jsonProvider"/>
             <bean class="org.openecomp.sdc.action.errors.ActionExceptionMapper"/>
             <bean class="org.openecomp.sdcrests.errors.DefaultExceptionMapper"/>
+            <bean class="org.openecomp.sdc.logging.servlet.jaxrs.LoggingRequestFilter">
+                <property name="requestIdHeaders" value="X-ECOMP-RequestID,X-ONAP-RequestID"/>
+                <property name="partnerNameHeaders" value="USER_ID"/>
+            </bean>
+            <bean class="org.openecomp.sdc.logging.servlet.jaxrs.LoggingResponseFilter"/>
         </jaxrs:providers>
 
         <jaxrs:outInterceptors>
index 64be5ab..b98ae4e 100644 (file)
     <filter-mapping>
         <filter-name>AuthZ</filter-name>
         <url-pattern>/workflow/v1.0/actions/*</url-pattern>
-    </filter-mapping>
-       <filter>
-               <filter-name>LoggingServletFilter</filter-name>
-               <filter-class>org.openecomp.sdc.logging.servlet.LoggingFilter</filter-class>
-       </filter>
-
-    <filter-mapping>
-        <filter-name>LoggingServletFilter</filter-name>
-        <url-pattern>/*</url-pattern>
     </filter-mapping>
     <filter>
         <filter-name>SessionContextFilter</filter-name>
     <!-- CXF -->
     <servlet>
         <servlet-name>CXFServlet</servlet-name>
-        <display-name>CXF Servlet</display-name>
         <servlet-class>
             org.apache.cxf.transport.servlet.CXFServlet
         </servlet-class>
         <servlet-name>CXFServlet</servlet-name>
         <url-pattern>/*</url-pattern>
     </servlet-mapping>
-    <!--servlet>
-        <servlet-name>logger-aspect</servlet-name>
-        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
-        <load-on-startup>2</load-on-startup>
-    </servlet-->
-
-
 
 </web-app>
index 927f454..754a103 100644 (file)
     </parent>
 
     <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 (file)
index 0000000..e8635b2
--- /dev/null
@@ -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
+    }
+}
index e3c9ea0..6706d6f 100644 (file)
@@ -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.
 
 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 (file)
index 0000000..4dcc197
--- /dev/null
@@ -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 (file)
index 0000000..e53f281
--- /dev/null
@@ -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 (file)
index 0000000..06660f3
--- /dev/null
@@ -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 (file)
index 0000000..fbe28a7
--- /dev/null
@@ -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 (file)
index 0000000..535a50c
--- /dev/null
@@ -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 (file)
index 0000000..3376c92
--- /dev/null
@@ -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 (file)
index 0000000..0e0be01
--- /dev/null
@@ -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 (file)
index 0000000..37627d5
--- /dev/null
@@ -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
index 14f54b0..dbaef2a 100644 (file)
@@ -45,8 +45,6 @@
             <version>${servlet.version}</version>
             <scope>provided</scope>
         </dependency>
-
-               <!-- for testing -->
         <dependency>
             <groupId>org.testng</groupId>
             <artifactId>testng</artifactId>
diff --git a/openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-core/src/main/java/org/openecomp/sdc/logging/servlet/LoggingFilter.java b/openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-core/src/main/java/org/openecomp/sdc/logging/servlet/LoggingFilter.java
deleted file mode 100644 (file)
index 0ca550a..0000000
+++ /dev/null
@@ -1,145 +0,0 @@
-/*
- * Copyright © 2016-2017 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 org.openecomp.sdc.logging.api.Logger;
-import org.openecomp.sdc.logging.api.LoggerFactory;
-import org.slf4j.MDC;
-
-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 java.io.IOException;
-import java.net.InetAddress;
-import java.net.UnknownHostException;
-import java.util.UUID;
-import java.util.concurrent.atomic.AtomicLong;
-
-
-/**
- *
- * <p>Pushes information required by EELF onto MDC (Mapped Diagnostic Context).</p>
- *
- * <p>This is servlet filter that should be configured in <i>web.xml</i> to be used. 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;/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/07/2016.
- */
-public class LoggingFilter implements Filter {
-
-    // should be cashed to avoid low-level call, but with a timeout to account for IP or FQDN changes
-    private static final HostAddressCache HOST_ADDRESS = new HostAddressCache();
-    private static final String UNKNOWN = "UNKNOWN";
-
-    private static final Logger LOGGER = LoggerFactory.getLogger(LoggingFilter.class);
-
-    public void destroy() {
-    }
-
-    public void doFilter(ServletRequest request, ServletResponse response,
-                         FilterChain chain) throws IOException, ServletException {
-
-        try {
-
-            MDC.clear();
-
-            MDC.put("RequestId", UUID.randomUUID().toString());
-            MDC.put("ServiceInstanceId", "N/A"); // not applicable
-            MDC.put("ServiceName", "ASDC");
-            MDC.put("InstanceUUID", "N/A");
-
-            // For some reason chooses IPv4 or IPv6 in a random way
-            MDC.put("RemoteHost", request.getRemoteHost());
-
-            InetAddress host = HOST_ADDRESS.get();
-
-            String ipAddress;
-            String hostName;
-            if (host == null) {
-                ipAddress = UNKNOWN;
-                hostName = UNKNOWN;
-            } else {
-                ipAddress = host.getHostAddress();
-                hostName = host.getHostName();
-            }
-
-            MDC.put("ServerIPAddress", ipAddress);
-            MDC.put("ServerFQDN", hostName);
-
-            if(request instanceof HttpServletRequest) {
-                String userName = ((HttpServletRequest) request).getHeader("USER_ID");
-                MDC.put("PartnerName", userName);
-            }
-            // TODO: Clarify what these stand for
-    //        MDC.put("AlertSeverity", );
-    //        MDC.put("Timer", );
-
-            chain.doFilter(request, response);
-
-        } finally {
-            MDC.clear();
-        }
-    }
-
-    public void init(FilterConfig config) throws ServletException { }
-
-    private static class HostAddressCache {
-
-        private static final long REFRESH_TIME = 1000L;
-
-        private final AtomicLong lastUpdated = new AtomicLong(0L);
-        private InetAddress hostAddress;
-
-        public InetAddress get() {
-
-            long current = System.currentTimeMillis();
-            if (current - lastUpdated.get() > REFRESH_TIME) {
-
-                synchronized (this) {
-
-                    try {
-                        lastUpdated.set(current); // set now to register the attempt even if failed
-                        hostAddress = InetAddress.getLocalHost();
-                    } catch (UnknownHostException e) {
-                        LOGGER.error("Failed to retrieve local hostname for logging", e);
-                        hostAddress = null;
-                    }
-                }
-            }
-
-            return hostAddress;
-        }
-    }
-}
index 4d3d6be..e4cd379 100644 (file)
@@ -1,12 +1,12 @@
 /*
- * Copyright © 2016-2017 European Support Limited
+ * 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.
 
 package org.openecomp.sdc.logging.aspects;
 
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.UUID;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Predicate;
 import org.aspectj.lang.ProceedingJoinPoint;
 import org.aspectj.lang.Signature;
 import org.aspectj.lang.reflect.SourceLocation;
@@ -30,399 +36,394 @@ import org.powermock.modules.testng.PowerMockTestCase;
 import org.testng.Assert;
 import org.testng.annotations.Test;
 
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.UUID;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.function.Predicate;
-
 /**
- * @author EVITALIY
- * @since 17/08/2016.
+ * Unit-tests metrics aspect (AOP) behavior.
+ *
+ * @author evitaliy
+ * @since 17 Aug 2016
  */
 @PrepareForTest(LoggerFactory.class)
 public class MetricsAspectTest extends PowerMockTestCase {
 
-  private static final Object OBJ_TO_RETURN = new Object();
-  private static final String EXPECTED_MESSAGE = "'{}' took {} milliseconds";
-
-  @Test
-  public void testLogExecutionTime() throws Throwable {
-
-    String className = UUID.randomUUID().toString();
-    String methodName = UUID.randomUUID().toString();
-
-    TestLogger logger = initLogging(className, true);
-
-    MetricsAspect aspect = new MetricsAspect();
-    MockProceedingJoinPoint pjp = new MockProceedingJoinPoint(className, methodName);
-    Object returned = aspect.logExecutionTime(pjp);
-
-    Assert.assertEquals(OBJ_TO_RETURN, returned);
-    assertExecution(methodName, pjp, logger);
-  }
-
-  @Test
-  public void testMetricsDisabled() throws Throwable {
-
-    String className = UUID.randomUUID().toString();
-    String methodName = UUID.randomUUID().toString();
-
-    TestLogger logger = initLogging(className, false);
-
-    MetricsAspect aspect = new MetricsAspect();
-    MockProceedingJoinPoint pjp = new MockProceedingJoinPoint(className, methodName);
-    Object returned = aspect.logExecutionTime(pjp);
-
-    Assert.assertEquals(OBJ_TO_RETURN, returned);
-    Assert.assertEquals(1, pjp.getCount());
-    // return any event - must be empty
-    Assert.assertFalse(logger.contains((event) -> true));
-  }
-
-  @Test(expectedExceptions = IllegalArgumentException.class)
-  public void testThrowingError() throws Throwable {
-
-    String className = UUID.randomUUID().toString();
-    String methodName = UUID.randomUUID().toString();
-
-    final TestLogger logger = initLogging(className, true);
-
-    MetricsAspect aspect = new MetricsAspect();
-    MockProceedingJoinPoint pjp = new MockProceedingJoinPointWithException(className, methodName);
-
-    try {
-      aspect.logExecutionTime(pjp);
-    } finally {
-      assertExecution(methodName, pjp, logger);
-    }
-  }
-
-  private TestLogger initLogging(String className, boolean enabled) {
-    TestLogger logger = new TestLogger(enabled);
-    PowerMock.mockStatic(LoggerFactory.class);
-    EasyMock.expect(LoggerFactory.getLogger(className)).andReturn(logger);
-    PowerMock.replay(LoggerFactory.class);
-    return logger;
-  }
-
-  private void assertExecution(String methodName, MockProceedingJoinPoint pjp, TestLogger logger) {
-
-    Assert.assertEquals(1, pjp.getCount());
-    Assert.assertTrue(logger.contains((event) ->
-        (event != null) && (event.length == 3) && EXPECTED_MESSAGE.equals(event[0])
-            && methodName.equals(event[1]) && (event[2] instanceof Long)));
-  }
-
-  private static class MockSignature implements Signature {
-
-    private final String className;
-    private final String methodName;
-
-    private MockSignature(String className, String methodName) {
-      this.className = className;
-      this.methodName = methodName;
-    }
-
-    @Override
-    public String toShortString() {
-      return null;
-    }
-
-    @Override
-    public String toLongString() {
-      return null;
-    }
-
-    @Override
-    public String getName() {
-      return methodName;
-    }
-
-    @Override
-    public int getModifiers() {
-      return 0;
-    }
-
-    @Override
-    public Class getDeclaringType() {
-      return null;
-    }
-
-    @Override
-    public String getDeclaringTypeName() {
-      return className;
-    }
-  }
-
-  private static class MockProceedingJoinPoint implements ProceedingJoinPoint {
-
-    private AtomicInteger count = new AtomicInteger(0);
-    private Signature signature;
-
-    MockProceedingJoinPoint(String className, String methodName) {
-      this.signature = new MockSignature(className, methodName);
-    }
-
-    int getCount() {
-      return count.get();
-    }
-
-    @Override
-    public Object proceed() throws Throwable {
-      count.incrementAndGet();
-      return OBJ_TO_RETURN;
-    }
+    private static final Object OBJ_TO_RETURN = new Object();
+    private static final String EXPECTED_MESSAGE = "'{}' took {} milliseconds";
 
-    @Override
-    public void set$AroundClosure(AroundClosure aroundClosure) {
+    @Test
+    public void testLogExecutionTime() throws Throwable {
 
-    }
+        String className = UUID.randomUUID().toString();
+        String methodName = UUID.randomUUID().toString();
 
-    @Override
-    public Object proceed(Object[] objects) throws Throwable {
-      return null;
-    }
+        TestLogger logger = initLogging(className, true);
 
-    @Override
-    public String toShortString() {
-      return null;
-    }
+        MetricsAspect aspect = new MetricsAspect();
+        MockProceedingJoinPoint pjp = new MockProceedingJoinPoint(className, methodName);
+        Object returned = aspect.logExecutionTime(pjp);
 
-    @Override
-    public String toLongString() {
-      return null;
+        Assert.assertEquals(OBJ_TO_RETURN, returned);
+        assertExecution(methodName, pjp, logger);
     }
 
-    @Override
-    public Object getThis() {
-      return null;
+    private TestLogger initLogging(String className, boolean enabled) {
+        TestLogger logger = new TestLogger(enabled);
+        PowerMock.mockStatic(LoggerFactory.class);
+        EasyMock.expect(LoggerFactory.getLogger(className)).andReturn(logger);
+        PowerMock.replay(LoggerFactory.class);
+        return logger;
     }
 
-    @Override
-    public Object getTarget() {
-      return null;
-    }
+    private void assertExecution(String methodName, MockProceedingJoinPoint pjp, TestLogger logger) {
 
-    @Override
-    public Object[] getArgs() {
-      return new Object[0];
+        Assert.assertEquals(1, pjp.getCount());
+        Assert.assertTrue(logger.contains(
+                (event) -> (event != null) && (event.length == 3) && EXPECTED_MESSAGE.equals(event[0]) && methodName
+                        .equals(event[1]) && (event[2] instanceof Long)));
     }
 
-    @Override
-    public Signature getSignature() {
-      return this.signature;
-    }
+    @Test
+    public void testMetricsDisabled() throws Throwable {
 
-    @Override
-    public SourceLocation getSourceLocation() {
-      return null;
-    }
+        String className = UUID.randomUUID().toString();
+        String methodName = UUID.randomUUID().toString();
 
-    @Override
-    public String getKind() {
-      return null;
-    }
-
-    @Override
-    public StaticPart getStaticPart() {
-      return null;
-    }
-  }
+        TestLogger logger = initLogging(className, false);
 
-  private static class MockProceedingJoinPointWithException extends MockProceedingJoinPoint {
+        MetricsAspect aspect = new MetricsAspect();
+        MockProceedingJoinPoint pjp = new MockProceedingJoinPoint(className, methodName);
+        Object returned = aspect.logExecutionTime(pjp);
 
-    MockProceedingJoinPointWithException(String className, String methodName) {
-      super(className, methodName);
+        Assert.assertEquals(OBJ_TO_RETURN, returned);
+        Assert.assertEquals(1, pjp.getCount());
+        // return any event - must be empty
+        Assert.assertFalse(logger.contains((event) -> true));
     }
 
-    @Override
-    public Object proceed() throws Throwable {
-      super.proceed();
-      throw new IllegalArgumentException();
-    }
-  }
+    @Test(expectedExceptions = IllegalArgumentException.class)
+    public void testThrowingError() throws Throwable {
 
-  private class TestLogger implements Logger {
+        String className = UUID.randomUUID().toString();
+        String methodName = UUID.randomUUID().toString();
 
-    private final boolean enabled;
-    private List<Object[]> events = Collections.synchronizedList(new ArrayList<>(10));
+        final TestLogger logger = initLogging(className, true);
 
-    TestLogger(boolean enabled) {
-      this.enabled = enabled;
-    }
+        MetricsAspect aspect = new MetricsAspect();
+        MockProceedingJoinPoint pjp = new MockProceedingJoinPointWithException(className, methodName);
 
-    @Override
-    public String getName() {
-      throw new RuntimeException("Not implemented");
+        try {
+            aspect.logExecutionTime(pjp);
+        } finally {
+            assertExecution(methodName, pjp, logger);
+        }
     }
 
-    @Override
-    public boolean isMetricsEnabled() {
-      return this.enabled;
-    }
+    private static class MockSignature implements Signature {
 
-    @Override
-    public void metrics(String var1) {
-      throw new RuntimeException("Not implemented");
-    }
+        private final String className;
+        private final String methodName;
 
-    @Override
-    public void metrics(String var1, Object var2) {
-      throw new RuntimeException("Not implemented");
-    }
+        private MockSignature(String className, String methodName) {
+            this.className = className;
+            this.methodName = methodName;
+        }
 
-    @Override
-    public void metrics(String var1, Object var2, Object var3) {
+        @Override
+        public String toShortString() {
+            return null;
+        }
 
-      if (this.enabled) {
-        events.add(new Object[]{var1, var2, var3});
-      }
-    }
+        @Override
+        public String toLongString() {
+            return null;
+        }
 
-    @Override
-    public void metrics(String var1, Object... var2) {
-      throw new RuntimeException("Not implemented");
-    }
+        @Override
+        public String getName() {
+            return methodName;
+        }
 
-    @Override
-    public void metrics(String var1, Throwable throwable) {
-      throw new RuntimeException("Not implemented");
-    }
+        @Override
+        public int getModifiers() {
+            return 0;
+        }
 
-    @Override
-    public boolean isAuditEnabled() {
-      throw new RuntimeException("Not implemented");
-    }
+        @Override
+        public Class getDeclaringType() {
+            return null;
+        }
 
-    @Override
-    public void audit(AuditData var1) {
-      throw new RuntimeException("Not implemented");
+        @Override
+        public String getDeclaringTypeName() {
+            return className;
+        }
     }
 
-    @Override
-    public boolean isDebugEnabled() {
-      throw new RuntimeException("Not implemented");
-    }
+    private static class MockProceedingJoinPoint implements ProceedingJoinPoint {
 
-    @Override
-    public void debug(String var1) {
-      throw new RuntimeException("Not implemented");
-    }
+        private final AtomicInteger count = new AtomicInteger(0);
+        private final Signature signature;
 
-    @Override
-    public void debug(String var1, Object var2) {
-      throw new RuntimeException("Not implemented");
-    }
+        MockProceedingJoinPoint(String className, String methodName) {
+            this.signature = new MockSignature(className, methodName);
+        }
 
-    @Override
-    public void debug(String var1, Object var2, Object var3) {
-      throw new RuntimeException("Not implemented");
-    }
+        int getCount() {
+            return count.get();
+        }
 
-    @Override
-    public void debug(String var1, Object... var2) {
-      throw new RuntimeException("Not implemented");
-    }
+        @Override
+        public void set$AroundClosure(AroundClosure aroundClosure) {
 
-    @Override
-    public void debug(String var1, Throwable throwable) {
-      throw new RuntimeException("Not implemented");
-    }
+        }
 
-    @Override
-    public boolean isInfoEnabled() {
-      throw new RuntimeException("Not implemented");
-    }
+        @Override
+        public Object proceed() throws Throwable {
+            count.incrementAndGet();
+            return OBJ_TO_RETURN;
+        }
 
-    @Override
-    public void info(String var1) {
-      throw new RuntimeException("Not implemented");
-    }
+        @Override
+        public Object proceed(Object[] objects) {
+            return null;
+        }
 
-    @Override
-    public void info(String var1, Object var2) {
-      throw new RuntimeException("Not implemented");
-    }
+        @Override
+        public String toShortString() {
+            return null;
+        }
 
-    @Override
-    public void info(String var1, Object var2, Object var3) {
-      throw new RuntimeException("Not implemented");
-    }
-
-    @Override
-    public void info(String var1, Object... var2) {
-      throw new RuntimeException("Not implemented");
-    }
-
-    @Override
-    public void info(String var1, Throwable throwable) {
-      throw new RuntimeException("Not implemented");
-    }
-
-    @Override
-    public boolean isWarnEnabled() {
-      throw new RuntimeException("Not implemented");
-    }
-
-    @Override
-    public void warn(String var1) {
-      throw new RuntimeException("Not implemented");
-    }
-
-    @Override
-    public void warn(String var1, Object var2) {
-      throw new RuntimeException("Not implemented");
-    }
-
-    @Override
-    public void warn(String var1, Object... var2) {
-      throw new RuntimeException("Not implemented");
-    }
-
-    @Override
-    public void warn(String var1, Object var2, Object var3) {
-      throw new RuntimeException("Not implemented");
-    }
-
-    @Override
-    public void warn(String var1, Throwable throwable) {
-      throw new RuntimeException("Not implemented");
-    }
-
-    @Override
-    public boolean isErrorEnabled() {
-      throw new RuntimeException("Not implemented");
-    }
-
-    @Override
-    public void error(String var1) {
-      throw new RuntimeException("Not implemented");
-    }
+        @Override
+        public String toLongString() {
+            return null;
+        }
 
-    @Override
-    public void error(String var1, Object var2) {
-      throw new RuntimeException("Not implemented");
-    }
-
-    @Override
-    public void error(String var1, Object var2, Object var3) {
-      throw new RuntimeException("Not implemented");
-    }
-
-    @Override
-    public void error(String var1, Object... var2) {
-      throw new RuntimeException("Not implemented");
-    }
-
-    @Override
-    public void error(String var1, Throwable throwable) {
-      throw new RuntimeException("Not implemented");
-    }
+        @Override
+        public Object getThis() {
+            return null;
+        }
 
-    public boolean contains(Predicate<Object[]> predicate) {
-      return events.stream().anyMatch(predicate);
+        @Override
+        public Object getTarget() {
+            return null;
+        }
+
+        @Override
+        public Object[] getArgs() {
+            return new Object[0];
+        }
+
+        @Override
+        public Signature getSignature() {
+            return this.signature;
+        }
+
+        @Override
+        public SourceLocation getSourceLocation() {
+            return null;
+        }
+
+        @Override
+        public String getKind() {
+            return null;
+        }
+
+        @Override
+        public StaticPart getStaticPart() {
+            return null;
+        }
+    }
+
+    private static class MockProceedingJoinPointWithException extends MockProceedingJoinPoint {
+
+        MockProceedingJoinPointWithException(String className, String methodName) {
+            super(className, methodName);
+        }
+
+        @Override
+        public Object proceed() throws Throwable {
+            super.proceed();
+            throw new IllegalArgumentException();
+        }
+    }
+
+    private class TestLogger implements Logger {
+
+        private final boolean enabled;
+        private final List<Object[]> events = Collections.synchronizedList(new ArrayList<>(10));
+
+        TestLogger(boolean enabled) {
+            this.enabled = enabled;
+        }
+
+        @Override
+        public String getName() {
+            throw new RuntimeException("Not implemented");
+        }
+
+        @Override
+        public boolean isMetricsEnabled() {
+            return this.enabled;
+        }
+
+        @Override
+        public void metrics(String var1) {
+            throw new RuntimeException("Not implemented");
+        }
+
+        @Override
+        public void metrics(String var1, Object var2) {
+            throw new RuntimeException("Not implemented");
+        }
+
+        @Override
+        public void metrics(String var1, Object var2, Object var3) {
+
+            if (this.enabled) {
+                events.add(new Object[] {var1, var2, var3});
+            }
+        }
+
+        @Override
+        public void metrics(String var1, Object... var2) {
+            throw new RuntimeException("Not implemented");
+        }
+
+        @Override
+        public void metrics(String var1, Throwable throwable) {
+            throw new RuntimeException("Not implemented");
+        }
+
+        @Override
+        public boolean isAuditEnabled() {
+            throw new RuntimeException("Not implemented");
+        }
+
+        @Override
+        public void audit(AuditData var1) {
+            throw new RuntimeException("Not implemented");
+        }
+
+        @Override
+        public boolean isDebugEnabled() {
+            throw new RuntimeException("Not implemented");
+        }
+
+        @Override
+        public void debug(String var1) {
+            throw new RuntimeException("Not implemented");
+        }
+
+        @Override
+        public void debug(String var1, Object var2) {
+            throw new RuntimeException("Not implemented");
+        }
+
+        @Override
+        public void debug(String var1, Object var2, Object var3) {
+            throw new RuntimeException("Not implemented");
+        }
+
+        @Override
+        public void debug(String var1, Object... var2) {
+            throw new RuntimeException("Not implemented");
+        }
+
+        @Override
+        public void debug(String var1, Throwable throwable) {
+            throw new RuntimeException("Not implemented");
+        }
+
+        @Override
+        public boolean isInfoEnabled() {
+            throw new RuntimeException("Not implemented");
+        }
+
+        @Override
+        public void info(String var1) {
+            throw new RuntimeException("Not implemented");
+        }
+
+        @Override
+        public void info(String var1, Object var2) {
+            throw new RuntimeException("Not implemented");
+        }
+
+        @Override
+        public void info(String var1, Object var2, Object var3) {
+            throw new RuntimeException("Not implemented");
+        }
+
+        @Override
+        public void info(String var1, Object... var2) {
+            throw new RuntimeException("Not implemented");
+        }
+
+        @Override
+        public void info(String var1, Throwable throwable) {
+            throw new RuntimeException("Not implemented");
+        }
+
+        @Override
+        public boolean isWarnEnabled() {
+            throw new RuntimeException("Not implemented");
+        }
+
+        @Override
+        public void warn(String var1) {
+            throw new RuntimeException("Not implemented");
+        }
+
+        @Override
+        public void warn(String var1, Object var2) {
+            throw new RuntimeException("Not implemented");
+        }
+
+        @Override
+        public void warn(String var1, Object... var2) {
+            throw new RuntimeException("Not implemented");
+        }
+
+        @Override
+        public void warn(String var1, Object var2, Object var3) {
+            throw new RuntimeException("Not implemented");
+        }
+
+        @Override
+        public void warn(String var1, Throwable throwable) {
+            throw new RuntimeException("Not implemented");
+        }
+
+        @Override
+        public boolean isErrorEnabled() {
+            throw new RuntimeException("Not implemented");
+        }
+
+        @Override
+        public void error(String var1) {
+            throw new RuntimeException("Not implemented");
+        }
+
+        @Override
+        public void error(String var1, Object var2) {
+            throw new RuntimeException("Not implemented");
+        }
+
+        @Override
+        public void error(String var1, Object var2, Object var3) {
+            throw new RuntimeException("Not implemented");
+        }
+
+        @Override
+        public void error(String var1, Object... var2) {
+            throw new RuntimeException("Not implemented");
+        }
+
+        @Override
+        public void error(String var1, Throwable throwable) {
+            throw new RuntimeException("Not implemented");
+        }
+
+        boolean contains(Predicate<Object[]> predicate) {
+            return events.stream().anyMatch(predicate);
+        }
     }
-  }
 }
diff --git a/openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-core/src/test/java/org/openecomp/sdc/logging/servlet/LoggingFilterTest.java b/openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-core/src/test/java/org/openecomp/sdc/logging/servlet/LoggingFilterTest.java
deleted file mode 100644 (file)
index 95a5421..0000000
+++ /dev/null
@@ -1,316 +0,0 @@
-/*
- * Copyright © 2016-2017 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 org.slf4j.MDC;
-import org.testng.Assert;
-import org.testng.annotations.Test;
-
-import javax.servlet.FilterChain;
-import javax.servlet.RequestDispatcher;
-import javax.servlet.ServletException;
-import javax.servlet.ServletInputStream;
-import javax.servlet.ServletOutputStream;
-import javax.servlet.ServletRequest;
-import javax.servlet.ServletResponse;
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.PrintWriter;
-import java.io.UnsupportedEncodingException;
-import java.net.InetAddress;
-import java.util.Enumeration;
-import java.util.Locale;
-import java.util.Map;
-import java.util.UUID;
-import java.util.concurrent.atomic.AtomicInteger;
-
-import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.assertNull;
-
-/**
- * TODO: Add more tests
- *
- * @author EVITALIY
- * @since 17/08/2016.
- */
-public class LoggingFilterTest {
-
-  private static final String REMOTE_HOST = UUID.randomUUID().toString();
-
-  @Test
-  public void testDoFilter() throws Exception {
-    LoggingFilter loggingFilter = new LoggingFilter();
-    ServletRequest mockRequest = new TestServletRequest();
-    ServletResponse mockResponse = new TestServletResponse();
-    TestFilterChain mockChain = new TestFilterChain();
-    loggingFilter.doFilter(mockRequest, mockResponse, mockChain);
-    assertEquals(1, mockChain.getCount());
-    assertNull(MDC.getCopyOfContextMap());
-  }
-
-  private static class TestServletRequest implements ServletRequest {
-
-    @Override
-    public Object getAttribute(String s) {
-      return null;
-    }
-
-    @Override
-    public Enumeration getAttributeNames() {
-      return null;
-    }
-
-    @Override
-    public String getCharacterEncoding() {
-      return null;
-    }
-
-    @Override
-    public void setCharacterEncoding(String s) throws UnsupportedEncodingException {
-
-    }
-
-    @Override
-    public int getContentLength() {
-      return 0;
-    }
-
-    @Override
-    public String getContentType() {
-      return null;
-    }
-
-    @Override
-    public ServletInputStream getInputStream() throws IOException {
-      return null;
-    }
-
-    @Override
-    public String getParameter(String s) {
-      return null;
-    }
-
-    @Override
-    public Enumeration getParameterNames() {
-      return null;
-    }
-
-    @Override
-    public String[] getParameterValues(String s) {
-      return new String[0];
-    }
-
-    @Override
-    public Map getParameterMap() {
-      return null;
-    }
-
-    @Override
-    public String getProtocol() {
-      return null;
-    }
-
-    @Override
-    public String getScheme() {
-      return null;
-    }
-
-    @Override
-    public String getServerName() {
-      return null;
-    }
-
-    @Override
-    public int getServerPort() {
-      return 0;
-    }
-
-    @Override
-    public BufferedReader getReader() throws IOException {
-      return null;
-    }
-
-    @Override
-    public String getRemoteAddr() {
-      return null;
-    }
-
-    @Override
-    public String getRemoteHost() {
-      return REMOTE_HOST;
-    }
-
-    @Override
-    public void setAttribute(String s, Object o) {
-
-    }
-
-    @Override
-    public void removeAttribute(String s) {
-
-    }
-
-    @Override
-    public Locale getLocale() {
-      return null;
-    }
-
-    @Override
-    public Enumeration getLocales() {
-      return null;
-    }
-
-    @Override
-    public boolean isSecure() {
-      return false;
-    }
-
-    @Override
-    public RequestDispatcher getRequestDispatcher(String s) {
-      return null;
-    }
-
-    @Override
-    public String getRealPath(String s) {
-      return null;
-    }
-
-    @Override
-    public int getRemotePort() {
-      return 0;
-    }
-
-    @Override
-    public String getLocalName() {
-      return null;
-    }
-
-    @Override
-    public String getLocalAddr() {
-      return null;
-    }
-
-    @Override
-    public int getLocalPort() {
-      return 0;
-    }
-  }
-
-  private static class TestFilterChain implements FilterChain {
-
-    private AtomicInteger count = new AtomicInteger(0);
-
-    @Override
-    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse)
-        throws IOException, ServletException {
-
-      Assert.assertNotNull(MDC.get("RequestId"));
-      Assert.assertEquals(MDC.get("ServiceInstanceId"), "N/A");
-      Assert.assertEquals(MDC.get("ServiceName"), "ASDC");
-      Assert.assertEquals(MDC.get("InstanceUUID"), "N/A");
-      Assert.assertEquals(MDC.get("RemoteHost"), REMOTE_HOST);
-
-      InetAddress host = InetAddress.getLocalHost();
-      Assert.assertEquals(MDC.get("ServerIPAddress"), host.getHostAddress());
-      Assert.assertEquals(MDC.get("ServerFQDN"), host.getHostName());
-
-      count.incrementAndGet();
-    }
-
-    public int getCount() {
-      return count.get();
-    }
-  }
-
-  private static class TestServletResponse implements ServletResponse {
-
-    @Override
-    public String getCharacterEncoding() {
-      return null;
-    }
-
-    @Override
-    public void setCharacterEncoding(String s) {
-
-    }
-
-    @Override
-    public String getContentType() {
-      return null;
-    }
-
-    @Override
-    public void setContentType(String s) {
-
-    }
-
-    @Override
-    public ServletOutputStream getOutputStream() throws IOException {
-      return null;
-    }
-
-    @Override
-    public PrintWriter getWriter() throws IOException {
-      return null;
-    }
-
-    @Override
-    public void setContentLength(int i) {
-
-    }
-
-    @Override
-    public int getBufferSize() {
-      return 0;
-    }
-
-    @Override
-    public void setBufferSize(int i) {
-
-    }
-
-    @Override
-    public void flushBuffer() throws IOException {
-
-    }
-
-    @Override
-    public void resetBuffer() {
-
-    }
-
-    @Override
-    public boolean isCommitted() {
-      return false;
-    }
-
-    @Override
-    public void reset() {
-
-    }
-
-    @Override
-    public Locale getLocale() {
-      return null;
-    }
-
-    @Override
-    public void setLocale(Locale locale) {
-
-    }
-  }
-}
index 9093f7d..2c5233f 100644 (file)
@@ -39,9 +39,12 @@ import org.slf4j.MDC;
 import org.testng.annotations.Test;
 
 /**
+ * Unit-test of SLF4J implementation of Logger.
+ *
  * @author evitaliy
  * @since 05 Mar 18
  */
+@SuppressWarnings("CheckStyle")
 public class SLF4JLoggerWrapperTest {
 
     @Test
@@ -170,21 +173,29 @@ public class SLF4JLoggerWrapperTest {
         @Override
         public Object invoke(Object proxy, Method method, Object[] args) {
 
-            // return the remembered MDC for spying
-            if (method.getName().equals("mdc")) {
+            if (isReturnMdcMethod(method)) {
                 return mdc;
             }
 
-            // filter out everything that's not related to audit
-            if (!method.getName().equals("info") || args.length == 0 || !args[0].equals(Markers.AUDIT)) {
-                throw new UnsupportedOperationException("Method " + method.getName() + " with arguments " +
-                    Arrays.toString(args) + " wasn't supposed to be called");
+            if (!isAuditMethod(method, args)) {
+                throw new UnsupportedOperationException("Method " + method.getName() + " with arguments "
+                        + Arrays.toString(args) + " wasn't supposed to be called");
             }
 
-            // remember the MDC that was active during the invocation
+            storeEffectiveMdc();
+            return null;
+        }
+
+        private boolean isAuditMethod(Method method, Object[] args) {
+            return (method.getName().equals("info") && args.length > 0 && args[0].equals(Markers.AUDIT));
+        }
+
+        private void storeEffectiveMdc() {
             mdc = MDC.getCopyOfContextMap();
+        }
 
-            return null;
+        private boolean isReturnMdcMethod(Method method) {
+            return method.equals(SpyLogger.class.getDeclaredMethods()[0]);
         }
     }
 }
\ No newline at end of file