blob: 8c90fb50876c4f0e1693f2d50365a10f4bdaf2e5 [file] [log] [blame]
/*
* 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;
}
}