package org.apache.logging.log4j.core.selector;
import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import org.apache.logging.log4j.core.LoggerContext;
import org.apache.logging.log4j.core.impl.ContextAnchor;
import org.apache.logging.log4j.spi.LoggerContextShutdownAware;
import org.apache.logging.log4j.status.StatusLogger;
import org.apache.logging.log4j.util.StackLocatorUtil;
public class ClassLoaderContextSelector implements ContextSelector, LoggerContextShutdownAware {
private static final AtomicReference<LoggerContext> DEFAULT_CONTEXT = new AtomicReference<>();
protected static final StatusLogger LOGGER = StatusLogger.getLogger();
protected static final ConcurrentMap<String, AtomicReference<WeakReference<LoggerContext>>> CONTEXT_MAP =
new ConcurrentHashMap<>();
@Override
public void shutdown(final String fqcn, final ClassLoader loader, final boolean currentContext,
final boolean allContexts) {
LoggerContext ctx = null;
if (currentContext) {
ctx = ContextAnchor.THREAD_CONTEXT.get();
} else if (loader != null) {
ctx = findContext(loader);
} else {
final Class<?> clazz = StackLocatorUtil.getCallerClass(fqcn);
if (clazz != null) {
ctx = findContext(clazz.getClassLoader());
}
if (ctx == null) {
ctx = ContextAnchor.THREAD_CONTEXT.get();
}
}
if (ctx != null) {
ctx.stop(DEFAULT_STOP_TIMEOUT, TimeUnit.MILLISECONDS);
}
}
@Override
public void contextShutdown(org.apache.logging.log4j.spi.LoggerContext loggerContext) {
if (loggerContext instanceof LoggerContext) {
removeContext((LoggerContext) loggerContext);
}
}
@Override
public boolean hasContext(final String fqcn, final ClassLoader loader, final boolean currentContext) {
LoggerContext ctx;
if (currentContext) {
ctx = ContextAnchor.THREAD_CONTEXT.get();
} else if (loader != null) {
ctx = findContext(loader);
} else {
final Class<?> clazz = StackLocatorUtil.getCallerClass(fqcn);
if (clazz != null) {
ctx = findContext(clazz.getClassLoader());
} else {
ctx = ContextAnchor.THREAD_CONTEXT.get();
}
}
return ctx != null && ctx.isStarted();
}
private LoggerContext findContext(ClassLoader loaderOrNull) {
final ClassLoader loader = loaderOrNull != null ? loaderOrNull : ClassLoader.getSystemClassLoader();
final String name = toContextMapKey(loader);
AtomicReference<WeakReference<LoggerContext>> ref = CONTEXT_MAP.get(name);
if (ref != null) {
final WeakReference<LoggerContext> weakRef = ref.get();
return weakRef.get();
}
return null;
}
@Override
public LoggerContext getContext(final String fqcn, final ClassLoader loader, final boolean currentContext) {
return getContext(fqcn, loader, currentContext, null);
}
@Override
public LoggerContext getContext(final String fqcn, final ClassLoader loader, final boolean currentContext,
final URI configLocation) {
return getContext(fqcn, loader, null, currentContext, configLocation);
}
@Override
public LoggerContext getContext(final String fqcn, final ClassLoader loader, final Map.Entry<String, Object> entry,
final boolean currentContext, final URI configLocation) {
if (currentContext) {
final LoggerContext ctx = ContextAnchor.THREAD_CONTEXT.get();
if (ctx != null) {
return ctx;
}
return getDefault();
} else if (loader != null) {
return locateContext(loader, entry, configLocation);
} else {
final Class<?> clazz = StackLocatorUtil.getCallerClass(fqcn);
if (clazz != null) {
return locateContext(clazz.getClassLoader(), entry, configLocation);
}
final LoggerContext lc = ContextAnchor.THREAD_CONTEXT.get();
if (lc != null) {
return lc;
}
return getDefault();
}
}
@Override
public void removeContext(final LoggerContext context) {
for (final Map.Entry<String, AtomicReference<WeakReference<LoggerContext>>> entry : CONTEXT_MAP.entrySet()) {
final LoggerContext ctx = entry.getValue().get().get();
if (ctx == context) {
CONTEXT_MAP.remove(entry.getKey());
}
}
}
@Override
public List<LoggerContext> getLoggerContexts() {
final List<LoggerContext> list = new ArrayList<>();
final Collection<AtomicReference<WeakReference<LoggerContext>>> coll = CONTEXT_MAP.values();
for (final AtomicReference<WeakReference<LoggerContext>> ref : coll) {
final LoggerContext ctx = ref.get().get();
if (ctx != null) {
list.add(ctx);
}
}
return Collections.unmodifiableList(list);
}
private LoggerContext locateContext(final ClassLoader loaderOrNull, final Map.Entry<String, Object> entry,
final URI configLocation) {
final ClassLoader loader = loaderOrNull != null ? loaderOrNull : ClassLoader.getSystemClassLoader();
final String name = toContextMapKey(loader);
AtomicReference<WeakReference<LoggerContext>> ref = CONTEXT_MAP.get(name);
if (ref == null) {
if (configLocation == null) {
ClassLoader parent = loader.getParent();
while (parent != null) {
ref = CONTEXT_MAP.get(toContextMapKey(parent));
if (ref != null) {
final WeakReference<LoggerContext> r = ref.get();
final LoggerContext ctx = r.get();
if (ctx != null) {
return ctx;
}
}
parent = parent.getParent();
}
}
LoggerContext ctx = createContext(name, configLocation);
if (entry != null) {
ctx.putObject(entry.getKey(), entry.getValue());
}
LoggerContext newContext = CONTEXT_MAP.computeIfAbsent(name,
k -> new AtomicReference<>(new WeakReference<>(ctx))).get().get();
if (newContext == ctx) {
ctx.addShutdownListener(this);
}
return newContext;
}
final WeakReference<LoggerContext> weakRef = ref.get();
LoggerContext ctx = weakRef.get();
if (ctx != null) {
if (entry != null && ctx.getObject(entry.getKey()) == null) {
ctx.putObject(entry.getKey(), entry.getValue());
}
if (ctx.getConfigLocation() == null && configLocation != null) {
LOGGER.debug("Setting configuration to {}", configLocation);
ctx.setConfigLocation(configLocation);
} else if (ctx.getConfigLocation() != null && configLocation != null
&& !ctx.getConfigLocation().equals(configLocation)) {
LOGGER.warn("locateContext called with URI {}. Existing LoggerContext has URI {}", configLocation,
ctx.getConfigLocation());
}
return ctx;
}
ctx = createContext(name, configLocation);
if (entry != null) {
ctx.putObject(entry.getKey(), entry.getValue());
}
ref.compareAndSet(weakRef, new WeakReference<>(ctx));
return ctx;
}
protected LoggerContext createContext(final String name, final URI configLocation) {
return new LoggerContext(name, null, configLocation);
}
protected String toContextMapKey(final ClassLoader loader) {
return Integer.toHexString(System.identityHashCode(loader));
}
protected LoggerContext getDefault() {
final LoggerContext ctx = DEFAULT_CONTEXT.get();
if (ctx != null) {
return ctx;
}
DEFAULT_CONTEXT.compareAndSet(null, createContext(defaultContextName(), null));
return DEFAULT_CONTEXT.get();
}
protected String defaultContextName() {
return "Default";
}
}