cb688c543ec6c27b00cc5483b01a71fa6b205bf7
[music.git] / src / main / java / org / onap / music / eelf / logging / MusicLoggingServletFilter.java
1 /*
2  * ============LICENSE_START==========================================
3  * org.onap.music
4  * ===================================================================
5  *  Copyright (c) 2017 AT&T Intellectual Property
6  * ===================================================================
7  *  Licensed under the Apache License, Version 2.0 (the "License");
8  *  you may not use this file except in compliance with the License.
9  *  You may obtain a copy of the License at
10  * 
11  *     http://www.apache.org/licenses/LICENSE-2.0
12  * 
13  *  Unless required by applicable law or agreed to in writing, software
14  *  distributed under the License is distributed on an "AS IS" BASIS,
15  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  *  See the License for the specific language governing permissions and
17  *  limitations under the License.
18  * 
19  * ============LICENSE_END=============================================
20  * ====================================================================
21  */
22 package org.onap.music.eelf.logging;
23
24 import java.io.IOException;
25 import java.util.Enumeration;
26 import java.util.HashMap;
27 import java.util.Map;
28 import java.util.stream.Collectors;
29
30 import javax.servlet.Filter;
31 import javax.servlet.FilterChain;
32 import javax.servlet.FilterConfig;
33 import javax.servlet.ServletException;
34 import javax.servlet.ServletRequest;
35 import javax.servlet.ServletResponse;
36 import javax.servlet.http.HttpServletRequest;
37 import javax.servlet.http.HttpServletResponse;
38
39 import org.onap.music.authentication.AuthorizationError;
40 import org.onap.music.main.MusicUtil;
41
42 import com.fasterxml.jackson.databind.ObjectMapper;
43
44 /**
45  * 
46  * This is the first filter in the chain to be executed before cadi
47  * authentication. The priority has been set in <code>MusicApplication</code>
48  * through filter registration bean
49  * 
50  * The responsibility of this filter is to validate header values as per
51  * contract and write it to MDC and http response header back.
52  * 
53  * 
54  * @author sp931a
55  *
56  */
57
58 public class MusicLoggingServletFilter implements Filter {
59
60     private EELFLoggerDelegate logger = EELFLoggerDelegate.getLogger(MusicLoggingServletFilter.class);
61     // client transaction id, specific to client system, set in properties
62     public static final String CONVERSATION_ID = MusicUtil.getConversationIdPrefix() + "ConversationId";
63
64     // can be used as correlation-id in case of callback, also this can be passed to
65     // other services for tracking.
66     public static final String MESSAGE_ID = MusicUtil.getMessageIdPrefix() + "MessageId";
67
68     // client id would be the unique client source-system-id, i;e VALET or CONDUCTOR
69     // etc
70     public static final String CLIENT_ID = MusicUtil.getClientIdPrefix() + "ClientId";
71
72     // unique transaction of the source system
73     private static final String TRANSACTION_ID = MusicUtil.getTransIdPrefix() + "Transaction-Id";
74
75     public MusicLoggingServletFilter() throws ServletException {
76         super();
77     }
78
79     @Override
80     public void init(FilterConfig filterConfig) throws ServletException {
81
82     }
83
84     @Override
85     public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
86             throws IOException, ServletException {
87
88         logger.info(EELFLoggerDelegate.applicationLogger,
89                 "In MusicLogginServletFilter doFilter start() ::::::::::::::::::::::: [\"+MusicUtil.getTransIdRequired()+\",\"+MusicUtil.getConversationIdRequired()+\",\"+MusicUtil.getClientIdRequired()+\",\"+MusicUtil.getMessageIdRequired()");
90
91         HttpServletRequest httpRequest = null;
92         HttpServletResponse httpResponse = null;
93         Map<String, String> headerMap = null;
94         Map<String, String> upperCaseHeaderMap = null;
95
96         if (null != request && null != response) {
97             httpRequest = (HttpServletRequest) request;
98             httpResponse = (HttpServletResponse) response;
99
100             headerMap = getHeadersInfo(httpRequest);
101
102             // The custom header values automatically converted into lower case, not sure
103             // why ? So i had to covert all keys to upper case
104             // The response header back to client will have all custom header values as
105             // upper case.
106             upperCaseHeaderMap = headerMap.entrySet().stream()
107                     .collect(Collectors.toMap(entry -> entry.getKey().toUpperCase(), entry -> entry.getValue()));
108             // Enable/disable keys are present in /opt/app/music/etc/music.properties
109
110             if (Boolean.valueOf(MusicUtil.getTransIdRequired())
111                     && !upperCaseHeaderMap.containsKey(TRANSACTION_ID.toUpperCase())) {
112                 populateError(httpResponse, "Transaction id '" + TRANSACTION_ID 
113                     + "' required on http header");
114                 return;
115             } else {
116                 populateMDCAndResponseHeader(upperCaseHeaderMap, TRANSACTION_ID, "transactionId",
117                     Boolean.valueOf(MusicUtil.getTransIdRequired()), httpResponse);
118             }
119
120             if (Boolean.valueOf(MusicUtil.getConversationIdRequired())
121                 && !upperCaseHeaderMap.containsKey(CONVERSATION_ID.toUpperCase())) {
122                 populateError(httpResponse, "Conversation Id '" + CONVERSATION_ID 
123                     + "' required on http header");
124                 return;
125             } else {
126                 populateMDCAndResponseHeader(upperCaseHeaderMap, CONVERSATION_ID, "conversationId",
127                     Boolean.valueOf(MusicUtil.getConversationIdRequired()), httpResponse);
128             }
129
130             if (Boolean.valueOf(MusicUtil.getMessageIdRequired())
131                 && !upperCaseHeaderMap.containsKey(MESSAGE_ID.toUpperCase())) {
132                 populateError(httpResponse, "Message Id '" + MESSAGE_ID 
133                     + "' required on http header");
134                 return;
135             } else {
136                 populateMDCAndResponseHeader(upperCaseHeaderMap, MESSAGE_ID, "messageId",
137                     Boolean.valueOf(MusicUtil.getMessageIdRequired()), httpResponse);
138             }
139
140             if (Boolean.valueOf(MusicUtil.getClientIdRequired())
141                 && !upperCaseHeaderMap.containsKey(CLIENT_ID.toUpperCase())) {
142                 populateError(httpResponse, "Client Id '" + CLIENT_ID 
143                     + "' required on http header");
144                 return;
145             } else {
146                 populateMDCAndResponseHeader(upperCaseHeaderMap, CLIENT_ID, "clientId",
147                     Boolean.valueOf(MusicUtil.getClientIdRequired()), httpResponse);
148             }
149
150         }
151
152         logger.info(EELFLoggerDelegate.applicationLogger,
153                 "In MusicLogginServletFilter doFilter. Header values validated sucessfully");
154
155         chain.doFilter(request, response);
156     }
157
158     private void populateError(HttpServletResponse httpResponse, String errMsg) throws IOException {
159         AuthorizationError authError = new AuthorizationError();
160         authError.setResponseCode(HttpServletResponse.SC_BAD_REQUEST);
161         authError.setResponseMessage(errMsg);
162
163         byte[] responseToSend = restResponseBytes(authError);
164         httpResponse.setHeader("Content-Type", "application/json");
165
166         // ideally the http response code should be 200, as this is a biz validation
167         // failure. For now, keeping it consistent with other places.
168         httpResponse.setStatus(HttpServletResponse.SC_BAD_REQUEST);
169         httpResponse.getOutputStream().write(responseToSend);
170     }
171
172     private void populateMDCAndResponseHeader(Map<String, String> headerMap, String idKey, String mdcKey,
173             boolean isRequired, HttpServletResponse httpResponse) throws ServletException, IOException {
174
175         idKey = idKey.trim().toUpperCase();
176
177         // 1. setting the keys & value in MDC for future use 2.setting the values in
178         // http response header back to client.
179         if (isRequired && (headerMap.containsKey(idKey))) {
180             EELFLoggerDelegate.mdcPut(mdcKey, headerMap.get(idKey));
181             httpResponse.addHeader(idKey, headerMap.get(idKey));
182         } else {
183             // do nothing
184         }
185     }
186
187     private Map<String, String> getHeadersInfo(HttpServletRequest request) {
188
189         Map<String, String> map = new HashMap<String, String>();
190
191         Enumeration<String> headerNames = request.getHeaderNames();
192         while (headerNames.hasMoreElements()) {
193             String key = (String) headerNames.nextElement();
194             String value = request.getHeader(key);
195             map.put(key, value);
196         }
197
198         return map;
199     }
200
201     private byte[] restResponseBytes(AuthorizationError eErrorResponse) throws IOException {
202         String serialized = new ObjectMapper().writeValueAsString(eErrorResponse);
203         return serialized.getBytes();
204     }
205 }