https://issues.apache.org/jira/browse/EXTSCRIPT-44
Save state of affairs, all beans kick through
git-svn-id: https://svn.apache.org/repos/asf/myfaces/extensions/scripting/trunk@1401723 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/extscript-core-root/extscript-spring/src/main/java/org/apache/myfaces/extensions/scripting/spring/bean/CompilationAwareRefreshableBeanFactory.java b/extscript-core-root/extscript-spring/src/main/java/org/apache/myfaces/extensions/scripting/spring/bean/CompilationAwareRefreshableBeanFactory.java
index 7631519..d4d8295 100644
--- a/extscript-core-root/extscript-spring/src/main/java/org/apache/myfaces/extensions/scripting/spring/bean/CompilationAwareRefreshableBeanFactory.java
+++ b/extscript-core-root/extscript-spring/src/main/java/org/apache/myfaces/extensions/scripting/spring/bean/CompilationAwareRefreshableBeanFactory.java
@@ -19,17 +19,26 @@
package org.apache.myfaces.extensions.scripting.spring.bean;
-import org.apache.myfaces.extensions.scripting.core.common.util.ReflectUtil;
+import org.apache.myfaces.extensions.scripting.core.api.WeavingContext;
import org.apache.myfaces.extensions.scripting.core.engine.ThrowAwayClassloader;
import org.apache.myfaces.extensions.scripting.spring.bean.support.CompilationAwareInstantiationStrategy;
+import org.apache.myfaces.extensions.scripting.spring.util.ProxyAopUtils;
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.FatalBeanException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.CannotLoadBeanClassException;
+import org.springframework.beans.factory.ObjectFactory;
+import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.core.DecoratingClassLoader;
import org.springframework.util.ClassUtils;
-import java.lang.reflect.Constructor;
+import java.beans.PropertyDescriptor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
/**
* @author Werner Punz (latest modification by $Author$)
@@ -76,6 +85,50 @@
return super.resolveBeanClass(mbd, beanName);
}
+ // ------------------------------------------ AbstractBeanFactory methods
+
+ @Override
+ public Object getBean(final String name, final Class requiredType, final Object[] args) throws BeansException
+ {
+ Object bean = super.getBean(name, requiredType, args);
+
+ BeanDefinition beanDefinition = getMergedBeanDefinition(name);
+ //if (beanDefinition.hasAttribute(
+ // RefreshableBeanAttribute.REFRESHABLE_BEAN_ATTRIBUTE)) { // Try to minimize synchronizing ..
+ // Note that the DefaultListableBeanFactory internally caches bean definitions,
+ // so I think it's safe to use the return value as monitor for the synchronized
+ // block.
+ synchronized (beanDefinition)
+ {
+ // Obtain the metadata attribute of the bean definition that contains the
+ // required information to determine wheter a refresh is required or not.
+ //RefreshableBeanAttribute refreshableAttribute =
+ // (RefreshableBeanAttribute) beanDefinition.getAttribute(
+ // RefreshableBeanAttribute.REFRESHABLE_BEAN_ATTRIBUTE);
+ if (requiresRefresh(bean, beanDefinition))
+ {
+ bean = refresh(bean, name, new ObjectFactory()
+ {
+ public Object getObject() throws BeansException
+ {
+ return CompilationAwareRefreshableBeanFactory.super.getBean(name, requiredType, args);
+ }
+ }, beanDefinition);
+ //refreshableAttribute.executedRefresh();
+ }
+ }
+ //}
+
+ return bean;
+ }
+
+ private boolean requiresRefresh(Object bean, BeanDefinition beanDefinition)
+ {
+ //TODO add the refreshable classes from the event handler there
+ Class clazz = ProxyAopUtils.resolveTargetClass(bean);
+ return WeavingContext.getInstance().isDynamic(clazz);
+ }
+
@Override
protected Class resolveBeanClass(RootBeanDefinition beanDefinition, String beanName, Class[] typesToMatch) throws CannotLoadBeanClassException
{
@@ -105,19 +158,6 @@
}
Class retVal = beanDefinition.resolveBeanClass(getBeanClassLoader());
- /* try
- {
- //spring caches the constructor, we have to set it anew
- //this imposes a limitation to () constructors but
- //I guess we can live with it
- Constructor ctor = retVal.getConstructor();
- ReflectUtil.setField(beanDefinition,"resolvedConstructorOrFactoryMethod", ctor, true);
- }
- catch (NoSuchMethodException e)
- {
- e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
- }*/
-
return retVal;
}
catch (ClassNotFoundException ex)
@@ -132,6 +172,117 @@
}
}
+ protected Object refresh(Object beanInstance, String beanName,
+ ObjectFactory objectFactory, BeanDefinition beanDefinition)
+ {
+ // In the case of a prototype-scoped bean, we don't have to worry about copying
+ // properties, etc. as it's definitely always a newly created beanInstance instance
+ // and so it's safe to assume that we're using the most recent Class reference.
+ if (!BeanDefinition.SCOPE_PROTOTYPE.equals(beanDefinition.getScope()))
+ {
+ // At first destroy the previously created bean (e.g. execute the according callbacks
+ // if the bean implements the DisposableBean interface and remove it from the according
+ // scope).
+ if (beanDefinition.isSingleton())
+ {
+ destroySingleton(beanName);
+ } else
+ {
+ destroyScopedBean(beanName);
+ }
+ // Now that the bean has already been destroyed, we'll create a new instance of it.
+ Object newBeanInstance = objectFactory.getObject();
+
+ // Copy the properties only if we're not dealing with a proxy. In that case the
+ // BeanFactory will copy the properties anyway once the target bean gets recreated (in
+ // fact, otherwise we would have to face dozens of InvocationTargetExceptions as it would
+ // create a huge mess with different versions of different Class references).
+ if (!ProxyAopUtils.isProxyClass(newBeanInstance.getClass()))
+ {
+ copyProperties(beanName, beanDefinition, beanInstance, newBeanInstance);
+ }
+
+ return newBeanInstance;
+ } else
+ {
+ return beanInstance;
+ }
+ }
+
+ /**
+ * <p>Copies the property values of the given source bean into the given target bean. Note that
+ * this method, in contrast to the {@link org.springframework.beans.BeanUtils#copyProperties(Object, Object)} method,
+ * is aware of the possibility that copying properties from one bean to another could override newer versions of beans
+ * that Spring has injected. </p>
+ */
+ private void copyProperties(String beanName, BeanDefinition beanDefinition, Object source, Object target)
+ {
+ // Definitely we don't want to mess around with properties of
+ // any proxy objects, so we'll use its target class here.
+ PropertyDescriptor[] targetDescriptors = BeanUtils.getPropertyDescriptors(
+ ProxyAopUtils.resolveTargetClass(target));
+
+ for (int i = 0; i < targetDescriptors.length; i++)
+ {
+ PropertyDescriptor targetDescriptor = targetDescriptors[i];
+ if (targetDescriptor.getWriteMethod() != null)
+ {
+ PropertyDescriptor sourceDescriptor =
+ BeanUtils.getPropertyDescriptor(source.getClass(), targetDescriptor.getName());
+ if (sourceDescriptor != null && sourceDescriptor.getReadMethod() != null)
+ {
+ try
+ {
+ Object value = invokeMethod(sourceDescriptor.getReadMethod(), source, new Object[0]);
+ if (value instanceof ThrowAwayClassloader)
+ {
+ //spring let spring handle the property assignment for a dynamic class
+ //so that property reloads work
+ } else
+ {
+ invokeMethod(targetDescriptor.getWriteMethod(), target, new Object[]{value});
+ }
+
+ /*if (getReloadingClassLoader().isOutdated(value.getClass()) &&
+ beanDefinition.getPropertyValues().contains(targetDescriptor.getName())) {
+ // If the currenty property has been injected by Spring already and the
+ // source object returned an outdated object, keep the one that has been
+ // injected by Spring as it's more likely to be the most recent one.
+
+ if (logger.isWarnEnabled()) {
+ logger.warn(" This factory will not copy the property '" + targetDescriptor.getName() + "' of the bean '" +
+ beanName + "' as the source object '" + source + "' only returns an outdated object '" + value + "'.");
+ }
+ }
+ else {
+ invokeMethod(targetDescriptor.getWriteMethod(), target, new Object[]{value});
+ /*}*/
+ }
+ catch (Throwable ex)
+ {
+ throw new FatalBeanException("Could not copy properties from source to target", ex);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * <p>Invokes the given method on the given target with the given arguments. Note that
+ * this method assumes that the method itself is already accessible (which should be the
+ * case as it's sole purpose is to invoke getters and setters which have to be public
+ * methods anyway).</p>
+ */
+ private static Object invokeMethod(Method method, Object target, Object[] args)
+ throws InvocationTargetException, IllegalAccessException
+ {
+ if (!Modifier.isPublic(method.getDeclaringClass().getModifiers()))
+ {
+ method.setAccessible(true);
+ }
+
+ return method.invoke(target, args);
+ }
}
diff --git a/extscript-core-root/extscript-spring/src/main/java/org/apache/myfaces/extensions/scripting/spring/bean/SpringWeavingContext.java b/extscript-core-root/extscript-spring/src/main/java/org/apache/myfaces/extensions/scripting/spring/bean/SpringWeavingContext.java
new file mode 100644
index 0000000..e2e8687
--- /dev/null
+++ b/extscript-core-root/extscript-spring/src/main/java/org/apache/myfaces/extensions/scripting/spring/bean/SpringWeavingContext.java
@@ -0,0 +1,33 @@
+/*
+ * 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.spring.bean;
+
+/**
+ * @author Werner Punz (latest modification by $Author$)
+ * @version $Revision$ $Date$
+ *
+ * A helper with additional meta information regarding spring
+ */
+
+public class SpringWeavingContext
+{
+
+
+}
diff --git a/extscript-core-root/extscript-spring/src/main/java/org/apache/myfaces/extensions/scripting/spring/util/ProxyAopUtils.java b/extscript-core-root/extscript-spring/src/main/java/org/apache/myfaces/extensions/scripting/spring/util/ProxyAopUtils.java
new file mode 100644
index 0000000..6a4d7fb
--- /dev/null
+++ b/extscript-core-root/extscript-spring/src/main/java/org/apache/myfaces/extensions/scripting/spring/util/ProxyAopUtils.java
@@ -0,0 +1,118 @@
+/*
+ * 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.spring.util;
+
+import org.springframework.aop.scope.ScopedObject;
+import org.springframework.aop.support.AopUtils;
+
+import java.lang.reflect.Proxy;
+
+/**
+ *
+ */
+public class ProxyAopUtils {
+
+ /**
+ * <p>Returns true if and only if the given Class reference is either a JDK
+ * dynamic proxy or a CGLIB proxy.</p>
+ *
+ * @param clazz the class to check
+ * @see #isJdkProxyClass(Class)
+ * @see #isCglibProxyClass(Class)
+ */
+ public static boolean isProxyClass(Class clazz) {
+ return isJdkProxyClass(clazz) || isCglibProxyClass(clazz);
+ }
+
+ /**
+ * <p>Returns true if and only if the specified class was dynamically
+ * generated to be a proxy class using the builtin JDK dynamic proxy
+ * facilities.</p>
+ *
+ * @param clazz the class to check
+ */
+ public static boolean isJdkProxyClass(Class clazz) {
+ return clazz != null && Proxy.isProxyClass(clazz);
+ }
+
+ /**
+ * <p>Returns true if and only if the specified class was dynamically
+ * generated to be a proxy class using the CGLIB dynamic proxy
+ * facilities.</p>
+ *
+ * @param clazz the class to check
+ */
+ public static boolean isCglibProxyClass(Class clazz) {
+ return clazz != null && clazz.getName().contains("$$");
+ }
+
+ public static boolean isScopedProxy(Object obj) {
+ if (isProxyClass(obj.getClass())) {
+ return ProxyAopUtilities.isScopedObject(obj);
+ }
+ else {
+ return false;
+ }
+ }
+
+ /**
+ * <p>Returns the target class of the given Class reference, i.e. if the
+ * given Class reference is a dynamiccaly generated proxy class, its target
+ * class will be returned (that is, the Class reference being proxied by
+ * this one). Otherwise the given Class reference will be returned.</p>
+ */
+ public static Class resolveTargetClass(Object proxy) {
+ if (isProxyClass(proxy.getClass())) {
+ return ProxyAopUtilities.resolveTargetClass(proxy);
+ }
+ else {
+ return proxy.getClass();
+ }
+ }
+
+ // ------------------------------------------ Private static classes
+
+ /**
+ * <p>Private static class that knows how to deal with dynamically generated proxy.
+ * Note that the implementation requires that Spring AOP is present on the classpath,
+ * which is why a private static class has been introduced. In doing so, Spring AOP
+ * is only required if we're definitely dealing with a proxy.</p>
+ */
+ private static class ProxyAopUtilities {
+
+ /**
+ * <p>Returns whether the given object is a scoped object, i.e. a proxy
+ * delegating to a bean that resides within a certain scope.</p>
+ */
+ public static boolean isScopedObject(Object obj) {
+ return obj instanceof ScopedObject;
+ }
+
+ /**
+ * <p>Returns the target class of the given proxy Class reference, i.e.
+ * the Class reference being proxied by this one.</p>
+ */
+ public static Class resolveTargetClass(Object proxy) {
+ return AopUtils.getTargetClass(proxy);
+ }
+
+ }
+
+}
diff --git a/extscript-core-root/extscript-spring/src/main/java/org/apache/myfaces/extensions/scripting/spring/util/ResourceUtils.java b/extscript-core-root/extscript-spring/src/main/java/org/apache/myfaces/extensions/scripting/spring/util/ResourceUtils.java
new file mode 100644
index 0000000..aff9ba5
--- /dev/null
+++ b/extscript-core-root/extscript-spring/src/main/java/org/apache/myfaces/extensions/scripting/spring/util/ResourceUtils.java
@@ -0,0 +1,92 @@
+/*
+ * 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.spring.util;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.core.io.FileSystemResource;
+import org.springframework.core.io.Resource;
+
+import java.io.IOException;
+import java.net.JarURLConnection;
+import java.net.URLConnection;
+
+/**
+ * <p>Utility methods for dealing with resource files in the
+ * file system. Mainly for internal use within the module.</p>
+ */
+public class ResourceUtils {
+
+ /**
+ * The logger instance for this class.
+ */
+ private static final Log logger = LogFactory.getLog(ResourceUtils.class);
+
+ // ------------------------------------------ Public static methods
+
+ /**
+ * <p>Determines whether the given resource file is already outdated compared
+ * to the given timestamp, i.e. it compares the given timestamp to the last
+ * modified timestamp of the given resource and returns <code>true</code>
+ * if the given timestamp is less than the timestamp of the given resource.</p>
+ * <p/>
+ * <p>Note that this methods assumes that the given resource exists, otherwise
+ * an exception will be thrown.</p>
+ *
+ * @param timestamp the timestamp that you want to compare the resource to
+ * @param resource the resource that you want to check
+ * @return <code>true</code>, if the given resource is outdated, otherwise
+ * <code>false</code>
+ * @throws java.io.IOException if an I/O error occurs
+ */
+ public static boolean isOutdated(long timestamp, Resource resource) throws IOException {
+ long lastModified;
+
+ if (resource instanceof FileSystemResource) {
+ lastModified = resource.getFile().lastModified();
+ }
+ else {
+ URLConnection connection = resource.getURL().openConnection();
+ connection.setUseCaches(false);
+
+ // The problem with JarURLConnections is that they seemingly don't care
+ // much about the last modified timestamp, they always return 0.
+ if (connection instanceof JarURLConnection) {
+ connection = ((JarURLConnection) connection).getJarFileURL().openConnection();
+ connection.setUseCaches(false);
+ }
+
+ try {
+ lastModified = connection.getLastModified();
+ } finally {
+ try {
+ connection.getInputStream().close();
+ } catch (IOException ex) {
+ if (logger.isErrorEnabled()) {
+ logger.error("An I/O error occured while closing the URL connection for the resource '" + resource + "'.", ex);
+ }
+ }
+ }
+ }
+
+ return timestamp < lastModified;
+ }
+
+}