313710745354f8c9fc3af9d423410910af6bdbc9
[ccsdk/cds.git] /
1 /*
2  * Copyright © 2018-2019 AT&T Intellectual Property.
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.onap.ccsdk.cds.blueprintsprocessor.rest.service
18
19 import kotlinx.coroutines.AbstractCoroutine
20 import kotlinx.coroutines.CoroutineScope
21 import kotlinx.coroutines.CoroutineStart
22 import kotlinx.coroutines.GlobalScope
23 import kotlinx.coroutines.InternalCoroutinesApi
24 import kotlinx.coroutines.handleCoroutineException
25 import kotlinx.coroutines.newCoroutineContext
26 import kotlinx.coroutines.reactor.ReactorContext
27 import kotlinx.coroutines.reactor.asCoroutineContext
28 import org.apache.http.message.BasicHeader
29 import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintConstants
30 import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintConstants.ONAP_INVOCATION_ID
31 import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintConstants.ONAP_PARTNER_NAME
32 import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintConstants.ONAP_REQUEST_ID
33 import org.onap.ccsdk.cds.controllerblueprints.core.MDCContext
34 import org.onap.ccsdk.cds.controllerblueprints.core.defaultToEmpty
35 import org.onap.ccsdk.cds.controllerblueprints.core.defaultToUUID
36 import org.onap.ccsdk.cds.controllerblueprints.core.logger
37 import org.slf4j.MDC
38 import org.springframework.http.server.reactive.ServerHttpRequest
39 import org.springframework.http.server.reactive.ServerHttpResponse
40 import reactor.core.Disposable
41 import reactor.core.publisher.Mono
42 import reactor.core.publisher.MonoSink
43 import java.net.InetAddress
44 import java.time.ZoneOffset
45 import java.time.ZonedDateTime
46 import java.time.format.DateTimeFormatter
47 import java.util.UUID
48 import kotlin.coroutines.CoroutineContext
49 import kotlin.coroutines.EmptyCoroutineContext
50
51 class RestLoggerService {
52     private val log = logger(RestLoggerService::class)
53
54     companion object {
55         /** Used before invoking any REST outbound request, Inbound Invocation ID is used as request Id
56          * for outbound Request, If invocation Id is missing then default Request Id will be generated.
57          */
58         fun httpInvoking(headers: Array<BasicHeader>) {
59             headers.plusElement(BasicHeader(ONAP_REQUEST_ID, MDC.get("InvocationID").defaultToUUID()))
60             headers.plusElement(BasicHeader(ONAP_INVOCATION_ID, UUID.randomUUID().toString()))
61             headers.plusElement(BasicHeader(ONAP_PARTNER_NAME, BluePrintConstants.APP_NAME))
62         }
63     }
64
65     fun entering(request: ServerHttpRequest) {
66         val localhost = InetAddress.getLocalHost()
67         val headers = request.headers
68         val requestID = headers.getFirst(ONAP_REQUEST_ID).defaultToUUID()
69         val invocationID = headers.getFirst(ONAP_INVOCATION_ID).defaultToUUID()
70         val partnerName = headers.getFirst(ONAP_PARTNER_NAME).defaultToEmpty()
71         MDC.put("InvokeTimestamp", ZonedDateTime.now(ZoneOffset.UTC).format(DateTimeFormatter.ISO_INSTANT))
72         MDC.put("RequestID", requestID)
73         MDC.put("InvocationID", invocationID)
74         MDC.put("PartnerName", partnerName)
75         MDC.put("ClientIPAddress", request.remoteAddress?.address?.hostAddress.defaultToEmpty())
76         MDC.put("ServerFQDN", localhost.hostName.defaultToEmpty())
77         if (MDC.get("ServiceName") == null || MDC.get("ServiceName").equals("", ignoreCase = true)) {
78             MDC.put("ServiceName", request.uri.path)
79         }
80     }
81
82     fun exiting(request: ServerHttpRequest, response: ServerHttpResponse) {
83         try {
84             val reqHeaders = request.headers
85             val resHeaders = response.headers
86             resHeaders[ONAP_REQUEST_ID] = MDC.get("RequestID")
87             resHeaders[ONAP_INVOCATION_ID] = MDC.get("InvocationID")
88             resHeaders[ONAP_PARTNER_NAME] = BluePrintConstants.APP_NAME
89         } catch (e: Exception) {
90             log.warn("couldn't set response headers", e)
91         } finally {
92             MDC.clear()
93         }
94     }
95 }
96
97 /** Used in Rest controller API methods to populate MDC context to nested coroutines from reactor web filter context. */
98 @UseExperimental(InternalCoroutinesApi::class)
99 fun <T> monoMdc(
100     context: CoroutineContext = EmptyCoroutineContext,
101     block: suspend CoroutineScope.() -> T?
102 ): Mono<T> = Mono.create { sink ->
103
104     val reactorContext = (context[ReactorContext]?.context?.putAll(sink.currentContext())
105         ?: sink.currentContext()).asCoroutineContext()
106     /** Populate MDC context only if present in Reactor Context */
107     val newContext = if (!reactorContext.context.isEmpty &&
108         reactorContext.context.hasKey(MDCContext)
109     ) {
110         val mdcContext = reactorContext.context.get<MDCContext>(MDCContext)
111         GlobalScope.newCoroutineContext(context + reactorContext + mdcContext)
112     } else GlobalScope.newCoroutineContext(context + reactorContext)
113
114     val coroutine = MonoMDCCoroutine(newContext, sink)
115     sink.onDispose(coroutine)
116     coroutine.start(CoroutineStart.DEFAULT, coroutine, block)
117 }
118
119 @InternalCoroutinesApi
120 class MonoMDCCoroutine<in T>(
121     parentContext: CoroutineContext,
122     private val sink: MonoSink<T>
123 ) : AbstractCoroutine<T>(parentContext, true), Disposable {
124
125     private var disposed = false
126
127     override fun onCompleted(value: T) {
128         if (!disposed) {
129             if (value == null) sink.success() else sink.success(value)
130         }
131     }
132
133     override fun onCancelled(cause: Throwable, handled: Boolean) {
134         if (!disposed) {
135             sink.error(cause)
136         } else if (!handled) {
137             handleCoroutineException(context, cause)
138         }
139     }
140
141     override fun dispose() {
142         disposed = true
143         cancel()
144     }
145
146     override fun isDisposed(): Boolean = disposed
147 }