| /* |
| * 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 <class, taint> to a three dimensional <class, taint, time> |
| * 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 <noOfEntries> 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); |
| } |
| |
| } |