Rename packages in order to make future stable documentation clearer.
[lgpl/argeo-commons.git] / org.argeo.cms / src / org / argeo / cms / internal / kernel / NodeLogger.java
index 4f58e7525cb620d6549a6926e020f0501bd87870..1d296c5b9cc45f2a9a057bbf827ddfa08d36ccf2 100644 (file)
  */
 package org.argeo.cms.internal.kernel;
 
+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;
@@ -37,22 +46,27 @@ import org.apache.log4j.LogManager;
 import org.apache.log4j.Logger;
 import org.apache.log4j.PropertyConfigurator;
 import org.apache.log4j.spi.LoggingEvent;
-import org.argeo.ArgeoException;
+import org.argeo.api.ArgeoLogListener;
+import org.argeo.api.ArgeoLogger;
+import org.argeo.api.NodeConstants;
+import org.argeo.cms.CmsException;
 import org.argeo.cms.auth.CurrentUser;
-import org.argeo.node.ArgeoLogListener;
-import org.argeo.node.ArgeoLogger;
-import org.argeo.node.NodeConstants;
 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;
-import org.osgi.service.log.LogService;
 
 /** Not meant to be used directly in standard log4j config */
 class NodeLogger implements ArgeoLogger, LogListener {
+       /** Internal debug for development purposes. */
+       private static Boolean debug = false;
+
+       // private final static Log log = LogFactory.getLog(NodeLogger.class);
 
        private Boolean disabled = false;
 
@@ -86,12 +100,33 @@ class NodeLogger implements ArgeoLogger, LogListener {
                }
        };
 
-       @SuppressWarnings("unchecked")
        public NodeLogger(LogReaderService lrs) {
                Enumeration<LogEntry> 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() {
@@ -109,7 +144,7 @@ class NodeLogger implements ArgeoLogger, LogListener {
                        logDispatcherThread = new LogDispatcherThread();
                        logDispatcherThread.start();
                } catch (Exception e) {
-                       throw new ArgeoException("Cannot initialize log4j");
+                       throw new CmsException("Cannot initialize log4j");
                }
        }
 
@@ -129,74 +164,110 @@ class NodeLogger implements ArgeoLogger, LogListener {
        // this.layout = layout;
        // }
 
+       public String toString() {
+               return "Node Logger";
+       }
+
        //
        // OSGi LOGGER
        //
        @Override
        public void logged(LogEntry status) {
                Log pluginLog = LogFactory.getLog(status.getBundle().getSymbolicName());
-               Integer severity = status.getLevel();
-               if (severity == LogService.LOG_ERROR) {
+               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 == LogService.LOG_WARNING)
-                       pluginLog.warn(msg(status), status.getException());
-               else if (severity == LogService.LOG_INFO && pluginLog.isDebugEnabled())
+               } 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 == LogService.LOG_DEBUG && pluginLog.isTraceEnabled())
+               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(status.getMessage());
+               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);
-                       sb.append(arrayToString(objectClasses));
+                       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(NodeConstants.CN);
                        if (cn != null)
                                sb.append(" " + NodeConstants.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);
-//                     }
+                       // 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)
-                               sb.append(" " + KernelConstants.WHITEBOARD_PATTERN_PROP + ": "
-                                               + arrayToString((String[]) whiteBoardPattern));
+                       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('{');
+               sb.append('[');
                for (int i = 0; i < arr.length; i++) {
                        if (i != 0)
                                sb.append(',');
                        sb.append(arr[i]);
                }
-               sb.append('}');
+               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
        //
@@ -204,7 +275,7 @@ class NodeLogger implements ArgeoLogger, LogListener {
        public synchronized void register(ArgeoLogListener listener, Integer numberOfPreviousEvents) {
                String username = CurrentUser.getUsername();
                if (username == null)
-                       throw new ArgeoException("Only authenticated users can register a log listener");
+                       throw new CmsException("Only authenticated users can register a log listener");
 
                if (!userListeners.containsKey(username)) {
                        List<ArgeoLogListener> lst = Collections.synchronizedList(new ArrayList<ArgeoLogListener>());
@@ -233,9 +304,9 @@ class NodeLogger implements ArgeoLogger, LogListener {
                if (username == null)// FIXME
                        return;
                if (!userListeners.containsKey(username))
-                       throw new ArgeoException("No user listeners " + listener + " registered for user " + username);
+                       throw new CmsException("No user listeners " + listener + " registered for user " + username);
                if (!userListeners.get(username).contains(listener))
-                       throw new ArgeoException("No user listeners " + listener + " registered for user " + username);
+                       throw new CmsException("No user listeners " + listener + " registered for user " + username);
                userListeners.get(username).remove(listener);
                if (userListeners.get(username).isEmpty())
                        userListeners.remove(username);
@@ -248,10 +319,23 @@ class NodeLogger implements ArgeoLogger, LogListener {
        }
 
        /** For development purpose, since using regular logging is not easy here */
-       static void stdOut(Object obj) {
+       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;
        // }
@@ -436,4 +520,43 @@ class NodeLogger implements ArgeoLogger, LogListener {
                }
 
        }
+
+       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());
+                       }
+               }
+       }
 }