498587414a2308054f60adb63e69cc6689e82d16
[sdc.git] /
1 /*
2  * Copyright © 2016-2018 European Support Limited
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 package org.openecomp.sdc.logging.servlet.jaxrs;
18
19 import static org.openecomp.sdc.logging.LoggingConstants.DEFAULT_PARTNER_NAME_HEADER;
20 import static org.openecomp.sdc.logging.LoggingConstants.DEFAULT_REQUEST_ID_HEADER;
21
22 import java.lang.reflect.Method;
23 import java.lang.reflect.Proxy;
24 import java.util.UUID;
25 import javax.ws.rs.container.ContainerRequestContext;
26 import javax.ws.rs.container.ContainerRequestFilter;
27 import javax.ws.rs.container.ResourceInfo;
28 import javax.ws.rs.core.Context;
29 import javax.ws.rs.ext.Provider;
30 import org.openecomp.sdc.logging.api.ContextData;
31 import org.openecomp.sdc.logging.api.Logger;
32 import org.openecomp.sdc.logging.api.LoggerFactory;
33 import org.openecomp.sdc.logging.api.LoggingContext;
34 import org.openecomp.sdc.logging.servlet.HttpHeader;
35
36 /**
37  * <p>Takes care of logging initialization an HTTP request hits the application. This includes populating logging
38  * context and storing the request processing start time, so that it can be used for audit. The filter was built
39  * <b>works in tandem</b> with {@link LoggingResponseFilter} or a similar implementation.</p>
40  * <p>The filter requires a few HTTP header names to be configured. These HTTP headers are used for propagating logging
41  * and tracing information between ONAP components.</p>
42  * <p>Sample configuration for a Spring environment:</p>
43  * <pre>
44  *     &lt;jaxrs:providers&gt;
45  *         &lt;bean class="org.openecomp.sdc.logging.ws.rs.LoggingRequestFilter"&gt;
46  *             &lt;property name="requestIdHeaders" value="X-ONAP-RequestID"/&gt;
47  *             &lt;property name="partnerNameHeaders" value="X-ONAP-InstanceID"/&gt;
48  *         &lt;/bean&gt;
49  *     &lt;/jaxrs:providers&gt;
50  * </pre>
51  * <p>Keep in mind that the filters does nothing in case when a request cannot be mapped to a working JAX-RS resource
52  * (implementation). For instance, when the path is invalid (404), or there is no handler for a particular method (405).
53  * </p>
54  *
55  * @author evitaliy, katyr
56  * @since 29 Oct 17
57  *
58  * @see ContainerRequestFilter
59  */
60 @Provider
61 public class LoggingRequestFilter implements ContainerRequestFilter {
62
63     static final String MULTI_VALUE_SEPARATOR = ",";
64
65     static final String START_TIME_KEY = "audit.start.time";
66
67     private static final Logger LOGGER = LoggerFactory.getLogger(LoggingRequestFilter.class);
68
69     private ResourceInfo resource;
70
71     private HttpHeader requestIdHeader = new HttpHeader(DEFAULT_REQUEST_ID_HEADER);
72     private HttpHeader partnerNameHeader = new HttpHeader(DEFAULT_PARTNER_NAME_HEADER);
73
74     /**
75      * Injection of a resource that matches the request from JAX-RS context.
76      *
77      * @param resource automatically injected by JAX-RS container
78      */
79     @Context
80     public void setResource(ResourceInfo resource) {
81         this.resource = resource;
82     }
83
84     /**
85      * Configuration parameter for request ID HTTP header.
86      */
87     public void setRequestIdHeaders(String requestIdHeaders) {
88         LOGGER.debug("Valid request ID headers: {}", requestIdHeaders);
89         this.requestIdHeader = new HttpHeader(requestIdHeaders.split(MULTI_VALUE_SEPARATOR));
90     }
91
92     /**
93      * Configuration parameter for partner name HTTP header.
94      */
95     public void setPartnerNameHeaders(String partnerNameHeaders) {
96         LOGGER.debug("Valid partner name headers: {}", partnerNameHeaders);
97         this.partnerNameHeader = new HttpHeader(partnerNameHeaders.split(MULTI_VALUE_SEPARATOR));
98     }
99
100     @Override
101     public void filter(ContainerRequestContext containerRequestContext) {
102
103         if (resource == null) {
104             // JAX-RS could not find a mapping this response, probably due to HTTP 404 (not found),
105             // 405 (method not allowed), 415 (unsupported media type), etc. with a message in Web server log
106
107             if (LOGGER.isDebugEnabled()) {
108                 LOGGER.debug("No matching resource was found for URI '{}' and method '{}'",
109                         containerRequestContext.getUriInfo().getPath(), containerRequestContext.getMethod());
110             }
111
112             return;
113         }
114
115         containerRequestContext.setProperty(START_TIME_KEY, System.currentTimeMillis());
116
117         LoggingContext.clear();
118
119         ContextData.ContextDataBuilder contextData = ContextData.builder();
120         contextData.serviceName(getServiceName());
121
122         String partnerName = partnerNameHeader.getAny(containerRequestContext::getHeaderString);
123         if (partnerName != null) {
124             contextData.partnerName(partnerName);
125         }
126
127         String requestId = requestIdHeader.getAny(containerRequestContext::getHeaderString);
128         contextData.requestId(requestId == null ? UUID.randomUUID().toString() : requestId);
129
130         LoggingContext.put(contextData.build());
131     }
132
133     private String getServiceName() {
134
135         Class<?> resourceClass = resource.getResourceClass();
136         Method resourceMethod = resource.getResourceMethod();
137
138         if (Proxy.isProxyClass(resourceClass)) {
139             LOGGER.debug("Proxy class injected for JAX-RS resource");
140             return getServiceNameFromJavaProxy(resourceClass, resourceMethod);
141         }
142
143         return formatServiceName(resourceClass, resourceMethod);
144     }
145
146     private String getServiceNameFromJavaProxy(Class<?> proxyType, Method resourceMethod) {
147
148         for (Class<?> interfaceType : proxyType.getInterfaces()) {
149
150             if (isMatchingInterface(interfaceType, resourceMethod)) {
151                 return formatServiceName(interfaceType, resourceMethod);
152             }
153         }
154
155         LOGGER.debug("Failed to find method '{}' in interfaces implemented by injected Java proxy", resourceMethod);
156         return formatServiceName(proxyType, resourceMethod);
157     }
158
159     private String formatServiceName(Class<?> resourceClass, Method resourceMethod) {
160         return resourceClass.getName() + "#" + resourceMethod.getName();
161     }
162
163     private boolean isMatchingInterface(Class<?> candidateType, Method requestedMethod) {
164
165         try {
166
167             Method candidate = candidateType.getDeclaredMethod(requestedMethod.getName(),
168                     requestedMethod.getParameterTypes());
169             return candidate != null;
170
171         } catch (NoSuchMethodException ignored) {
172             // ignore and move on to the next
173             LOGGER.debug("Failed to find method '{}' in interface '{}'", requestedMethod, candidateType);
174         }
175
176         return false;
177     }
178 }