package com.linecorp.armeria.common.logback;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static java.util.Objects.requireNonNull;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import com.linecorp.armeria.common.http.HttpHeaderNames;
import com.linecorp.armeria.common.logback.RequestContextExporter.ExportEntry;
import io.netty.util.AsciiString;
import io.netty.util.AttributeKey;
final class RequestContextExporterBuilder {
private static final String PREFIX_ATTRS = "attrs.";
private static final String PREFIX_HTTP_REQ_HEADERS = "req.http_headers.";
private static final String PREFIX_HTTP_RES_HEADERS = "res.http_headers.";
private final Set<BuiltInProperty> builtIns = EnumSet.noneOf(BuiltInProperty.class);
private final Set<ExportEntry<AttributeKey<?>>> attrs = new HashSet<>();
private final Set<ExportEntry<AsciiString>> httpReqHeaders = new HashSet<>();
private final Set<ExportEntry<AsciiString>> httpResHeaders = new HashSet<>();
void addBuiltIn(BuiltInProperty property) {
builtIns.add(requireNonNull(property, "property"));
}
boolean containsBuiltIn(BuiltInProperty property) {
return builtIns.contains(requireNonNull(property, "property"));
}
Set<BuiltInProperty> getBuiltIns() {
return Collections.unmodifiableSet(builtIns);
}
void addAttribute(String alias, AttributeKey<?> attrKey) {
requireNonNull(alias, "alias");
requireNonNull(attrKey, "attrKey");
attrs.add(new ExportEntry<>(attrKey, PREFIX_ATTRS + alias, null));
}
void addAttribute(String alias, AttributeKey<?> attrKey, Function<?, String> stringifier) {
requireNonNull(alias, "alias");
requireNonNull(attrKey, "attrKey");
requireNonNull(stringifier, "stringifier");
attrs.add(new ExportEntry<>(attrKey, PREFIX_ATTRS + alias, stringifier));
}
boolean containsAttribute(AttributeKey<?> key) {
requireNonNull(key, "key");
return attrs.stream().anyMatch(e -> e.key.equals(key));
}
Map<String, AttributeKey<?>> getAttributes() {
return Collections.unmodifiableMap(attrs.stream().collect(
Collectors.toMap(e -> e.mdcKey.substring(PREFIX_ATTRS.length()), e -> e.key)));
}
void addHttpRequestHeader(CharSequence name) {
addHttpHeader(PREFIX_HTTP_REQ_HEADERS, httpReqHeaders, name);
}
void addHttpResponseHeader(CharSequence name) {
addHttpHeader(PREFIX_HTTP_RES_HEADERS, httpResHeaders, name);
}
private static void addHttpHeader(
String mdcKeyPrefix, Set<ExportEntry<AsciiString>> httpHeaders, CharSequence name) {
final AsciiString key = toHeaderName(name);
final String value = mdcKeyPrefix + key;
httpHeaders.add(new ExportEntry<>(key, value, null));
}
boolean containsHttpRequestHeader(CharSequence name) {
return httpReqHeaders.stream().anyMatch(e -> e.key.contentEqualsIgnoreCase(name));
}
boolean containsHttpResponseHeader(CharSequence name) {
return httpResHeaders.stream().anyMatch(e -> e.key.contentEqualsIgnoreCase(name));
}
private static AsciiString toHeaderName(CharSequence name) {
return HttpHeaderNames.of(requireNonNull(name, "name").toString());
}
Set<AsciiString> getHttpRequestHeaders() {
return httpReqHeaders.stream().map(e -> e.key).collect(toImmutableSet());
}
Set<AsciiString> getHttpResponseHeaders() {
return httpResHeaders.stream().map(e -> e.key).collect(toImmutableSet());
}
void export(String mdcKey) {
requireNonNull(mdcKey, "mdcKey");
final Optional<BuiltInProperty> opt = BuiltInProperty.findByMdcKey(mdcKey);
if (opt.isPresent()) {
builtIns.add(opt.get());
return;
}
if (mdcKey.startsWith(PREFIX_ATTRS)) {
final String[] components = mdcKey.split(":");
switch (components.length) {
case 2:
addAttribute(components[0].substring(PREFIX_ATTRS.length()),
AttributeKey.valueOf(components[1]));
break;
case 3:
final Function<?, String> stringifier = newStringifier(mdcKey, components[2]);
addAttribute(components[0].substring(PREFIX_ATTRS.length()),
AttributeKey.valueOf(components[1]),
stringifier);
break;
default:
throw new IllegalArgumentException(
"invalid attribute export: " + mdcKey +
" (expected: attrs.<alias>:<AttributeKey.name>[:<FQCN of Function<?, String>>])");
}
return;
}
if (mdcKey.startsWith(PREFIX_HTTP_REQ_HEADERS)) {
addHttpRequestHeader(mdcKey.substring(PREFIX_HTTP_REQ_HEADERS.length()));
return;
}
if (mdcKey.startsWith(PREFIX_HTTP_RES_HEADERS)) {
addHttpResponseHeader(mdcKey.substring(PREFIX_HTTP_RES_HEADERS.length()));
return;
}
throw new IllegalArgumentException("unknown MDC key: " + mdcKey);
}
@SuppressWarnings("unchecked")
private Function<?, String> newStringifier(String mdcKey, String className) {
final Function<?, String> stringifier;
try {
stringifier = (Function<?, String>) Class.forName(
className, true, getClass().getClassLoader()).newInstance();
} catch (Exception e) {
throw new IllegalArgumentException("failed to instantiate a stringifier function: " +
mdcKey, e);
}
return stringifier;
}
RequestContextExporter build() {
return new RequestContextExporter(builtIns, attrs, httpReqHeaders, httpResHeaders);
}
}