package org.sef4j.core.api.logger; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeMap; import org.sef4j.core.api.EventSender; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * */ public class EventLoggerContext { public static interface EventLoggerContextListener { // public void onStop(); // public void onStart(); public void onChangeInheritedLoggers(String eventLoggerName); } private static final Logger LOG = LoggerFactory.getLogger(EventLoggerContext.class); public static final String ROOT_LOGGER_NAME = ""; private Object lock = new Object(); private List<EventLoggerContextListener> contextListeners = new ArrayList<EventLoggerContextListener>(); private Map<String,EventSender<Object>> appenders = new HashMap<String,EventSender<Object>>(); private Map<String,PerLoggerContext> perLoggerContexts = new HashMap<String,PerLoggerContext>(); public static class PerLoggerContext { private final String eventLoggerName; Map<String,AppenderRefContext> toAppenderRefs = new HashMap<String,AppenderRefContext>(); // parent-child relationship where intermediate node are not always present // example: "a", "a.b.c" ... "a.b" is not present, a.b.c has first ancestor "a" private PerLoggerContext firstAncestor; private Map<String,PerLoggerContext> descendants = new HashMap<String,PerLoggerContext>(); // computed field from toAppenderRefs+ recurse on parent PerLoggerContext private EventSender<Object>[] inheritedAppenders; public PerLoggerContext(String eventLoggerName) { this.eventLoggerName = eventLoggerName; } } public static class AppenderRefContext { private final String appenderName; boolean addOrRemoveInheritable; // TOADD: EventFilter... => would wrap EventSender per logger?! public AppenderRefContext(String appenderName) { this.appenderName = appenderName; } public String getAppenderName() { return appenderName; } public boolean getAddOrRemoveInheritable() { return addOrRemoveInheritable; } public void setAddOrRemoveInheritable(boolean addOrRemoveInheritable) { this.addOrRemoveInheritable = addOrRemoveInheritable; } } // ------------------------------------------------------------------------ @SuppressWarnings("unchecked") public EventLoggerContext() { PerLoggerContext rootLoggerCtx = new PerLoggerContext(ROOT_LOGGER_NAME); rootLoggerCtx.inheritedAppenders = (EventSender<Object>[]) new EventSender<?>[0]; this.perLoggerContexts.put(ROOT_LOGGER_NAME, rootLoggerCtx); } // ------------------------------------------------------------------------ public void addContextListener(EventLoggerContextListener listener) { synchronized(lock) { contextListeners.add(listener); } } public void removeContextListener(EventLoggerContextListener listener) { synchronized(lock) { contextListeners.remove(listener); } } @SuppressWarnings("unchecked") public <T> void addAppender(String appenderName, EventSender<T> eventSender) { synchronized(lock) { if (appenders.get(appenderName) != null) { throw new IllegalArgumentException("appender name '" + appenderName + "' already used"); }; appenders.put(appenderName, (EventSender<Object>) eventSender); // nothing to fire event here (cf addLoggerToAppenderRef() instead) } } public void removeAppender(String appenderName) { synchronized(lock) { EventSender<Object> appender = appenders.remove(appenderName); if (appender == null) { throw new IllegalArgumentException("appender name '" + appenderName + "' not found"); }; // remove all corresponding loggerToAppenderRef + fire events Map<String,PerLoggerContext> loggerToReeval = new TreeMap<String,PerLoggerContext>(); // in sorted order: "a" before "a.b" ... for(PerLoggerContext loggerConf : perLoggerContexts.values()) { AppenderRefContext optL2a = loggerConf.toAppenderRefs.get(appenderName); if (optL2a != null) { // removeLoggerToAppenderRef(eventLoggerName, appenderName); loggerConf.toAppenderRefs.remove(appenderName); loggerToReeval.put(loggerConf.eventLoggerName, loggerConf); } } if (! loggerToReeval.isEmpty()) { for(PerLoggerContext l : loggerToReeval.values()) { reevalInheritedAppendersForDescendentLoggers(l, true); } } } } public void addLoggerToAppenderRef(String eventLoggerName, String appenderName, boolean addOrRemoveInheritable // TOADD: EventFilter... ){ synchronized(lock) { EventSender<Object> appender = this.appenders.get(appenderName); if (appender == null) { throw new IllegalArgumentException("appender not found"); } PerLoggerContext loggerCtx = getOrCreateLoggerContext(eventLoggerName); AppenderRefContext toAppenderCtx = loggerCtx.toAppenderRefs.get(appenderName); if (toAppenderCtx != null) { throw new IllegalArgumentException("appender ref already configured for logger"); } toAppenderCtx = new AppenderRefContext(appenderName); toAppenderCtx.addOrRemoveInheritable = addOrRemoveInheritable; loggerCtx.toAppenderRefs.put(appenderName, toAppenderCtx); reevalInheritedAppendersForDescendentLoggers(loggerCtx, true); } } public void removeLoggerToAppenderRef(String eventLoggerName, String appenderName){ synchronized(lock) { EventSender<Object> appender = this.appenders.get(appenderName); if (appender == null) { throw new IllegalArgumentException("appender not found"); } PerLoggerContext loggerCtx = perLoggerContexts.get(eventLoggerName); if (loggerCtx == null) { throw new IllegalArgumentException("logger not found"); } AppenderRefContext toAppenderConf = loggerCtx.toAppenderRefs.get(appenderName); if (toAppenderConf == null) { throw new IllegalArgumentException("appender ref not found for logger"); } loggerCtx.toAppenderRefs.remove(appenderName); reevalInheritedAppendersForDescendentLoggers(loggerCtx, true); } } // SPI, called from EventLoggerFactory // ------------------------------------------------------------------------ @SuppressWarnings("unchecked") public <E> EventSender<E>[] getInheritedAppendersFor(String eventLoggerName) { synchronized(lock) { PerLoggerContext loggerCxt = findFirstAncestorLoggerContextFor(eventLoggerName); return (EventSender<E>[]) loggerCxt.inheritedAppenders; } } @SuppressWarnings("unchecked") public <E> Map<String, EventSender<E>[]> getInheritedAppendersFor(Set<String> eventLoggerNames) { Map<String, EventSender<E>[]> res = new HashMap<String, EventSender<E>[]>(); synchronized(lock) { for(String eventLoggerName : eventLoggerNames) { PerLoggerContext loggerCxt = findFirstAncestorLoggerContextFor(eventLoggerName); res.put(eventLoggerName, (EventSender<E>[]) loggerCxt.inheritedAppenders); } } return res; } // internal // ------------------------------------------------------------------------ private PerLoggerContext getOrCreateLoggerContext(String eventLoggerName) { PerLoggerContext loggerCtx = this.perLoggerContexts.get(eventLoggerName); if (loggerCtx == null) { PerLoggerContext ancestor = findFirstAncestorLoggerContextFor(eventLoggerName); loggerCtx = new PerLoggerContext(eventLoggerName); this.perLoggerContexts.put(eventLoggerName, loggerCtx); // reeval parent-child ancestor relationships // transfer appropriate descendants from previous ancestor to new node if (ancestor.descendants != null && !ancestor.descendants.isEmpty()) { for(Iterator<PerLoggerContext> iter = ancestor.descendants.values().iterator(); iter.hasNext(); ) { PerLoggerContext descendant = iter.next(); if (descendant.eventLoggerName.startsWith(eventLoggerName)) { descendant.firstAncestor = loggerCtx; loggerCtx.descendants.put(descendant.eventLoggerName, descendant); iter.remove(); } } } // add this loggerCtx in parent loggerCtx.firstAncestor = ancestor; if (ancestor.descendants == null) { ancestor.descendants = new HashMap<String, EventLoggerContext.PerLoggerContext>(); } ancestor.descendants.put(loggerCtx.eventLoggerName, loggerCtx); } return loggerCtx; } private PerLoggerContext findFirstAncestorLoggerContextFor(String eventLoggerName) { PerLoggerContext res; for(String name = eventLoggerName; ; name = parentNameOf(name)) { PerLoggerContext tmpres = this.perLoggerContexts.get(name); if (tmpres != null) { res = tmpres; break; } } return res; } private static String parentNameOf(String loggerName) { String res; int indexLastDot = loggerName.lastIndexOf('.'); if (indexLastDot == -1) { res = ROOT_LOGGER_NAME; } else { res = loggerName.substring(0, indexLastDot); } return res; } @SuppressWarnings("unchecked") protected void reevalInheritedAppendersForDescendentLoggers(PerLoggerContext loggerContext, boolean fireChgEvent) { // EventSender[] oldInheritedAppenders = loggerContext.inheritedAppenders; List<EventSender<Object>> tmpAppenders = new ArrayList<EventSender<Object>>(); PerLoggerContext ancestorCtx = loggerContext.firstAncestor; if (ancestorCtx != null && ancestorCtx.inheritedAppenders != null) { tmpAppenders.addAll(Arrays.asList(ancestorCtx.inheritedAppenders)); } collectAddOrRemoveAppenders(tmpAppenders, loggerContext); EventSender<Object>[] newInheritedAppenders = (EventSender<Object>[]) tmpAppenders.toArray(new EventSender<?>[tmpAppenders.size()]); loggerContext.inheritedAppenders = newInheritedAppenders; boolean fireDescendantChgEvent = false; if (fireChgEvent) { // TOADD OPTIM: may compare for old equals new.. fireEventChangeInheritedLoggers(loggerContext.eventLoggerName); } if (loggerContext.descendants != null && !loggerContext.descendants.isEmpty()) { for(PerLoggerContext descendant : loggerContext.descendants.values()) { // *** recurse *** reevalInheritedAppendersForDescendentLoggers(descendant, fireDescendantChgEvent); } } } protected void fireEventChangeInheritedLoggers(String eventLoggerName) { for(EventLoggerContextListener listener : contextListeners) { try { listener.onChangeInheritedLoggers(eventLoggerName); } catch(Exception ex) { LOG.error("Failed to notify listener.onChangeInheritedLoggers() ... ignore, no rethrow!", ex); } } } private void collectAddOrRemoveAppenders(List<EventSender<Object>> resAppenders, PerLoggerContext eventLoggerConf) { for(AppenderRefContext l2a : eventLoggerConf.toAppenderRefs.values()) { String appenderName = l2a.getAppenderName(); EventSender<Object> appender = appenders.get(appenderName); if (l2a.addOrRemoveInheritable) { // add resAppenders.add(appender); } else { // remove resAppenders.remove(appender); } } } }