Renaming Files having BluePrint to have Blueprint
[ccsdk/cds.git] / ms / blueprintsprocessor / modules / commons / rest-lib / src / main / kotlin / org / onap / ccsdk / cds / blueprintsprocessor / rest / service / RestLoggerService.kt
1 /*
2  * Copyright © 2018-2019 AT&T Intellectual Property.
3  * Modifications Copyright © 2020 Bell Canada.
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  *     http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17
18 package org.onap.ccsdk.cds.blueprintsprocessor.rest.service
19
20 import kotlinx.coroutines.AbstractCoroutine
21 import kotlinx.coroutines.CoroutineScope
22 import kotlinx.coroutines.CoroutineStart
23 import kotlinx.coroutines.GlobalScope
24 import kotlinx.coroutines.InternalCoroutinesApi
25 import kotlinx.coroutines.coroutineScope
26 import kotlinx.coroutines.handleCoroutineException
27 import kotlinx.coroutines.newCoroutineContext
28 import kotlinx.coroutines.reactor.ReactorContext
29 import kotlinx.coroutines.reactor.asCoroutineContext
30 import kotlinx.coroutines.withContext
31 import org.apache.http.message.BasicHeader
32 import org.onap.ccsdk.cds.controllerblueprints.core.BlueprintConstants
33 import org.onap.ccsdk.cds.controllerblueprints.core.BlueprintConstants.ONAP_INVOCATION_ID
34 import org.onap.ccsdk.cds.controllerblueprints.core.BlueprintConstants.ONAP_ORIGINATOR_ID
35 import org.onap.ccsdk.cds.controllerblueprints.core.BlueprintConstants.ONAP_PARTNER_NAME
36 import org.onap.ccsdk.cds.controllerblueprints.core.BlueprintConstants.ONAP_REQUEST_ID
37 import org.onap.ccsdk.cds.controllerblueprints.core.BlueprintConstants.ONAP_SUBREQUEST_ID
38 import org.onap.ccsdk.cds.controllerblueprints.core.MDCContext
39 import org.onap.ccsdk.cds.controllerblueprints.core.defaultToEmpty
40 import org.onap.ccsdk.cds.controllerblueprints.core.defaultToUUID
41 import org.onap.ccsdk.cds.controllerblueprints.core.logger
42 import org.slf4j.MDC
43 import org.springframework.http.server.reactive.ServerHttpRequest
44 import org.springframework.http.server.reactive.ServerHttpResponse
45 import reactor.core.Disposable
46 import reactor.core.publisher.Mono
47 import reactor.core.publisher.MonoSink
48 import java.net.InetAddress
49 import java.time.ZoneOffset
50 import java.time.ZonedDateTime
51 import java.time.format.DateTimeFormatter
52 import java.util.UUID
53 import kotlin.coroutines.CoroutineContext
54 import kotlin.coroutines.EmptyCoroutineContext
55
56 class RestLoggerService {
57
58     private val log = logger(RestLoggerService::class)
59
60     companion object {
61
62         /** Used before invoking any REST outbound request, Inbound Invocation ID is used as request Id
63          * for outbound Request, If invocation Id is missing then default Request Id will be generated.
64          */
65         fun httpInvoking(headers: Array<BasicHeader>) {
66             headers.plusElement(BasicHeader(ONAP_REQUEST_ID, MDC.get("InvocationID").defaultToUUID()))
67             headers.plusElement(BasicHeader(ONAP_INVOCATION_ID, UUID.randomUUID().toString()))
68             headers.plusElement(BasicHeader(ONAP_PARTNER_NAME, BlueprintConstants.APP_NAME))
69         }
70     }
71
72     fun entering(request: ServerHttpRequest) {
73         val localhost = InetAddress.getLocalHost()
74         val headers = request.headers
75         val requestID = headers.getFirst(ONAP_REQUEST_ID).defaultToUUID()
76         val subrequestID = headers.getFirst(ONAP_SUBREQUEST_ID).defaultToEmpty()
77         val originatorID = headers.getFirst(ONAP_ORIGINATOR_ID).defaultToEmpty()
78         val invocationID = headers.getFirst(ONAP_INVOCATION_ID).defaultToUUID()
79         val partnerName = headers.getFirst(ONAP_PARTNER_NAME).defaultToEmpty()
80         MDC.put("InvokeTimestamp", ZonedDateTime.now(ZoneOffset.UTC).format(DateTimeFormatter.ISO_INSTANT))
81         MDC.put("RequestID", requestID)
82         MDC.put("SubRequestID", subrequestID)
83         MDC.put("OriginatorID", originatorID)
84         MDC.put("InvocationID", invocationID)
85         MDC.put("PartnerName", partnerName)
86         MDC.put("ClientIPAddress", request.remoteAddress?.address?.hostAddress.defaultToEmpty())
87         MDC.put("ServerFQDN", localhost.hostName.defaultToEmpty())
88         if (MDC.get("ServiceName") == null || MDC.get("ServiceName").equals("", ignoreCase = true)) {
89             MDC.put("ServiceName", request.uri.path)
90         }
91     }
92
93     fun exiting(request: ServerHttpRequest, response: ServerHttpResponse) {
94         try {
95             val reqHeaders = request.headers
96             val resHeaders = response.headers
97             resHeaders[ONAP_REQUEST_ID] = MDC.get("RequestID")
98             resHeaders[ONAP_SUBREQUEST_ID] = MDC.get("SubRequestID")
99             resHeaders[ONAP_ORIGINATOR_ID] = MDC.get("OriginatorID")
100             resHeaders[ONAP_INVOCATION_ID] = MDC.get("InvocationID")
101             resHeaders[ONAP_PARTNER_NAME] = BlueprintConstants.APP_NAME
102         } catch (e: Exception) {
103             log.warn("couldn't set response headers", e)
104         } finally {
105             MDC.clear()
106         }
107     }
108 }
109
110 /** Used in Rest controller API methods to populate MDC context to nested coroutines from reactor web filter context. */
111 suspend fun <T> mdcWebCoroutineScope(
112     block: suspend CoroutineScope.() -> T
113 ) = coroutineScope {
114     val reactorContext = this.coroutineContext[ReactorContext]
115
116     /** Populate MDC context only if present in Reactor Context */
117     val newContext = if (reactorContext != null &&
118         !reactorContext.context.isEmpty &&
119         reactorContext.context.hasKey(MDCContext)
120     ) {
121         val mdcContext = reactorContext.context.get<MDCContext>(MDCContext)
122         if (mdcContext != null)
123             newCoroutineContext(this.coroutineContext + reactorContext + mdcContext)
124         else
125             newCoroutineContext(this.coroutineContext + reactorContext)
126     } else this.coroutineContext
127     // Execute the block with new and old context
128     withContext(newContext) {
129         block()
130     }
131 }
132
133 @Deprecated(
134     message = "Now CDS supports Coruoutin rest controller",
135     replaceWith = ReplaceWith("mdcWebCoroutineScope")
136 )
137 /** Used in Rest controller API methods to populate MDC context to nested coroutines from reactor web filter context. */
138 @UseExperimental(InternalCoroutinesApi::class)
139 fun <T> monoMdc(
140     context: CoroutineContext = EmptyCoroutineContext,
141     block: suspend CoroutineScope.() -> T?
142 ): Mono<T> = Mono.create { sink ->
143
144     val reactorContext = (
145         context[ReactorContext]?.context?.putAll(sink.currentContext())
146             ?: sink.currentContext()
147         ).asCoroutineContext()
148
149     /** Populate MDC context only if present in Reactor Context */
150     val newContext = if (!reactorContext.context.isEmpty &&
151         reactorContext.context.hasKey(MDCContext)
152     ) {
153         val mdcContext = reactorContext.context.get<MDCContext>(MDCContext)
154         GlobalScope.newCoroutineContext(context + reactorContext + mdcContext)
155     } else GlobalScope.newCoroutineContext(context + reactorContext)
156
157     val coroutine = MonoMDCCoroutine(newContext, sink)
158     sink.onDispose(coroutine)
159     coroutine.start(CoroutineStart.DEFAULT, coroutine, block)
160 }
161
162 @InternalCoroutinesApi
163 class MonoMDCCoroutine<in T>(
164     parentContext: CoroutineContext,
165     private val sink: MonoSink<T>
166 ) : AbstractCoroutine<T>(parentContext, true), Disposable {
167
168     private var disposed = false
169
170     override fun onCompleted(value: T) {
171         if (!disposed) {
172             if (value == null) sink.success() else sink.success(value)
173         }
174     }
175
176     override fun onCancelled(cause: Throwable, handled: Boolean) {
177         if (!disposed) {
178             sink.error(cause)
179         } else if (!handled) {
180             handleCoroutineException(context, cause)
181         }
182     }
183
184     override fun dispose() {
185         disposed = true
186         cancel()
187     }
188
189     override fun isDisposed(): Boolean = disposed
190 }