dbb98f849a6ea644487b7df93c500f107ca559f4
[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.ONAP_INVOCATION_ID
30 import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintConstants.ONAP_PARTNER_NAME
31 import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintConstants.ONAP_REQUEST_ID
32 import org.onap.ccsdk.cds.controllerblueprints.core.MDCContext
33 import org.onap.ccsdk.cds.controllerblueprints.core.defaultToEmpty
34 import org.onap.ccsdk.cds.controllerblueprints.core.defaultToUUID
35 import org.onap.ccsdk.cds.controllerblueprints.core.logger
36 import org.slf4j.MDC
37 import org.springframework.http.server.reactive.ServerHttpRequest
38 import org.springframework.http.server.reactive.ServerHttpResponse
39 import reactor.core.Disposable
40 import reactor.core.publisher.Mono
41 import reactor.core.publisher.MonoSink
42 import java.net.InetAddress
43 import java.time.ZoneOffset
44 import java.time.ZonedDateTime
45 import java.time.format.DateTimeFormatter
46 import java.util.UUID
47 import kotlin.coroutines.CoroutineContext
48 import kotlin.coroutines.EmptyCoroutineContext
49
50 class RestLoggerService {
51     private val log = logger(RestLoggerService::class)
52
53     companion object {
54         /** Used before invoking any REST outbound request, Inbound Invocation ID is used as request Id
55          * for outbound Request, If invocation Id is missing then default Request Id will be generated.
56          */
57         fun httpInvoking(headers: Array<BasicHeader>) {
58             headers.plusElement(BasicHeader(ONAP_REQUEST_ID, MDC.get("InvocationID").defaultToUUID()))
59             headers.plusElement(BasicHeader(ONAP_INVOCATION_ID, UUID.randomUUID().toString()))
60             val partnerName = System.getProperty("APPNAME") ?: "BlueprintsProcessor"
61             headers.plusElement(BasicHeader(ONAP_PARTNER_NAME, partnerName))
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             val partnerName = System.getProperty("APPNAME") ?: "BlueprintsProcessor"
89             resHeaders[ONAP_PARTNER_NAME] = partnerName
90         } catch (e: Exception) {
91             log.warn("couldn't set response headers", e)
92         } finally {
93             MDC.clear()
94         }
95     }
96 }
97
98 /** Used in Rest controller API methods to populate MDC context to nested coroutines from reactor web filter context. */
99 @UseExperimental(InternalCoroutinesApi::class)
100 fun <T> monoMdc(
101     context: CoroutineContext = EmptyCoroutineContext,
102     block: suspend CoroutineScope.() -> T?
103 ): Mono<T> = Mono.create { sink ->
104
105     val reactorContext = (context[ReactorContext]?.context?.putAll(sink.currentContext())
106         ?: sink.currentContext()).asCoroutineContext()
107     /** Populate MDC context only if present in Reactor Context */
108     val newContext = if (!reactorContext.context.isEmpty &&
109         reactorContext.context.hasKey(MDCContext)
110     ) {
111         val mdcContext = reactorContext.context.get<MDCContext>(MDCContext)
112         GlobalScope.newCoroutineContext(context + reactorContext + mdcContext)
113     } else GlobalScope.newCoroutineContext(context + reactorContext)
114
115     val coroutine = MonoMDCCoroutine(newContext, sink)
116     sink.onDispose(coroutine)
117     coroutine.start(CoroutineStart.DEFAULT, coroutine, block)
118 }
119
120 @InternalCoroutinesApi
121 class MonoMDCCoroutine<in T>(
122     parentContext: CoroutineContext,
123     private val sink: MonoSink<T>
124 ) : AbstractCoroutine<T>(parentContext, true), Disposable {
125
126     private var disposed = false
127
128     override fun onCompleted(value: T) {
129         if (!disposed) {
130             if (value == null) sink.success() else sink.success(value)
131         }
132     }
133
134     override fun onCancelled(cause: Throwable, handled: Boolean) {
135         if (!disposed) {
136             sink.error(cause)
137         } else if (!handled) {
138             handleCoroutineException(context, cause)
139         }
140     }
141
142     override fun dispose() {
143         disposed = true
144         cancel()
145     }
146
147     override fun isDisposed(): Boolean = disposed
148 }