2 * Copyright © 2018-2019 AT&T Intellectual Property.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package org.onap.ccsdk.cds.blueprintsprocessor.rest.service
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.coroutineScope
25 import kotlinx.coroutines.handleCoroutineException
26 import kotlinx.coroutines.newCoroutineContext
27 import kotlinx.coroutines.reactor.ReactorContext
28 import kotlinx.coroutines.reactor.asCoroutineContext
29 import kotlinx.coroutines.withContext
30 import org.apache.http.message.BasicHeader
31 import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintConstants
32 import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintConstants.ONAP_INVOCATION_ID
33 import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintConstants.ONAP_PARTNER_NAME
34 import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintConstants.ONAP_REQUEST_ID
35 import org.onap.ccsdk.cds.controllerblueprints.core.MDCContext
36 import org.onap.ccsdk.cds.controllerblueprints.core.defaultToEmpty
37 import org.onap.ccsdk.cds.controllerblueprints.core.defaultToUUID
38 import org.onap.ccsdk.cds.controllerblueprints.core.logger
40 import org.springframework.http.server.reactive.ServerHttpRequest
41 import org.springframework.http.server.reactive.ServerHttpResponse
42 import reactor.core.Disposable
43 import reactor.core.publisher.Mono
44 import reactor.core.publisher.MonoSink
45 import java.net.InetAddress
46 import java.time.ZoneOffset
47 import java.time.ZonedDateTime
48 import java.time.format.DateTimeFormatter
50 import kotlin.coroutines.CoroutineContext
51 import kotlin.coroutines.EmptyCoroutineContext
53 class RestLoggerService {
54 private val log = logger(RestLoggerService::class)
57 /** Used before invoking any REST outbound request, Inbound Invocation ID is used as request Id
58 * for outbound Request, If invocation Id is missing then default Request Id will be generated.
60 fun httpInvoking(headers: Array<BasicHeader>) {
61 headers.plusElement(BasicHeader(ONAP_REQUEST_ID, MDC.get("InvocationID").defaultToUUID()))
62 headers.plusElement(BasicHeader(ONAP_INVOCATION_ID, UUID.randomUUID().toString()))
63 headers.plusElement(BasicHeader(ONAP_PARTNER_NAME, BluePrintConstants.APP_NAME))
67 fun entering(request: ServerHttpRequest) {
68 val localhost = InetAddress.getLocalHost()
69 val headers = request.headers
70 val requestID = headers.getFirst(ONAP_REQUEST_ID).defaultToUUID()
71 val invocationID = headers.getFirst(ONAP_INVOCATION_ID).defaultToUUID()
72 val partnerName = headers.getFirst(ONAP_PARTNER_NAME).defaultToEmpty()
73 MDC.put("InvokeTimestamp", ZonedDateTime.now(ZoneOffset.UTC).format(DateTimeFormatter.ISO_INSTANT))
74 MDC.put("RequestID", requestID)
75 MDC.put("InvocationID", invocationID)
76 MDC.put("PartnerName", partnerName)
77 MDC.put("ClientIPAddress", request.remoteAddress?.address?.hostAddress.defaultToEmpty())
78 MDC.put("ServerFQDN", localhost.hostName.defaultToEmpty())
79 if (MDC.get("ServiceName") == null || MDC.get("ServiceName").equals("", ignoreCase = true)) {
80 MDC.put("ServiceName", request.uri.path)
84 fun exiting(request: ServerHttpRequest, response: ServerHttpResponse) {
86 val reqHeaders = request.headers
87 val resHeaders = response.headers
88 resHeaders[ONAP_REQUEST_ID] = MDC.get("RequestID")
89 resHeaders[ONAP_INVOCATION_ID] = MDC.get("InvocationID")
90 resHeaders[ONAP_PARTNER_NAME] = BluePrintConstants.APP_NAME
91 } catch (e: Exception) {
92 log.warn("couldn't set response headers", e)
99 /** Used in Rest controller API methods to populate MDC context to nested coroutines from reactor web filter context. */
100 suspend fun <T> mdcWebCoroutineScope(
101 block: suspend CoroutineScope.() -> T
103 val reactorContext = this.coroutineContext[ReactorContext]
104 /** Populate MDC context only if present in Reactor Context */
105 val newContext = if (reactorContext != null &&
106 !reactorContext.context.isEmpty &&
107 reactorContext.context.hasKey(MDCContext)
109 val mdcContext = reactorContext.context.get<MDCContext>(MDCContext)
110 if (mdcContext != null)
111 newCoroutineContext(this.coroutineContext + reactorContext + mdcContext)
113 newCoroutineContext(this.coroutineContext + reactorContext)
114 } else this.coroutineContext
115 // Execute the block with new and old context
116 withContext(newContext) {
122 message = "Now CDS supports Coruoutin rest controller",
123 replaceWith = ReplaceWith("mdcWebCoroutineScope")
125 /** Used in Rest controller API methods to populate MDC context to nested coroutines from reactor web filter context. */
126 @UseExperimental(InternalCoroutinesApi::class)
128 context: CoroutineContext = EmptyCoroutineContext,
129 block: suspend CoroutineScope.() -> T?
130 ): Mono<T> = Mono.create { sink ->
132 val reactorContext = (context[ReactorContext]?.context?.putAll(sink.currentContext())
133 ?: sink.currentContext()).asCoroutineContext()
134 /** Populate MDC context only if present in Reactor Context */
135 val newContext = if (!reactorContext.context.isEmpty &&
136 reactorContext.context.hasKey(MDCContext)
138 val mdcContext = reactorContext.context.get<MDCContext>(MDCContext)
139 GlobalScope.newCoroutineContext(context + reactorContext + mdcContext)
140 } else GlobalScope.newCoroutineContext(context + reactorContext)
142 val coroutine = MonoMDCCoroutine(newContext, sink)
143 sink.onDispose(coroutine)
144 coroutine.start(CoroutineStart.DEFAULT, coroutine, block)
147 @InternalCoroutinesApi
148 class MonoMDCCoroutine<in T>(
149 parentContext: CoroutineContext,
150 private val sink: MonoSink<T>
151 ) : AbstractCoroutine<T>(parentContext, true), Disposable {
153 private var disposed = false
155 override fun onCompleted(value: T) {
157 if (value == null) sink.success() else sink.success(value)
161 override fun onCancelled(cause: Throwable, handled: Boolean) {
164 } else if (!handled) {
165 handleCoroutineException(context, cause)
169 override fun dispose() {
174 override fun isDisposed(): Boolean = disposed