package org.apache.logging.log4j.core.selector;
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.atomic.AtomicReference;
import org.apache.logging.log4j.core.LoggerContext;
import org.apache.logging.log4j.core.impl.ContextAnchor;
import org.apache.logging.log4j.status.StatusLogger;
import org.apache.logging.log4j.util.StackLocatorUtil;
public class ClassLoaderContextSelector implements ContextSelector {
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 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) {
if (currentContext) {
final LoggerContext ctx = ContextAnchor.THREAD_CONTEXT.get();
if (ctx != null) {
return ctx;
}
return getDefault();
} else if (loader != null) {
return locateContext(loader, configLocation);
} else {
final Class<?> clazz = StackLocatorUtil.getCallerClass(fqcn);
if (clazz != null) {
return locateContext(clazz.getClassLoader(), 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 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);
final AtomicReference<WeakReference<LoggerContext>> r = new AtomicReference<>();
r.set(new WeakReference<>(ctx));
CONTEXT_MAP.putIfAbsent(name, r);
ctx = CONTEXT_MAP.get(name).get().get();
return ctx;
}
final WeakReference<LoggerContext> weakRef = ref.get();
LoggerContext ctx = weakRef.get();
if (ctx != null) {
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);
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";
}
}