Response message parsing for save
[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  *
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.*
20 import kotlinx.coroutines.reactor.ReactorContext
21 import kotlinx.coroutines.reactor.asCoroutineContext
22 import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintConstants.ONAP_INVOCATION_ID
23 import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintConstants.ONAP_PARTNER_NAME
24 import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintConstants.ONAP_REQUEST_ID
25 import org.onap.ccsdk.cds.controllerblueprints.core.MDCContext
26 import org.onap.ccsdk.cds.controllerblueprints.core.defaultToEmpty
27 import org.onap.ccsdk.cds.controllerblueprints.core.defaultToUUID
28 import org.onap.ccsdk.cds.controllerblueprints.core.logger
29 import org.slf4j.MDC
30 import org.springframework.http.server.reactive.ServerHttpRequest
31 import org.springframework.http.server.reactive.ServerHttpResponse
32 import reactor.core.Disposable
33 import reactor.core.publisher.Mono
34 import reactor.core.publisher.MonoSink
35 import java.time.ZoneOffset
36 import java.time.ZonedDateTime
37 import java.time.format.DateTimeFormatter
38 import kotlin.coroutines.CoroutineContext
39 import kotlin.coroutines.EmptyCoroutineContext
40
41 class RestLoggerService {
42     private val log = logger(RestLoggerService::class)
43
44
45     fun entering(request: ServerHttpRequest) {
46         val headers = request.headers
47         val requestID = headers.getFirst(ONAP_REQUEST_ID).defaultToUUID()
48         val invocationID = headers.getFirst(ONAP_INVOCATION_ID).defaultToUUID()
49         val partnerName = headers.getFirst(ONAP_PARTNER_NAME).defaultToEmpty()
50         MDC.put("InvokeTimestamp", ZonedDateTime.now(ZoneOffset.UTC).format(DateTimeFormatter.ISO_INSTANT))
51         MDC.put("RequestID", requestID)
52         MDC.put("InvocationID", invocationID)
53         MDC.put("PartnerName", partnerName)
54         MDC.put("ClientIPAddress", request.remoteAddress?.address?.hostAddress.defaultToEmpty())
55         MDC.put("ServerFQDN", request.remoteAddress?.hostString.defaultToEmpty())
56         if (MDC.get("ServiceName") == null || MDC.get("ServiceName").equals("", ignoreCase = true)) {
57             MDC.put("ServiceName", request.uri.path)
58         }
59     }
60
61     fun exiting(request: ServerHttpRequest, response: ServerHttpResponse) {
62         try {
63             val reqHeaders = request.headers
64             val resHeaders = response.headers
65             resHeaders[ONAP_REQUEST_ID] = MDC.get("RequestID")
66             resHeaders[ONAP_INVOCATION_ID] = MDC.get("InvocationID")
67             val partnerName = System.getProperty("APPNAME") ?: "BlueprintsProcessor"
68             resHeaders[ONAP_PARTNER_NAME] = partnerName
69         } catch (e: Exception) {
70             log.warn("couldn't set response headers", e)
71         } finally {
72             MDC.clear()
73         }
74     }
75 }
76
77
78 /** Used in Rest controller API methods to populate MDC context to nested coroutines from reactor web filter context. */
79 @UseExperimental(InternalCoroutinesApi::class)
80 fun <T> monoMdc(context: CoroutineContext = EmptyCoroutineContext,
81                 block: suspend CoroutineScope.() -> T?): Mono<T> = Mono.create { sink ->
82
83     val reactorContext = (context[ReactorContext]?.context?.putAll(sink.currentContext())
84             ?: sink.currentContext()).asCoroutineContext()
85     /** Populate MDC context only if present in Reactor Context */
86     val newContext = if (!reactorContext.context.isEmpty
87             && reactorContext.context.hasKey(MDCContext)) {
88         val mdcContext = reactorContext.context.get<MDCContext>(MDCContext)
89         GlobalScope.newCoroutineContext(context + reactorContext + mdcContext)
90     } else GlobalScope.newCoroutineContext(context + reactorContext)
91
92     val coroutine = MonoMDCCoroutine(newContext, sink)
93     sink.onDispose(coroutine)
94     coroutine.start(CoroutineStart.DEFAULT, coroutine, block)
95 }
96
97 @InternalCoroutinesApi
98 class MonoMDCCoroutine<in T>(
99         parentContext: CoroutineContext,
100         private val sink: MonoSink<T>
101 ) : AbstractCoroutine<T>(parentContext, true), Disposable {
102     private var disposed = false
103
104     override fun onCompleted(value: T) {
105         if (!disposed) {
106             if (value == null) sink.success() else sink.success(value)
107         }
108     }
109
110     override fun onCancelled(cause: Throwable, handled: Boolean) {
111         if (!disposed) {
112             sink.error(cause)
113         } else if (!handled) {
114             handleCoroutineException(context, cause)
115         }
116     }
117
118     override fun dispose() {
119         disposed = true
120         cancel()
121     }
122
123     override fun isDisposed(): Boolean = disposed
124 }