From: Mathieu Baudier Date: Fri, 14 Jan 2022 07:07:23 +0000 (+0100) Subject: Adapt after Argeo Suite refactoring. X-Git-Tag: argeo-commons-2.3.5~87 X-Git-Url: https://git.argeo.org/?p=lgpl%2Fargeo-commons.git;a=commitdiff_plain;h=6862cee138ca8ed2bbf6427b20b389a56b5df32f Adapt after Argeo Suite refactoring. --- diff --git a/org.argeo.cms.jcr/OSGI-INF/dataServletContext.xml b/org.argeo.cms.jcr/OSGI-INF/dataServletContext.xml index ffd8804b5..f5fc8deaa 100644 --- a/org.argeo.cms.jcr/OSGI-INF/dataServletContext.xml +++ b/org.argeo.cms.jcr/OSGI-INF/dataServletContext.xml @@ -1,6 +1,6 @@ - + diff --git a/org.argeo.cms.jcr/OSGI-INF/filesServletContext.xml b/org.argeo.cms.jcr/OSGI-INF/filesServletContext.xml index 049270c6a..5fb56e3ed 100644 --- a/org.argeo.cms.jcr/OSGI-INF/filesServletContext.xml +++ b/org.argeo.cms.jcr/OSGI-INF/filesServletContext.xml @@ -1,6 +1,6 @@ - + diff --git a/org.argeo.cms.jcr/OSGI-INF/jcrDeployment.xml b/org.argeo.cms.jcr/OSGI-INF/jcrDeployment.xml index fcba11cc5..a94b15168 100644 --- a/org.argeo.cms.jcr/OSGI-INF/jcrDeployment.xml +++ b/org.argeo.cms.jcr/OSGI-INF/jcrDeployment.xml @@ -1,5 +1,5 @@ - + diff --git a/org.argeo.cms.jcr/OSGI-INF/jcrServletContext.xml b/org.argeo.cms.jcr/OSGI-INF/jcrServletContext.xml index dd83c1d1c..a0885bbc5 100644 --- a/org.argeo.cms.jcr/OSGI-INF/jcrServletContext.xml +++ b/org.argeo.cms.jcr/OSGI-INF/jcrServletContext.xml @@ -1,6 +1,6 @@ - + diff --git a/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/CmsJcrDeployment.java b/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/CmsJcrDeployment.java index 62899cdf6..340d13782 100644 --- a/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/CmsJcrDeployment.java +++ b/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/CmsJcrDeployment.java @@ -75,15 +75,15 @@ public class CmsJcrDeployment { CmsDeployment cmsDeployment; public CmsJcrDeployment() { - dataModels = new DataModels(bc); // initTrackers(); } - public void init() { + public void start() { + dataModels = new DataModels(bc); ServiceTracker repoContextSt = new RepositoryContextStc(); - // repoContextSt.open(); - KernelUtils.asyncOpen(repoContextSt); + repoContextSt.open(); + //KernelUtils.asyncOpen(repoContextSt); // nodeDeployment = CmsJcrActivator.getService(NodeDeployment.class); @@ -91,7 +91,7 @@ public class CmsJcrDeployment { } - public void destroy() { + public void stop() { // if (nodeHttp != null) // nodeHttp.destroy(); diff --git a/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/DataModels.java b/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/DataModels.java index 5a790f2ea..f2196bd41 100644 --- a/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/DataModels.java +++ b/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/DataModels.java @@ -9,7 +9,6 @@ import java.util.Map; import java.util.TreeMap; import org.argeo.api.cms.CmsLog; -import org.argeo.cms.CmsException; import org.argeo.cms.osgi.DataModelNamespace; import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; @@ -129,7 +128,7 @@ class DataModels implements BundleListener { assert requiredDataModelName != null; DataModel requiredDataModel = dataModels.get(requiredDataModelName); if (requiredDataModel == null) - throw new CmsException("No required data model " + requiredDataModelName); + throw new IllegalStateException("No required data model " + requiredDataModelName); req.add(requiredDataModel); } required = Collections.unmodifiableList(req); diff --git a/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/RepositoryContextsFactory.java b/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/RepositoryContextsFactory.java index 2b9ad0043..2ffac9c4c 100644 --- a/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/RepositoryContextsFactory.java +++ b/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/RepositoryContextsFactory.java @@ -38,10 +38,14 @@ public class RepositoryContextsFactory implements ManagedServiceFactory { for (String pid : repositories.keySet()) { try { RepositoryContext repositoryContext = repositories.get(pid); - repositoryContext.getRepository().shutdown(); - if (log.isDebugEnabled()) - log.debug("Shut down repository " + pid - + (pidToCn.containsKey(pid) ? " (" + pidToCn.get(pid) + ")" : "")); + // Must start in another thread otherwise shutdown is interrupted + // TODO use an executor? + new Thread(() -> { + repositoryContext.getRepository().shutdown(); + if (log.isDebugEnabled()) + log.debug("Shut down repository " + pid + + (pidToCn.containsKey(pid) ? " (" + pidToCn.get(pid) + ")" : "")); + }, "Shutdown JCR repository " + pid).start(); } catch (Exception e) { log.error("Error when shutting down Jackrabbit repository " + pid, e); } diff --git a/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/DataServletContext.java b/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/DataServletContext.java new file mode 100644 index 000000000..2f60e97d9 --- /dev/null +++ b/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/DataServletContext.java @@ -0,0 +1,8 @@ +package org.argeo.cms.jcr.internal.servlet; + +import org.argeo.cms.servlet.CmsServletContext; + +/** Internal subclass, so that config resources can be loaded from our bundle. */ +public class DataServletContext extends CmsServletContext { + +} diff --git a/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/JcrServletContext.java b/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/JcrServletContext.java new file mode 100644 index 000000000..21046f34e --- /dev/null +++ b/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/JcrServletContext.java @@ -0,0 +1,8 @@ +package org.argeo.cms.jcr.internal.servlet; + +import org.argeo.cms.servlet.PrivateWwwAuthServletContext; + +/** Internal subclass, so that config resources can be loaded from our bundle. */ +public class JcrServletContext extends PrivateWwwAuthServletContext { + +} diff --git a/org.argeo.cms.servlet/OSGI-INF/pkgServlet.xml b/org.argeo.cms.servlet/OSGI-INF/pkgServlet.xml index 30e52311c..00fcaff99 100644 --- a/org.argeo.cms.servlet/OSGI-INF/pkgServlet.xml +++ b/org.argeo.cms.servlet/OSGI-INF/pkgServlet.xml @@ -1,6 +1,6 @@ - + diff --git a/org.argeo.cms.servlet/src/org/argeo/cms/servlet/CmsServletContext.java b/org.argeo.cms.servlet/src/org/argeo/cms/servlet/CmsServletContext.java index 40404c5d4..1ae6286ac 100644 --- a/org.argeo.cms.servlet/src/org/argeo/cms/servlet/CmsServletContext.java +++ b/org.argeo.cms.servlet/src/org/argeo/cms/servlet/CmsServletContext.java @@ -90,6 +90,8 @@ public class CmsServletContext extends ServletContextHelper { @Override public URL getResource(String name) { + // TODO make it more robust and versatile + // if used directly it can only load from within this bundle return bundle.getResource(name); } diff --git a/org.argeo.cms/src/org/argeo/cms/ArgeoLogListener.java b/org.argeo.cms/src/org/argeo/cms/ArgeoLogListener.java index e4a9941c7..a01858aa9 100644 --- a/org.argeo.cms/src/org/argeo/cms/ArgeoLogListener.java +++ b/org.argeo.cms/src/org/argeo/cms/ArgeoLogListener.java @@ -1,6 +1,7 @@ package org.argeo.cms; /** Framework agnostic interface for log notifications */ +@Deprecated public interface ArgeoLogListener { /** * Appends a log diff --git a/org.argeo.cms/src/org/argeo/cms/ArgeoLogger.java b/org.argeo.cms/src/org/argeo/cms/ArgeoLogger.java index 81a40f50c..71c503900 100644 --- a/org.argeo.cms/src/org/argeo/cms/ArgeoLogger.java +++ b/org.argeo.cms/src/org/argeo/cms/ArgeoLogger.java @@ -4,6 +4,7 @@ package org.argeo.cms; * Logging framework agnostic identifying a logging service, to which one can * register */ +@Deprecated public interface ArgeoLogger { /** * Register for events by threads with the same authentication (or all diff --git a/org.argeo.cms/src/org/argeo/cms/internal/osgi/CmsActivator.java b/org.argeo.cms/src/org/argeo/cms/internal/osgi/CmsActivator.java index 264f0c1b9..038d7029c 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/osgi/CmsActivator.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/osgi/CmsActivator.java @@ -35,7 +35,7 @@ public class CmsActivator implements BundleActivator { private LogReaderService logReaderService; - private NodeLogger logger; + private CmsOsgiLogger logger; // private CmsStateImpl nodeState; // private CmsDeploymentImpl nodeDeployment; // private CmsContextImpl nodeInstance; @@ -55,6 +55,7 @@ public class CmsActivator implements BundleActivator { // this.bc = bundleContext; if (bundleContext != null) this.logReaderService = getService(LogReaderService.class); + initArgeoLogger(); // this.internalExecutorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); // // try { @@ -130,7 +131,7 @@ public class CmsActivator implements BundleActivator { } private void initArgeoLogger() { - logger = new NodeLogger(logReaderService); + logger = new CmsOsgiLogger(logReaderService); if (bundleContext != null) bundleContext.registerService(ArgeoLogger.class, logger, null); } diff --git a/org.argeo.cms/src/org/argeo/cms/internal/osgi/CmsOsgiLogger.java b/org.argeo.cms/src/org/argeo/cms/internal/osgi/CmsOsgiLogger.java new file mode 100644 index 000000000..91628d344 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/internal/osgi/CmsOsgiLogger.java @@ -0,0 +1,541 @@ +package org.argeo.cms.internal.osgi; + +import java.io.IOException; +import java.net.URI; +import java.nio.file.FileSystems; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardWatchEventKinds; +import java.nio.file.WatchEvent; +import java.nio.file.WatchKey; +import java.nio.file.WatchService; +import java.security.SignatureException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.ListIterator; +import java.util.Map; +import java.util.Properties; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; + +import org.argeo.api.cms.CmsConstants; +import org.argeo.api.cms.CmsLog; +import org.argeo.cms.ArgeoLogListener; +import org.argeo.cms.ArgeoLogger; +import org.argeo.cms.CmsException; +import org.argeo.cms.auth.CurrentUser; +import org.argeo.cms.internal.runtime.KernelConstants; +import org.argeo.cms.internal.runtime.KernelUtils; +import org.argeo.osgi.useradmin.UserAdminConf; +import org.osgi.framework.Bundle; +import org.osgi.framework.Constants; +import org.osgi.framework.ServiceReference; +import org.osgi.service.cm.ConfigurationAdmin; +import org.osgi.service.log.LogEntry; +import org.osgi.service.log.LogLevel; +import org.osgi.service.log.LogListener; +import org.osgi.service.log.LogReaderService; + +/** Not meant to be used directly in standard log4j config */ +public class CmsOsgiLogger implements ArgeoLogger, LogListener { + /** Internal debug for development purposes. */ + private static Boolean debug = false; + + private Boolean disabled = false; + + private String level = null; + +// private Level log4jLevel = null; + + private Properties configuration; + + private AppenderImpl appender; + + private final List everythingListeners = Collections + .synchronizedList(new ArrayList()); + private final List allUsersListeners = Collections + .synchronizedList(new ArrayList()); + private final Map> userListeners = Collections + .synchronizedMap(new HashMap>()); + + private BlockingQueue events; + private LogDispatcherThread logDispatcherThread = new LogDispatcherThread(); + + private Integer maxLastEventsCount = 10 * 1000; + + /** Marker to prevent stack overflow */ + private ThreadLocal dispatching = new ThreadLocal() { + + @Override + protected Boolean initialValue() { + return false; + } + }; + + public CmsOsgiLogger(LogReaderService lrs) { + if (lrs != null) { + Enumeration logEntries = lrs.getLog(); + while (logEntries.hasMoreElements()) + logged(logEntries.nextElement()); + lrs.addLogListener(this); + + // configure log4j watcher +// String log4jConfiguration = KernelUtils.getFrameworkProp("log4j.configuration"); +// if (log4jConfiguration != null && log4jConfiguration.startsWith("file:")) { +// if (log4jConfiguration.contains("..")) { +// if (log4jConfiguration.startsWith("file://")) +// log4jConfiguration = log4jConfiguration.substring("file://".length()); +// else if (log4jConfiguration.startsWith("file:")) +// log4jConfiguration = log4jConfiguration.substring("file:".length()); +// } +// try { +// Path log4jconfigPath; +// if (log4jConfiguration.startsWith("file:")) +// log4jconfigPath = Paths.get(new URI(log4jConfiguration)); +// else +// log4jconfigPath = Paths.get(log4jConfiguration); +// Thread log4jConfWatcher = new Log4jConfWatcherThread(log4jconfigPath); +// log4jConfWatcher.start(); +// } catch (Exception e) { +// stdErr("Badly formatted log4j configuration URI " + log4jConfiguration + ": " + e.getMessage()); +// } +// } + } + } + + public void init() { + try { + events = new LinkedBlockingQueue(); + + // if (layout != null) + // setLayout(layout); + // else + // setLayout(new PatternLayout(pattern)); + appender = new AppenderImpl(); + reloadConfiguration(); +// Logger.getRootLogger().addAppender(appender); + + logDispatcherThread = new LogDispatcherThread(); + logDispatcherThread.start(); + } catch (Exception e) { + throw new CmsException("Cannot initialize log4j"); + } + } + + public void destroy() throws Exception { +// Logger.getRootLogger().removeAppender(appender); + allUsersListeners.clear(); + for (List lst : userListeners.values()) + lst.clear(); + userListeners.clear(); + + events.clear(); + events = null; + logDispatcherThread.interrupt(); + } + + // public void setLayout(Layout layout) { + // this.layout = layout; + // } + + public String toString() { + return "Node Logger"; + } + + // + // OSGi LOGGER + // + @Override + public void logged(LogEntry status) { + CmsLog pluginLog = CmsLog.getLog(status.getBundle().getSymbolicName()); + LogLevel severity = status.getLogLevel(); + if (severity.equals(LogLevel.ERROR) && pluginLog.isErrorEnabled()) { + // FIXME Fix Argeo TP + if (status.getException() instanceof SignatureException) + return; + pluginLog.error(msg(status), status.getException()); + } else if (severity.equals(LogLevel.WARN) && pluginLog.isWarnEnabled()) { + if (pluginLog.isTraceEnabled()) + pluginLog.warn(msg(status), status.getException()); + else + pluginLog.warn(msg(status)); + } else if (severity.equals(LogLevel.INFO) && pluginLog.isDebugEnabled()) + pluginLog.debug(msg(status), status.getException()); + else if (severity.equals(LogLevel.DEBUG) && pluginLog.isTraceEnabled()) + pluginLog.trace(msg(status), status.getException()); + else if (severity.equals(LogLevel.TRACE) && pluginLog.isTraceEnabled()) + pluginLog.trace(msg(status), status.getException()); + } + + private String msg(LogEntry status) { + StringBuilder sb = new StringBuilder(); + sb.append(status.getMessage()); + Bundle bundle = status.getBundle(); + if (bundle != null) { + sb.append(" '" + bundle.getSymbolicName() + "'"); + } + ServiceReference sr = status.getServiceReference(); + if (sr != null) { + sb.append(' '); + String[] objectClasses = (String[]) sr.getProperty(Constants.OBJECTCLASS); + if (isSpringApplicationContext(objectClasses)) { + sb.append("{org.springframework.context.ApplicationContext}"); + Object symbolicName = sr.getProperty(Constants.BUNDLE_SYMBOLICNAME); + if (symbolicName != null) + sb.append(" " + Constants.BUNDLE_SYMBOLICNAME + ": " + symbolicName); + } else { + sb.append(arrayToString(objectClasses)); + } + Object cn = sr.getProperty(CmsConstants.CN); + if (cn != null) + sb.append(" " + CmsConstants.CN + ": " + cn); + Object factoryPid = sr.getProperty(ConfigurationAdmin.SERVICE_FACTORYPID); + if (factoryPid != null) + sb.append(" " + ConfigurationAdmin.SERVICE_FACTORYPID + ": " + factoryPid); + // else { + // Object servicePid = sr.getProperty(Constants.SERVICE_PID); + // if (servicePid != null) + // sb.append(" " + Constants.SERVICE_PID + ": " + servicePid); + // } + // servlets + Object whiteBoardPattern = sr.getProperty(KernelConstants.WHITEBOARD_PATTERN_PROP); + if (whiteBoardPattern != null) { + if (whiteBoardPattern instanceof String) { + sb.append(" " + KernelConstants.WHITEBOARD_PATTERN_PROP + ": " + whiteBoardPattern); + } else { + sb.append(" " + KernelConstants.WHITEBOARD_PATTERN_PROP + ": " + + arrayToString((String[]) whiteBoardPattern)); + } + } + // RWT + Object contextName = sr.getProperty(KernelConstants.CONTEXT_NAME_PROP); + if (contextName != null) + sb.append(" " + KernelConstants.CONTEXT_NAME_PROP + ": " + contextName); + + // user directories + Object baseDn = sr.getProperty(UserAdminConf.baseDn.name()); + if (baseDn != null) + sb.append(" " + UserAdminConf.baseDn.name() + ": " + baseDn); + + } + return sb.toString(); + } + + private String arrayToString(Object[] arr) { + StringBuilder sb = new StringBuilder(); + sb.append('['); + for (int i = 0; i < arr.length; i++) { + if (i != 0) + sb.append(','); + sb.append(arr[i]); + } + sb.append(']'); + return sb.toString(); + } + + private boolean isSpringApplicationContext(String[] objectClasses) { + for (String clss : objectClasses) { + if (clss.equals("org.eclipse.gemini.blueprint.context.DelegatedExecutionOsgiBundleApplicationContext")) { + return true; + } + } + return false; + } + + // + // ARGEO LOGGER + // + + public synchronized void register(ArgeoLogListener listener, Integer numberOfPreviousEvents) { + String username = CurrentUser.getUsername(); + if (username == null) + throw new CmsException("Only authenticated users can register a log listener"); + + if (!userListeners.containsKey(username)) { + List lst = Collections.synchronizedList(new ArrayList()); + userListeners.put(username, lst); + } + userListeners.get(username).add(listener); + List lastEvents = logDispatcherThread.getLastEvents(username, numberOfPreviousEvents); + for (LogEvent evt : lastEvents) + dispatchEvent(listener, evt); + } + + public synchronized void registerForAll(ArgeoLogListener listener, Integer numberOfPreviousEvents, + boolean everything) { + if (everything) + everythingListeners.add(listener); + else + allUsersListeners.add(listener); + List lastEvents = logDispatcherThread.getLastEvents(null, numberOfPreviousEvents); + for (LogEvent evt : lastEvents) + if (everything || evt.getUsername() != null) + dispatchEvent(listener, evt); + } + + public synchronized void unregister(ArgeoLogListener listener) { + String username = CurrentUser.getUsername(); + if (username == null)// FIXME + return; + if (!userListeners.containsKey(username)) + throw new IllegalStateException("No user listeners " + listener + " registered for user " + username); + if (!userListeners.get(username).contains(listener)) + throw new IllegalStateException("No user listeners " + listener + " registered for user " + username); + userListeners.get(username).remove(listener); + if (userListeners.get(username).isEmpty()) + userListeners.remove(username); + + } + + public synchronized void unregisterForAll(ArgeoLogListener listener) { + everythingListeners.remove(listener); + allUsersListeners.remove(listener); + } + + /** For development purpose, since using regular logging is not easy here */ + private static void stdOut(Object obj) { + System.out.println(obj); + } + + private static void stdErr(Object obj) { + System.err.println(obj); + } + + private static void debug(Object obj) { + if (debug) + System.out.println(obj); + } + + private static boolean isInternalDebugEnabled() { + return debug; + } + + // public void setPattern(String pattern) { + // this.pattern = pattern; + // } + + public void setDisabled(Boolean disabled) { + this.disabled = disabled; + } + + public void setLevel(String level) { + this.level = level; + } + + public void setConfiguration(Properties configuration) { + this.configuration = configuration; + } + + public void updateConfiguration(Properties configuration) { + setConfiguration(configuration); + reloadConfiguration(); + } + + public Properties getConfiguration() { + return configuration; + } + + /** + * Reloads configuration (if the configuration {@link Properties} is set) + */ + protected void reloadConfiguration() { + if (configuration != null) { +// LogManager.resetConfiguration(); +// PropertyConfigurator.configure(configuration); + } + } + + protected synchronized void processLoggingEvent(LogEvent event) { + if (disabled) + return; + + if (dispatching.get()) + return; + + if (level != null && !level.trim().equals("")) { +// if (log4jLevel == null || !log4jLevel.toString().equals(level)) +// try { +// log4jLevel = Level.toLevel(level); +// } catch (Exception e) { +// System.err.println("Log4j level could not be set for level '" + level + "', resetting it to null."); +// e.printStackTrace(); +// level = null; +// } +// +// if (log4jLevel != null && !event.getLoggingEvent().getLevel().isGreaterOrEqual(log4jLevel)) { +// return; +// } + } + + try { + // admin listeners + Iterator everythingIt = everythingListeners.iterator(); + while (everythingIt.hasNext()) + dispatchEvent(everythingIt.next(), event); + + if (event.getUsername() != null) { + Iterator allUsersIt = allUsersListeners.iterator(); + while (allUsersIt.hasNext()) + dispatchEvent(allUsersIt.next(), event); + + if (userListeners.containsKey(event.getUsername())) { + Iterator userIt = userListeners.get(event.getUsername()).iterator(); + while (userIt.hasNext()) + dispatchEvent(userIt.next(), event); + } + } + } catch (Exception e) { + stdOut("Cannot process logging event"); + e.printStackTrace(); + } + } + + protected void dispatchEvent(ArgeoLogListener logListener, LogEvent evt) { +// LoggingEvent event = evt.getLoggingEvent(); +// logListener.appendLog(evt.getUsername(), event.getTimeStamp(), event.getLevel().toString(), +// event.getLoggerName(), event.getThreadName(), event.getMessage(), event.getThrowableStrRep()); + } + + private class AppenderImpl { //extends AppenderSkeleton { + public boolean requiresLayout() { + return false; + } + + public void close() { + } + +// @Override +// protected void append(LoggingEvent event) { +// if (events != null) { +// try { +// String username = CurrentUser.getUsername(); +// events.put(new LogEvent(username, event)); +// } catch (InterruptedException e) { +// // silent +// } +// } +// } + + } + + private class LogDispatcherThread extends Thread { + /** encapsulated in order to simplify concurrency management */ + private LinkedList lastEvents = new LinkedList(); + + public LogDispatcherThread() { + super("Argeo Logging Dispatcher Thread"); + } + + public void run() { + while (events != null) { + try { + LogEvent loggingEvent = events.take(); + processLoggingEvent(loggingEvent); + addLastEvent(loggingEvent); + } catch (InterruptedException e) { + if (events == null) + return; + } + } + } + + protected synchronized void addLastEvent(LogEvent loggingEvent) { + if (lastEvents.size() >= maxLastEventsCount) + lastEvents.poll(); + lastEvents.add(loggingEvent); + } + + public synchronized List getLastEvents(String username, Integer maxCount) { + LinkedList evts = new LinkedList(); + ListIterator it = lastEvents.listIterator(lastEvents.size()); + int count = 0; + while (it.hasPrevious() && (count < maxCount)) { + LogEvent evt = it.previous(); + if (username == null || username.equals(evt.getUsername())) { + evts.push(evt); + count++; + } + } + return evts; + } + } + + private class LogEvent { + private final String username; +// private final LoggingEvent loggingEvent; + + public LogEvent(String username) { + super(); + this.username = username; +// this.loggingEvent = loggingEvent; + } + +// @Override +// public int hashCode() { +// return loggingEvent.hashCode(); +// } +// +// @Override +// public boolean equals(Object obj) { +// return loggingEvent.equals(obj); +// } +// +// @Override +// public String toString() { +// return username + "@ " + loggingEvent.toString(); +// } + + public String getUsername() { + return username; + } + +// public LoggingEvent getLoggingEvent() { +// return loggingEvent; +// } + + } + + private class Log4jConfWatcherThread extends Thread { + private Path log4jConfigurationPath; + + public Log4jConfWatcherThread(Path log4jConfigurationPath) { + super("Log4j Configuration Watcher"); + try { + this.log4jConfigurationPath = log4jConfigurationPath.toRealPath(); + } catch (IOException e) { + this.log4jConfigurationPath = log4jConfigurationPath.toAbsolutePath(); + stdOut("Cannot determine real path for " + log4jConfigurationPath + ": " + e.getMessage()); + } + } + + public void run() { + Path parentDir = log4jConfigurationPath.getParent(); + try (final WatchService watchService = FileSystems.getDefault().newWatchService()) { + parentDir.register(watchService, StandardWatchEventKinds.ENTRY_MODIFY); + WatchKey wk; + watching: while ((wk = watchService.take()) != null) { + for (WatchEvent event : wk.pollEvents()) { + final Path changed = (Path) event.context(); + if (log4jConfigurationPath.equals(parentDir.resolve(changed))) { + if (isInternalDebugEnabled()) + debug(log4jConfigurationPath + " has changed, reloading."); +// PropertyConfigurator.configure(log4jConfigurationPath.toUri().toURL()); + } + } + // reset the key + boolean valid = wk.reset(); + if (!valid) { + break watching; + } + } + } catch (IOException | InterruptedException e) { + stdErr("Log4j configuration watcher failed: " + e.getMessage()); + } + } + } +} diff --git a/org.argeo.cms/src/org/argeo/cms/internal/osgi/GogoShellKiller.java b/org.argeo.cms/src/org/argeo/cms/internal/osgi/GogoShellKiller.java index 85c686a0a..a8c901644 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/osgi/GogoShellKiller.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/osgi/GogoShellKiller.java @@ -1,7 +1,5 @@ package org.argeo.cms.internal.osgi; -import java.io.IOException; - /** * Workaround for killing Gogo shell by system shutdown. * diff --git a/org.argeo.cms/src/org/argeo/cms/internal/osgi/NodeLogger.java b/org.argeo.cms/src/org/argeo/cms/internal/osgi/NodeLogger.java deleted file mode 100644 index 69dbec9f7..000000000 --- a/org.argeo.cms/src/org/argeo/cms/internal/osgi/NodeLogger.java +++ /dev/null @@ -1,541 +0,0 @@ -package org.argeo.cms.internal.osgi; - -import java.io.IOException; -import java.net.URI; -import java.nio.file.FileSystems; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.nio.file.StandardWatchEventKinds; -import java.nio.file.WatchEvent; -import java.nio.file.WatchKey; -import java.nio.file.WatchService; -import java.security.SignatureException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Enumeration; -import java.util.HashMap; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; -import java.util.ListIterator; -import java.util.Map; -import java.util.Properties; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.LinkedBlockingQueue; - -import org.argeo.api.cms.CmsConstants; -import org.argeo.api.cms.CmsLog; -import org.argeo.cms.ArgeoLogListener; -import org.argeo.cms.ArgeoLogger; -import org.argeo.cms.CmsException; -import org.argeo.cms.auth.CurrentUser; -import org.argeo.cms.internal.runtime.KernelConstants; -import org.argeo.cms.internal.runtime.KernelUtils; -import org.argeo.osgi.useradmin.UserAdminConf; -import org.osgi.framework.Bundle; -import org.osgi.framework.Constants; -import org.osgi.framework.ServiceReference; -import org.osgi.service.cm.ConfigurationAdmin; -import org.osgi.service.log.LogEntry; -import org.osgi.service.log.LogLevel; -import org.osgi.service.log.LogListener; -import org.osgi.service.log.LogReaderService; - -/** Not meant to be used directly in standard log4j config */ -public class NodeLogger implements ArgeoLogger, LogListener { - /** Internal debug for development purposes. */ - private static Boolean debug = false; - - private Boolean disabled = false; - - private String level = null; - -// private Level log4jLevel = null; - - private Properties configuration; - - private AppenderImpl appender; - - private final List everythingListeners = Collections - .synchronizedList(new ArrayList()); - private final List allUsersListeners = Collections - .synchronizedList(new ArrayList()); - private final Map> userListeners = Collections - .synchronizedMap(new HashMap>()); - - private BlockingQueue events; - private LogDispatcherThread logDispatcherThread = new LogDispatcherThread(); - - private Integer maxLastEventsCount = 10 * 1000; - - /** Marker to prevent stack overflow */ - private ThreadLocal dispatching = new ThreadLocal() { - - @Override - protected Boolean initialValue() { - return false; - } - }; - - public NodeLogger(LogReaderService lrs) { - if (lrs != null) { - Enumeration logEntries = lrs.getLog(); - while (logEntries.hasMoreElements()) - logged(logEntries.nextElement()); - lrs.addLogListener(this); - - // configure log4j watcher - String log4jConfiguration = KernelUtils.getFrameworkProp("log4j.configuration"); - if (log4jConfiguration != null && log4jConfiguration.startsWith("file:")) { - if (log4jConfiguration.contains("..")) { - if (log4jConfiguration.startsWith("file://")) - log4jConfiguration = log4jConfiguration.substring("file://".length()); - else if (log4jConfiguration.startsWith("file:")) - log4jConfiguration = log4jConfiguration.substring("file:".length()); - } - try { - Path log4jconfigPath; - if (log4jConfiguration.startsWith("file:")) - log4jconfigPath = Paths.get(new URI(log4jConfiguration)); - else - log4jconfigPath = Paths.get(log4jConfiguration); - Thread log4jConfWatcher = new Log4jConfWatcherThread(log4jconfigPath); - log4jConfWatcher.start(); - } catch (Exception e) { - stdErr("Badly formatted log4j configuration URI " + log4jConfiguration + ": " + e.getMessage()); - } - } - } - } - - public void init() { - try { - events = new LinkedBlockingQueue(); - - // if (layout != null) - // setLayout(layout); - // else - // setLayout(new PatternLayout(pattern)); - appender = new AppenderImpl(); - reloadConfiguration(); -// Logger.getRootLogger().addAppender(appender); - - logDispatcherThread = new LogDispatcherThread(); - logDispatcherThread.start(); - } catch (Exception e) { - throw new CmsException("Cannot initialize log4j"); - } - } - - public void destroy() throws Exception { -// Logger.getRootLogger().removeAppender(appender); - allUsersListeners.clear(); - for (List lst : userListeners.values()) - lst.clear(); - userListeners.clear(); - - events.clear(); - events = null; - logDispatcherThread.interrupt(); - } - - // public void setLayout(Layout layout) { - // this.layout = layout; - // } - - public String toString() { - return "Node Logger"; - } - - // - // OSGi LOGGER - // - @Override - public void logged(LogEntry status) { - CmsLog pluginLog = CmsLog.getLog(status.getBundle().getSymbolicName()); - LogLevel severity = status.getLogLevel(); - if (severity.equals(LogLevel.ERROR) && pluginLog.isErrorEnabled()) { - // FIXME Fix Argeo TP - if (status.getException() instanceof SignatureException) - return; - pluginLog.error(msg(status), status.getException()); - } else if (severity.equals(LogLevel.WARN) && pluginLog.isWarnEnabled()) { - if (pluginLog.isTraceEnabled()) - pluginLog.warn(msg(status), status.getException()); - else - pluginLog.warn(msg(status)); - } else if (severity.equals(LogLevel.INFO) && pluginLog.isDebugEnabled()) - pluginLog.debug(msg(status), status.getException()); - else if (severity.equals(LogLevel.DEBUG) && pluginLog.isTraceEnabled()) - pluginLog.trace(msg(status), status.getException()); - else if (severity.equals(LogLevel.TRACE) && pluginLog.isTraceEnabled()) - pluginLog.trace(msg(status), status.getException()); - } - - private String msg(LogEntry status) { - StringBuilder sb = new StringBuilder(); - sb.append(status.getMessage()); - Bundle bundle = status.getBundle(); - if (bundle != null) { - sb.append(" '" + bundle.getSymbolicName() + "'"); - } - ServiceReference sr = status.getServiceReference(); - if (sr != null) { - sb.append(' '); - String[] objectClasses = (String[]) sr.getProperty(Constants.OBJECTCLASS); - if (isSpringApplicationContext(objectClasses)) { - sb.append("{org.springframework.context.ApplicationContext}"); - Object symbolicName = sr.getProperty(Constants.BUNDLE_SYMBOLICNAME); - if (symbolicName != null) - sb.append(" " + Constants.BUNDLE_SYMBOLICNAME + ": " + symbolicName); - } else { - sb.append(arrayToString(objectClasses)); - } - Object cn = sr.getProperty(CmsConstants.CN); - if (cn != null) - sb.append(" " + CmsConstants.CN + ": " + cn); - Object factoryPid = sr.getProperty(ConfigurationAdmin.SERVICE_FACTORYPID); - if (factoryPid != null) - sb.append(" " + ConfigurationAdmin.SERVICE_FACTORYPID + ": " + factoryPid); - // else { - // Object servicePid = sr.getProperty(Constants.SERVICE_PID); - // if (servicePid != null) - // sb.append(" " + Constants.SERVICE_PID + ": " + servicePid); - // } - // servlets - Object whiteBoardPattern = sr.getProperty(KernelConstants.WHITEBOARD_PATTERN_PROP); - if (whiteBoardPattern != null) { - if (whiteBoardPattern instanceof String) { - sb.append(" " + KernelConstants.WHITEBOARD_PATTERN_PROP + ": " + whiteBoardPattern); - } else { - sb.append(" " + KernelConstants.WHITEBOARD_PATTERN_PROP + ": " - + arrayToString((String[]) whiteBoardPattern)); - } - } - // RWT - Object contextName = sr.getProperty(KernelConstants.CONTEXT_NAME_PROP); - if (contextName != null) - sb.append(" " + KernelConstants.CONTEXT_NAME_PROP + ": " + contextName); - - // user directories - Object baseDn = sr.getProperty(UserAdminConf.baseDn.name()); - if (baseDn != null) - sb.append(" " + UserAdminConf.baseDn.name() + ": " + baseDn); - - } - return sb.toString(); - } - - private String arrayToString(Object[] arr) { - StringBuilder sb = new StringBuilder(); - sb.append('['); - for (int i = 0; i < arr.length; i++) { - if (i != 0) - sb.append(','); - sb.append(arr[i]); - } - sb.append(']'); - return sb.toString(); - } - - private boolean isSpringApplicationContext(String[] objectClasses) { - for (String clss : objectClasses) { - if (clss.equals("org.eclipse.gemini.blueprint.context.DelegatedExecutionOsgiBundleApplicationContext")) { - return true; - } - } - return false; - } - - // - // ARGEO LOGGER - // - - public synchronized void register(ArgeoLogListener listener, Integer numberOfPreviousEvents) { - String username = CurrentUser.getUsername(); - if (username == null) - throw new CmsException("Only authenticated users can register a log listener"); - - if (!userListeners.containsKey(username)) { - List lst = Collections.synchronizedList(new ArrayList()); - userListeners.put(username, lst); - } - userListeners.get(username).add(listener); - List lastEvents = logDispatcherThread.getLastEvents(username, numberOfPreviousEvents); - for (LogEvent evt : lastEvents) - dispatchEvent(listener, evt); - } - - public synchronized void registerForAll(ArgeoLogListener listener, Integer numberOfPreviousEvents, - boolean everything) { - if (everything) - everythingListeners.add(listener); - else - allUsersListeners.add(listener); - List lastEvents = logDispatcherThread.getLastEvents(null, numberOfPreviousEvents); - for (LogEvent evt : lastEvents) - if (everything || evt.getUsername() != null) - dispatchEvent(listener, evt); - } - - public synchronized void unregister(ArgeoLogListener listener) { - String username = CurrentUser.getUsername(); - if (username == null)// FIXME - return; - if (!userListeners.containsKey(username)) - throw new CmsException("No user listeners " + listener + " registered for user " + username); - if (!userListeners.get(username).contains(listener)) - throw new CmsException("No user listeners " + listener + " registered for user " + username); - userListeners.get(username).remove(listener); - if (userListeners.get(username).isEmpty()) - userListeners.remove(username); - - } - - public synchronized void unregisterForAll(ArgeoLogListener listener) { - everythingListeners.remove(listener); - allUsersListeners.remove(listener); - } - - /** For development purpose, since using regular logging is not easy here */ - private static void stdOut(Object obj) { - System.out.println(obj); - } - - private static void stdErr(Object obj) { - System.err.println(obj); - } - - private static void debug(Object obj) { - if (debug) - System.out.println(obj); - } - - private static boolean isInternalDebugEnabled() { - return debug; - } - - // public void setPattern(String pattern) { - // this.pattern = pattern; - // } - - public void setDisabled(Boolean disabled) { - this.disabled = disabled; - } - - public void setLevel(String level) { - this.level = level; - } - - public void setConfiguration(Properties configuration) { - this.configuration = configuration; - } - - public void updateConfiguration(Properties configuration) { - setConfiguration(configuration); - reloadConfiguration(); - } - - public Properties getConfiguration() { - return configuration; - } - - /** - * Reloads configuration (if the configuration {@link Properties} is set) - */ - protected void reloadConfiguration() { - if (configuration != null) { -// LogManager.resetConfiguration(); -// PropertyConfigurator.configure(configuration); - } - } - - protected synchronized void processLoggingEvent(LogEvent event) { - if (disabled) - return; - - if (dispatching.get()) - return; - - if (level != null && !level.trim().equals("")) { -// if (log4jLevel == null || !log4jLevel.toString().equals(level)) -// try { -// log4jLevel = Level.toLevel(level); -// } catch (Exception e) { -// System.err.println("Log4j level could not be set for level '" + level + "', resetting it to null."); -// e.printStackTrace(); -// level = null; -// } -// -// if (log4jLevel != null && !event.getLoggingEvent().getLevel().isGreaterOrEqual(log4jLevel)) { -// return; -// } - } - - try { - // admin listeners - Iterator everythingIt = everythingListeners.iterator(); - while (everythingIt.hasNext()) - dispatchEvent(everythingIt.next(), event); - - if (event.getUsername() != null) { - Iterator allUsersIt = allUsersListeners.iterator(); - while (allUsersIt.hasNext()) - dispatchEvent(allUsersIt.next(), event); - - if (userListeners.containsKey(event.getUsername())) { - Iterator userIt = userListeners.get(event.getUsername()).iterator(); - while (userIt.hasNext()) - dispatchEvent(userIt.next(), event); - } - } - } catch (Exception e) { - stdOut("Cannot process logging event"); - e.printStackTrace(); - } - } - - protected void dispatchEvent(ArgeoLogListener logListener, LogEvent evt) { -// LoggingEvent event = evt.getLoggingEvent(); -// logListener.appendLog(evt.getUsername(), event.getTimeStamp(), event.getLevel().toString(), -// event.getLoggerName(), event.getThreadName(), event.getMessage(), event.getThrowableStrRep()); - } - - private class AppenderImpl { //extends AppenderSkeleton { - public boolean requiresLayout() { - return false; - } - - public void close() { - } - -// @Override -// protected void append(LoggingEvent event) { -// if (events != null) { -// try { -// String username = CurrentUser.getUsername(); -// events.put(new LogEvent(username, event)); -// } catch (InterruptedException e) { -// // silent -// } -// } -// } - - } - - private class LogDispatcherThread extends Thread { - /** encapsulated in order to simplify concurrency management */ - private LinkedList lastEvents = new LinkedList(); - - public LogDispatcherThread() { - super("Argeo Logging Dispatcher Thread"); - } - - public void run() { - while (events != null) { - try { - LogEvent loggingEvent = events.take(); - processLoggingEvent(loggingEvent); - addLastEvent(loggingEvent); - } catch (InterruptedException e) { - if (events == null) - return; - } - } - } - - protected synchronized void addLastEvent(LogEvent loggingEvent) { - if (lastEvents.size() >= maxLastEventsCount) - lastEvents.poll(); - lastEvents.add(loggingEvent); - } - - public synchronized List getLastEvents(String username, Integer maxCount) { - LinkedList evts = new LinkedList(); - ListIterator it = lastEvents.listIterator(lastEvents.size()); - int count = 0; - while (it.hasPrevious() && (count < maxCount)) { - LogEvent evt = it.previous(); - if (username == null || username.equals(evt.getUsername())) { - evts.push(evt); - count++; - } - } - return evts; - } - } - - private class LogEvent { - private final String username; -// private final LoggingEvent loggingEvent; - - public LogEvent(String username) { - super(); - this.username = username; -// this.loggingEvent = loggingEvent; - } - -// @Override -// public int hashCode() { -// return loggingEvent.hashCode(); -// } -// -// @Override -// public boolean equals(Object obj) { -// return loggingEvent.equals(obj); -// } -// -// @Override -// public String toString() { -// return username + "@ " + loggingEvent.toString(); -// } - - public String getUsername() { - return username; - } - -// public LoggingEvent getLoggingEvent() { -// return loggingEvent; -// } - - } - - private class Log4jConfWatcherThread extends Thread { - private Path log4jConfigurationPath; - - public Log4jConfWatcherThread(Path log4jConfigurationPath) { - super("Log4j Configuration Watcher"); - try { - this.log4jConfigurationPath = log4jConfigurationPath.toRealPath(); - } catch (IOException e) { - this.log4jConfigurationPath = log4jConfigurationPath.toAbsolutePath(); - stdOut("Cannot determine real path for " + log4jConfigurationPath + ": " + e.getMessage()); - } - } - - public void run() { - Path parentDir = log4jConfigurationPath.getParent(); - try (final WatchService watchService = FileSystems.getDefault().newWatchService()) { - parentDir.register(watchService, StandardWatchEventKinds.ENTRY_MODIFY); - WatchKey wk; - watching: while ((wk = watchService.take()) != null) { - for (WatchEvent event : wk.pollEvents()) { - final Path changed = (Path) event.context(); - if (log4jConfigurationPath.equals(parentDir.resolve(changed))) { - if (isInternalDebugEnabled()) - debug(log4jConfigurationPath + " has changed, reloading."); -// PropertyConfigurator.configure(log4jConfigurationPath.toUri().toURL()); - } - } - // reset the key - boolean valid = wk.reset(); - if (!valid) { - break watching; - } - } - } catch (IOException | InterruptedException e) { - stdErr("Log4j configuration watcher failed: " + e.getMessage()); - } - } - } -} diff --git a/org.argeo.init/src/org/argeo/init/logging/ThinLogging.java b/org.argeo.init/src/org/argeo/init/logging/ThinLogging.java index 871dc8d7f..cdcabcb03 100644 --- a/org.argeo.init/src/org/argeo/init/logging/ThinLogging.java +++ b/org.argeo.init/src/org/argeo/init/logging/ThinLogging.java @@ -285,6 +285,7 @@ class ThinLogging implements Consumer> { private StackTraceElement findCallLocation(Level level, Thread thread) { assert level != null; assert thread != null; + // TODO rather use a StackWalker and make it smarter StackTraceElement callLocation = null; if (level.getSeverity() >= callLocationLevel.getSeverity()) { StackTraceElement[] stack = thread.getStackTrace(); @@ -293,12 +294,15 @@ class ThinLogging implements Consumer> { String className = stack[i].getClassName(); switch (className) { // TODO make it more configurable + // FIXME deal with privileges stacks (in Equinox) case "java.lang.System$Logger": case "java.util.logging.Logger": case "org.apache.commons.logging.Log": case "org.osgi.service.log.Logger": + case "org.eclipse.osgi.internal.log.LoggerImpl": case "org.argeo.api.cms.CmsLog": case "org.slf4j.impl.ArgeoLogger": + case "org.argeo.cms.internal.osgi.CmsOsgiLogger": case "org.eclipse.jetty.util.log.Slf4jLog": case "sun.util.logging.internal.LoggingProviderImpl$JULWrapper": lowestLoggerInterface = i;