| /* |
| * 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.myfaces.extensions.scripting.monitor; |
| |
| import org.apache.myfaces.extensions.scripting.api.ScriptingConst; |
| import org.apache.myfaces.extensions.scripting.api.ScriptingWeaver; |
| import org.apache.myfaces.extensions.scripting.core.dependencyScan.core.ClassDependencies; |
| import org.apache.myfaces.extensions.scripting.core.util.WeavingContext; |
| import org.apache.myfaces.extensions.scripting.api.extensionevents.ClassTaintedEvent; |
| |
| import javax.servlet.ServletContext; |
| import java.io.File; |
| import java.lang.ref.WeakReference; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.concurrent.ConcurrentHashMap; |
| import java.util.logging.Level; |
| import java.util.logging.Logger; |
| |
| /** |
| * Central daemon thread which watches the resources |
| * for changes and marks them as changed. |
| * This watchdog daemon is the central core |
| * of the entire scripting engine it runs asynchronously |
| * to your program and keeps an eye on the resources |
| * and their dependencies, once a file has changed |
| * all the referring dependencies are also marked |
| * as being to reloaded. |
| * |
| * @author Werner Punz (latest modification by $Author$) |
| * @version $Revision$ $Date$ |
| */ |
| public class ResourceMonitor extends Thread { |
| |
| private static final String CONTEXT_KEY = "extscriptDaemon"; |
| |
| static ResourceMonitor _instance = null; |
| |
| Map<String, ClassResource> _classMap = new ConcurrentHashMap<String, ClassResource>(8, 0.75f, 1); |
| ClassDependencies _dependencyMap = new ClassDependencies(); |
| |
| /** |
| * This map is a shortcut for the various scripting engines. |
| * It keeps track whether the engines source paths |
| * have dirty files or not and if true we enforce a recompile at the |
| * next refresh! |
| * <p/> |
| * We keep track on engine level to avoid to search the classMap for every refresh |
| * the classMap still is needed for various identification tasks which are reload |
| * related |
| */ |
| Map<Integer, Boolean> _systemRecompileMap = new ConcurrentHashMap<Integer, Boolean>(8, 0.75f, 1); |
| |
| boolean _running = false; |
| boolean _contextInitialized = false; |
| Logger _log = Logger.getLogger(ResourceMonitor.class.getName()); |
| ScriptingWeaver _weavers = null; |
| static WeakReference<ServletContext> _externalContext; |
| |
| public static synchronized void startup(ServletContext externalContext) { |
| if (_externalContext != null) return; |
| _externalContext = new WeakReference<ServletContext>(externalContext); |
| |
| //we currently keep it as singleton but in the long run we will move it into the context |
| //like everything else singleton-wise |
| if (WeavingContext.isScriptingEnabled() && _instance == null) { |
| _instance = new ResourceMonitor(); |
| externalContext.setAttribute(CONTEXT_KEY, _instance); |
| /** |
| * daemon thread to allow forced |
| * shutdowns for web context restarts |
| */ |
| _instance.setDaemon(true); |
| _instance.setRunning(true); |
| _instance.start(); |
| |
| } |
| |
| } |
| |
| public static synchronized void clear() { |
| |
| } |
| |
| public static synchronized ResourceMonitor getInstance() { |
| //we do it in this complicated manner because of find bugs |
| //practically this cannot really happen except for shutdown were it is not important anymore |
| ServletContext context = _externalContext.get(); |
| if (context != null) { |
| return (ResourceMonitor) context.getAttribute(CONTEXT_KEY); |
| } |
| return null; |
| } |
| |
| /** |
| * Central run method |
| * which performs the entire scanning process |
| */ |
| public void run() { |
| while (WeavingContext.isScriptingEnabled() && _running) { |
| if (_externalContext != null && _externalContext.get() != null && !_contextInitialized) { |
| WeavingContext.initThread((ServletContext) _externalContext.get()); |
| _contextInitialized = true; |
| } |
| try { |
| try { |
| Thread.sleep(ScriptingConst.TAINT_INTERVAL); |
| } catch (InterruptedException e) { |
| //if the server shuts down while we are in sleep we get an error |
| //which we better should swallow |
| } |
| |
| if (_classMap == null || _classMap.size() == 0) |
| continue; |
| if (_contextInitialized) |
| checkForChanges(); |
| } catch (Throwable e) { |
| _log.log(Level.SEVERE, "[EXT-SCRIPTING]", e); |
| |
| } |
| } |
| if (_log.isLoggable(Level.INFO)) { |
| _log.info("[EXT-SCRIPTING] Dynamic reloading watch daemon is shutting down"); |
| } |
| } |
| |
| /** |
| * central tainted mark method which keeps |
| * track if some file in one of the supported engines has changed |
| * and if yes marks the file as tainted as well |
| * as marks the engine as having to do a full recompile |
| */ |
| private final void checkForChanges() { |
| ScriptingWeaver weaver = WeavingContext.getWeaver(); |
| if (weaver == null) return; |
| weaver.scanForAddedClasses(); |
| |
| for (Map.Entry<String, ClassResource> it : this._classMap.entrySet()) { |
| |
| File proxyFile = it.getValue().getFile(); |
| if (isModified(it, proxyFile)) { |
| |
| _systemRecompileMap.put(it.getValue().getScriptingEngine(), Boolean.TRUE); |
| ClassResource meta = it.getValue(); |
| meta.getRefreshAttribute().requestRefresh(); |
| printInfo(it, proxyFile); |
| |
| dependencyTainted(meta.getAClass().getName()); |
| |
| //we add our log entry for further reference |
| WeavingContext.getRefreshContext().addTaintLogEntry(meta); |
| WeavingContext.getExtensionEventRegistry().sendEvent(new ClassTaintedEvent(meta)); |
| } |
| //} |
| } |
| //we clean up the taint log |
| WeavingContext.getRefreshContext().gcTaintLog(); |
| } |
| |
| /** |
| * recursive walk over our meta data to taint also the classes |
| * which refer to our refreshing class so that those |
| * are reloaded as well, this helps to avoid classcast |
| * exceptions caused by imports and casts on long running artifacts |
| * |
| * @param className the origin classname which needs to be walked recursively |
| */ |
| private void dependencyTainted(String className) { |
| Set<String> referrers = _dependencyMap.getReferringClasses(className); |
| if (referrers == null) return; |
| for (String referrer : referrers) { |
| ClassResource metaData = _classMap.get(referrer); |
| if (metaData == null) continue; |
| if (metaData.getRefreshAttribute().requiresRefresh()) continue; |
| printInfo(metaData); |
| |
| metaData.getRefreshAttribute().requestRefresh(); |
| |
| dependencyTainted(metaData.getAClass().getName()); |
| WeavingContext.getRefreshContext().addTaintLogEntry(metaData); |
| WeavingContext.getExtensionEventRegistry().sendEvent(new ClassTaintedEvent(metaData)); |
| } |
| } |
| |
| private final boolean isModified(Map.Entry<String, ClassResource> it, File proxyFile) { |
| return proxyFile.lastModified() > it.getValue().getRefreshAttribute().getRequestedRefreshDate() ; |
| } |
| |
| private void printInfo(ClassResource it) { |
| if (_log.isLoggable(Level.INFO)) { |
| _log.log(Level.INFO, "[EXT-SCRIPTING] Tainting Dependency: {0}", it.getFile().getAbsolutePath()); |
| } |
| } |
| |
| private void printInfo(Map.Entry<String, ClassResource> it, File proxyFile) { |
| if (_log.isLoggable(Level.INFO)) { |
| _log.log(Level.INFO, "[EXT-SCRIPTING] comparing {0} Dates: {1} {2} ", new String[]{it.getKey(), Long.toString(proxyFile.lastModified()), Long.toString(it.getValue().getRefreshAttribute().getExecutedRefreshDate())}); |
| _log.log(Level.INFO, "[EXT-SCRIPTING] Tainting: {0}", it.getValue().getFile().getAbsolutePath()); |
| } |
| } |
| |
| public boolean isRunning() { |
| return _running; |
| } |
| |
| public void setRunning(boolean running) { |
| this._running = running; |
| } |
| |
| public Map<Integer, Boolean> getSystemRecompileMap() { |
| return _systemRecompileMap; |
| } |
| |
| public void setSystemRecompileMap(Map<Integer, Boolean> systemRecompileMap) { |
| this._systemRecompileMap = systemRecompileMap; |
| } |
| |
| public Map<String, ClassResource> getClassMap() { |
| return _classMap; |
| } |
| |
| public void setClassMap(Map<String, ClassResource> classMap) { |
| this._classMap = classMap; |
| } |
| |
| public ScriptingWeaver getWeavers() { |
| return _weavers; |
| } |
| |
| public void setWeavers(ScriptingWeaver weavers) { |
| _weavers = weavers; |
| } |
| |
| public ClassDependencies getDependencyMap() { |
| return _dependencyMap; |
| } |
| } |
| |