com.icthh.xm.commons.timeline.TimelineEventProducer Maven / Gradle / Ivy
package com.icthh.xm.commons.timeline;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.icthh.xm.commons.logging.util.MdcUtils;
import com.icthh.xm.commons.timeline.domain.ApiMaskConfig;
import com.icthh.xm.commons.timeline.domain.ApiMaskRule;
import com.jayway.jsonpath.JsonPath;
import com.jayway.jsonpath.PathNotFoundException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.util.ContentCachingRequestWrapper;
import org.springframework.web.util.ContentCachingResponseWrapper;
import java.time.Instant;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
@Slf4j
@Component
public class TimelineEventProducer {
private final KafkaTemplate template;
private final ObjectMapper mapper = new ObjectMapper();
private final AntPathMatcher matcher = new AntPathMatcher();
@Value("${spring.application.name}")
private String appName;
private List maskRules;
public TimelineEventProducer(KafkaTemplate template,
ApiMaskConfig apiIgnore) {
this.template = template;
this.maskRules = apiIgnore != null ? apiIgnore.getMaskRules() : null;
}
/**
* Create event json string.
*
* @param request the http request
* @param response the http response
*/
public String createEventJson(HttpServletRequest request,
HttpServletResponse response,
String tenant,
String userLogin,
String userKey) {
try {
String requestBody = getRequestContent(request);
String responseBody = getResponseContent(response);
Instant startDate = Instant.ofEpochMilli(System.currentTimeMillis() - MdcUtils.getExecTimeMs());
Map data = new LinkedHashMap<>();
data.put("rid", MdcUtils.getRid());
data.put("login", userLogin);
data.put("userKey", userKey);
data.put("tenant", tenant);
data.put("msName", appName);
data.put("operationName", getResourceName(request.getRequestURI())
+ " " + getOperation(request.getMethod()));
data.put("operationUrl", request.getRequestURI());
data.put("startDate", startDate.toString());
data.put("httpMethod", request.getMethod());
data.put("requestBody", maskContent(requestBody, request.getRequestURI(), true, request.getMethod()));
data.put("requestLength", requestBody.length());
data.put("responseBody", maskContent(responseBody, request.getRequestURI(), false, request.getMethod()));
data.put("responseLength", responseBody.length());
data.put("requestHeaders", getRequestHeaders(request));
data.put("responseHeaders", getResponseHeaders(response));
data.put("httpStatusCode", response.getStatus());
data.put("channelType", "HTTP");
data.put("entityId", getEntityField(responseBody, "id"));
data.put("entityKey", getEntityField(responseBody, "key"));
data.put("entityTypeKey", getEntityField(responseBody, "typeKey"));
data.put("execTime", MdcUtils.getExecTimeMs());
return mapper.writeValueAsString(data);
} catch (Exception e) {
log.warn("Error creating timeline event", e);
}
return null;
}
/**
* Send event to kafka.
*
* @param topic the kafka topic
* @param content the event content
*/
@Async
public void send(String topic, String content) {
try {
if (!StringUtils.isBlank(content)) {
log.debug("Sending kafka event with data {} to topic {}", content, topic);
template.send(topic, content);
}
} catch (Exception e) {
log.error("Error send timeline event", e);
throw e;
}
}
private static String getRequestContent(HttpServletRequest request) {
if (request instanceof ContentCachingRequestWrapper) {
return new String(((ContentCachingRequestWrapper) request).getContentAsByteArray());
}
if (request instanceof HttpServletRequestWrapper
&& ((HttpServletRequestWrapper) request).getRequest() instanceof ContentCachingRequestWrapper) {
return new String(((ContentCachingRequestWrapper) ((HttpServletRequestWrapper) request)
.getRequest()).getContentAsByteArray());
}
log.warn("Empty request content because of unsupported request class {}", request.getClass());
return "";
}
private static String getResponseContent(HttpServletResponse response) {
if (response instanceof ContentCachingResponseWrapper) {
return new String(((ContentCachingResponseWrapper) response).getContentAsByteArray());
}
if (response instanceof HttpServletResponseWrapper
&& ((HttpServletResponseWrapper) response).getResponse() instanceof ContentCachingResponseWrapper) {
return new String(((ContentCachingResponseWrapper) ((HttpServletResponseWrapper) response)
.getResponse()).getContentAsByteArray());
}
log.warn("Empty response content because of unsupported response class {}", response.getClass());
return "";
}
private static Map getRequestHeaders(HttpServletRequest request) {
Map headers = new LinkedHashMap<>();
Set excludedHeaders = getExcludeHeaders();
Enumeration names = request.getHeaderNames();
while (names.hasMoreElements()) {
String name = names.nextElement();
if (!excludedHeaders.contains(name.toLowerCase())) {
headers.put(name.toLowerCase(), getHeaderValue(request, name));
}
}
return headers;
}
private static Set getExcludeHeaders() {
Set excludedHeaders = new HashSet<>();
excludedHeaders.add("cookie");
excludedHeaders.add("authorization");
return excludedHeaders;
}
private static Object getHeaderValue(HttpServletRequest request, String name) {
List value = Collections.list(request.getHeaders(name));
if (value.size() == 1) {
return value.get(0);
}
if (value.isEmpty()) {
return "";
}
return value;
}
private static Map getResponseHeaders(HttpServletResponse response) {
Map headers = new LinkedHashMap<>();
for (String header : response.getHeaderNames()) {
String value = response.getHeader(header);
headers.put(header.toLowerCase(), value);
}
headers.remove("set-cookie");
return headers;
}
private static Object getEntityField(String entity, String field) {
try {
return JsonPath.read(entity, "$." + field);
} catch (Exception ex) {
log.trace("JsonPath exception", ex);
try {
return JsonPath.read(entity, "$.xmEntity." + field);
} catch (Exception e) {
log.trace("JsonPath exception", e);
return "";
}
}
}
private static String getOperation(String method) {
switch (method) {
case "GET":
return "viewed";
case "POST":
return "created";
case "PUT":
return "changed";
case "DELETE":
return "deleted";
default:
return "";
}
}
private static String getResourceName(String path) {
String name = StringUtils.removeStart(path, "/api/");
if (StringUtils.startsWith(name, "_search")) {
name = StringUtils.substringAfter(name, "/");
}
return StringUtils.defaultIfBlank(StringUtils.substringBefore(name, "/"), "unknown");
}
private String maskContent(String content, String uri, boolean request, String httpMethod) {
if (CollectionUtils.isEmpty(maskRules) || StringUtils.isBlank(content)) {
return content;
}
return maskRules
.stream()
.filter(rule -> ((request && rule.isMaskRequest()) || (!request && rule.isMaskResponse()))
&& matcher.match(rule.getEndpointToMask(), uri)
&& rule.getHttpMethod().stream().anyMatch(method -> StringUtils.equalsIgnoreCase(method, httpMethod)))
.map(rule -> applyMask(content, rule))
.findAny()
.orElse(content);
}
private static String applyMask(String content, ApiMaskRule rule) {
String maskedContent = content;
for (String path : rule.getPathToMask()) {
try {
maskedContent = JsonPath.parse(maskedContent).set(path, rule.getMask()).jsonString();
} catch (PathNotFoundException e) {
log.debug("Path {} not found, when masking content data", path);
} catch (Exception e) {
log.warn("Failed to mask content data", e);
}
}
return maskedContent;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy