aboutsummaryrefslogtreecommitdiffstats
path: root/reference/slf4j-reference/src/main/java/org/onap/logging/ref/slf4j/demo/component/AbstractComponent.java
blob: 236345f2e9777ae4e6bc40dcff2febc0d09fb4d7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
/**
 * ============LICENSE_START=======================================================
 * org.onap.logging
 * ================================================================================
 * Copyright © 2018 Amdocs
 * All rights reserved.
 * ================================================================================
 * 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.
 * ============LICENSE_END=========================================================
 */

package org.onap.logging.ref.slf4j.demo.component;

import java.util.Locale;
import java.util.Map;
import java.util.UUID;

import javax.servlet.http.HttpServletRequest;

import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.JsonNode;
import com.mashape.unirest.http.Unirest;
import com.mashape.unirest.http.exceptions.UnirestException;
import org.apache.commons.lang3.StringUtils;
import org.json.JSONObject;
import org.onap.logging.ref.slf4j.common.ONAPLogAdapter;
import org.onap.logging.ref.slf4j.common.ONAPLogConstants;
import org.onap.logging.ref.slf4j.demo.bean.Request;
import org.onap.logging.ref.slf4j.demo.bean.Response;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

/**
 * Base class for <tt>Alpha</tt>, <tt>Beta</tt> and <tt>Gamma</tt>
 * and <tt>Delta</tt> controllers, implementing all the actual logic.
 *
 * <p>(The subclasses provide nothing but identifiers to allow them
 * to be distinguished from one another, for the purposes of addressing
 * requests and generating the call graph from their logger output.)</p>
 */
@RestController
public abstract class AbstractComponent {

    /**
     * Test switch, routing invocations between components in-process,
     * rather than via REST over HTTP.
     */
    private static boolean sInProcess;

    /**
     * Get service identifier, used to derive {@link #getServiceName()},
     * <tt>PartnerName</tt>, etc.
     * @return <tt>alpha</tt>, <tt>beta</tt>, <tt>gamma</tt>.
     */
    protected abstract String getId();

    /**
     * Get component UUID.
     * @return globally unique ID string.
     */
    protected abstract String getInstanceUUID();

    /**
     * Execute REST request.
     * @param request request data.
     * @param http HTTP request.
     * @return response data.
     * @throws UnirestException REST error.
     */
    @RequestMapping(value = "/invoke",
            method = RequestMethod.POST,
            consumes = MediaType.APPLICATION_JSON_VALUE,
            produces = MediaType.APPLICATION_JSON_VALUE)
    public Response execute(final Request request,
                            final HttpServletRequest http) throws UnirestException {

        final ONAPLogAdapter adapter = new ONAPLogAdapter(this.getLogger());

        try {

            adapter.entering(new ONAPLogAdapter.HttpServletRequestAdapter(http));

            final Response response = new Response();
            response.setService(request.getService());
            final String code = StringUtils.defaultString(request.getCode(), "OK").toUpperCase();
            response.setCode(this.getId() + "." + code);
            response.setSeverity(StringUtils.defaultString(request.getSeverity(), "INFO"));

            for (final Request target : request.getRequests()) {
                final Response targetResponse = this.executeDelegate(target, http, adapter);
                response.getResponses().add(targetResponse);
            }

            return response;
        }
        finally {
            adapter.exiting();
        }
    }

    /**
     * Set in-process mode, for unit testing.
     */
    static void setInProcess() {
        sInProcess = true;
    }

    /**
     * Execute request.
     * @param request to be executed.
     * @param http incoming HTTP request.
     * @param logger logging adapter.
     * @return response
     */
    private Response executeDelegate(final Request request,
                                     final HttpServletRequest http,
                                     final ONAPLogAdapter logger) {


        notNull(request);
        notNull(http);

        // Downstream call.

        try {

            if (sInProcess) {
                return this.executeInProcess(request, logger);
            }

            return this.executeREST(request, http, logger);
        }
        catch (final UnirestException | ReflectiveOperationException e) {
            logger.unwrap().error("Execute error", e);
            final Response response = new Response();
            response.setCode((this.getServiceName() + ".INVOKE_ERROR").toUpperCase(Locale.getDefault()));
            response.setSeverity("ERROR");
            return response;
        }
    }

    /**
     * Execute invocation over REST.
     * @param request mock request to be executed.
     * @param http HTTP request, used (only) to address the outgoing request.
     * @param logger logger adapter.
     * @return invocation response.
     * @throws UnirestException REST error.
     */
    private Response executeREST(final Request request,
                                 final HttpServletRequest http,
                                 final ONAPLogAdapter logger) throws UnirestException {
        // Otherwise via REST.

        logger.unwrap().info("Sending:\n{}", request);
        final StringBuilder url = new StringBuilder();
        url.append(http.getProtocol()).append("://");
        url.append(http.getServerName()).append(':');
        url.append(http.getServerPort()).append("/services/").append(request.getService());

        final UUID invocationID = logger.invoke(ONAPLogConstants.InvocationMode.SYNCHRONOUS);
        final HttpResponse<JsonNode> response =
                Unirest.post(url.toString())
                        .header(ONAPLogConstants.Headers.REQUEST_ID, MDC.get(ONAPLogConstants.MDCs.REQUEST_ID))
                        .header(ONAPLogConstants.Headers.INVOCATION_ID, invocationID.toString())
                        .header(ONAPLogConstants.Headers.PARTNER_NAME, this.getServiceName())
                        .header("Accept", MediaType.APPLICATION_JSON_VALUE)
                        .header("Content-Type", MediaType.APPLICATION_JSON_VALUE)
                        .body(request)
                        .asJson();

        // Parse response.

        final JSONObject responseJSON = response.getBody().getObject();
        logger.unwrap().info("Received:\n{}", responseJSON);
        return Response.fromJSON(responseJSON);
    }

    /**
     * Execute request in-process.
     * @param request mock request to be executed.
     * @param logger logger adapter.
     * @return invocation response.
     * @throws ReflectiveOperationException error loading target class.
     * @throws UnirestException REST error.
     */
    private Response executeInProcess(final Request request,
                                      final ONAPLogAdapter logger) throws ReflectiveOperationException, UnirestException {

        logger.unwrap().info("Executing in-process:\n{}", request);

        // Derive the name of the delegate class.

        final String delegateClass
                = AbstractComponent.class.getPackage().getName() + "." + request.getService()
                + ".Component" + request.getService().substring(0, 1).toUpperCase()
                + request.getService().substring(1);
        logger.unwrap().info("Invoking in-process [{}].", delegateClass);
        final AbstractComponent component = (AbstractComponent)Class.forName(delegateClass).newInstance();

        // Using Spring mock since we're not *actually* going over HTTP.

        final MockHttpServletRequest mock = new MockHttpServletRequest();

        // Generate INVOCATION_ID, and set MDCs aside for safekeeping.
        // (This is because when mocking, everything happens in the same thread.)

        final UUID invocationID = logger.invoke(ONAPLogConstants.InvocationMode.SYNCHRONOUS);
        final String requestID = MDC.get(ONAPLogConstants.MDCs.REQUEST_ID);
        final Map<String, String> safekeeping = MDC.getCopyOfContextMap();

        // Set headers.

        mock.addHeader(ONAPLogConstants.Headers.REQUEST_ID, StringUtils.defaultString(requestID));
        mock.addHeader(ONAPLogConstants.Headers.INVOCATION_ID, invocationID.toString());
        mock.addHeader(ONAPLogConstants.Headers.PARTNER_NAME, this.getServiceName());

        try {

            MDC.clear();

            // Execute.

            return component.execute(request, mock);
        }
        finally {

            // Restore MDCs.

            safekeeping.forEach((k, v) -> MDC.put(k, v));
        }
    }

    /**
     * Ensure non-nullness.
     * @param in to be checked.
     * @param <T> type.
     * @return input value, not null.
     */
    private static <T> T notNull(final T in) {
        if (in == null) {
            throw new AssertionError("");
        }
        return in;
    }

    /**
     * Get service name, with default.
     * @return service name, suitable for logging as MDC.
     */
    private String getServiceName() {
        return "service." + StringUtils.defaultString(this.getId(), "unnamed");
    }

    /**
     * Get logger instance.
     * @return logger.
     */
    private Logger getLogger() {
        return LoggerFactory.getLogger(this.getClass());
    }
}