| /* |
| * 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.api; |
| |
| import org.apache.myfaces.config.RuntimeConfig; |
| import org.apache.myfaces.config.annotation.LifecycleProvider; |
| import org.apache.myfaces.config.annotation.LifecycleProviderFactory; |
| import org.apache.myfaces.config.element.ManagedBean; |
| import org.apache.myfaces.config.element.ManagedProperty; |
| import org.apache.myfaces.extensions.scripting.core.util.ReflectUtil; |
| import org.apache.myfaces.extensions.scripting.core.util.WeavingContext; |
| import org.apache.myfaces.extensions.scripting.monitor.ClassResource; |
| import org.apache.myfaces.extensions.scripting.monitor.RefreshContext; |
| import org.apache.myfaces.extensions.scripting.monitor.RefreshAttribute; |
| import org.apache.myfaces.util.ContainerUtils; |
| |
| import javax.el.ELContext; |
| import javax.el.ExpressionFactory; |
| import javax.faces.context.FacesContext; |
| import java.lang.reflect.InvocationTargetException; |
| import java.util.*; |
| import java.util.logging.Level; |
| import java.util.logging.Logger; |
| |
| /** |
| * Bean handler implementation |
| * which encapsulates the myfaces specific parts |
| * of the bean processing |
| */ |
| public class MyFacesBeanHandler implements BeanHandler { |
| |
| final Logger _logger = Logger.getLogger(MyFacesBeanHandler.class.getName()); |
| |
| /** |
| * scripting engine for this bean handler |
| */ |
| int _scriptingEngine; |
| |
| /** |
| * constructor |
| * |
| * @param scriptingEngine the scripting engine the bean handler |
| * currently has to attach to for its |
| * operations |
| */ |
| public MyFacesBeanHandler(int scriptingEngine) { |
| this._scriptingEngine = scriptingEngine; |
| } |
| |
| /** |
| * scans all bean dependencies according to |
| * their IoC information stored by the runtime |
| * (in our case the MyFaces runtime confing) |
| * and adds those into our backward referencing dependency map |
| * to add further dependency information on IoC level |
| * (we can have IoC dependencies which are bound by object |
| * types, this is a corner case but it still can happen) |
| */ |
| public void scanDependencies() { |
| |
| } |
| |
| /** |
| * scans the dependencies on el level |
| * Not working out for now |
| */ |
| /* public void scanElDependencies() { |
| Map<String, ManagedBean> mbeans = RuntimeConfig.getCurrentInstance(FacesContext.getCurrentInstance().getExternalContext()).getManagedBeans(); |
| for(Map.Entry<String, ManagedBean> entry: mbeans.entrySet()) { |
| Object bean = entry.getValue(); |
| if(bean instanceof org.apache.myfaces.config.impl.digester.elements.ManagedBean) { |
| org.apache.myfaces.config.impl.digester.elements.ManagedBean workBean = (org.apache.myfaces.config.impl.digester.elements.ManagedBean) bean; |
| |
| Object props = ReflectUtil.executeMethod(workBean,"getManagedProperties"); |
| if(props instanceof Iterator) { |
| //myfaces 1.2 |
| } else { |
| for(ManagedProperty prop: ((Collection<ManagedProperty>) props)) { |
| ExpressionFactory expFactory = FacesContext.getCurrentInstance().getApplication().getExpressionFactory(); |
| ELContext elContext = FacesContext.getCurrentInstance().getELContext(); |
| expFactory.coerceToType("#{myFactory['booga']}"); |
| //if(ContainerUtils.isValueReference((String) prop.getV)) |
| elContext.getELResolver().getType(elContext,"myFactory","booga"); |
| } |
| } |
| |
| |
| } |
| //Iterator<ManagedProperty> it = bean.getManagedProperties(); |
| //we rescan all managed props to cover pure object |
| //references as well as class references |
| //while(it.hasNext()) { |
| // ManagedProperty prop = it.next(); |
| //} |
| } |
| } */ |
| |
| /** |
| * Refreshes all managed beans |
| * session, and personal scoped ones |
| * <p/> |
| * personal scoped beans are beans which |
| * have either |
| * <li> session scope </li> |
| * <li> page scope </li> |
| * <li> custom scope </li> |
| */ |
| public void refreshAllManagedBeans() { |
| if (FacesContext.getCurrentInstance() == null) { |
| return;//no npe allowed |
| } |
| |
| Set<String> tainted = getTaintedClasses(); |
| |
| //scanElDependencies(); |
| |
| if (tainted.size() > 0) { |
| //We now have to check if the tainted classes belong to the managed beans |
| Set<String> managedBeanClasses = new HashSet<String>(); |
| |
| Map<String, ManagedBean> mbeans = RuntimeConfig.getCurrentInstance(FacesContext.getCurrentInstance().getExternalContext()).getManagedBeans(); |
| Map<String, ManagedBean> mbeansSnapshotView; |
| |
| synchronized (RefreshContext.BEAN_SYNC_MONITOR) { |
| mbeansSnapshotView = makeSnapshot(mbeans); |
| } |
| for (Map.Entry<String, ManagedBean> entry : mbeansSnapshotView.entrySet()) { |
| managedBeanClasses.add(entry.getValue().getManagedBeanClassName()); |
| } |
| |
| boolean managedBeanTainted = isAnyManagedBeanTainted(tainted, managedBeanClasses); |
| markPersonalScopeRefreshRecommended(); |
| getLog().info("[EXT-SCRIPTING] Tainting all beans to avoid classcast exceptions"); |
| if (managedBeanTainted) { |
| globalManagedBeanRefresh(mbeansSnapshotView); |
| //personalScopeRefresh(); |
| } |
| } |
| } |
| |
| /** |
| * Exposed personal scope refresh |
| */ |
| public void personalScopeRefresh() { |
| //shortcut to avoid heavier operations in the beginning |
| long globalBeanRefreshTimeout = WeavingContext.getRefreshContext().getPersonalScopedBeanRefresh(); |
| if (globalBeanRefreshTimeout == -1l) return; |
| |
| Map sessionMap = FacesContext.getCurrentInstance().getExternalContext().getSessionMap(); |
| Long timeOut = (Long) sessionMap.get(ScriptingConst.SESS_BEAN_REFRESH_TIMER); |
| if (timeOut == null || timeOut <= globalBeanRefreshTimeout) { |
| refreshPersonalScopedBeans(); |
| } |
| } |
| |
| /** |
| * removes all bean references which have been tainted |
| * (note for now we remove all dynamic references until we |
| * get a more sophisticated handling of managed beans) |
| * |
| * @param workCopy the managed beam snapshot view |
| */ |
| private void globalManagedBeanRefresh(Map<String, ManagedBean> workCopy) { |
| Set<String> tainted = getTaintedClasses(); |
| |
| for (Map.Entry<String, ManagedBean> entry : workCopy.entrySet()) { |
| Class managedBeanClass = entry.getValue().getManagedBeanClass(); |
| if (hasToBeRefreshed(tainted, managedBeanClass)) { |
| //managed bean class found we drop the class from our session |
| removeBeanReferences(entry.getValue()); |
| } |
| //one bean tainted we have to taint all dynamic beans otherwise we will get classcast |
| //exceptions |
| /*getLog().info("[EXT-SCRIPTING] Tainting "); |
| RefreshAttribute metaData = WeavingContext.getFileChangedDaemon().getClassMap().get(managedBeanClass.getName()); |
| if (metaData != null) { |
| metaData.requestRefresh(); |
| }*/ |
| } |
| } |
| |
| /** |
| * determines whether any bean in our managed bean list |
| * is tainted or not |
| * |
| * @param tainted a list of classes which are tainted in this iteration |
| * @param managedBeanClasses a ist of classes which are our managed beans |
| * @return true if one of the beans is tainted |
| */ |
| private boolean isAnyManagedBeanTainted(Set<String> tainted, Set<String> managedBeanClasses) { |
| boolean managedBeanTainted = false; |
| for (String taintedClass : tainted) { |
| if (managedBeanClasses.contains(taintedClass)) { |
| managedBeanTainted = true; |
| break; |
| } |
| } |
| return managedBeanTainted; |
| } |
| |
| /** |
| * helper which returns all tainted classes |
| * |
| * @return the tainted classes |
| */ |
| private Set<String> getTaintedClasses() { |
| Set<String> tainted = new HashSet<String>(); |
| |
| for (Map.Entry<String, ClassResource> it : WeavingContext.getFileChangedDaemon().getClassMap().entrySet()) { |
| if (it.getValue().getScriptingEngine() == getScriptingEngine() && it.getValue().getRefreshAttribute().requiresRefresh()) { |
| tainted.add(it.getKey()); |
| } |
| } |
| return tainted; |
| } |
| |
| /** |
| * refreshes all personal scoped beans (aka beans which |
| * have an assumed lifecycle <= session) |
| * <p/> |
| * This is needed for multiuser purposes because if one user alters some beans |
| * other users have to drop their non application scoped beans as well! |
| */ |
| private void refreshPersonalScopedBeans() { |
| //the refreshing is only allowed if no compile is in progress |
| //and vice versa |
| Map sessionMap = FacesContext.getCurrentInstance().getExternalContext().getSessionMap(); |
| Long taintingPeriod = (Long) sessionMap.get(ScriptingConst.SESS_BEAN_REFRESH_TIMER); |
| if (taintingPeriod == null) { |
| taintingPeriod = -1l; |
| } |
| Set<String> taintedInTime = WeavingContext.getRefreshContext().getTaintHistoryClasses(taintingPeriod); |
| |
| synchronized (RefreshContext.BEAN_SYNC_MONITOR) { |
| Map<String, ManagedBean> mbeans = RuntimeConfig.getCurrentInstance(FacesContext.getCurrentInstance().getExternalContext()).getManagedBeans(); |
| //the map is immutable but in between scanning might change it so we make a full copy of the map |
| |
| //We can synchronized the refresh, but if someone alters |
| //the bean map from outside we still get race conditions |
| //But for most cases this mutex should be enough |
| Map<String, ManagedBean> mbeansSnapshotView = makeSnapshot(mbeans); |
| |
| for (Map.Entry<String, ManagedBean> entry : mbeansSnapshotView.entrySet()) { |
| Class managedBeanClass = entry.getValue().getManagedBeanClass(); |
| if (hasToBeRefreshed(taintedInTime, managedBeanClass)) { |
| FacesContext.getCurrentInstance().getExternalContext().getSessionMap().remove(entry.getValue().getManagedBeanName()); |
| removeCustomScopedBean(entry.getValue()); |
| } |
| } |
| updateBeanRefreshTime(); |
| } |
| } |
| |
| /** |
| * removes the references from out static scope |
| * for jsf2 we probably have some kind of notification mechanism |
| * which notifies custom scopes |
| * |
| * @param bean the managed bean which all references have to be removed from |
| */ |
| |
| private void removeBeanReferences(ManagedBean bean) { |
| if (getLog().isLoggable(Level.FINE)) { |
| getLog().log(Level.FINE, "[EXT-SCRIPTING] JavaScriptingWeaver.removeBeanReferences({0})", bean.getManagedBeanName()); |
| } |
| FacesContext.getCurrentInstance().getExternalContext().getSessionMap().remove(bean.getManagedBeanName()); |
| FacesContext.getCurrentInstance().getExternalContext().getApplicationMap().remove(bean.getManagedBeanName()); |
| removeCustomScopedBean(bean); |
| } |
| |
| /** |
| * @return the log for this class |
| */ |
| protected Logger getLog() { |
| return Logger.getLogger(this.getClass().getName()); |
| } |
| |
| /** |
| * jsf2 helper to remove custom scoped beans |
| * |
| * @param bean the managed bean which has to be removed from the custom scope from |
| */ |
| private void removeCustomScopedBean(ManagedBean bean) { |
| Object scopeImpl = FacesContext.getCurrentInstance().getExternalContext().getApplicationMap().get(bean.getManagedBeanScope()); |
| if (scopeImpl == null) return; //scope not implemented |
| //we now have to revert to introspection here because scopes are a pure jsf2 construct |
| //so we use a messaging pattern here to cope with it |
| |
| Object beanInstance = ReflectUtil.executeMethod(scopeImpl, "get", bean.getManagedBeanName()); |
| LifecycleProvider lifecycleProvider = |
| LifecycleProviderFactory.getLifecycleProviderFactory().getLifecycleProvider(FacesContext.getCurrentInstance().getExternalContext()); |
| try { |
| lifecycleProvider.destroyInstance(beanInstance); |
| } catch (IllegalAccessException e) { |
| _logger.log(Level.WARNING, "removeCustomScopedBean():", e); |
| } catch (InvocationTargetException e) { |
| _logger.log(Level.WARNING, "removeCustomScopedBean():", e); |
| } |
| } |
| |
| /** |
| * MyFaces 2.0 keeps an immutable map over the session |
| * and request scoped beans |
| * if we alter that during our loop we get a concurrent modification exception |
| * taking a snapshot in time fixes that |
| * |
| * @param mbeans the internal managed bean map which has to be investigated |
| * @return a map with the class name as key and the managed bean info |
| * as value of the current state of the internal runtime config bean map |
| */ |
| private Map<String, ManagedBean> makeSnapshot(Map<String, ManagedBean> mbeans) { |
| Map<String, ManagedBean> workCopy; |
| |
| workCopy = new HashMap<String, ManagedBean>(mbeans.size()); |
| for (Map.Entry<String, ManagedBean> entry : mbeans.entrySet()) { |
| workCopy.put(entry.getKey(), entry.getValue()); |
| } |
| |
| return workCopy; |
| } |
| |
| /** |
| * updates the internal timer |
| * for our personal scoped beans so that |
| * we dont get updates on beans we have refreshed already |
| */ |
| private void updateBeanRefreshTime() { |
| long sessionRefreshTime = System.currentTimeMillis(); |
| FacesContext.getCurrentInstance().getExternalContext().getSessionMap().put(ScriptingConst.SESS_BEAN_REFRESH_TIMER, sessionRefreshTime); |
| } |
| |
| /** |
| * sets the internal timer for other processes |
| * to update their beans as well |
| */ |
| private void markPersonalScopeRefreshRecommended() { |
| long sessionRefreshTime = System.currentTimeMillis(); |
| WeavingContext.getRefreshContext().setPersonalScopedBeanRefresh(sessionRefreshTime); |
| FacesContext.getCurrentInstance().getExternalContext().getSessionMap().put(ScriptingConst.SESS_BEAN_REFRESH_TIMER, sessionRefreshTime); |
| } |
| |
| /** |
| * important, this method determines whether a managed bean class |
| * has to be refreshed or not |
| * |
| * @param tainted set of tainted classes |
| * @param managedBeanClass the class to be checked for refresh criteria |
| * @return true if the current bean class fulfills our refresh criteria |
| */ |
| protected boolean hasToBeRefreshed(Set<String> tainted, Class managedBeanClass) { |
| |
| return WeavingContext.isDynamic(managedBeanClass) && tainted.contains(managedBeanClass.getName()); |
| } |
| |
| /** |
| * returns the scripting engine which is attached |
| * to this bean handler |
| * |
| * @return the current scripting engine |
| */ |
| private int getScriptingEngine() { |
| return _scriptingEngine; |
| } |
| } |