SLF4J adapter (in 'common') + call graph demo
[logging-analytics.git] / reference / slf4j-reference / src / main / java / org / onap / logging / ref / slf4j / demo / component / AbstractComponent.java
1 /**
2  * ============LICENSE_START=======================================================
3  * org.onap.logging
4  * ================================================================================
5  * Copyright © 2018 Amdocs
6  * All rights reserved.
7  * ================================================================================
8  * Licensed under the Apache License, Version 2.0 (the "License");
9  * you may not use this file except in compliance with the License.
10  * You may obtain a copy of the License at
11  *
12  *    http://www.apache.org/licenses/LICENSE-2.0
13  *
14  * Unless required by applicable law or agreed to in writing, software
15  * distributed under the License is distributed on an "AS IS" BASIS,
16  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17  * See the License for the specific language governing permissions and
18  * limitations under the License.
19  * ============LICENSE_END=========================================================
20  */
21
22 package org.onap.logging.ref.slf4j.demo.component;
23
24 import java.util.Locale;
25 import java.util.Map;
26 import java.util.UUID;
27
28 import javax.servlet.http.HttpServletRequest;
29
30 import com.mashape.unirest.http.HttpResponse;
31 import com.mashape.unirest.http.JsonNode;
32 import com.mashape.unirest.http.Unirest;
33 import com.mashape.unirest.http.exceptions.UnirestException;
34 import org.apache.commons.lang3.StringUtils;
35 import org.json.JSONObject;
36 import org.onap.logging.ref.slf4j.common.ONAPLogAdapter;
37 import org.onap.logging.ref.slf4j.common.ONAPLogConstants;
38 import org.onap.logging.ref.slf4j.demo.bean.Request;
39 import org.onap.logging.ref.slf4j.demo.bean.Response;
40 import org.slf4j.Logger;
41 import org.slf4j.LoggerFactory;
42 import org.slf4j.MDC;
43 import org.springframework.http.MediaType;
44 import org.springframework.mock.web.MockHttpServletRequest;
45 import org.springframework.web.bind.annotation.RequestMapping;
46 import org.springframework.web.bind.annotation.RequestMethod;
47 import org.springframework.web.bind.annotation.RestController;
48
49 /**
50  * Base class for <tt>Alpha</tt>, <tt>Beta</tt> and <tt>Gamma</tt>
51  * and <tt>Delta</tt> controllers, implementing all the actual logic.
52  *
53  * <p>(The subclasses provide nothing but identifiers to allow them
54  * to be distinguished from one another, for the purposes of addressing
55  * requests and generating the call graph from their logger output.)</p>
56  */
57 @RestController
58 public abstract class AbstractComponent {
59
60     /**
61      * Test switch, routing invocations between components in-process,
62      * rather than via REST over HTTP.
63      */
64     private static boolean sInProcess;
65
66     /**
67      * Get service identifier, used to derive {@link #getServiceName()},
68      * <tt>PartnerName</tt>, etc.
69      * @return <tt>alpha</tt>, <tt>beta</tt>, <tt>gamma</tt>.
70      */
71     protected abstract String getId();
72
73     /**
74      * Get component UUID.
75      * @return globally unique ID string.
76      */
77     protected abstract String getInstanceUUID();
78
79     /**
80      * Execute REST request.
81      * @param request request data.
82      * @param http HTTP request.
83      * @return response data.
84      * @throws UnirestException REST error.
85      */
86     @RequestMapping(value = "/invoke",
87             method = RequestMethod.POST,
88             consumes = MediaType.APPLICATION_JSON_VALUE,
89             produces = MediaType.APPLICATION_JSON_VALUE)
90     public Response execute(final Request request,
91                             final HttpServletRequest http) throws UnirestException {
92
93         final ONAPLogAdapter adapter = new ONAPLogAdapter(this.getLogger());
94
95         try {
96
97             adapter.entering(new ONAPLogAdapter.HttpServletRequestAdapter(http));
98
99             final Response response = new Response();
100             response.setService(request.getService());
101             final String code = StringUtils.defaultString(request.getCode(), "OK").toUpperCase();
102             response.setCode(this.getId() + "." + code);
103             response.setSeverity(StringUtils.defaultString(request.getSeverity(), "INFO"));
104
105             for (final Request target : request.getRequests()) {
106                 final Response targetResponse = this.executeDelegate(target, http, adapter);
107                 response.getResponses().add(targetResponse);
108             }
109
110             return response;
111         }
112         finally {
113             adapter.exiting();
114         }
115     }
116
117     /**
118      * Set in-process mode, for unit testing.
119      */
120     static void setInProcess() {
121         sInProcess = true;
122     }
123
124     /**
125      * Execute request.
126      * @param request to be executed.
127      * @param http incoming HTTP request.
128      * @param logger logging adapter.
129      * @return response
130      */
131     private Response executeDelegate(final Request request,
132                                      final HttpServletRequest http,
133                                      final ONAPLogAdapter logger) {
134
135
136         notNull(request);
137         notNull(http);
138
139         // Downstream call.
140
141         try {
142
143             if (sInProcess) {
144                 return this.executeInProcess(request, logger);
145             }
146
147             return this.executeREST(request, http, logger);
148         }
149         catch (final UnirestException | ReflectiveOperationException e) {
150             logger.unwrap().error("Execute error", e);
151             final Response response = new Response();
152             response.setCode((this.getServiceName() + ".INVOKE_ERROR").toUpperCase(Locale.getDefault()));
153             response.setSeverity("ERROR");
154             return response;
155         }
156     }
157
158     /**
159      * Execute invocation over REST.
160      * @param request mock request to be executed.
161      * @param http HTTP request, used (only) to address the outgoing request.
162      * @param logger logger adapter.
163      * @return invocation response.
164      * @throws UnirestException REST error.
165      */
166     private Response executeREST(final Request request,
167                                  final HttpServletRequest http,
168                                  final ONAPLogAdapter logger) throws UnirestException {
169         // Otherwise via REST.
170
171         logger.unwrap().info("Sending:\n{}", request);
172         final StringBuilder url = new StringBuilder();
173         url.append(http.getProtocol()).append("://");
174         url.append(http.getServerName()).append(':');
175         url.append(http.getServerPort()).append("/services/").append(request.getService());
176
177         final UUID invocationID = logger.invoke(ONAPLogConstants.InvocationMode.SYNCHRONOUS);
178         final HttpResponse<JsonNode> response =
179                 Unirest.post(url.toString())
180                         .header(ONAPLogConstants.Headers.REQUEST_ID, MDC.get(ONAPLogConstants.MDCs.REQUEST_ID))
181                         .header(ONAPLogConstants.Headers.INVOCATION_ID, invocationID.toString())
182                         .header(ONAPLogConstants.Headers.PARTNER_NAME, this.getServiceName())
183                         .header("Accept", MediaType.APPLICATION_JSON_VALUE)
184                         .header("Content-Type", MediaType.APPLICATION_JSON_VALUE)
185                         .body(request)
186                         .asJson();
187
188         // Parse response.
189
190         final JSONObject responseJSON = response.getBody().getObject();
191         logger.unwrap().info("Received:\n{}", responseJSON);
192         return Response.fromJSON(responseJSON);
193     }
194
195     /**
196      * Execute request in-process.
197      * @param request mock request to be executed.
198      * @param logger logger adapter.
199      * @return invocation response.
200      * @throws ReflectiveOperationException error loading target class.
201      * @throws UnirestException REST error.
202      */
203     private Response executeInProcess(final Request request,
204                                       final ONAPLogAdapter logger) throws ReflectiveOperationException, UnirestException {
205
206         logger.unwrap().info("Executing in-process:\n{}", request);
207
208         // Derive the name of the delegate class.
209
210         final String delegateClass
211                 = AbstractComponent.class.getPackage().getName() + "." + request.getService()
212                 + ".Component" + request.getService().substring(0, 1).toUpperCase()
213                 + request.getService().substring(1);
214         logger.unwrap().info("Invoking in-process [{}].", delegateClass);
215         final AbstractComponent component = (AbstractComponent)Class.forName(delegateClass).newInstance();
216
217         // Using Spring mock since we're not *actually* going over HTTP.
218
219         final MockHttpServletRequest mock = new MockHttpServletRequest();
220
221         // Generate INVOCATION_ID, and set MDCs aside for safekeeping.
222         // (This is because when mocking, everything happens in the same thread.)
223
224         final UUID invocationID = logger.invoke(ONAPLogConstants.InvocationMode.SYNCHRONOUS);
225         final String requestID = MDC.get(ONAPLogConstants.MDCs.REQUEST_ID);
226         final Map<String, String> safekeeping = MDC.getCopyOfContextMap();
227
228         // Set headers.
229
230         mock.addHeader(ONAPLogConstants.Headers.REQUEST_ID, StringUtils.defaultString(requestID));
231         mock.addHeader(ONAPLogConstants.Headers.INVOCATION_ID, invocationID.toString());
232         mock.addHeader(ONAPLogConstants.Headers.PARTNER_NAME, this.getServiceName());
233
234         try {
235
236             MDC.clear();
237
238             // Execute.
239
240             return component.execute(request, mock);
241         }
242         finally {
243
244             // Restore MDCs.
245
246             safekeeping.forEach((k, v) -> MDC.put(k, v));
247         }
248     }
249
250     /**
251      * Ensure non-nullness.
252      * @param in to be checked.
253      * @param <T> type.
254      * @return input value, not null.
255      */
256     private static <T> T notNull(final T in) {
257         if (in == null) {
258             throw new AssertionError("");
259         }
260         return in;
261     }
262
263     /**
264      * Get service name, with default.
265      * @return service name, suitable for logging as MDC.
266      */
267     private String getServiceName() {
268         return "service." + StringUtils.defaultString(this.getId(), "unnamed");
269     }
270
271     /**
272      * Get logger instance.
273      * @return logger.
274      */
275     private Logger getLogger() {
276         return LoggerFactory.getLogger(this.getClass());
277     }
278 }