From 42863a3b62225ec3bbd1f4e0f081211a64dbd1fc Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Wed, 18 Dec 2013 14:36:54 -0500 Subject: [PATCH] Upgrade to Spring Framework 4.0 The build now compiles with Servlet API 3.0 and JPA 2.0 both of which are required with Spring Framework 4.0. However there are no hard dependencies in Web Flow on either. Issue: SWF-1600 --- build.gradle | 31 +- .../AbstractCachingMapDecorator.java | 295 ++++++++++++++++++ .../convert/converters/ArrayToCollection.java | 10 +- .../converters/CollectionToCollection.java | 10 +- .../converters/ObjectToCollection.java | 10 +- .../converters/StringToLabeledEnum.java | 44 --- .../message/DefaultMessageContext.java | 11 +- .../binding/method/MethodInvoker.java | 10 +- .../SpringBeanWebFlowVariableResolver.java | 109 ++++++- .../faces/webflow/TreeStructureManager.java | 2 +- .../webflow/action/DispatchMethodInvoker.java | 7 +- .../action/ResultObjectBasedEventFactory.java | 10 +- .../impl/ConversationContainer.java | 6 +- .../SimpleFlowExecutionSnapshotGroup.java | 7 +- .../builder/FlowResourceFlowViewResolver.java | 3 +- .../webflow/mvc/builder/MvcEnvironment.java | 7 +- .../JpaFlowExecutionListenerTests.java | 28 +- ...lowManagedPersistenceIntegrationTests.java | 9 +- ...JpaPersistenceContextPropagationTests.java | 14 +- .../webflow/persistence/TestBean.hbm.xml | 2 +- .../webflow/persistence/TestBean.java | 4 + .../webflow/persistence/persistence.xml | 4 + 22 files changed, 492 insertions(+), 141 deletions(-) create mode 100644 spring-binding/src/main/java/org/springframework/binding/collection/AbstractCachingMapDecorator.java delete mode 100644 spring-binding/src/main/java/org/springframework/binding/convert/converters/StringToLabeledEnum.java diff --git a/build.gradle b/build.gradle index b299841b..9cf50a19 100644 --- a/build.gradle +++ b/build.gradle @@ -18,12 +18,19 @@ configure(allprojects) { } configure(subprojects) { subproject -> + apply plugin: "java" apply plugin: "propdeps" apply from: "${rootProject.projectDir}/publish-maven.gradle" - sourceCompatibility=1.5 - targetCompatibility=1.5 + compileJava { + sourceCompatibility=1.5 + targetCompatibility=1.5 + } + compileTestJava { + sourceCompatibility=1.6 + targetCompatibility=1.6 + } [compileJava, compileTestJava]*.options*.compilerArgs = ["-Xlint:none"] @@ -54,7 +61,7 @@ configure(subprojects.findAll {it.name != "spring-js-resources"}) { subproject - } subproject.ext { - springVersion = "3.2.1.RELEASE" + springVersion = "4.0.0.RELEASE" springSecurityVersion = "3.1.3.RELEASE" slf4jVersion = "1.6.1" log4jVersion = "1.2.15" @@ -124,7 +131,7 @@ project("spring-js") { dependencies { compile(project(":spring-js-resources")) compile("commons-logging:commons-logging:1.1.1") - provided("javax.servlet:servlet-api:2.4") + provided("javax.servlet:javax.servlet-api:3.0.1") optional("org.apache.tiles:tiles-api:2.1.2") optional("org.apache.tiles:tiles-core:2.1.2") optional("org.apache.tiles:tiles-jsp:2.1.2") @@ -153,13 +160,12 @@ project("spring-webflow") { compile(project(":spring-js")) compile("commons-logging:commons-logging:1.1.1") provided("javax.el:el-api:2.2") - provided("javax.persistence:persistence-api:1.0.2") - provided("javax.servlet:servlet-api:2.4") + provided("org.eclipse.persistence:javax.persistence:2.0.0") + provided("javax.servlet:javax.servlet-api:3.0.1") provided("javax.portlet:portlet-api:2.0") - provided("javax.transaction:transaction-api:1.1-rev-1") provided("junit:junit:3.8.2") compile("opensymphony:ognl:2.6.11") - optional("org.hibernate:hibernate:3.2.7.ga") { + optional("org.hibernate:hibernate-core:3.6.9.Final") { exclude group: "org.slf4j", module: "slf4j-api" } optional("backport-util-concurrent:backport-util-concurrent:3.0") @@ -175,10 +181,9 @@ project("spring-webflow") { optional("org.springframework:spring-webmvc-portlet:$springVersion") optional("org.springframework.security:spring-security-core:$springSecurityVersion") testCompile("javax.validation:validation-api:1.0.0.GA") - testCompile("org.apache.openjpa:openjpa:1.1.0") - testCompile("org.apache.openjpa:openjpa-lib:1.1.0") - testCompile("org.apache.openjpa:openjpa-persistence:1.1.0") - testCompile("org.apache.openjpa:openjpa-persistence-jdbc:1.1.0") + testCompile("org.hibernate:hibernate-core:3.6.9.Final") + testCompile("org.hibernate:hibernate-entitymanager:3.6.9.Final") + testCompile("org.hibernate:hibernate-validator:4.3.0.Final") testCompile("org.hsqldb:hsqldb:2.2.8") testCompile("org.jboss.el:jboss-el:2.0.1.GA") testCompile("org.springframework:spring-aop:$springVersion") @@ -194,7 +199,7 @@ project("spring-faces") { compile(project(":spring-binding")) compile(project(":spring-webflow")) compile("commons-logging:commons-logging:1.1.1") - provided("javax.servlet:servlet-api:2.4") + provided("javax.servlet:javax.servlet-api:3.0.1") provided("javax.portlet:portlet-api:2.0") provided("javax.el:el-api:1.0") compile("org.springframework:spring-beans:$springVersion") diff --git a/spring-binding/src/main/java/org/springframework/binding/collection/AbstractCachingMapDecorator.java b/spring-binding/src/main/java/org/springframework/binding/collection/AbstractCachingMapDecorator.java new file mode 100644 index 00000000..c03f2b22 --- /dev/null +++ b/spring-binding/src/main/java/org/springframework/binding/collection/AbstractCachingMapDecorator.java @@ -0,0 +1,295 @@ +package org.springframework.binding.collection; + +import java.io.Serializable; +import java.lang.ref.Reference; +import java.lang.ref.WeakReference; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.LinkedList; +import java.util.Map; +import java.util.Set; +import java.util.WeakHashMap; + +import org.springframework.util.Assert; + +/** + * A simple decorator for a Map, encapsulating the workflow for caching + * expensive values in a target Map. Supports caching weak or strong keys. + *

+ * This class is an abstract template. Caching Map implementations should + * subclass and override the create(key) method which encapsulates + * expensive creation of a new object. + * + * @author Keith Donald + * @author Juergen Hoeller + * @since 2.4 + */ +@SuppressWarnings("serial") +public abstract class AbstractCachingMapDecorator implements Map, Serializable { + + private static Object NULL_VALUE = new Object(); + + + private final Map targetMap; + + private final boolean synchronize; + + private final boolean weak; + + + /** + * Create a CachingMapDecorator with strong keys, + * using an underlying synchronized Map. + */ + public AbstractCachingMapDecorator() { + this(false); + } + + /** + * Create a CachingMapDecorator, + * using an underlying synchronized Map. + * @param weak whether to use weak references for keys and values + */ + public AbstractCachingMapDecorator(boolean weak) { + Map internalMap = (weak ? new WeakHashMap() : new HashMap()); + this.targetMap = Collections.synchronizedMap(internalMap); + this.synchronize = true; + this.weak = weak; + } + + /** + * Create a CachingMapDecorator with initial size, + * using an underlying synchronized Map. + * @param weak whether to use weak references for keys and values + * @param size the initial cache size + */ + public AbstractCachingMapDecorator(boolean weak, int size) { + Map internalMap = weak ? new WeakHashMap (size) : new HashMap(size); + this.targetMap = Collections.synchronizedMap(internalMap); + this.synchronize = true; + this.weak = weak; + } + + /** + * Create a CachingMapDecorator for the given Map. + *

The passed-in Map won't get synchronized explicitly, + * so make sure to pass in a properly synchronized Map, if desired. + * @param targetMap the Map to decorate + */ + public AbstractCachingMapDecorator(Map targetMap) { + this(targetMap, false, false); + } + + /** + * Create a CachingMapDecorator for the given Map. + *

The passed-in Map won't get synchronized explicitly unless + * you specify "synchronize" as "true". + * @param targetMap the Map to decorate + * @param synchronize whether to synchronize on the given Map + * @param weak whether to use weak references for values + */ + @SuppressWarnings("unchecked") + public AbstractCachingMapDecorator(Map targetMap, boolean synchronize, boolean weak) { + Assert.notNull(targetMap, "'targetMap' must not be null"); + this.targetMap = (Map) (synchronize ? Collections.synchronizedMap(targetMap) : targetMap); + this.synchronize = synchronize; + this.weak = weak; + } + + + public int size() { + return this.targetMap.size(); + } + + public boolean isEmpty() { + return this.targetMap.isEmpty(); + } + + public boolean containsKey(Object key) { + return this.targetMap.containsKey(key); + } + + public boolean containsValue(Object value) { + Object valueToCheck = (value != null ? value : NULL_VALUE); + if (this.synchronize) { + synchronized (this.targetMap) { + return containsValueOrReference(valueToCheck); + } + } + else { + return containsValueOrReference(valueToCheck); + } + } + + private boolean containsValueOrReference(Object value) { + if (this.targetMap.containsValue(value)) { + return true; + } + for (Object mapVal : this.targetMap.values()) { + if (mapVal instanceof Reference && value.equals(((Reference) mapVal).get())) { + return true; + } + } + return false; + } + + public V remove(Object key) { + return unwrapReturnValue(this.targetMap.remove(key)); + } + + @SuppressWarnings("unchecked") + private V unwrapReturnValue(Object value) { + Object returnValue = value; + if (returnValue instanceof Reference) { + returnValue = ((Reference) returnValue).get(); + } + return (returnValue == NULL_VALUE ? null : (V) returnValue); + } + + public void putAll(Map map) { + this.targetMap.putAll(map); + } + + public void clear() { + this.targetMap.clear(); + } + + public Set keySet() { + if (this.synchronize) { + synchronized (this.targetMap) { + return new LinkedHashSet(this.targetMap.keySet()); + } + } + else { + return new LinkedHashSet(this.targetMap.keySet()); + } + } + + public Collection values() { + if (this.synchronize) { + synchronized (this.targetMap) { + return valuesCopy(); + } + } + else { + return valuesCopy(); + } + } + + @SuppressWarnings("unchecked") + private Collection valuesCopy() { + LinkedList values = new LinkedList(); + for (Iterator it = this.targetMap.values().iterator(); it.hasNext();) { + Object value = it.next(); + if (value instanceof Reference) { + value = ((Reference) value).get(); + if (value == null) { + it.remove(); + continue; + } + } + values.add(value == NULL_VALUE ? null : (V) value); + } + return values; + } + + public Set> entrySet() { + if (this.synchronize) { + synchronized (this.targetMap) { + return entryCopy(); + } + } + else { + return entryCopy(); + } + } + + @SuppressWarnings("unchecked") + private Set> entryCopy() { + Map entries = new LinkedHashMap(); + for (Iterator> it = this.targetMap.entrySet().iterator(); it.hasNext();) { + Entry entry = it.next(); + Object value = entry.getValue(); + if (value instanceof Reference) { + value = ((Reference) value).get(); + if (value == null) { + it.remove(); + continue; + } + } + entries.put(entry.getKey(), value == NULL_VALUE ? null : (V) value); + } + return entries.entrySet(); + } + + + /** + * Put an object into the cache, possibly wrapping it with a weak + * reference. + * @see #useWeakValue(Object, Object) + */ + public V put(K key, V value) { + Object newValue = value; + if (value == null) { + newValue = NULL_VALUE; + } + else if (useWeakValue(key, value)) { + newValue = new WeakReference(newValue); + } + return unwrapReturnValue(this.targetMap.put(key, newValue)); + } + + /** + * Decide whether to use a weak reference for the value of + * the given key-value pair. + * @param key the candidate key + * @param value the candidate value + * @return true in order to use a weak reference; + * false otherwise. + */ + protected boolean useWeakValue(K key, V value) { + return this.weak; + } + + /** + * Get value for key. + * Creates and caches value if it doesn't already exist in the cache. + *

This implementation is not synchronized: This is highly + * concurrent but does not guarantee unique instances in the cache, + * as multiple values for the same key could get created in parallel. + * Consider overriding this method to synchronize it, if desired. + * @see #create(Object) + */ + @SuppressWarnings("unchecked") + public V get(Object key) { + Object value = this.targetMap.get(key); + if (value instanceof Reference) { + value = ((Reference) value).get(); + } + if (value == null) { + V newValue = create((K) key); + put((K) key, newValue); + return newValue; + } + return (value == NULL_VALUE ? null : (V) value); + } + + /** + * Create a value to cache for the given key. + * Called by get if there is no value cached already. + * @param key the cache key + * @see #get(Object) + */ + protected abstract V create(K key); + + + @Override + public String toString() { + return "CachingMapDecorator [" + getClass().getName() + "]:" + this.targetMap; + } + +} \ No newline at end of file diff --git a/spring-binding/src/main/java/org/springframework/binding/convert/converters/ArrayToCollection.java b/spring-binding/src/main/java/org/springframework/binding/convert/converters/ArrayToCollection.java index 4a854f86..9655c0fd 100644 --- a/spring-binding/src/main/java/org/springframework/binding/convert/converters/ArrayToCollection.java +++ b/spring-binding/src/main/java/org/springframework/binding/convert/converters/ArrayToCollection.java @@ -138,12 +138,10 @@ public class ArrayToCollection implements TwoWayConverter { if (elementConverter != null) { return elementConverter; } else { - if (JdkVersion.isAtLeastJava15()) { - Class elementType = GenericCollectionTypeResolver.getCollectionType(targetClass); - if (elementType != null) { - Class componentType = source.getClass().getComponentType(); - return conversionService.getConversionExecutor(componentType, elementType); - } + Class elementType = GenericCollectionTypeResolver.getCollectionType(targetClass); + if (elementType != null) { + Class componentType = source.getClass().getComponentType(); + return conversionService.getConversionExecutor(componentType, elementType); } return null; } diff --git a/spring-binding/src/main/java/org/springframework/binding/convert/converters/CollectionToCollection.java b/spring-binding/src/main/java/org/springframework/binding/convert/converters/CollectionToCollection.java index f4b81442..a6698e53 100644 --- a/spring-binding/src/main/java/org/springframework/binding/convert/converters/CollectionToCollection.java +++ b/spring-binding/src/main/java/org/springframework/binding/convert/converters/CollectionToCollection.java @@ -90,12 +90,10 @@ public class CollectionToCollection implements Converter { if (elementConverter != null) { return elementConverter; } else { - if (JdkVersion.isAtLeastJava15()) { - Class elementType = GenericCollectionTypeResolver.getCollectionType(targetClass); - if (elementType != null) { - Class componentType = source.getClass().getComponentType(); - return conversionService.getConversionExecutor(componentType, elementType); - } + Class elementType = GenericCollectionTypeResolver.getCollectionType(targetClass); + if (elementType != null) { + Class componentType = source.getClass().getComponentType(); + return conversionService.getConversionExecutor(componentType, elementType); } return null; } diff --git a/spring-binding/src/main/java/org/springframework/binding/convert/converters/ObjectToCollection.java b/spring-binding/src/main/java/org/springframework/binding/convert/converters/ObjectToCollection.java index 6228685a..c8be6534 100644 --- a/spring-binding/src/main/java/org/springframework/binding/convert/converters/ObjectToCollection.java +++ b/spring-binding/src/main/java/org/springframework/binding/convert/converters/ObjectToCollection.java @@ -105,12 +105,10 @@ public class ObjectToCollection implements Converter { if (elementConverter != null) { return elementConverter; } else { - if (JdkVersion.isAtLeastJava15()) { - Class elementType = GenericCollectionTypeResolver.getCollectionType(targetClass); - if (elementType != null) { - Class componentType = source.getClass().getComponentType(); - return conversionService.getConversionExecutor(componentType, elementType); - } + Class elementType = GenericCollectionTypeResolver.getCollectionType(targetClass); + if (elementType != null) { + Class componentType = source.getClass().getComponentType(); + return conversionService.getConversionExecutor(componentType, elementType); } return null; } diff --git a/spring-binding/src/main/java/org/springframework/binding/convert/converters/StringToLabeledEnum.java b/spring-binding/src/main/java/org/springframework/binding/convert/converters/StringToLabeledEnum.java deleted file mode 100644 index 3baab27a..00000000 --- a/spring-binding/src/main/java/org/springframework/binding/convert/converters/StringToLabeledEnum.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2004-2008 the original author or authors. - * - * Licensed 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.springframework.binding.convert.converters; - -import org.springframework.core.enums.LabeledEnum; -import org.springframework.core.enums.LabeledEnumResolver; -import org.springframework.core.enums.StaticLabeledEnumResolver; - -/** - * Converts from a textual representation to a {@link LabeledEnum}. The text should be the enum's label. - * - * @author Keith Donald - */ -public class StringToLabeledEnum extends StringToObject { - - private LabeledEnumResolver labeledEnumResolver = StaticLabeledEnumResolver.instance(); - - public StringToLabeledEnum() { - super(LabeledEnum.class); - } - - protected Object toObject(String string, Class targetClass) throws Exception { - return labeledEnumResolver.getLabeledEnumByLabel(targetClass, string); - } - - protected String toString(Object object) throws Exception { - LabeledEnum labeledEnum = (LabeledEnum) object; - return labeledEnum.getLabel(); - } - -} \ No newline at end of file diff --git a/spring-binding/src/main/java/org/springframework/binding/message/DefaultMessageContext.java b/spring-binding/src/main/java/org/springframework/binding/message/DefaultMessageContext.java index 0be57f58..e33df0c9 100644 --- a/spring-binding/src/main/java/org/springframework/binding/message/DefaultMessageContext.java +++ b/spring-binding/src/main/java/org/springframework/binding/message/DefaultMessageContext.java @@ -26,11 +26,11 @@ import java.util.Map; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.springframework.binding.collection.AbstractCachingMapDecorator; import org.springframework.context.MessageSource; import org.springframework.context.i18n.LocaleContextHolder; import org.springframework.context.support.AbstractMessageSource; import org.springframework.core.style.ToStringCreator; -import org.springframework.util.CachingMapDecorator; /** * The default message context implementation. Uses a {@link MessageSource} to resolve messages that are added by @@ -44,9 +44,12 @@ public class DefaultMessageContext implements StateManageableMessageContext { private MessageSource messageSource; - private Map sourceMessages = new CachingMapDecorator(new LinkedHashMap()) { - protected Object create(Object source) { - return new ArrayList(); + @SuppressWarnings("serial") + private Map> sourceMessages = new AbstractCachingMapDecorator>( + new LinkedHashMap>()) { + + protected List create(Object source) { + return new ArrayList(); } }; diff --git a/spring-binding/src/main/java/org/springframework/binding/method/MethodInvoker.java b/spring-binding/src/main/java/org/springframework/binding/method/MethodInvoker.java index 49f4dd1c..677cb67e 100644 --- a/spring-binding/src/main/java/org/springframework/binding/method/MethodInvoker.java +++ b/spring-binding/src/main/java/org/springframework/binding/method/MethodInvoker.java @@ -17,13 +17,14 @@ package org.springframework.binding.method; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.util.Map; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.springframework.binding.collection.AbstractCachingMapDecorator; import org.springframework.binding.convert.ConversionService; import org.springframework.binding.convert.service.DefaultConversionService; import org.springframework.core.style.StylerUtils; -import org.springframework.util.CachingMapDecorator; /** * A helper for invoking typed methods on arbitrary objects, with support for argument value type conversion from values @@ -44,9 +45,10 @@ public class MethodInvoker { /** * A cache of invoked bean methods, keyed weakly. */ - private CachingMapDecorator methodCache = new CachingMapDecorator(true) { - public Object create(Object key) { - return ((MethodKey) key).getMethod(); + @SuppressWarnings("serial") + private Map methodCache = new AbstractCachingMapDecorator(true) { + public Method create(MethodKey key) { + return key.getMethod(); } }; diff --git a/spring-faces/src/main/java/org/springframework/faces/webflow/SpringBeanWebFlowVariableResolver.java b/spring-faces/src/main/java/org/springframework/faces/webflow/SpringBeanWebFlowVariableResolver.java index 3e410b74..ad7adb8c 100644 --- a/spring-faces/src/main/java/org/springframework/faces/webflow/SpringBeanWebFlowVariableResolver.java +++ b/spring-faces/src/main/java/org/springframework/faces/webflow/SpringBeanWebFlowVariableResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2004-2008 the original author or authors. + * Copyright 2004-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,25 +16,124 @@ package org.springframework.faces.webflow; import javax.faces.context.FacesContext; +import javax.faces.el.EvaluationException; import javax.faces.el.VariableResolver; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.support.StaticListableBeanFactory; -import org.springframework.web.jsf.SpringBeanVariableResolver; +import org.springframework.util.Assert; import org.springframework.webflow.execution.RequestContext; import org.springframework.webflow.execution.RequestContextHolder; /** - * JSF 1.1 variable resolver for Spring Beans accessible to the flow's local bean factory. + * JSF 1.1 {@code VariableResolver} that delegates to the + * flow's local Spring bean factory (for resolving Spring beans) and then + * to the original resolver of the underlying JSF implementation + * (for resolving managed-bean objects as defined in {@code faces-config.xml} + * as well as well-known implicit EL attributes). + * + *

Configure this resolver in your {@code faces-config.xml} file as follows: + * + *

+ * <application>
+ *   ...
+ *   <variable-resolver>org.springframework.faces.webflow.SpringBeanWebFlowVariableResolver</variable-resolver>
+ * </application>
+ * + * All your JSF expressions can then implicitly refer to the names of + * Spring-managed service layer beans, for example in property values of + * JSF-managed beans: + * + *
+ * <managed-bean>
+ *   <managed-bean-name>myJsfManagedBean</managed-bean-name>
+ *   <managed-bean-class>example.MyJsfManagedBean</managed-bean-class>
+ *   <managed-bean-scope>session</managed-bean-scope>
+ *   <managed-property>
+ *     <property-name>mySpringManagedBusinessObject</property-name>
+ *     <value>#{mySpringManagedBusinessObject}</value>
+ *   </managed-property>
+ * </managed-bean>
+ * + * with "mySpringManagedBusinessObject" defined as Spring bean in + * applicationContext.xml: + * + *
+ * <bean id="mySpringManagedBusinessObject" class="example.MySpringManagedBusinessObject">
+ *   ...
+ * </bean>
* * @author Jeremy Grelle */ -public class SpringBeanWebFlowVariableResolver extends SpringBeanVariableResolver { +public class SpringBeanWebFlowVariableResolver extends VariableResolver { private static final BeanFactory EMPTY_BEAN_FACTORY = new StaticListableBeanFactory(); + /** Logger available to subclasses */ + protected final Log logger = LogFactory.getLog(getClass()); + + protected final VariableResolver originalVariableResolver; + + public SpringBeanWebFlowVariableResolver(VariableResolver originalVariableResolver) { - super(originalVariableResolver); + Assert.notNull(originalVariableResolver, "Original JSF VariableResolver must not be null"); + this.originalVariableResolver = originalVariableResolver; + } + + /** + * Return the original JSF VariableResolver that this resolver delegates to. + * Used to resolve standard JSF-managed beans. + */ + protected final VariableResolver getOriginalVariableResolver() { + return this.originalVariableResolver; + } + + + /** + * Try to resolve the variable as Spring bean in the flow local bean factory. + * Then delegate to the original VariableResolver. + */ + @Override + public Object resolveVariable(FacesContext facesContext, String name) throws EvaluationException { + Object bean = resolveSpringBean(facesContext, name); + if (bean != null) { + return bean; + } + Object value = resolveOriginal(facesContext, name); + if (value != null) { + return value; + } + return null; + + } + + /** + * Resolve the attribute via the original JSF VariableResolver. + */ + protected Object resolveOriginal(FacesContext facesContext, String name) { + Object value = getOriginalVariableResolver().resolveVariable(facesContext, name); + if (value != null && logger.isTraceEnabled()) { + logger.trace("Successfully resolved variable '" + name + "' via original VariableResolver"); + } + return value; + } + + /** + * Resolve the attribute as a Spring bean in the ApplicationContext. + */ + protected Object resolveSpringBean(FacesContext facesContext, String name) { + BeanFactory bf = getBeanFactory(facesContext); + if (bf.containsBean(name)) { + if (logger.isTraceEnabled()) { + logger.trace("Successfully resolved variable '" + name + "' in Spring BeanFactory"); + } + return bf.getBean(name); + } + else { + return null; + } } protected BeanFactory getBeanFactory(FacesContext facesContext) { diff --git a/spring-faces/src/main/java/org/springframework/faces/webflow/TreeStructureManager.java b/spring-faces/src/main/java/org/springframework/faces/webflow/TreeStructureManager.java index 853c508c..b14b5c8e 100644 --- a/spring-faces/src/main/java/org/springframework/faces/webflow/TreeStructureManager.java +++ b/spring-faces/src/main/java/org/springframework/faces/webflow/TreeStructureManager.java @@ -94,7 +94,7 @@ class TreeStructureManager { String compId = treeStructComp.getComponentId(); UIComponent component; try { - component = (UIComponent) BeanUtils.instantiateClass(ClassUtils.forName(compClass)); + component = (UIComponent) BeanUtils.instantiateClass(ClassUtils.forName(compClass, TreeStructureManager.class.getClassLoader())); } catch (Exception ex) { throw new FacesException("Could not restore the component tree structure.", ex); } diff --git a/spring-webflow/src/main/java/org/springframework/webflow/action/DispatchMethodInvoker.java b/spring-webflow/src/main/java/org/springframework/webflow/action/DispatchMethodInvoker.java index 9f0278fb..b1e9f969 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/action/DispatchMethodInvoker.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/action/DispatchMethodInvoker.java @@ -19,10 +19,10 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Map; +import org.springframework.binding.collection.AbstractCachingMapDecorator; import org.springframework.binding.method.InvalidMethodKeyException; import org.springframework.binding.method.MethodKey; import org.springframework.util.Assert; -import org.springframework.util.CachingMapDecorator; /** * Invoker and cache for dispatch methods that all share the same target object. The dispatch methods typically share @@ -46,8 +46,9 @@ class DispatchMethodInvoker { /** * The resolved method cache. */ - private Map methodCache = new CachingMapDecorator() { - public Object create(Object key) { + @SuppressWarnings("serial") + private Map methodCache = new AbstractCachingMapDecorator() { + public Method create(String key) { String methodName = (String) key; try { return new MethodKey(target.getClass(), methodName, parameterTypes).getMethod(); diff --git a/spring-webflow/src/main/java/org/springframework/webflow/action/ResultObjectBasedEventFactory.java b/spring-webflow/src/main/java/org/springframework/webflow/action/ResultObjectBasedEventFactory.java index a4df2342..f296f2e0 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/action/ResultObjectBasedEventFactory.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/action/ResultObjectBasedEventFactory.java @@ -16,7 +16,6 @@ package org.springframework.webflow.action; import org.springframework.core.JdkVersion; -import org.springframework.core.enums.LabeledEnum; import org.springframework.util.StringUtils; import org.springframework.webflow.execution.Event; import org.springframework.webflow.execution.RequestContext; @@ -75,9 +74,6 @@ public class ResultObjectBasedEventFactory extends EventFactorySupport implement return event(source, getNullEventId()); } else if (isBoolean(resultObject.getClass())) { return event(source, ((Boolean) resultObject).booleanValue()); - } else if (isLabeledEnum(resultObject.getClass())) { - String resultId = ((LabeledEnum) resultObject).getLabel(); - return event(source, resultId, getResultAttributeName(), resultObject); } else if (isJdk5Enum(resultObject.getClass())) { String eventId = EnumUtils.getEnumName(resultObject); return event(source, eventId, getResultAttributeName(), resultObject); @@ -101,7 +97,7 @@ public class ResultObjectBasedEventFactory extends EventFactorySupport implement * Check whether or not given type is mapped to a corresponding event using special mapping rules. */ public boolean isMappedValueType(Class type) { - return isBoolean(type) || isLabeledEnum(type) || isJdk5Enum(type) || isString(type) || isEvent(type); + return isBoolean(type) || isJdk5Enum(type) || isString(type) || isEvent(type); } // internal helpers to determine the 'type' of a class @@ -110,10 +106,6 @@ public class ResultObjectBasedEventFactory extends EventFactorySupport implement return Boolean.class.equals(type) || boolean.class.equals(type); } - private boolean isLabeledEnum(Class type) { - return LabeledEnum.class.isAssignableFrom(type); - } - private boolean isJdk5Enum(Class type) { if (JdkVersion.getMajorJavaVersion() >= JdkVersion.JAVA_15) { return EnumUtils.isEnum(type); diff --git a/spring-webflow/src/main/java/org/springframework/webflow/conversation/impl/ConversationContainer.java b/spring-webflow/src/main/java/org/springframework/webflow/conversation/impl/ConversationContainer.java index 2a52b777..6bf61309 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/conversation/impl/ConversationContainer.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/conversation/impl/ConversationContainer.java @@ -111,11 +111,7 @@ class ConversationContainer implements Serializable { } private ConversationId nextId() { - if (JdkVersion.isAtLeastJava15()) { - return new SimpleConversationId(Integer.valueOf(++conversationIdSequence)); - } else { - return new SimpleConversationId(new Integer(++conversationIdSequence)); - } + return new SimpleConversationId(Integer.valueOf(++conversationIdSequence)); } /** diff --git a/spring-webflow/src/main/java/org/springframework/webflow/execution/repository/impl/SimpleFlowExecutionSnapshotGroup.java b/spring-webflow/src/main/java/org/springframework/webflow/execution/repository/impl/SimpleFlowExecutionSnapshotGroup.java index 642c7f74..ba6ee553 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/execution/repository/impl/SimpleFlowExecutionSnapshotGroup.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/execution/repository/impl/SimpleFlowExecutionSnapshotGroup.java @@ -109,12 +109,7 @@ class SimpleFlowExecutionSnapshotGroup implements FlowExecutionSnapshotGroup, Se } public Serializable nextSnapshotId() { - Integer nextSnapshotId; - if (JdkVersion.isAtLeastJava15()) { - nextSnapshotId = Integer.valueOf(snapshotIdSequence); - } else { - nextSnapshotId = new Integer(snapshotIdSequence); - } + Integer nextSnapshotId = Integer.valueOf(snapshotIdSequence); snapshotIdSequence++; return nextSnapshotId; } diff --git a/spring-webflow/src/main/java/org/springframework/webflow/mvc/builder/FlowResourceFlowViewResolver.java b/spring-webflow/src/main/java/org/springframework/webflow/mvc/builder/FlowResourceFlowViewResolver.java index da0f9923..ca202b59 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/mvc/builder/FlowResourceFlowViewResolver.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/mvc/builder/FlowResourceFlowViewResolver.java @@ -35,7 +35,8 @@ import org.springframework.webflow.mvc.view.FlowViewResolver; */ public class FlowResourceFlowViewResolver implements FlowViewResolver { - private static final boolean JSTL_PRESENT = ClassUtils.isPresent("javax.servlet.jsp.jstl.fmt.LocalizationContext"); + private static final boolean JSTL_PRESENT = ClassUtils.isPresent( + "javax.servlet.jsp.jstl.fmt.LocalizationContext", FlowResourceFlowViewResolver.class.getClassLoader()); private String defaultViewSuffix = ".jsp"; diff --git a/spring-webflow/src/main/java/org/springframework/webflow/mvc/builder/MvcEnvironment.java b/spring-webflow/src/main/java/org/springframework/webflow/mvc/builder/MvcEnvironment.java index a2de6d0c..9956f586 100644 --- a/spring-webflow/src/main/java/org/springframework/webflow/mvc/builder/MvcEnvironment.java +++ b/spring-webflow/src/main/java/org/springframework/webflow/mvc/builder/MvcEnvironment.java @@ -43,7 +43,8 @@ public enum MvcEnvironment { * @return the web environment the context is running in, or null if not running in a web environment */ public static MvcEnvironment environmentFor(ApplicationContext applicationContext) { - if (ClassUtils.isPresent("javax.portlet.PortletContext") && isPortletApplicationContext(applicationContext)) { + if (ClassUtils.isPresent("javax.portlet.PortletContext", + MvcEnvironment.class.getClassLoader()) && isPortletApplicationContext(applicationContext)) { return MvcEnvironment.PORTLET; } else if (applicationContext instanceof WebApplicationContext) { return MvcEnvironment.SERVLET; @@ -53,8 +54,8 @@ public enum MvcEnvironment { } private static boolean isPortletApplicationContext(ApplicationContext applicationContext) { - return ClassUtils.isPresent("org.springframework.web.portlet.context.ConfigurablePortletApplicationContext") - && applicationContext instanceof ConfigurablePortletApplicationContext; + return ClassUtils.isPresent("org.springframework.web.portlet.context.ConfigurablePortletApplicationContext", + MvcEnvironment.class.getClassLoader()) && applicationContext instanceof ConfigurablePortletApplicationContext; } } diff --git a/spring-webflow/src/test/java/org/springframework/webflow/persistence/JpaFlowExecutionListenerTests.java b/spring-webflow/src/test/java/org/springframework/webflow/persistence/JpaFlowExecutionListenerTests.java index 612b1c0b..9bcf4486 100644 --- a/spring-webflow/src/test/java/org/springframework/webflow/persistence/JpaFlowExecutionListenerTests.java +++ b/spring-webflow/src/test/java/org/springframework/webflow/persistence/JpaFlowExecutionListenerTests.java @@ -1,19 +1,21 @@ package org.springframework.webflow.persistence; +import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; import javax.sql.DataSource; import junit.framework.TestCase; import org.springframework.core.io.ClassPathResource; +import org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.datasource.DriverManagerDataSource; import org.springframework.jdbc.datasource.init.DataSourceInitializer; import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator; -import org.springframework.orm.jpa.JpaTemplate; +import org.springframework.orm.jpa.EntityManagerFactoryUtils; import org.springframework.orm.jpa.JpaTransactionManager; import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; -import org.springframework.orm.jpa.vendor.OpenJpaVendorAdapter; +import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter; import org.springframework.transaction.support.TransactionSynchronizationManager; import org.springframework.webflow.engine.EndState; import org.springframework.webflow.execution.FlowExecutionException; @@ -28,8 +30,6 @@ public class JpaFlowExecutionListenerTests extends TestCase { private JdbcTemplate jdbcTemplate; - private JpaTemplate jpaTemplate; - public void testTemp() { } @@ -41,7 +41,6 @@ public class JpaFlowExecutionListenerTests extends TestCase { entityManagerFactory = getEntityManagerFactory(dataSource); JpaTransactionManager tm = new JpaTransactionManager(entityManagerFactory); jpaListener = new JpaFlowExecutionListener(entityManagerFactory, tm); - jpaTemplate = new JpaTemplate(entityManagerFactory); } public void testFlowNotAPersistenceContext() { @@ -61,7 +60,8 @@ public class JpaFlowExecutionListenerTests extends TestCase { assertSessionBound(); TestBean bean = new TestBean(1, "Keith Donald"); - jpaTemplate.persist(bean); + EntityManager em = EntityManagerFactoryUtils.getTransactionalEntityManager(entityManagerFactory); + em.persist(bean); assertEquals("Table should still only have one row", 1, jdbcTemplate.queryForInt("select count(*) from T_BEAN")); EndState endState = new EndState(flowSession.getDefinitionInternal(), "success"); @@ -84,14 +84,17 @@ public class JpaFlowExecutionListenerTests extends TestCase { assertSessionBound(); TestBean bean1 = new TestBean(1, "Keith Donald"); - jpaTemplate.persist(bean1); + + EntityManager em = EntityManagerFactoryUtils.getTransactionalEntityManager(entityManagerFactory); + em.persist(bean1); assertEquals("Table should still only have one row", 1, jdbcTemplate.queryForInt("select count(*) from T_BEAN")); jpaListener.paused(context); assertSessionNotBound(); jpaListener.resuming(context); TestBean bean2 = new TestBean(2, "Keith Donald"); - jpaTemplate.persist(bean2); + em = EntityManagerFactoryUtils.getTransactionalEntityManager(entityManagerFactory); + em.persist(bean2); assertEquals("Table should still only have one row", 1, jdbcTemplate.queryForInt("select count(*) from T_BEAN")); assertSessionBound(); @@ -116,7 +119,8 @@ public class JpaFlowExecutionListenerTests extends TestCase { assertSessionBound(); TestBean bean = new TestBean(1, "Keith Donald"); - jpaTemplate.persist(bean); + EntityManager emf = EntityManagerFactoryUtils.getTransactionalEntityManager(entityManagerFactory); + emf.persist(bean); assertEquals("Table should still only have one row", 1, jdbcTemplate.queryForInt("select count(*) from T_BEAN")); EndState endState = new EndState(flowSession.getDefinitionInternal(), "cancel"); @@ -157,7 +161,8 @@ public class JpaFlowExecutionListenerTests extends TestCase { assertSessionBound(); TestBean bean = new TestBean(1, "Keith Donald"); - jpaTemplate.persist(bean); + EntityManager emf = EntityManagerFactoryUtils.getTransactionalEntityManager(entityManagerFactory); + emf.persist(bean); assertEquals("Table should still only have one row", 1, jdbcTemplate.queryForInt("select count(*) from T_BEAN")); jpaListener.exceptionThrown(context, new FlowExecutionException("bla", "bla", "bla")); assertEquals("Table should still only have one row", 1, jdbcTemplate.queryForInt("select count(*) from T_BEAN")); @@ -197,8 +202,7 @@ public class JpaFlowExecutionListenerTests extends TestCase { LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean(); factory.setDataSource(dataSource); factory.setPersistenceXmlLocation("classpath:org/springframework/webflow/persistence/persistence.xml"); - OpenJpaVendorAdapter openJpa = new OpenJpaVendorAdapter(); - factory.setJpaVendorAdapter(openJpa); + factory.setJpaVendorAdapter(new HibernateJpaVendorAdapter()); factory.afterPropertiesSet(); return factory.getObject(); } diff --git a/spring-webflow/src/test/java/org/springframework/webflow/persistence/JpaFlowManagedPersistenceIntegrationTests.java b/spring-webflow/src/test/java/org/springframework/webflow/persistence/JpaFlowManagedPersistenceIntegrationTests.java index 055c65e4..14e1b6a5 100644 --- a/spring-webflow/src/test/java/org/springframework/webflow/persistence/JpaFlowManagedPersistenceIntegrationTests.java +++ b/spring-webflow/src/test/java/org/springframework/webflow/persistence/JpaFlowManagedPersistenceIntegrationTests.java @@ -6,7 +6,7 @@ import javax.sql.DataSource; import org.springframework.orm.jpa.JpaTransactionManager; import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; -import org.springframework.orm.jpa.vendor.OpenJpaVendorAdapter; +import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter; import org.springframework.transaction.support.TransactionSynchronizationManager; import org.springframework.webflow.execution.Action; import org.springframework.webflow.execution.Event; @@ -31,7 +31,7 @@ public class JpaFlowManagedPersistenceIntegrationTests extends AbstractFlowManag public Event execute(RequestContext context) throws Exception { assertSessionBound(); EntityManager em = (EntityManager) context.getFlowScope().get("persistenceContext"); - TestBean bean = (TestBean) em.getReference(TestBean.class, new Integer(0)); + TestBean bean = (TestBean) em.getReference(TestBean.class, new Long(0)); bean.incrementCount(); assertNotNull(bean); return new Event(this, "success"); @@ -46,7 +46,7 @@ public class JpaFlowManagedPersistenceIntegrationTests extends AbstractFlowManag public void execute(RequestContext context, int expected) throws Exception { assertSessionBound(); EntityManager em = (EntityManager) context.getFlowScope().get("persistenceContext"); - TestBean bean = (TestBean) em.getReference(TestBean.class, new Integer(0)); + TestBean bean = (TestBean) em.getReference(TestBean.class, new Long(0)); assertEquals(expected, bean.getCount()); } }; @@ -63,8 +63,7 @@ public class JpaFlowManagedPersistenceIntegrationTests extends AbstractFlowManag LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean(); factory.setDataSource(dataSource); factory.setPersistenceXmlLocation("classpath:org/springframework/webflow/persistence/persistence.xml"); - OpenJpaVendorAdapter openJpa = new OpenJpaVendorAdapter(); - factory.setJpaVendorAdapter(openJpa); + factory.setJpaVendorAdapter(new HibernateJpaVendorAdapter()); factory.afterPropertiesSet(); return factory.getObject(); } diff --git a/spring-webflow/src/test/java/org/springframework/webflow/persistence/JpaPersistenceContextPropagationTests.java b/spring-webflow/src/test/java/org/springframework/webflow/persistence/JpaPersistenceContextPropagationTests.java index 588d7920..baa7993b 100644 --- a/spring-webflow/src/test/java/org/springframework/webflow/persistence/JpaPersistenceContextPropagationTests.java +++ b/spring-webflow/src/test/java/org/springframework/webflow/persistence/JpaPersistenceContextPropagationTests.java @@ -1,11 +1,14 @@ package org.springframework.webflow.persistence; +import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; import javax.sql.DataSource; -import org.springframework.orm.jpa.JpaTemplate; +import org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver; +import org.springframework.orm.jpa.EntityManagerFactoryUtils; import org.springframework.orm.jpa.JpaTransactionManager; import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; +import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter; import org.springframework.orm.jpa.vendor.OpenJpaVendorAdapter; import org.springframework.transaction.support.TransactionSynchronizationManager; import org.springframework.webflow.execution.FlowExecutionListener; @@ -16,8 +19,6 @@ public class JpaPersistenceContextPropagationTests extends AbstractPersistenceCo private JpaFlowExecutionListener executionListener; - private JpaTemplate jpaTemplate; - private int rowCount; @Override @@ -25,7 +26,6 @@ public class JpaPersistenceContextPropagationTests extends AbstractPersistenceCo entityManagerFactory = getEntityManagerFactory(dataSource); JpaTransactionManager tm = new JpaTransactionManager(entityManagerFactory); executionListener = new JpaFlowExecutionListener(entityManagerFactory, tm); - jpaTemplate = new JpaTemplate(entityManagerFactory); rowCount = 1; } @@ -47,7 +47,8 @@ public class JpaPersistenceContextPropagationTests extends AbstractPersistenceCo @Override protected void assertCommitState(boolean insertRow, boolean isCommited) { if (insertRow) { - jpaTemplate.persist(new TestBean(rowCount++, "Keith Donald")); + EntityManager em = EntityManagerFactoryUtils.getTransactionalEntityManager(entityManagerFactory); + em.persist(new TestBean(rowCount++, "Keith Donald")); } if (!isCommited) { assertEquals("Nothing should be committed yet", 1, @@ -62,8 +63,7 @@ public class JpaPersistenceContextPropagationTests extends AbstractPersistenceCo LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean(); factory.setDataSource(dataSource); factory.setPersistenceXmlLocation("classpath:org/springframework/webflow/persistence/persistence.xml"); - OpenJpaVendorAdapter openJpa = new OpenJpaVendorAdapter(); - factory.setJpaVendorAdapter(openJpa); + factory.setJpaVendorAdapter(new HibernateJpaVendorAdapter()); factory.afterPropertiesSet(); return factory.getObject(); } diff --git a/spring-webflow/src/test/java/org/springframework/webflow/persistence/TestBean.hbm.xml b/spring-webflow/src/test/java/org/springframework/webflow/persistence/TestBean.hbm.xml index 2b5a50fb..4660b23a 100644 --- a/spring-webflow/src/test/java/org/springframework/webflow/persistence/TestBean.hbm.xml +++ b/spring-webflow/src/test/java/org/springframework/webflow/persistence/TestBean.hbm.xml @@ -5,7 +5,7 @@ - + diff --git a/spring-webflow/src/test/java/org/springframework/webflow/persistence/TestBean.java b/spring-webflow/src/test/java/org/springframework/webflow/persistence/TestBean.java index fd6caba0..f5d36e33 100644 --- a/spring-webflow/src/test/java/org/springframework/webflow/persistence/TestBean.java +++ b/spring-webflow/src/test/java/org/springframework/webflow/persistence/TestBean.java @@ -69,6 +69,10 @@ public class TestBean { return count; } + public void setCount(int count) { + this.count = count; + } + public void incrementCount() { this.count++; } diff --git a/spring-webflow/src/test/java/org/springframework/webflow/persistence/persistence.xml b/spring-webflow/src/test/java/org/springframework/webflow/persistence/persistence.xml index b3a5d2af..88c49edb 100644 --- a/spring-webflow/src/test/java/org/springframework/webflow/persistence/persistence.xml +++ b/spring-webflow/src/test/java/org/springframework/webflow/persistence/persistence.xml @@ -11,6 +11,10 @@ true + + + +