LOG4J2-1136 - Add file watching for scripts

Project: http://git-wip-us.apache.org/repos/asf/logging-log4j2/repo
Commit: http://git-wip-us.apache.org/repos/asf/logging-log4j2/commit/f2d06757
Tree: http://git-wip-us.apache.org/repos/asf/logging-log4j2/tree/f2d06757
Diff: http://git-wip-us.apache.org/repos/asf/logging-log4j2/diff/f2d06757

Branch: refs/heads/LOG4J2-1136
Commit: f2d0675758217df94a7e4c059615ae6bc9524ae1
Parents: 2be1f0f
Author: Ralph Goers <[email protected]>
Authored: Sat Oct 3 22:07:10 2015 -0700
Committer: Ralph Goers <[email protected]>
Committed: Sat Oct 3 23:15:31 2015 -0700

----------------------------------------------------------------------
 .../core/config/AbstractConfiguration.java      | 30 +++++-
 .../core/config/builder/api/package-info.java   | 20 ++++
 .../config/builder/impl/BuiltConfiguration.java |  8 +-
 .../core/config/builder/impl/package-info.java  | 20 ++++
 .../core/config/json/JsonConfiguration.java     |  7 +-
 .../core/config/properties/package-info.java    | 20 ++++
 .../log4j/core/config/xml/XmlConfiguration.java |  7 +-
 .../logging/log4j/core/script/ScriptFile.java   | 70 +++++++++++---
 .../log4j/core/script/ScriptManager.java        | 62 ++++++++++++-
 .../logging/log4j/core/script/package-info.java | 20 ++++
 .../core/util/ExtensionLanguageMapping.java     | 71 ++++++++++++++
 .../logging/log4j/core/util/FileUtils.java      | 12 +++
 .../logging/log4j/core/util/FileWatcher.java    | 27 ++++++
 .../logging/log4j/core/util/WatchManager.java   | 98 ++++++++++++++++++++
 .../log4j/core/util/WatchManagerTest.java       | 87 +++++++++++++++++
 .../test/resources/log4j-scriptFile-filters.xml |  4 +-
 16 files changed, 538 insertions(+), 25 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/f2d06757/log4j-core/src/main/java/org/apache/logging/log4j/core/config/AbstractConfiguration.java
----------------------------------------------------------------------
diff --git 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/AbstractConfiguration.java
 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/AbstractConfiguration.java
index 7d6535e..5481e55 100644
--- 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/AbstractConfiguration.java
+++ 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/AbstractConfiguration.java
@@ -32,6 +32,8 @@ import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
 import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
 
 import org.apache.logging.log4j.Level;
 import org.apache.logging.log4j.LogManager;
@@ -59,6 +61,7 @@ import org.apache.logging.log4j.core.selector.ContextSelector;
 import org.apache.logging.log4j.core.util.Constants;
 import org.apache.logging.log4j.core.util.Loader;
 import org.apache.logging.log4j.core.util.NameUtil;
+import org.apache.logging.log4j.core.util.WatchManager;
 import org.apache.logging.log4j.spi.LoggerContextFactory;
 import org.apache.logging.log4j.util.PropertiesUtil;
 
@@ -118,6 +121,8 @@ public abstract class AbstractConfiguration extends 
AbstractFilterable implement
     private final ConcurrentMap<String, Object> componentMap = new 
ConcurrentHashMap<>();
     private final ConfigurationSource configurationSource;
     private ScriptManager scriptManager;
+    private ScheduledExecutorService executorService;
+    private final WatchManager watchManager = new WatchManager();
 
     /**
      * Constructor.
@@ -128,6 +133,7 @@ public abstract class AbstractConfiguration extends 
AbstractFilterable implement
         pluginManager = new PluginManager(Node.CATEGORY);
         rootNode = new Node();
         setState(State.INITIALIZING);
+
     }
 
     @Override
@@ -150,13 +156,25 @@ public abstract class AbstractConfiguration extends 
AbstractFilterable implement
         return scriptManager;
     }
 
+    public WatchManager getWatchManager() {
+        return watchManager;
+    }
+
+    public ScheduledExecutorService getExecutorService() {
+        return executorService;
+    }
+
     /**
      * Initialize the configuration.
      */
     @Override
     public void initialize() {
         LOGGER.debug("Initializing configuration {}", this);
-        scriptManager = new ScriptManager();
+        if (watchManager.getIntervalSeconds() > 0) {
+            executorService = new ScheduledThreadPoolExecutor(1);
+            watchManager.setExecutorService(executorService);
+        }
+        scriptManager = new ScriptManager(watchManager);
         pluginManager.collectPlugins(pluginPackages);
         final PluginManager levelPlugins = new PluginManager(Level.CATEGORY);
         levelPlugins.collectPlugins(pluginPackages);
@@ -190,6 +208,9 @@ public abstract class AbstractConfiguration extends 
AbstractFilterable implement
         }
         LOGGER.debug("Starting configuration {}", this);
         this.setStarting();
+        if (watchManager.getIntervalSeconds() > 0) {
+            watchManager.start();
+        }
         final Set<LoggerConfig> alreadyStarted = new HashSet<>();
         for (final LoggerConfig logger : loggers.values()) {
             logger.start();
@@ -297,6 +318,13 @@ public abstract class AbstractConfiguration extends 
AbstractFilterable implement
         }
         LOGGER.trace("AbstractConfiguration stopped {} LoggerConfigs.", 
loggerCount);
 
+        if (watchManager.isStarted()) {
+            watchManager.stop();
+        }
+        if (executorService != null) {
+            executorService.shutdown();
+        }
+
         // AsyncLoggerConfigHelper decreases its ref count when an 
AsyncLoggerConfig is stopped.
         // Stopping the same AsyncLoggerConfig twice results in an incorrect 
ref count and
         // the shared Disruptor may be shut down prematurely, resulting in NPE 
or other errors.

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/f2d06757/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/package-info.java
----------------------------------------------------------------------
diff --git 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/package-info.java
 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/package-info.java
new file mode 100644
index 0000000..64dd1b5
--- /dev/null
+++ 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+/**
+ * Configuration Builder API.
+ */
+package org.apache.logging.log4j.core.config.builder.api;

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/f2d06757/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/BuiltConfiguration.java
----------------------------------------------------------------------
diff --git 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/BuiltConfiguration.java
 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/BuiltConfiguration.java
index 06135e8..7db9721 100644
--- 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/BuiltConfiguration.java
+++ 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/BuiltConfiguration.java
@@ -141,8 +141,12 @@ public class BuiltConfiguration extends 
AbstractConfiguration {
             final ConfigurationSource configSource = getConfigurationSource();
             if (configSource != null) {
                 final File configFile = configSource.getFile();
-                if (intervalSeconds > 0 && configFile != null) {
-                    monitor = new 
FileConfigurationMonitor((Reconfigurable)this, configFile, listeners, 
intervalSeconds);
+                if (intervalSeconds > 0) {
+                    getWatchManager().setIntervalSeconds(intervalSeconds);
+                    if (configFile != null) {
+                        monitor = new 
FileConfigurationMonitor((Reconfigurable)this, configFile, listeners,
+                                intervalSeconds);
+                    }
                 }
             }
         }

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/f2d06757/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/package-info.java
----------------------------------------------------------------------
diff --git 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/package-info.java
 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/package-info.java
new file mode 100644
index 0000000..985e84a
--- /dev/null
+++ 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+/**
+ * Configuration Builder Implementation.
+ */
+package org.apache.logging.log4j.core.config.builder.impl;

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/f2d06757/log4j-core/src/main/java/org/apache/logging/log4j/core/config/json/JsonConfiguration.java
----------------------------------------------------------------------
diff --git 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/json/JsonConfiguration.java
 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/json/JsonConfiguration.java
index 1fc3e50..bc08922 100644
--- 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/json/JsonConfiguration.java
+++ 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/json/JsonConfiguration.java
@@ -87,8 +87,11 @@ public class JsonConfiguration extends AbstractConfiguration 
implements Reconfig
                     setName(value);
                 } else if ("monitorInterval".equalsIgnoreCase(key)) {
                     final int intervalSeconds = Integer.parseInt(value);
-                    if (intervalSeconds > 0 && configFile != null) {
-                        monitor = new FileConfigurationMonitor(this, 
configFile, listeners, intervalSeconds);
+                    if (intervalSeconds > 0) {
+                        getWatchManager().setIntervalSeconds(intervalSeconds);
+                        if (configFile != null) {
+                            monitor = new FileConfigurationMonitor(this, 
configFile, listeners, intervalSeconds);
+                        }
                     }
                 } else if ("advertiser".equalsIgnoreCase(key)) {
                     createAdvertiser(value, configSource, buffer, 
"application/json");

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/f2d06757/log4j-core/src/main/java/org/apache/logging/log4j/core/config/properties/package-info.java
----------------------------------------------------------------------
diff --git 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/properties/package-info.java
 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/properties/package-info.java
new file mode 100644
index 0000000..727a7cb
--- /dev/null
+++ 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/properties/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+/**
+ * Configuration using Properties files.
+ */
+package org.apache.logging.log4j.core.config.properties;

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/f2d06757/log4j-core/src/main/java/org/apache/logging/log4j/core/config/xml/XmlConfiguration.java
----------------------------------------------------------------------
diff --git 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/xml/XmlConfiguration.java
 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/xml/XmlConfiguration.java
index af13000..8008e62 100644
--- 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/xml/XmlConfiguration.java
+++ 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/xml/XmlConfiguration.java
@@ -132,8 +132,11 @@ public class XmlConfiguration extends 
AbstractConfiguration implements Reconfigu
                     schemaResource = value;
                 } else if ("monitorInterval".equalsIgnoreCase(key)) {
                     final int intervalSeconds = Integer.parseInt(value);
-                    if (intervalSeconds > 0 && configFile != null) {
-                        monitor = new FileConfigurationMonitor(this, 
configFile, listeners, intervalSeconds);
+                    if (intervalSeconds > 0) {
+                        getWatchManager().setIntervalSeconds(intervalSeconds);
+                        if (configFile != null) {
+                            monitor = new FileConfigurationMonitor(this, 
configFile, listeners, intervalSeconds);
+                        }
                     }
                 } else if ("advertiser".equalsIgnoreCase(key)) {
                     createAdvertiser(value, configSource, buffer, "text/xml");

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/f2d06757/log4j-core/src/main/java/org/apache/logging/log4j/core/script/ScriptFile.java
----------------------------------------------------------------------
diff --git 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/script/ScriptFile.java 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/script/ScriptFile.java
index 6ff6e27..f1bf300 100644
--- 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/script/ScriptFile.java
+++ 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/script/ScriptFile.java
@@ -1,3 +1,19 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
 package org.apache.logging.log4j.core.script;
 
 import java.io.File;
@@ -7,12 +23,15 @@ import java.io.InputStreamReader;
 import java.io.Reader;
 import java.net.URI;
 import java.nio.charset.Charset;
+import java.nio.file.Path;
+import java.nio.file.Paths;
 
 import org.apache.logging.log4j.Logger;
 import org.apache.logging.log4j.core.config.Node;
 import org.apache.logging.log4j.core.config.plugins.Plugin;
 import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
 import org.apache.logging.log4j.core.config.plugins.PluginFactory;
+import org.apache.logging.log4j.core.util.ExtensionLanguageMapping;
 import org.apache.logging.log4j.core.util.FileUtils;
 import org.apache.logging.log4j.core.util.IOUtils;
 import org.apache.logging.log4j.core.util.NetUtils;
@@ -25,40 +44,67 @@ import org.apache.logging.log4j.status.StatusLogger;
 public class ScriptFile extends AbstractScript {
 
     private static final Logger logger = StatusLogger.getLogger();
+    private final Path filePath;
+    private final boolean isWatched;
+
+
+    public ScriptFile(Path filePath, String language, boolean isWatched, 
String scriptText) {
+        super(filePath.toString(), language, scriptText);
+        this.filePath = filePath;
+        this.isWatched = isWatched;
+    }
+
+    public Path getPath() {
+        return this.filePath;
+    }
 
-    public ScriptFile(String name, String language, String scriptText) {
-        super(name, language, scriptText);
+    public boolean isWatched() {
+        return isWatched;
     }
 
     @PluginFactory
     public static ScriptFile createScript(
             // @formatter:off
-            @PluginAttribute("name") final String name,
             @PluginAttribute("language") String language, 
             @PluginAttribute("path") final String filePathOrUri,
+            @PluginAttribute("isWatched") final Boolean isWatched,
             @PluginAttribute("charset") final Charset charset) {
             // @formatter:on
-        if (language == null) {
-            logger.info("No script language supplied, defaulting to {}", 
DEFAULT_LANGUAGE);
-            language = DEFAULT_LANGUAGE;
-        }
         if (filePathOrUri == null) {
-            logger.error("No script path provided for ScriptFile {}", name);
+            logger.error("No script path provided for ScriptFile");
             return null;
         }
-        final Charset actualCharset = charset == null ? 
Charset.defaultCharset() : charset;
         final URI uri = NetUtils.toURI(filePathOrUri);
         final File file = FileUtils.fileFromUri(uri);
+        if (language == null && file != null) {
+            String fileExtension = FileUtils.getFileExtension(file);
+            if (fileExtension != null) {
+                ExtensionLanguageMapping mapping = 
ExtensionLanguageMapping.getByExtension(fileExtension);
+                if (mapping != null) {
+                    language = mapping.getLanguage();
+                }
+            }
+        }
+        if (language == null) {
+            logger.info("No script language supplied, defaulting to {}", 
DEFAULT_LANGUAGE);
+            language = DEFAULT_LANGUAGE;
+        }
+
+        final Charset actualCharset = charset == null ? 
Charset.defaultCharset() : charset;
         String scriptText;
         try (final Reader reader = new InputStreamReader(
                 file != null ? new FileInputStream(file) : 
uri.toURL().openStream(), actualCharset)) {
             scriptText = IOUtils.toString(reader);
         } catch (IOException e) {
-            logger.error("{}: name={}, language={}, path={}, 
actualCharset={}", e.getClass().getSimpleName(), name,
+            logger.error("{}: language={}, path={}, actualCharset={}", 
e.getClass().getSimpleName(),
                     language, filePathOrUri, actualCharset);
             return null;
         }
-        return new ScriptFile(name, language, scriptText);
-
+        Path path = file != null ? Paths.get(file.toURI()) : Paths.get(uri);
+        if (path == null) {
+            logger.error("Unable to convert {} to a Path", uri.toString());
+            return null;
+        }
+        return new ScriptFile(path, language, isWatched == null ? 
Boolean.FALSE : isWatched, scriptText);
     }
 }

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/f2d06757/log4j-core/src/main/java/org/apache/logging/log4j/core/script/ScriptManager.java
----------------------------------------------------------------------
diff --git 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/script/ScriptManager.java
 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/script/ScriptManager.java
index f189f9e..b7b701a 100644
--- 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/script/ScriptManager.java
+++ 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/script/ScriptManager.java
@@ -17,6 +17,8 @@
 package org.apache.logging.log4j.core.script;
 
 import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.core.util.FileWatcher;
+import org.apache.logging.log4j.core.util.WatchManager;
 import org.apache.logging.log4j.status.StatusLogger;
 
 import javax.script.Bindings;
@@ -26,6 +28,8 @@ import javax.script.ScriptEngine;
 import javax.script.ScriptEngineFactory;
 import javax.script.ScriptEngineManager;
 import javax.script.ScriptException;
+import java.io.File;
+import java.nio.file.Path;
 import java.util.List;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
@@ -33,15 +37,18 @@ import java.util.concurrent.ConcurrentMap;
 /**
  * Manages the scripts use by the Configuration.
  */
-public class ScriptManager {
+public class ScriptManager implements FileWatcher {
 
     private static final String KEY_THREADING = "THREADING";
     private static final Logger logger = StatusLogger.getLogger();
+    private static final long serialVersionUID = -2534169384971965196L;
     private final ScriptEngineManager manager = new ScriptEngineManager();
     private final ConcurrentMap<String, ScriptRunner> scripts = new 
ConcurrentHashMap<>();
     private final String languages;
+    private final WatchManager watchManager;
 
-    public ScriptManager() {
+    public ScriptManager(WatchManager watchManager) {
+        this.watchManager = watchManager;
         final List<ScriptEngineFactory> factories = 
manager.getEngineFactories();
         if (logger.isDebugEnabled()) {
             final StringBuilder sb = new StringBuilder();
@@ -86,7 +93,7 @@ public class ScriptManager {
         final ScriptEngine engine = 
manager.getEngineByName(script.getLanguage());
         if (engine == null) {
             logger.error("No ScriptEngine found for language " + 
script.getLanguage() + ". Available languages are: " +
-                languages);
+                    languages);
             return;
         }
         if (engine.getFactory().getParameter(KEY_THREADING) == null) {
@@ -94,6 +101,31 @@ public class ScriptManager {
         } else {
             scripts.put(script.getName(), new MainScriptRunner(engine, 
script));
         }
+
+        if (script instanceof ScriptFile) {
+            ScriptFile scriptFile = (ScriptFile)script;
+            Path path = scriptFile.getPath();
+            if (scriptFile.isWatched() && path != null) {
+                watchManager.watchFile(path.toFile(), this);
+            }
+        }
+    }
+
+    @Override
+    public void fileModified(final File file) {
+        ScriptRunner runner = scripts.get(file.toString());
+        if (runner == null) {
+            logger.info("{} is not a running script");
+            return;
+        }
+        ScriptEngine engine = runner.getScriptEngine();
+        AbstractScript script = runner.getScript();
+        if (engine.getFactory().getParameter(KEY_THREADING) == null) {
+            scripts.put(script.getName(), new ThreadLocalScriptRunner(script));
+        } else {
+            scripts.put(script.getName(), new MainScriptRunner(engine, 
script));
+        }
+
     }
 
     public Object execute(final String name, final Bindings bindings) {
@@ -108,6 +140,10 @@ public class ScriptManager {
     private interface ScriptRunner {
 
         public Object execute(Bindings bindings);
+
+        public AbstractScript getScript();
+
+        public ScriptEngine getScriptEngine();
     }
 
     private class MainScriptRunner implements ScriptRunner {
@@ -135,6 +171,11 @@ public class ScriptManager {
         }
 
         @Override
+        public ScriptEngine getScriptEngine() {
+            return this.scriptEngine;
+        }
+
+        @Override
         public Object execute(final Bindings bindings) {
             if (compiledScript != null) {
                 try {
@@ -152,6 +193,10 @@ public class ScriptManager {
             }
         }
 
+        @Override
+        public AbstractScript getScript() {
+            return script;
+        }
     }
 
     private class ThreadLocalScriptRunner implements ScriptRunner {
@@ -172,6 +217,15 @@ public class ScriptManager {
         public Object execute(final Bindings bindings) {
             return runners.get().execute(bindings);
         }
-    }
 
+        @Override
+        public AbstractScript getScript() {
+            return script;
+        }
+
+        @Override
+        public ScriptEngine getScriptEngine() {
+            return runners.get().getScriptEngine();
+        }
+    }
 }

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/f2d06757/log4j-core/src/main/java/org/apache/logging/log4j/core/script/package-info.java
----------------------------------------------------------------------
diff --git 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/script/package-info.java
 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/script/package-info.java
new file mode 100644
index 0000000..4964b30
--- /dev/null
+++ 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/script/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+/**
+ * Log4j 2 Script support.
+ */
+package org.apache.logging.log4j.core.script;

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/f2d06757/log4j-core/src/main/java/org/apache/logging/log4j/core/util/ExtensionLanguageMapping.java
----------------------------------------------------------------------
diff --git 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/ExtensionLanguageMapping.java
 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/ExtensionLanguageMapping.java
new file mode 100644
index 0000000..90a0d61
--- /dev/null
+++ 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/ExtensionLanguageMapping.java
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.logging.log4j.core.util;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ *
+ */
+public enum ExtensionLanguageMapping {
+    JS("js", "JavaScript"), JAVASCRIPT("javascript", "JavaScript"), GVY("gvy", 
"Groovy"),
+    GROOVY("groovy", "Groovy"), BSH("bsh", "beanshell"), 
BEANSHELL("beanshell", "beanshell"),
+    JY("jy", "jython"), JYTHON("jython", "jython"), FTL("ftl", "freemarker"),
+    FREEMARKER("freemarker", "freemarker"), VM("vm", "velocity"), 
VELOCITY("velocity", "velocity"),
+    AWK("awk", "awk"), EJS("ejs", "ejs"), TCL("tcl", "tcl"), HS("hs", 
"jaskell"), JELLY("jelly", "jelly"),
+    JEP("jep", "jep"), JEXL("jexl", "jexl"), JEXL2("jexl2", "jexl2"),
+    RB("rb", "ruby"), RUBY("ruby", "ruby"), JUDO("judo", "judo"), JUDI("judi", 
"judo"), SCALA("scala", "scala"),
+    CLJ("clj", "Clojure");
+
+
+    private final String extension;
+    private final String language;
+
+    private ExtensionLanguageMapping(String extension, String language) {
+        this.extension = extension;
+        this.language = language;
+    }
+
+    public String getExtension() {
+        return this.extension;
+    }
+
+    public String getLanguage() {
+        return this.language;
+    }
+
+    public static ExtensionLanguageMapping getByExtension(String extension) {
+        for (ExtensionLanguageMapping mapping : values()) {
+            if (mapping.extension.equals(extension)) {
+                return mapping;
+            }
+        }
+        return null;
+    }
+
+    public static List<ExtensionLanguageMapping> getByLanguage(String 
language) {
+        List<ExtensionLanguageMapping> list = new ArrayList<>();
+        for (ExtensionLanguageMapping mapping : values()) {
+            if (mapping.language.equals(language)) {
+                list.add(mapping);
+            }
+        }
+        return list;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/f2d06757/log4j-core/src/main/java/org/apache/logging/log4j/core/util/FileUtils.java
----------------------------------------------------------------------
diff --git 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/FileUtils.java 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/FileUtils.java
index c14954a..d8c77fe 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/FileUtils.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/FileUtils.java
@@ -24,6 +24,9 @@ import java.net.URI;
 import java.net.URL;
 import java.net.URLDecoder;
 import java.nio.charset.StandardCharsets;
+import java.nio.file.FileSystem;
+import java.nio.file.FileSystems;
+import java.nio.file.Path;
 
 import org.apache.logging.log4j.Logger;
 import org.apache.logging.log4j.status.StatusLogger;
@@ -93,6 +96,15 @@ public final class FileUtils {
         return url != null && (url.getProtocol().equals(PROTOCOL_FILE) || 
url.getProtocol().equals(JBOSS_FILE));
     }
 
+    public static String getFileExtension(File file) {
+        String fileName = file.getName();
+        if (fileName.lastIndexOf(".") != -1 && fileName.lastIndexOf(".") != 0) 
{
+            return fileName.substring(fileName.lastIndexOf(".") + 1);
+        } else {
+            return null;
+        }
+    }
+
     /**
      * Asserts that the given directory exists and creates it if necessary.
      * 

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/f2d06757/log4j-core/src/main/java/org/apache/logging/log4j/core/util/FileWatcher.java
----------------------------------------------------------------------
diff --git 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/FileWatcher.java 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/FileWatcher.java
new file mode 100644
index 0000000..5c1679b
--- /dev/null
+++ 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/FileWatcher.java
@@ -0,0 +1,27 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.logging.log4j.core.util;
+
+import java.io.File;
+
+/**
+ * Watches for changes in a Path and performs an action when the file is 
modified.
+ */
+public interface FileWatcher<T> {
+
+    void fileModified(File file);
+}

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/f2d06757/log4j-core/src/main/java/org/apache/logging/log4j/core/util/WatchManager.java
----------------------------------------------------------------------
diff --git 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/WatchManager.java 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/WatchManager.java
new file mode 100644
index 0000000..6b6880d
--- /dev/null
+++ 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/WatchManager.java
@@ -0,0 +1,98 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.logging.log4j.core.util;
+
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.core.AbstractLifeCycle;
+import org.apache.logging.log4j.status.StatusLogger;
+
+import java.io.File;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Manages FileWatchers.
+ */
+public class WatchManager extends AbstractLifeCycle {
+
+    private static final long serialVersionUID = 8998356999926962686L;
+    private static Logger logger = StatusLogger.getLogger();
+    private final ConcurrentMap<File, FileMonitor> watchers = new 
ConcurrentHashMap<>();
+    private ScheduledExecutorService executorService;
+    private int intervalSeconds = 0;
+    private ScheduledFuture<?> future;
+
+    public void setExecutorService(ScheduledExecutorService executorService) {
+        this.executorService = executorService;
+    }
+
+    public void setIntervalSeconds(int intervalSeconds) {
+        this.intervalSeconds = intervalSeconds;
+    }
+
+    public int getIntervalSeconds() {
+        return this.intervalSeconds;
+    }
+
+    public void start() {
+        super.start();
+        if (intervalSeconds > 0) {
+            future = executorService.scheduleWithFixedDelay(new WatchWorker(), 
intervalSeconds, intervalSeconds,
+                    TimeUnit.SECONDS);
+        }
+    }
+
+    public void stop() {
+        future.cancel(true);
+        super.stop();
+    }
+
+    public void watchFile(File file, FileWatcher watcher) {
+        watchers.put(file, new FileMonitor(file.lastModified(), watcher));
+
+    }
+
+    private class WatchWorker implements Runnable {
+
+        public void run() {
+            for (Map.Entry<File, FileMonitor> entry : watchers.entrySet()) {
+                File file = entry.getKey();
+                FileMonitor fileMonitor = entry.getValue();
+                long lastModfied = file.lastModified();
+                if (lastModfied > fileMonitor.lastModified) {
+                    logger.info("File {} was modified", file.toString());
+                    fileMonitor.lastModified = lastModfied;
+                    fileMonitor.fileWatcher.fileModified(file);
+                }
+            }
+        }
+    }
+
+    private class FileMonitor {
+        private final FileWatcher fileWatcher;
+        private long lastModified;
+
+        public FileMonitor(long lastModified, FileWatcher fileWatcher) {
+            this.fileWatcher = fileWatcher;
+            this.lastModified = lastModified;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/f2d06757/log4j-core/src/test/java/org/apache/logging/log4j/core/util/WatchManagerTest.java
----------------------------------------------------------------------
diff --git 
a/log4j-core/src/test/java/org/apache/logging/log4j/core/util/WatchManagerTest.java
 
b/log4j-core/src/test/java/org/apache/logging/log4j/core/util/WatchManagerTest.java
new file mode 100644
index 0000000..900494f
--- /dev/null
+++ 
b/log4j-core/src/test/java/org/apache/logging/log4j/core/util/WatchManagerTest.java
@@ -0,0 +1,87 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.logging.log4j.core.util;
+
+import org.junit.Test;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardCopyOption;
+import java.util.Queue;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+import static org.junit.Assert.*;
+
+/**
+ * Test the WatchManager
+ */
+public class WatchManagerTest {
+
+    private String testFile = "target/testWatchFile";
+    private String originalFile = "target/test-classes/log4j-test1.xml";
+    private String newFile = "target/test-classes/log4j-test1.yaml";
+
+    @Test
+    public void testWatchManager() throws Exception {
+        ScheduledExecutorService executorService = new 
ScheduledThreadPoolExecutor(1);
+        WatchManager watchManager = new WatchManager();
+        watchManager.setIntervalSeconds(1);
+        watchManager.setExecutorService(executorService);
+        watchManager.start();
+        try {
+            File sourceFile = new File(originalFile);
+            FileOutputStream targetStream = new FileOutputStream(testFile);
+            File updateFile = new File(newFile);
+            Path source = Paths.get(sourceFile.toURI());
+            Files.copy(source, targetStream);
+            File targetFile = new File(testFile);
+            BlockingQueue<File> queue = new LinkedBlockingQueue<>();
+            watchManager.watchFile(targetFile, new TestWatcher(queue));
+            Thread.sleep(1000);
+            source = Paths.get(updateFile.toURI());
+            Files.copy(source, Paths.get(targetFile.toURI()), 
StandardCopyOption.REPLACE_EXISTING);
+            Thread.sleep(1000);
+            File f = queue.poll(1, TimeUnit.SECONDS);
+            assertNotNull("File change not detected", f);
+        } finally {
+            watchManager.stop();
+            executorService.shutdown();
+        }
+    }
+
+    private class TestWatcher implements FileWatcher {
+
+        private final Queue<File> queue;
+
+        public TestWatcher(Queue<File> queue) {
+            this.queue = queue;
+        }
+
+        @Override
+        public void fileModified(File file) {
+            System.out.println(file.toString() + " was modified");
+            queue.add(file);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/f2d06757/log4j-core/src/test/resources/log4j-scriptFile-filters.xml
----------------------------------------------------------------------
diff --git a/log4j-core/src/test/resources/log4j-scriptFile-filters.xml 
b/log4j-core/src/test/resources/log4j-scriptFile-filters.xml
index 0fef7b5..694da53 100644
--- a/log4j-core/src/test/resources/log4j-scriptFile-filters.xml
+++ b/log4j-core/src/test/resources/log4j-scriptFile-filters.xml
@@ -25,14 +25,14 @@
     <Logger name="TestJavaScriptFilter" level="trace" additivity="false">
       <AppenderRef ref="List">
         <ScriptFilter onMatch="ACCEPT" onMisMatch="DENY">
-          <ScriptFile name="JavascriptFilter" language="JavaScript" 
path="src/test/resources/scripts/filter.js" charset="UTF-8" />
+          <ScriptFile language="JavaScript" 
path="src/test/resources/scripts/filter.js" charset="UTF-8" />
         </ScriptFilter>
       </AppenderRef>
     </Logger>
     <Logger name="TestGroovyFilter" level="trace" additivity="false">
       <AppenderRef ref="List">
         <ScriptFilter onMatch="ACCEPT" onMisMatch="DENY">
-          <ScriptFile name="GroovyFilter" language="groovy" 
path="src/test/resources/scripts/filter.groovy" charset="UTF-8" />
+          <ScriptFile path="src/test/resources/scripts/filter.groovy" 
charset="UTF-8" />
         </ScriptFilter>
       </AppenderRef>
     </Logger>

Reply via email to