blob: 82facc41c9aed5a98bf65970ad4859ea9e94a25d [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.core.api;
import org.apache.myfaces.extensions.scripting.core.api.eventhandling.WeavingEvent;
import org.apache.myfaces.extensions.scripting.core.api.eventhandling.WeavingEventListener;
import org.apache.myfaces.extensions.scripting.core.api.eventhandling.events.BeginCompiling;
import org.apache.myfaces.extensions.scripting.core.api.eventhandling.events.EndCompiling;
import org.apache.myfaces.extensions.scripting.core.common.util.ClassUtils;
import org.apache.myfaces.extensions.scripting.core.common.util.ReflectUtil;
import org.apache.myfaces.extensions.scripting.core.engine.FactoryEngines;
import org.apache.myfaces.extensions.scripting.core.engine.ThrowAwayClassloader;
import org.apache.myfaces.extensions.scripting.core.engine.api.ClassScanner;
import org.apache.myfaces.extensions.scripting.core.engine.api.CompilationResult;
import org.apache.myfaces.extensions.scripting.core.engine.api.ScriptingEngine;
import org.apache.myfaces.extensions.scripting.core.monitor.ClassResource;
import org.apache.myfaces.extensions.scripting.core.monitor.WatchedResource;
import org.apache.myfaces.extensions.scripting.core.reloading.GlobalReloadingStrategy;
import org.apache.myfaces.extensions.scripting.core.reloading.MethodLevelReloadingHandler;
import org.apache.myfaces.extensions.scripting.jsf.adapters.MyFacesSPI;
import java.io.IOException;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Logger;
/**
* @author Werner Punz (latest modification by $Author$)
* @version $Revision$ $Date$
* <p/>
* Central weaving context
*/
public class WeavingContext
{
static final Logger log = Logger.getLogger(WeavingContext.class.getName());
/**
* lock var which can be used for recompilation
*/
public AtomicBoolean recompileLock = new AtomicBoolean(false);
/**
* configuration which stores all external configuration entries
*/
protected Configuration configuration = new Configuration();
//ClassDependencies _dependencyMap = new ClassDependencies();
/**
* Service provider for the implementation under which this extension
* runs
*/
ImplementationService _implementation = null;
/**
* the collection of reloading strategies depending on their artifact type
*/
GlobalReloadingStrategy _reloadingStrategy = new GlobalReloadingStrategy();
/**
* the annotation scanning reference
*/
ClassScanner _annotationScanner = null;
/**
* true only if the startup has performed without errors
*/
boolean _scriptingEnabled = true;
/**
* holder for various operations within our lifecycle
*/
ConcurrentHashMap<String, Long> lifecycleRegistry = new ConcurrentHashMap<String, Long>();
WeakHashMap<WeavingEventListener, String> _listeners = new WeakHashMap<WeavingEventListener, String>();
/**
* This is a log which keeps track of the taints
* over time, we need that mostly for bean refreshes
* in multiuser surroundings, because only tainted beans need
* to be refreshed.
* Now if a user misses multiple updates he has to get a full
* set of changed classes to be able to drop all personal scoped beans tainted
* since the he refreshed last! Hence we have to move away from our
* two dimensional &lt;class, taint&gt; to a three dimensional &lt;class, taint, time&gt;
* view of things
*/
private List<TaintingHistoryEntry> _taintLog = Collections.synchronizedList(new LinkedList<TaintingHistoryEntry>());
/**
* compilation results holder for the compiler listeners (components etc...)
*/
private static final Map<Integer, CompilationResult> _compilationResults = new ConcurrentHashMap<Integer, CompilationResult>();
/**
* we keep a 10 minutes timeout period to keep the performance in place
*/
private final long TAINT_HISTORY_TIMEOUT = 10 * 60 * 1000;
/**
* internal class used by our own history log
*/
static class TaintingHistoryEntry
{
long _timestamp;
ClassResource _data;
public TaintingHistoryEntry(ClassResource data)
{
_data = data;
_timestamp = System.currentTimeMillis();
}
public long getTimestamp()
{
return _timestamp;
}
public ClassResource getData()
{
return _data;
}
}
public void addListener(WeavingEventListener listener) {
if(!_listeners.containsKey(listener)) {
_listeners.put(listener,"");
}
}
public void removeListener(WeavingEventListener listener) {
_listeners.remove(listener);
}
public void sendWeavingEvent(WeavingEvent evt) {
for(WeavingEventListener listener: _listeners.keySet()) {
listener.onEvent(evt);
}
}
public void initEngines() throws IOException
{
FactoryEngines.getInstance().init();
initScanner();
}
public void initScanner()
{
try
{
Class scanner = ClassUtils.getContextClassLoader().loadClass("org.apache.myfaces.extensions.scripting.jsf.annotation.GenericAnnotationScanner");
this._annotationScanner = (ClassScanner) ReflectUtil.instantiate(scanner);
}
catch (ClassNotFoundException e)
{
//we do nothing here
//generic annotation scanner can be missing in jsf 1.2 environments
//_logger.log(Level.FINER, "", e);
}
}
public Collection<ScriptingEngine> getEngines()
{
return FactoryEngines.getInstance().getEngines();
}
public ScriptingEngine getEngine(int engineType)
{
return FactoryEngines.getInstance().getEngine(engineType);
}
public ScriptingEngine getAssociatedEngine(Object artifact) {
for(ScriptingEngine engine : getEngines()) {
if(engine.isArtifactOfEngine(artifact)) return engine;
}
return null;
}
/**
* returns the mitable watche resource maps for the various engines
*
* @return
*/
public Map<Integer, Map<String, ClassResource>> getWatchedResources()
{
Map<Integer, Map<String, ClassResource>> ret = new HashMap<Integer, Map<String, ClassResource>>();
for (ScriptingEngine engine : getEngines())
{
ret.put(engine.getEngineType(), engine.getWatchedResources());
}
return ret;
}
/**
* @return a map of all watched resources over all engines
*/
public Map<String, ClassResource> getAllWatchedResources()
{
Map<String, ClassResource> ret = new HashMap<String, ClassResource>();
for (ScriptingEngine engine : getEngines())
{
Map<String, ClassResource> watchedResourceMap = engine.getWatchedResources();
for (Map.Entry<String, ClassResource> entry : watchedResourceMap.entrySet())
{
ret.put(entry.getKey(), entry.getValue());
}
}
return ret;
}
/**
* @param key the watched resource classname
* @return the watched resource from the given key or null
*/
public ClassResource getWatchedResource(String key)
{
for (ScriptingEngine engine : getEngines())
{
if (!engine.getWatchedResources().containsKey(key)) continue;
return engine.getWatchedResources().get(key);
}
return null;
}
public Collection<ClassResource> getTaintedClasses(int scriptingEngine) {
Map<String, ClassResource> watchedResources = getEngine(scriptingEngine).getWatchedResources();
List<ClassResource> res = new LinkedList<ClassResource>();
for(Map.Entry<String, ClassResource> entry: watchedResources.entrySet()) {
if(entry.getValue().isTainted()) {
res.add(entry.getValue());
}
}
return res;
}
public Collection<ClassResource> getTaintedClasses() {
Map<String, ClassResource> watchedResources = getAllWatchedResources();
List<ClassResource> res = new LinkedList<ClassResource>();
for(Map.Entry<String, ClassResource> entry: watchedResources.entrySet()) {
if(entry.getValue().isTainted()) {
res.add(entry.getValue());
}
}
return res;
}
/**
* checks if a resource idenified by key is tainted
*
* @param key the identifier for the resource
* @return true in case of being tainted
*/
public boolean isTainted(String key)
{
ClassResource res = getWatchedResource(key);
if (res == null) return false;
return res.isTainted();
}
public Set<String> loadPossibleDynamicClasses()
{
return getAllWatchedResources().keySet();
}
public Configuration getConfiguration()
{
return configuration;
}
public void setConfiguration(Configuration configuration)
{
this.configuration = configuration;
}
public boolean needsRecompile()
{
for (ScriptingEngine engine : getEngines())
{
//log.info("[EXT-SCRIPTING] scanning " + engine.getEngineType() + " files");
if (engine.needsRecompile()) return true;
//log.info("[EXT-SCRIPTING] scanning " + engine.getEngineType() + " files done");
}
return false;
}
public void fullScan()
{
for (ScriptingEngine engine : getEngines())
{
engine.scanForAddedDeleted();
}
//the scanner scans only the tainted classes
//hence this should work whatever happens
}
public void annotationScan()
{
if (_annotationScanner != null)
_annotationScanner.scanPaths();
}
public boolean compile()
{
boolean compile = false;
for (ScriptingEngine engine : getEngines())
{
if (!engine.needsRecompile()) continue;
sendWeavingEvent(new BeginCompiling());
compile = true;
log.info("[EXT-SCRIPTING] compiling " + engine.getEngineTypeAsStr() + " files");
CompilationResult result = engine.compile();
if(result != null) {
WeavingContext.getInstance().setCompilationResult(engine.getEngineType(), result);
}
sendWeavingEvent(new EndCompiling());
log.info("[EXT-SCRIPTING] compiling " + engine.getEngineTypeAsStr() + " files done");
}
return compile;
}
public void scanDependencies()
{
for (ScriptingEngine engine : getEngines())
{
if (engine.isTainted())
{
log.info("[EXT-SCRIPTING] scanning " + engine.getEngineTypeAsStr() + " dependencies");
engine.scanDependencies();
log.info("[EXT-SCRIPTING] scanning " + engine.getEngineTypeAsStr() + " dependencies end");
}
}
}
public void markTaintedDependends()
{
for (ScriptingEngine engine : getEngines())
{
engine.markTaintedDependencies();
}
}
public WatchedResource getResource(String className)
{
WatchedResource ret = null;
for (ScriptingEngine engine : getEngines())
{
ret = engine.getWatchedResources().get(className);
if (ret != null) return ret;
}
return ret;
}
public boolean isDynamic(Class clazz)
{
return (clazz.getClassLoader() instanceof ThrowAwayClassloader)/*<> || (clazz.getClassLoader() instanceof
ScannerClassloader))*/;
}
/**
* we create a proxy to an existing object
* which does reloading of the internal class
* on method level
* <p/>
* this works only on classes which implement contractual interfaces
* it cannot work on things like the navigation handler
* which rely on base classes
*
* @param o the source object to be proxied
* @param theInterface the proxying interface
* @param artifactType the artifact type to be reloaded
* @return a proxied reloading object of type theInterface
*/
public Object createMethodReloadingProxyFromObject(Object o, Class theInterface, int artifactType)
{
//if (!isScriptingEnabled()) {
// return o;
//}
return Proxy.newProxyInstance(o.getClass().getClassLoader(),
new Class[]{theInterface},
new MethodLevelReloadingHandler(o, artifactType));
}
public Class forName(String name) {
return _implementation.forName(name);
}
/**
* reload the class dynamically
*/
public Class reload(Class clazz)
{
if (!isDynamic(clazz)) return clazz;
ClassResource resource = (ClassResource) getResource(clazz.getName());
if (resource == null) return clazz;
if (resource.isTainted() || resource.getAClass() == null)
{
clazz = _implementation.forName(clazz.getName());
//TODO not needed anymore, done by the forName now
resource.setAClass(clazz);
}
return clazz;
}
public Object reload(Object instance, int strategyType)
{
int engineType = getAssociatedEngine(instance).getEngineType();
return _reloadingStrategy.reload(instance, engineType, strategyType);
}
/**
* we create a proxy to an existing object
* which does reloading of the internal class
* on newInstance level
*
* @param o the original object
* @param theInterface the proxy interface
* @param artifactType the artifact type to be handled
* @return the proxy of the object if scripting is enabled, the original one otherwise
*/
@SuppressWarnings("unused")
public static Object createConstructorReloadingProxyFromObject(Object o, Class theInterface, int artifactType)
{
//if (!isScriptingEnabled()) {
// return o;
//}
return Proxy.newProxyInstance(o.getClass().getClassLoader(),
new Class[]{theInterface},
new MethodLevelReloadingHandler(o, artifactType));
}
/**
* un-mapping of a proxied object
*
* @param o the proxied object
* @return the un-proxied object
*/
public static Object getDelegateFromProxy(Object o)
{
if (o == null)
{
return null;
}
if (o instanceof Decorated)
return ((Decorated) o).getDelegate();
if (!Proxy.isProxyClass(o.getClass())) return o;
InvocationHandler handler = Proxy.getInvocationHandler(o);
if (handler instanceof Decorated)
{
return ((Decorated) handler).getDelegate();
}
return o;
}
public void addDependency(int engineType, String fromClass, String toClass)
{
//TODO implement this tomorrow
}
public ImplementationService getImplementationSPI() {
return MyFacesSPI.getInstance();
}
//----------------------------------------------------------------------
//lifecycle related tasks
public boolean isPostInit()
{
return (lifecycleRegistry.get("LIFECYCLE_POST_INIT") != null);
}
public void markPostInit()
{
lifecycleRegistry.put("LIFECYCLE_POST_INIT", System.currentTimeMillis());
}
public void markLastTaint()
{
lifecycleRegistry.put("LIFECYCLE_LAST_TAINTED", System.currentTimeMillis());
}
/**
* @return the time value of the last taint happening
*/
public long getLastTaint()
{
Long lastTainted = lifecycleRegistry.get("LIFECYCLE_LAST_TAINTED");
lastTainted = (lastTainted != null) ? lastTainted : -1L;
return lastTainted;
}
/**
* marks the last annotation scan
*/
public void markLastAnnotationScan()
{
lifecycleRegistry.put("LIFECYCLE_LAST_ANN_SCAN", System.currentTimeMillis());
}
/**
* @return a the time value of the last annotation scan
*/
public long getLastAnnotationScan()
{
Long lastTainted = lifecycleRegistry.get("LIFECYCLE_LAST_ANN_SCAN");
lastTainted = (lastTainted != null) ? lastTainted : -1L;
return lastTainted;
}
//------------------------------ tainting history entries -----------------------
/**
* adds a new entry into our taint log
* which allows us to access tainting data
* from a given point in time
*
* @param data the tainting data to be added
*/
public void addTaintLogEntry(ClassResource data)
{
_taintLog.add(new TaintingHistoryEntry(data));
}
/**
* garbage collects our tainting data
* and removes all entries which are not
* present anymore due to timeout
* this gc code is called asynchronously
* from our tainting thread to keep the
* performance intact
*/
public void gcTaintLog()
{
long timeoutTimestamp = System.currentTimeMillis() - TAINT_HISTORY_TIMEOUT;
Iterator<TaintingHistoryEntry> it = _taintLog.iterator();
while (it.hasNext())
{
TaintingHistoryEntry entry = it.next();
if (entry.getTimestamp() < timeoutTimestamp)
{
it.remove();
}
}
}
/**
* returns the last noOfEntries entries in the taint history
*
* @param noOfEntries the number of entries to be delivered
* @return a collection of the last &lt;noOfEntries&gt; entries
*/
public Collection<ClassResource> getLastTainted(int noOfEntries)
{
Iterator<TaintingHistoryEntry> it = _taintLog.subList(Math.max(_taintLog.size() - noOfEntries, 0), _taintLog.size()).iterator();
List<ClassResource> retVal = new LinkedList<ClassResource>();
while (it.hasNext())
{
TaintingHistoryEntry entry = it.next();
retVal.add(entry.getData());
}
return retVal;
}
/**
* Returns a set of tainting data from a given point in time up until now
*
* @param timestamp the point in time from which the tainting data has to be derived from
* @return a set of entries which are a union of all points in time beginning from timestamp
*/
public Collection<ClassResource> getTaintHistory(long timestamp)
{
List<ClassResource> retVal = new LinkedList<ClassResource>();
Iterator<TaintingHistoryEntry> it = _taintLog.iterator();
while (it.hasNext())
{
TaintingHistoryEntry entry = it.next();
if (entry.getTimestamp() >= timestamp)
{
retVal.add(entry.getData());
}
}
return retVal;
}
/**
* Returns a set of tainted classes from a given point in time up until now
*
* @param timestamp the point in time from which the tainting data has to be derived from
* @return a set of classnames which are a union of all points in time beginning from timestamp
*/
public Set<String> getTaintHistoryClasses(long timestamp)
{
Set<String> retVal = new HashSet<String>();
Iterator<TaintingHistoryEntry> it = _taintLog.iterator();
while (it.hasNext())
{
TaintingHistoryEntry entry = it.next();
if (entry.getTimestamp() >= timestamp)
{
if (entry.getData() instanceof ClassResource)
{
retVal.add(((ClassResource) entry.getData()).getAClass().getName());
} else
{
retVal.add(entry.getData().getFile().getAbsolutePath());
}
}
}
return retVal;
}
//----------------------------------------------------------------------
/*public ClassDependencies getDependencyMap()
{
return _dependencyMap;
}
public void setDependencyMap(ClassDependencies dependencyMap)
{
_dependencyMap = dependencyMap;
} */
protected static WeavingContext _instance = new WeavingContext();
public static WeavingContext getInstance()
{
return _instance;
}
public ImplementationService getImplementation()
{
return _implementation;
}
public void setImplementation(ImplementationService implementation)
{
_implementation = implementation;
}
public boolean isScriptingEnabled()
{
return _scriptingEnabled;
}
public void setScriptingEnabled(boolean scriptingEnabled)
{
_scriptingEnabled = scriptingEnabled;
}
public CompilationResult getCompilationResult(Integer scriptingEngine)
{
return _compilationResults.get(scriptingEngine);
}
public void setCompilationResult(Integer scriptingEngine, CompilationResult result)
{
_compilationResults.put(scriptingEngine, result);
}
}