diff --git a/README.md b/README.md index 5aa077745ea..d4549d3a5fb 100644 --- a/README.md +++ b/README.md @@ -20,8 +20,8 @@ Instructions on via Maven and other build systems are available via the project wiki. ## Documentation -See the current [Javadoc](http://static.springsource.org/spring/docs/current/javadoc-api) -and [Reference docs](http://static.springsource.org/spring/docs/current/spring-framework-reference). +See the current [Javadoc](http://static.springsource.org/spring-framework/docs/current/api) +and [Reference docs](http://static.springsource.org/spring-framework/docs/current/reference). ## Getting support Check out the [Spring forums](http://forum.springsource.org) and the diff --git a/build-spring-framework/resources/changelog.txt b/build-spring-framework/resources/changelog.txt index 2a723d7b2cf..eff2824e93e 100644 --- a/build-spring-framework/resources/changelog.txt +++ b/build-spring-framework/resources/changelog.txt @@ -6,7 +6,7 @@ http://www.springsource.org Changes in version 3.1.1 (2012-02-06) ------------------------------------- -* official support for Hibernate 4.0 GA +* official support for Hibernate 4.0 GA (as released in the meantime) * JBossNativeJdbcExtractor is compatible with JBoss AS 7 as well * context:property-placeholder's "file-encoding" attribute value is being applied correctly * DataBinder correctly handles ParseException from Formatter for String->String case @@ -19,8 +19,14 @@ Changes in version 3.1.1 (2012-02-06) * Hibernate 4 LocalSessionFactoryBean implements PersistenceExceptionTranslator interface as well * Hibernate 4 LocalSessionFactoryBean does not insist on a "dataSource" reference being set * added "entityInterceptor" property to Hibernate 4 LocalSessionFactoryBean +* added "getConfiguration" accessor to Hibernate 4 LocalSessionFactoryBean * corrected fix for QuartzJobBean to work with Quartz 2.0/2.1 - +* JMS CachingConnectionFactory never caches consumers for temporary queues and topics +* JMS SimpleMessageListenerContainer silently falls back to lazy registration of consumers +* fix regresion in UriUtils +* allow adding flash attributes in methods with a ModelAndView return value +* preserve quotes in MediaType parameters +* make flash attributes available in the model of ParameterizableViewController and UrlFilenameViewController Changes in version 3.1 GA (2011-12-12) -------------------------------------- diff --git a/org.springframework.jms/src/main/java/org/springframework/jms/connection/CachingConnectionFactory.java b/org.springframework.jms/src/main/java/org/springframework/jms/connection/CachingConnectionFactory.java index d2b2249e08b..35456ea4581 100644 --- a/org.springframework.jms/src/main/java/org/springframework/jms/connection/CachingConnectionFactory.java +++ b/org.springframework.jms/src/main/java/org/springframework/jms/connection/CachingConnectionFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2012 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. @@ -34,6 +34,8 @@ import javax.jms.MessageConsumer; import javax.jms.MessageProducer; import javax.jms.QueueSession; import javax.jms.Session; +import javax.jms.TemporaryQueue; +import javax.jms.TemporaryTopic; import javax.jms.Topic; import javax.jms.TopicSession; @@ -323,16 +325,18 @@ public class CachingConnectionFactory extends SingleConnectionFactory { // let raw JMS invocation throw an exception if Destination (i.e. args[0]) is null if ((methodName.equals("createConsumer") || methodName.equals("createReceiver") || methodName.equals("createSubscriber"))) { - if (args[0] != null) { - return getCachedConsumer((Destination) args[0], + Destination dest = (Destination) args[0]; + if (dest != null && !(dest instanceof TemporaryQueue || dest instanceof TemporaryTopic)) { + return getCachedConsumer(dest, (args.length > 1 ? (String) args[1] : null), (args.length > 2 && (Boolean) args[2]), null); } } else if (methodName.equals("createDurableSubscriber")) { - if (args[0] != null) { - return getCachedConsumer((Destination) args[0], + Destination dest = (Destination) args[0]; + if (dest != null) { + return getCachedConsumer(dest, (args.length > 2 ? (String) args[2] : null), (args.length > 3 && (Boolean) args[3]), (String) args[1]); diff --git a/org.springframework.jms/src/main/java/org/springframework/jms/listener/AbstractJmsListeningContainer.java b/org.springframework.jms/src/main/java/org/springframework/jms/listener/AbstractJmsListeningContainer.java index d6303305fe0..6a2dc5e0d43 100644 --- a/org.springframework.jms/src/main/java/org/springframework/jms/listener/AbstractJmsListeningContainer.java +++ b/org.springframework.jms/src/main/java/org/springframework/jms/listener/AbstractJmsListeningContainer.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2012 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. @@ -151,6 +151,7 @@ public abstract class AbstractJmsListeningContainer extends JmsDestinationAccess /** * Delegates to {@link #validateConfiguration()} and {@link #initialize()}. */ + @Override public void afterPropertiesSet() { super.afterPropertiesSet(); validateConfiguration(); diff --git a/org.springframework.jms/src/main/java/org/springframework/jms/listener/AbstractMessageListenerContainer.java b/org.springframework.jms/src/main/java/org/springframework/jms/listener/AbstractMessageListenerContainer.java index 21efc557e7b..b735a6632f0 100644 --- a/org.springframework.jms/src/main/java/org/springframework/jms/listener/AbstractMessageListenerContainer.java +++ b/org.springframework.jms/src/main/java/org/springframework/jms/listener/AbstractMessageListenerContainer.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2012 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. @@ -410,6 +410,7 @@ public abstract class AbstractMessageListenerContainer extends AbstractJmsListen return this.acceptMessagesWhileStopping; } + @Override protected void validateConfiguration() { if (this.destination == null) { throw new IllegalArgumentException("Property 'destination' or 'destinationName' is required"); diff --git a/org.springframework.jms/src/main/java/org/springframework/jms/listener/AbstractPollingMessageListenerContainer.java b/org.springframework.jms/src/main/java/org/springframework/jms/listener/AbstractPollingMessageListenerContainer.java index ac7e0e06a32..566e3f9de99 100644 --- a/org.springframework.jms/src/main/java/org/springframework/jms/listener/AbstractPollingMessageListenerContainer.java +++ b/org.springframework.jms/src/main/java/org/springframework/jms/listener/AbstractPollingMessageListenerContainer.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2010 the original author or authors. + * Copyright 2002-2012 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. @@ -75,8 +75,7 @@ import org.springframework.transaction.support.TransactionSynchronizationUtils; * @see #receiveAndExecute * @see #setTransactionManager */ -public abstract class AbstractPollingMessageListenerContainer extends AbstractMessageListenerContainer - implements BeanNameAware { +public abstract class AbstractPollingMessageListenerContainer extends AbstractMessageListenerContainer { /** * The default receive timeout: 1000 ms = 1 second. @@ -100,6 +99,7 @@ public abstract class AbstractPollingMessageListenerContainer extends AbstractMe private volatile Boolean commitAfterNoMessageReceived; + @Override public void setSessionTransacted(boolean sessionTransacted) { super.setSessionTransacted(sessionTransacted); this.sessionTransactedCalled = true; @@ -188,6 +188,7 @@ public abstract class AbstractPollingMessageListenerContainer extends AbstractMe } + @Override public void initialize() { // Set sessionTransacted=true in case of a non-JTA transaction manager. if (!this.sessionTransactedCalled && @@ -374,6 +375,7 @@ public abstract class AbstractPollingMessageListenerContainer extends AbstractMe * container's "sessionTransacted" flag being set to "true". * @see org.springframework.jms.connection.JmsResourceHolder */ + @Override protected boolean isSessionLocallyTransacted(Session session) { if (!super.isSessionLocallyTransacted(session)) { return false; diff --git a/org.springframework.jms/src/main/java/org/springframework/jms/listener/DefaultMessageListenerContainer.java b/org.springframework.jms/src/main/java/org/springframework/jms/listener/DefaultMessageListenerContainer.java index 7e12561fe6a..a2d8dfabfdb 100644 --- a/org.springframework.jms/src/main/java/org/springframework/jms/listener/DefaultMessageListenerContainer.java +++ b/org.springframework.jms/src/main/java/org/springframework/jms/listener/DefaultMessageListenerContainer.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2012 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. @@ -470,6 +470,7 @@ public class DefaultMessageListenerContainer extends AbstractPollingMessageListe } } + @Override protected void validateConfiguration() { super.validateConfiguration(); synchronized (this.lifecycleMonitor) { @@ -484,6 +485,7 @@ public class DefaultMessageListenerContainer extends AbstractPollingMessageListe // Implementation of AbstractMessageListenerContainer's template methods //------------------------------------------------------------------------- + @Override public void initialize() { // Adapt default cache level. if (this.cacheLevel == CACHE_AUTO) { @@ -516,6 +518,7 @@ public class DefaultMessageListenerContainer extends AbstractPollingMessageListe * @see #scheduleNewInvoker * @see #setTaskExecutor */ + @Override protected void doInitialize() throws JMSException { synchronized (this.lifecycleMonitor) { for (int i = 0; i < this.concurrentConsumers; i++) { @@ -527,6 +530,7 @@ public class DefaultMessageListenerContainer extends AbstractPollingMessageListe /** * Destroy the registered JMS Sessions and associated MessageConsumers. */ + @Override protected void doShutdown() throws JMSException { logger.debug("Waiting for shutdown of message listener invokers"); try { @@ -549,6 +553,7 @@ public class DefaultMessageListenerContainer extends AbstractPollingMessageListe /** * Overridden to reset the stop callback, if any. */ + @Override public void start() throws JmsException { synchronized (this.lifecycleMonitor) { this.stopCallback = null; @@ -658,6 +663,7 @@ public class DefaultMessageListenerContainer extends AbstractPollingMessageListe * @see #setCacheLevel * @see #CACHE_CONNECTION */ + @Override protected final boolean sharedConnectionEnabled() { return (getCacheLevel() >= CACHE_CONNECTION); } @@ -666,6 +672,7 @@ public class DefaultMessageListenerContainer extends AbstractPollingMessageListe * Re-executes the given task via this listener container's TaskExecutor. * @see #setTaskExecutor */ + @Override protected void doRescheduleTask(Object task) { this.taskExecutor.execute((Runnable) task); } @@ -674,6 +681,7 @@ public class DefaultMessageListenerContainer extends AbstractPollingMessageListe * Tries scheduling a new invoker, since we know messages are coming in... * @see #scheduleNewInvokerIfAppropriate() */ + @Override protected void messageReceived(Object invoker, Session session) { ((AsyncMessageListenerInvoker) invoker).setIdle(false); scheduleNewInvokerIfAppropriate(); @@ -682,6 +690,7 @@ public class DefaultMessageListenerContainer extends AbstractPollingMessageListe /** * Marks the affected invoker as idle. */ + @Override protected void noMessageReceived(Object invoker, Session session) { ((AsyncMessageListenerInvoker) invoker).setIdle(true); } @@ -745,6 +754,7 @@ public class DefaultMessageListenerContainer extends AbstractPollingMessageListe * asynchronous invokers to establish the shared Connection on first access. * @see #refreshConnectionUntilSuccessful() */ + @Override protected void establishSharedConnection() { try { super.establishSharedConnection(); @@ -760,6 +770,7 @@ public class DefaultMessageListenerContainer extends AbstractPollingMessageListe * Connection.start(), relying on listeners to perform * appropriate recovery. */ + @Override protected void startSharedConnection() { try { super.startSharedConnection(); @@ -774,6 +785,7 @@ public class DefaultMessageListenerContainer extends AbstractPollingMessageListe * Connection.stop(), relying on listeners to perform * appropriate recovery after a restart. */ + @Override protected void stopSharedConnection() { try { super.stopSharedConnection(); diff --git a/org.springframework.jms/src/main/java/org/springframework/jms/listener/SimpleMessageListenerContainer.java b/org.springframework.jms/src/main/java/org/springframework/jms/listener/SimpleMessageListenerContainer.java index a47d1f99e21..82d953564b1 100644 --- a/org.springframework.jms/src/main/java/org/springframework/jms/listener/SimpleMessageListenerContainer.java +++ b/org.springframework.jms/src/main/java/org/springframework/jms/listener/SimpleMessageListenerContainer.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2010 the original author or authors. + * Copyright 2002-2012 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. @@ -62,6 +62,8 @@ public class SimpleMessageListenerContainer extends AbstractMessageListenerConta private boolean pubSubNoLocal = false; + private boolean connectLazily = false; + private int concurrentConsumers = 1; private Executor taskExecutor; @@ -89,6 +91,20 @@ public class SimpleMessageListenerContainer extends AbstractMessageListenerConta return this.pubSubNoLocal; } + /** + * Specify whether to connect lazily, i.e. whether to establish the JMS Connection + * and the corresponding Sessions and MessageConsumers as late as possible - + * in the start phase of this container. + *

Default is "false": connecting early, i.e. during the bean initialization phase. + * Set this flag to "true" in order to switch to lazy connecting if your target broker + * is likely to not have started up yet and you prefer to not even try a connection. + * @see #start() + * @see #initialize() + */ + public void setConnectLazily(boolean connectLazily) { + this.connectLazily = connectLazily; + } + /** * Specify concurrency limits via a "lower-upper" String, e.g. "5-10", or a simple * upper limit String, e.g. "10". @@ -159,6 +175,7 @@ public class SimpleMessageListenerContainer extends AbstractMessageListenerConta this.taskExecutor = taskExecutor; } + @Override protected void validateConfiguration() { super.validateConfiguration(); if (isSubscriptionDurable() && this.concurrentConsumers != 1) { @@ -174,6 +191,7 @@ public class SimpleMessageListenerContainer extends AbstractMessageListenerConta /** * Always use a shared JMS Connection. */ + @Override protected final boolean sharedConnectionEnabled() { return true; } @@ -183,15 +201,25 @@ public class SimpleMessageListenerContainer extends AbstractMessageListenerConta * in the form of a JMS Session plus associated MessageConsumer. * @see #createListenerConsumer */ + @Override protected void doInitialize() throws JMSException { - establishSharedConnection(); - initializeConsumers(); + if (!this.connectLazily) { + try { + establishSharedConnection(); + } + catch (JMSException ex) { + logger.debug("Could not connect on initialization - registering message consumers lazily", ex); + return; + } + initializeConsumers(); + } } /** * Re-initializes this container's JMS message consumers, * if not initialized already. */ + @Override protected void doStart() throws JMSException { super.doStart(); initializeConsumers(); @@ -200,6 +228,7 @@ public class SimpleMessageListenerContainer extends AbstractMessageListenerConta /** * Registers this listener container as JMS ExceptionListener on the shared connection. */ + @Override protected void prepareSharedConnection(Connection connection) throws JMSException { super.prepareSharedConnection(connection); connection.setExceptionListener(this); @@ -320,6 +349,7 @@ public class SimpleMessageListenerContainer extends AbstractMessageListenerConta /** * Destroy the registered JMS Sessions and associated MessageConsumers. */ + @Override protected void doShutdown() throws JMSException { logger.debug("Closing JMS MessageConsumers"); for (MessageConsumer consumer : this.consumers) { diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/LocalSessionFactoryBean.java b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/LocalSessionFactoryBean.java index 0cb0d7335f1..887ceaed812 100644 --- a/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/LocalSessionFactoryBean.java +++ b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate3/LocalSessionFactoryBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2010 the original author or authors. + * Copyright 2002-2012 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. @@ -787,7 +787,7 @@ public class LocalSessionFactoryBean extends AbstractSessionFactoryBean implemen configTimeTransactionManagerHolder.remove(); } if (this.cacheRegionFactory != null) { - configTimeCacheProviderHolder.remove(); + configTimeRegionFactoryHolder.remove(); } if (this.cacheProvider != null) { configTimeCacheProviderHolder.remove(); @@ -862,7 +862,7 @@ public class LocalSessionFactoryBean extends AbstractSessionFactoryBean implemen /** * Return the Configuration object used to build the SessionFactory. - * Allows access to configuration metadata stored there (rarely needed). + * Allows for access to configuration metadata stored there (rarely needed). * @throws IllegalStateException if the Configuration object has not been initialized yet */ public final Configuration getConfiguration() { diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/hibernate4/LocalSessionFactoryBean.java b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate4/LocalSessionFactoryBean.java index 50e161d8f84..7c2ecf1e8b7 100644 --- a/org.springframework.orm/src/main/java/org/springframework/orm/hibernate4/LocalSessionFactoryBean.java +++ b/org.springframework.orm/src/main/java/org/springframework/orm/hibernate4/LocalSessionFactoryBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2012 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. @@ -23,6 +23,7 @@ import javax.sql.DataSource; import org.hibernate.Interceptor; import org.hibernate.SessionFactory; +import org.hibernate.cfg.Configuration; import org.hibernate.cfg.NamingStrategy; import org.springframework.beans.factory.DisposableBean; @@ -37,16 +38,15 @@ import org.springframework.core.io.support.ResourcePatternResolver; import org.springframework.core.io.support.ResourcePatternUtils; /** - * {@link org.springframework.beans.factory.FactoryBean} that creates a - * Hibernate {@link org.hibernate.SessionFactory}. This is the usual way to - * set up a shared Hibernate SessionFactory in a Spring application context; - * the SessionFactory can then be passed to Hibernate-based DAOs via - * dependency injection. + * {@link org.springframework.beans.factory.FactoryBean} that creates a Hibernate + * {@link org.hibernate.SessionFactory}. This is the usual way to set up a shared + * Hibernate SessionFactory in a Spring application context; the SessionFactory can + * then be passed to Hibernate-based data access objects via dependency injection. * - *

NOTE: This variant of LocalSessionFactoryBean requires Hibernate 4.0 - * or higher. It is similar in role to the same-named class in the orm.hibernate3 - * package. However, in practice, it is closer to AnnotationSessionFactoryBean - * since its core purpose is to bootstrap a SessionFactory from annotation scanning. + *

NOTE: This variant of LocalSessionFactoryBean requires Hibernate 4.0 or higher. + * It is similar in role to the same-named class in the orm.hibernate3 package. + * However, in practice, it is closer to AnnotationSessionFactoryBean since + * its core purpose is to bootstrap a SessionFactory from annotation scanning. * * @author Juergen Hoeller * @since 3.1 @@ -84,6 +84,8 @@ public class LocalSessionFactoryBean extends HibernateExceptionTranslator private ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver(); + private Configuration configuration; + private SessionFactory sessionFactory; @@ -328,7 +330,36 @@ public class LocalSessionFactoryBean extends HibernateExceptionTranslator sfb.scanPackages(this.packagesToScan); } - this.sessionFactory = sfb.buildSessionFactory(); + // Build SessionFactory instance. + this.configuration = sfb; + this.sessionFactory = buildSessionFactory(sfb); + } + + /** + * Subclasses can override this method to perform custom initialization + * of the SessionFactory instance, creating it via the given Configuration + * object that got prepared by this LocalSessionFactoryBean. + *

The default implementation invokes LocalSessionFactoryBuilder's buildSessionFactory. + * A custom implementation could prepare the instance in a specific way (e.g. applying + * a custom ServiceRegistry) or use a custom SessionFactoryImpl subclass. + * @param sfb LocalSessionFactoryBuilder prepared by this LocalSessionFactoryBean + * @return the SessionFactory instance + * @see LocalSessionFactoryBuilder#buildSessionFactory + */ + protected SessionFactory buildSessionFactory(LocalSessionFactoryBuilder sfb) { + return sfb.buildSessionFactory(); + } + + /** + * Return the Hibernate Configuration object used to build the SessionFactory. + * Allows for access to configuration metadata stored there (rarely needed). + * @throws IllegalStateException if the Configuration object has not been initialized yet + */ + public final Configuration getConfiguration() { + if (this.configuration == null) { + throw new IllegalStateException("Configuration not initialized yet"); + } + return this.configuration; } diff --git a/org.springframework.oxm/src/main/java/org/springframework/oxm/jaxb/ClassPathJaxb2TypeScanner.java b/org.springframework.oxm/src/main/java/org/springframework/oxm/jaxb/ClassPathJaxb2TypeScanner.java new file mode 100644 index 00000000000..c6f97d2493c --- /dev/null +++ b/org.springframework.oxm/src/main/java/org/springframework/oxm/jaxb/ClassPathJaxb2TypeScanner.java @@ -0,0 +1,120 @@ +/* + * Copyright 2002-2012 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.oxm.jaxb; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import javax.xml.bind.annotation.XmlEnum; +import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.XmlSeeAlso; +import javax.xml.bind.annotation.XmlType; + +import org.springframework.core.io.Resource; +import org.springframework.core.io.ResourceLoader; +import org.springframework.core.io.support.PathMatchingResourcePatternResolver; +import org.springframework.core.io.support.ResourcePatternResolver; +import org.springframework.core.io.support.ResourcePatternUtils; +import org.springframework.core.type.classreading.CachingMetadataReaderFactory; +import org.springframework.core.type.classreading.MetadataReader; +import org.springframework.core.type.classreading.MetadataReaderFactory; +import org.springframework.core.type.filter.AnnotationTypeFilter; +import org.springframework.core.type.filter.TypeFilter; +import org.springframework.oxm.UncategorizedMappingException; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; + +/** + * Helper class for {@link Jaxb2Marshaller} that scans given packages for classes marked with JAXB2 annotations. + * + * @author Arjen Poutsma + * @author David Harrigan + * @see #scanPackages() + */ +class ClassPathJaxb2TypeScanner { + + private static final String RESOURCE_PATTERN = "/**/*.class"; + + private final TypeFilter[] jaxb2TypeFilters = + new TypeFilter[]{new AnnotationTypeFilter(XmlRootElement.class, false), + new AnnotationTypeFilter(XmlType.class, false), new AnnotationTypeFilter(XmlSeeAlso.class, false), + new AnnotationTypeFilter(XmlEnum.class, false)}; + + private final String[] packagesToScan; + + private ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver(); + + private List> jaxb2Classes = new ArrayList>(); + + /** Constructs a new {@code ClassPathJaxb2TypeScanner} for the given packages. */ + ClassPathJaxb2TypeScanner(String[] packagesToScan) { + Assert.notEmpty(packagesToScan, "'packagesToScan' must not be empty"); + this.packagesToScan = packagesToScan; + } + + void setResourceLoader(ResourceLoader resourceLoader) { + if (resourceLoader != null) { + this.resourcePatternResolver = ResourcePatternUtils.getResourcePatternResolver(resourceLoader); + } + } + + /** Returns the JAXB2 classes found in the specified packages. */ + Class[] getJaxb2Classes() { + return jaxb2Classes.toArray(new Class[jaxb2Classes.size()]); + } + + /** + * Scans the packages for classes marked with JAXB2 annotations. + * + * @throws UncategorizedMappingException in case of errors + */ + void scanPackages() throws UncategorizedMappingException { + try { + for (String packageToScan : packagesToScan) { + String pattern = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + + ClassUtils.convertClassNameToResourcePath(packageToScan) + RESOURCE_PATTERN; + Resource[] resources = resourcePatternResolver.getResources(pattern); + MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(resourcePatternResolver); + for (Resource resource : resources) { + MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(resource); + if (isJaxb2Class(metadataReader, metadataReaderFactory)) { + String className = metadataReader.getClassMetadata().getClassName(); + Class jaxb2AnnotatedClass = resourcePatternResolver.getClassLoader().loadClass(className); + jaxb2Classes.add(jaxb2AnnotatedClass); + } + } + } + } + catch (IOException ex) { + throw new UncategorizedMappingException("Failed to scan classpath for unlisted classes", ex); + } + catch (ClassNotFoundException ex) { + throw new UncategorizedMappingException("Failed to load annotated classes from classpath", ex); + } + } + + private boolean isJaxb2Class(MetadataReader reader, MetadataReaderFactory factory) throws IOException { + for (TypeFilter filter : jaxb2TypeFilters) { + if (filter.match(reader, factory)) { + return true; + } + } + return false; + } + + +} diff --git a/org.springframework.oxm/src/main/java/org/springframework/oxm/jaxb/Jaxb2Marshaller.java b/org.springframework.oxm/src/main/java/org/springframework/oxm/jaxb/Jaxb2Marshaller.java index 12cae278baa..e16ba7e6e95 100644 --- a/org.springframework.oxm/src/main/java/org/springframework/oxm/jaxb/Jaxb2Marshaller.java +++ b/org.springframework.oxm/src/main/java/org/springframework/oxm/jaxb/Jaxb2Marshaller.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2012 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,7 +16,7 @@ package org.springframework.oxm.jaxb; -import java.awt.Image; +import java.awt.*; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; @@ -65,10 +65,20 @@ import javax.xml.transform.sax.SAXSource; import javax.xml.validation.Schema; import javax.xml.validation.SchemaFactory; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.w3c.dom.ls.LSResourceResolver; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; +import org.xml.sax.XMLReader; +import org.xml.sax.helpers.XMLReaderFactory; + import org.springframework.beans.factory.BeanClassLoaderAware; import org.springframework.beans.factory.InitializingBean; +import org.springframework.context.ResourceLoaderAware; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.core.io.Resource; +import org.springframework.core.io.ResourceLoader; import org.springframework.oxm.GenericMarshaller; import org.springframework.oxm.GenericUnmarshaller; import org.springframework.oxm.MarshallingFailureException; @@ -87,14 +97,6 @@ import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; import org.springframework.util.xml.StaxUtils; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.w3c.dom.ls.LSResourceResolver; -import org.xml.sax.InputSource; -import org.xml.sax.SAXException; -import org.xml.sax.XMLReader; -import org.xml.sax.helpers.XMLReaderFactory; - /** * Implementation of the Marshaller interface for JAXB 2.0. * @@ -117,7 +119,7 @@ import org.xml.sax.helpers.XMLReaderFactory; */ public class Jaxb2Marshaller implements MimeMarshaller, MimeUnmarshaller, GenericMarshaller, GenericUnmarshaller, BeanClassLoaderAware, - InitializingBean { + ResourceLoaderAware, InitializingBean { private static final String CID = "cid:"; @@ -130,6 +132,8 @@ public class Jaxb2Marshaller private String contextPath; private Class[] classesToBeBound; + + private String[] packagesToScan; private Map jaxbContextProperties; @@ -153,6 +157,8 @@ public class Jaxb2Marshaller private ClassLoader beanClassLoader; + private ResourceLoader resourceLoader; + private JAXBContext jaxbContext; private Schema schema; @@ -161,7 +167,7 @@ public class Jaxb2Marshaller private boolean supportJaxbElementClass = false; - private LSResourceResolver schemaResourceResolver; + private LSResourceResolver schemaResourceResolver; /** @@ -175,6 +181,8 @@ public class Jaxb2Marshaller /** * Set a JAXB context path. + *

Setting this property, {@link #setClassesToBeBound "classesToBeBound"}, or + * {@link #setPackagesToScan "packagesToScan"} is required. */ public void setContextPath(String contextPath) { Assert.hasText(contextPath, "'contextPath' must not be null"); @@ -190,7 +198,8 @@ public class Jaxb2Marshaller /** * Set the list of Java classes to be recognized by a newly created JAXBContext. - * Setting this property or {@link #setContextPath "contextPath"} is required. + *

Setting this property, {@link #setContextPath "contextPath"}, or + * {@link #setPackagesToScan "packagesToScan"} is required. */ public void setClassesToBeBound(Class... classesToBeBound) { Assert.notEmpty(classesToBeBound, "'classesToBeBound' must not be empty"); @@ -204,6 +213,23 @@ public class Jaxb2Marshaller return this.classesToBeBound; } + /** + * Set the packages to search using Spring-based scanning for classes with JAXB2 annotations in the classpath. + *

Setting this property, {@link #setContextPath "contextPath"}, or + * {@link #setClassesToBeBound "classesToBeBound"} is required. This is analogous to Spring's component-scan feature + * ({@link org.springframework.context.annotation.ClassPathBeanDefinitionScanner}). + */ + public void setPackagesToScan(String[] packagesToScan) { + this.packagesToScan = packagesToScan; + } + + /** + * Returns the packages to search for JAXB2 annotations. + */ + public String[] getPackagesToScan() { + return packagesToScan; + } + /** * Set the JAXBContext properties. These implementation-specific * properties will be set on the underlying JAXBContext. @@ -289,17 +315,18 @@ public class Jaxb2Marshaller this.schemaLanguage = schemaLanguage; } - /** - * Sets the resource resolver, as used to load the schema resources. - * @see SchemaFactory#setResourceResolver(org.w3c.dom.ls.LSResourceResolver) - * @see #setSchema(Resource) - * @see #setSchemas(Resource[]) - */ - public void setSchemaResourceResolver(LSResourceResolver schemaResourceResolver) { - this.schemaResourceResolver = schemaResourceResolver; - } + /** + * Sets the resource resolver, as used to load the schema resources. + * + * @see SchemaFactory#setResourceResolver(org.w3c.dom.ls.LSResourceResolver) + * @see #setSchema(Resource) + * @see #setSchemas(Resource[]) + */ + public void setSchemaResourceResolver(LSResourceResolver schemaResourceResolver) { + this.schemaResourceResolver = schemaResourceResolver; + } - /** + /** * Specify whether MTOM support should be enabled or not. * Default is false: marshalling using XOP/MTOM not being enabled. */ @@ -336,13 +363,23 @@ public class Jaxb2Marshaller this.beanClassLoader = classLoader; } + public void setResourceLoader(ResourceLoader resourceLoader) { + this.resourceLoader = resourceLoader; + } public final void afterPropertiesSet() throws Exception { - if (StringUtils.hasLength(getContextPath()) && !ObjectUtils.isEmpty(getClassesToBeBound())) { - throw new IllegalArgumentException("Specify either 'contextPath' or 'classesToBeBound property'; not both"); + boolean hasContextPath = StringUtils.hasLength(getContextPath()); + boolean hasClassesToBeBound = !ObjectUtils.isEmpty(getClassesToBeBound()); + boolean hasPackagesToScan = !ObjectUtils.isEmpty(getPackagesToScan()); + + if (hasContextPath && (hasClassesToBeBound || hasPackagesToScan) || + (hasClassesToBeBound && hasPackagesToScan)) { + throw new IllegalArgumentException("Specify either 'contextPath', 'classesToBeBound', " + + "or 'packagesToScan'"); } - else if (!StringUtils.hasLength(getContextPath()) && ObjectUtils.isEmpty(getClassesToBeBound())) { - throw new IllegalArgumentException("Setting either 'contextPath' or 'classesToBeBound' is required"); + if (!hasContextPath && !hasClassesToBeBound && !hasPackagesToScan) { + throw new IllegalArgumentException( + "Setting either 'contextPath', 'classesToBeBound', " + "or 'packagesToScan' is required"); } if (!this.lazyInit) { getJaxbContext(); @@ -361,6 +398,9 @@ public class Jaxb2Marshaller else if (!ObjectUtils.isEmpty(getClassesToBeBound())) { this.jaxbContext = createJaxbContextFromClasses(); } + else if (!ObjectUtils.isEmpty(getPackagesToScan())) { + this.jaxbContext = createJaxbContextFromPackages(); + } } catch (JAXBException ex) { throw convertJaxbException(ex); @@ -404,6 +444,26 @@ public class Jaxb2Marshaller } } + private JAXBContext createJaxbContextFromPackages() throws JAXBException { + if (logger.isInfoEnabled()) { + logger.info("Creating JAXBContext by scanning packages [" + + StringUtils.arrayToCommaDelimitedString(getPackagesToScan()) + "]"); + } + ClassPathJaxb2TypeScanner scanner = new ClassPathJaxb2TypeScanner(getPackagesToScan()); + scanner.setResourceLoader(this.resourceLoader); + scanner.scanPackages(); + Class[] jaxb2Classes = scanner.getJaxb2Classes(); + if (logger.isDebugEnabled()) { + logger.debug("Found JAXB2 classes: [" + StringUtils.arrayToCommaDelimitedString(jaxb2Classes) + "]"); + } + if (this.jaxbContextProperties != null) { + return JAXBContext.newInstance(jaxb2Classes, this.jaxbContextProperties); + } + else { + return JAXBContext.newInstance(jaxb2Classes); + } + } + private Schema loadSchema(Resource[] resources, String schemaLanguage) throws IOException, SAXException { if (logger.isDebugEnabled()) { logger.debug("Setting validation schema to " + StringUtils.arrayToCommaDelimitedString(this.schemaResources)); @@ -420,9 +480,9 @@ public class Jaxb2Marshaller schemaSources[i] = new SAXSource(xmlReader, inputSource); } SchemaFactory schemaFactory = SchemaFactory.newInstance(schemaLanguage); - if (schemaResourceResolver != null) { - schemaFactory.setResourceResolver(schemaResourceResolver); - } + if (schemaResourceResolver != null) { + schemaFactory.setResourceResolver(schemaResourceResolver); + } return schemaFactory.newSchema(schemaSources); } diff --git a/org.springframework.oxm/src/test/java/org/springframework/oxm/jaxb/Jaxb2MarshallerTests.java b/org.springframework.oxm/src/test/java/org/springframework/oxm/jaxb/Jaxb2MarshallerTests.java index 8e743037072..63af36289fa 100644 --- a/org.springframework.oxm/src/test/java/org/springframework/oxm/jaxb/Jaxb2MarshallerTests.java +++ b/org.springframework.oxm/src/test/java/org/springframework/oxm/jaxb/Jaxb2MarshallerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2012 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. @@ -279,6 +279,13 @@ public class Jaxb2MarshallerTests extends AbstractMarshallerTests { assertTrue("No XML written", writer.toString().length() > 0); } + @Test + public void supportsPackagesToScan() throws Exception { + marshaller = new Jaxb2Marshaller(); + marshaller.setPackagesToScan(new String[] {CONTEXT_PATH}); + marshaller.afterPropertiesSet(); + } + @XmlRootElement public static class DummyRootElement { diff --git a/org.springframework.oxm/template.mf b/org.springframework.oxm/template.mf index 5cf0002e593..221f362d580 100644 --- a/org.springframework.oxm/template.mf +++ b/org.springframework.oxm/template.mf @@ -12,6 +12,7 @@ Import-Template: org.exolab.castor.*;version="[1.2.0, 2.0.0)";resolution:=optional, org.jibx.runtime.*;version="[1.1.5, 2.0.0)";resolution:=optional, org.springframework.beans.*;version=${spring.osgi.range}, + org.springframework.context.*;version=${spring.osgi.range}, org.springframework.core.*;version=${spring.osgi.range}, org.springframework.util.*;version=${spring.osgi.range}, org.w3c.dom.*;version="0", diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/DispatcherServlet.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/DispatcherServlet.java index 1875eead59e..da5bb9b7121 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/DispatcherServlet.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/DispatcherServlet.java @@ -815,7 +815,7 @@ public class DispatcherServlet extends FrameworkServlet { } } - this.flashMapManager.requestStarted(request); + this.flashMapManager.requestStarted(request, response); // Make framework objects available to handlers and view objects. request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext()); @@ -827,7 +827,7 @@ public class DispatcherServlet extends FrameworkServlet { doDispatch(request, response); } finally { - this.flashMapManager.requestCompleted(request); + this.flashMapManager.requestCompleted(request, response); // Restore the original attribute snapshot, in case of an include. if (attributesSnapshot != null) { diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/FlashMap.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/FlashMap.java index 0b7bc82ad03..cb6450ca89b 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/FlashMap.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/FlashMap.java @@ -63,6 +63,8 @@ public final class FlashMap extends HashMap implements Comparabl /** * Create a new instance with an id uniquely identifying the creator of * this FlashMap. + * @param createdBy identifies the FlashMapManager instance that created + * and will manage this FlashMap instance (e.g. via a hashCode) */ public FlashMap(int createdBy) { this.createdBy = createdBy; diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/FlashMapManager.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/FlashMapManager.java index 066da4231d8..6d765ec8473 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/FlashMapManager.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/FlashMapManager.java @@ -17,6 +17,7 @@ package org.springframework.web.servlet; import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; /** * A strategy interface for storing, retrieving, and managing {@code FlashMap} @@ -64,8 +65,9 @@ public interface FlashMapManager { *

  • Clean expired FlashMap instances. * * @param request the current request + * @param response the current response */ - void requestStarted(HttpServletRequest request); + void requestStarted(HttpServletRequest request, HttpServletResponse response); /** * Start the expiration period of the "output" FlashMap save it in the @@ -73,7 +75,8 @@ public interface FlashMapManager { *

    The "output" FlashMap should not be saved if it is empty or if it was * not created by the current FlashMapManager instance. * @param request the current request + * @param response the current response */ - void requestCompleted(HttpServletRequest request); + void requestCompleted(HttpServletRequest request, HttpServletResponse response); } diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMethodExceptionResolver.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMethodExceptionResolver.java index edf5a770f19..4481118b3c2 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMethodExceptionResolver.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMethodExceptionResolver.java @@ -23,13 +23,14 @@ import org.springframework.web.method.HandlerMethod; import org.springframework.web.servlet.ModelAndView; /** - * Abstract base class for {@link org.springframework.web.servlet.HandlerExceptionResolver HandlerExceptionResolver} - * implementations that support handling exceptions from {@link HandlerMethod}s rather than handlers. + * Abstract base class for + * {@link org.springframework.web.servlet.HandlerExceptionResolver HandlerExceptionResolver} + * implementations that support handling exceptions from handlers of type {@link HandlerMethod}. * * @author Rossen Stoyanchev * @since 3.1 */ -public class AbstractHandlerMethodExceptionResolver extends AbstractHandlerExceptionResolver { +public abstract class AbstractHandlerMethodExceptionResolver extends AbstractHandlerExceptionResolver { /** * Checks if the handler is a {@link HandlerMethod} instance and performs the check against the bean @@ -52,10 +53,10 @@ public class AbstractHandlerMethodExceptionResolver extends AbstractHandlerExcep } @Override - protected final ModelAndView doResolveException(HttpServletRequest request, - HttpServletResponse response, - Object handler, - Exception ex) { + protected final ModelAndView doResolveException( + HttpServletRequest request, HttpServletResponse response, + Object handler, Exception ex) { + return doResolveHandlerMethodException(request, response, (HandlerMethod) handler, ex); } @@ -73,11 +74,8 @@ public class AbstractHandlerMethodExceptionResolver extends AbstractHandlerExcep * @param ex the exception that got thrown during handler execution * @return a corresponding ModelAndView to forward to, or null for default processing */ - protected ModelAndView doResolveHandlerMethodException(HttpServletRequest request, - HttpServletResponse response, - HandlerMethod handlerMethod, - Exception ex) { - return null; - } - + protected abstract ModelAndView doResolveHandlerMethodException( + HttpServletRequest request, HttpServletResponse response, + HandlerMethod handlerMethod, Exception ex); + } diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/AbstractUrlViewController.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/AbstractUrlViewController.java index 746a9b1bc40..2e7056342d0 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/AbstractUrlViewController.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/AbstractUrlViewController.java @@ -21,6 +21,7 @@ import javax.servlet.http.HttpServletResponse; import org.springframework.util.Assert; import org.springframework.web.servlet.ModelAndView; +import org.springframework.web.servlet.support.RequestContextUtils; import org.springframework.web.util.UrlPathHelper; /** @@ -86,7 +87,8 @@ public abstract class AbstractUrlViewController extends AbstractController { /** * Retrieves the URL path to use for lookup and delegates to - * {@link #getViewNameForRequest}. + * {@link #getViewNameForRequest}. Also adds the content of + * {@link RequestContextUtils#getInputFlashMap} to the model. */ @Override protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) { @@ -95,7 +97,7 @@ public abstract class AbstractUrlViewController extends AbstractController { if (logger.isDebugEnabled()) { logger.debug("Returning view name '" + viewName + "' for lookup path [" + lookupPath + "]"); } - return new ModelAndView(viewName); + return new ModelAndView(viewName, RequestContextUtils.getInputFlashMap(request)); } /** diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/ParameterizableViewController.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/ParameterizableViewController.java index b444e003494..68499f29455 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/ParameterizableViewController.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/ParameterizableViewController.java @@ -20,6 +20,7 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.web.servlet.ModelAndView; +import org.springframework.web.servlet.support.RequestContextUtils; /** *

    Trivial controller that always returns a named view. The view @@ -87,12 +88,13 @@ public class ParameterizableViewController extends AbstractController { /** * Return a ModelAndView object with the specified view name. + * The content of {@link RequestContextUtils#getInputFlashMap} is also added to the model. * @see #getViewName() */ @Override protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception { - return new ModelAndView(getViewName()); + return new ModelAndView(getViewName(), RequestContextUtils.getInputFlashMap(request)); } } diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/condition/ProducesRequestCondition.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/condition/ProducesRequestCondition.java index 0e46ffe4e1e..78cee038273 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/condition/ProducesRequestCondition.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/condition/ProducesRequestCondition.java @@ -173,7 +173,7 @@ public final class ProducesRequestCondition extends AbstractRequestCondition *

  • Sort 'Accept' header media types by quality value via * {@link MediaType#sortByQualityValue(List)} and iterate the list. - *
  • Get the lowest index of matching media types from each "produces" + *
  • Get the first index of matching media types in each "produces" * condition first matching with {@link MediaType#equals(Object)} and * then with {@link MediaType#includes(MediaType)}. *
  • If a lower index is found, the condition at that index wins. @@ -220,7 +220,9 @@ public final class ProducesRequestCondition extends AbstractRequestConditionAn {@link HttpEntity} return type has a set purpose. Therefore this - * handler should be configured ahead of handlers that support any return + * both {@link HttpEntity} and {@link ResponseEntity} return values. + * + *

    An {@link HttpEntity} return type has a set purpose. Therefore this + * handler should be configured ahead of handlers that support any return * value type annotated with {@code @ModelAttribute} or {@code @ResponseBody} * to ensure they don't take over. - * + * * @author Arjen Poutsma * @author Rossen Stoyanchev * @since 3.1 @@ -66,10 +66,9 @@ public class HttpEntityMethodProcessor extends AbstractMessageConverterMethodPro return HttpEntity.class.equals(parameterType) || ResponseEntity.class.equals(parameterType); } - public Object resolveArgument(MethodParameter parameter, - ModelAndViewContainer mavContainer, - NativeWebRequest webRequest, - WebDataBinderFactory binderFactory) + public Object resolveArgument( + MethodParameter parameter, ModelAndViewContainer mavContainer, + NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws IOException, HttpMediaTypeNotSupportedException { HttpInputMessage inputMessage = createInputMessage(webRequest); @@ -100,11 +99,11 @@ public class HttpEntityMethodProcessor extends AbstractMessageConverterMethodPro + "in method " + parameter.getMethod() + "is not parameterized"); } - public void handleReturnValue(Object returnValue, - MethodParameter returnType, - ModelAndViewContainer mavContainer, - NativeWebRequest webRequest) throws Exception { - + public void handleReturnValue( + Object returnValue, MethodParameter returnType, + ModelAndViewContainer mavContainer, NativeWebRequest webRequest) + throws Exception { + mavContainer.setRequestHandled(true); if (returnValue == null) { @@ -135,4 +134,4 @@ public class HttpEntityMethodProcessor extends AbstractMessageConverterMethodPro } } -} \ No newline at end of file +} diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ModelAndViewMethodReturnValueHandler.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ModelAndViewMethodReturnValueHandler.java index b5494f50614..cca424048e4 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ModelAndViewMethodReturnValueHandler.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ModelAndViewMethodReturnValueHandler.java @@ -25,15 +25,15 @@ import org.springframework.web.servlet.SmartView; import org.springframework.web.servlet.View; /** - * Handles return values of type {@link ModelAndView} copying view and model + * Handles return values of type {@link ModelAndView} copying view and model * information to the {@link ModelAndViewContainer}. - * - *

    If the return value is {@code null}, the - * {@link ModelAndViewContainer#setRequestHandled(boolean)} flag is set to + * + *

    If the return value is {@code null}, the + * {@link ModelAndViewContainer#setRequestHandled(boolean)} flag is set to * {@code false} to indicate the request was handled directly. - * - *

    A {@link ModelAndView} return type has a set purpose. Therefore this - * handler should be configured ahead of handlers that support any return + * + *

    A {@link ModelAndView} return type has a set purpose. Therefore this + * handler should be configured ahead of handlers that support any return * value type annotated with {@code @ModelAttribute} or {@code @ResponseBody} * to ensure they don't take over. * @@ -41,20 +41,21 @@ import org.springframework.web.servlet.View; * @since 3.1 */ public class ModelAndViewMethodReturnValueHandler implements HandlerMethodReturnValueHandler { - + public boolean supportsReturnType(MethodParameter returnType) { return ModelAndView.class.isAssignableFrom(returnType.getParameterType()); } - public void handleReturnValue(Object returnValue, - MethodParameter returnType, - ModelAndViewContainer mavContainer, - NativeWebRequest webRequest) throws Exception { + public void handleReturnValue( + Object returnValue, MethodParameter returnType, + ModelAndViewContainer mavContainer, NativeWebRequest webRequest) + throws Exception { + if (returnValue == null) { mavContainer.setRequestHandled(true); return; } - + ModelAndView mav = (ModelAndView) returnValue; if (mav.isReference()) { String viewName = mav.getViewName(); @@ -75,4 +76,4 @@ public class ModelAndViewMethodReturnValueHandler implements HandlerMethodReturn mavContainer.addAllAttributes(mav.getModel()); } -} \ No newline at end of file +} diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ModelAndViewResolverMethodReturnValueHandler.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ModelAndViewResolverMethodReturnValueHandler.java index 121ad3ecc0c..d1ede55c576 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ModelAndViewResolverMethodReturnValueHandler.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ModelAndViewResolverMethodReturnValueHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2012 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. @@ -29,25 +29,25 @@ import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.mvc.annotation.ModelAndViewResolver; /** - * This return value handler is intended to be ordered after all others as it + * This return value handler is intended to be ordered after all others as it * attempts to handle _any_ return value type (i.e. returns {@code true} for * all return types). - * + * *

    The return value is handled either with a {@link ModelAndViewResolver} - * or otherwise by regarding it as a model attribute if it is a non-simple - * type. If neither of these succeeds (essentially simple type other than + * or otherwise by regarding it as a model attribute if it is a non-simple + * type. If neither of these succeeds (essentially simple type other than * String), {@link UnsupportedOperationException} is raised. - * - *

    Note: This class is primarily needed to support + * + *

    Note: This class is primarily needed to support * {@link ModelAndViewResolver}, which unfortunately cannot be properly - * adapted to the {@link HandlerMethodReturnValueHandler} contract since the + * adapted to the {@link HandlerMethodReturnValueHandler} contract since the * {@link HandlerMethodReturnValueHandler#supportsReturnType} method * cannot be implemented. Hence {@code ModelAndViewResolver}s are limited - * to always being invoked at the end after all other return value - * handlers have been given a chance. It is recommended to re-implement + * to always being invoked at the end after all other return value + * handlers have been given a chance. It is recommended to re-implement * a {@code ModelAndViewResolver} as {@code HandlerMethodReturnValueHandler}, * which also provides better access to the return type and method information. - * + * * @author Rossen Stoyanchev * @since 3.1 */ @@ -71,10 +71,10 @@ public class ModelAndViewResolverMethodReturnValueHandler implements HandlerMeth return true; } - public void handleReturnValue(Object returnValue, - MethodParameter returnType, - ModelAndViewContainer mavContainer, - NativeWebRequest request) throws Exception { + public void handleReturnValue( + Object returnValue, MethodParameter returnType, + ModelAndViewContainer mavContainer, NativeWebRequest request) + throws Exception { if (this.mavResolvers != null) { for (ModelAndViewResolver mavResolver : this.mavResolvers) { @@ -93,7 +93,7 @@ public class ModelAndViewResolverMethodReturnValueHandler implements HandlerMeth } } - // No suitable ModelAndViewResolver.. + // No suitable ModelAndViewResolver.. if (this.modelAttributeProcessor.supportsReturnType(returnType)) { this.modelAttributeProcessor.handleReturnValue(returnValue, returnType, mavContainer, request); @@ -104,4 +104,4 @@ public class ModelAndViewResolverMethodReturnValueHandler implements HandlerMeth } } -} \ No newline at end of file +} diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RedirectAttributesMethodArgumentResolver.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RedirectAttributesMethodArgumentResolver.java index 272ab04ff9e..89d4849c3d4 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RedirectAttributesMethodArgumentResolver.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RedirectAttributesMethodArgumentResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2012 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. @@ -30,8 +30,8 @@ import org.springframework.web.servlet.mvc.support.RedirectAttributes; import org.springframework.web.servlet.mvc.support.RedirectAttributesModelMap; /** - * Resolves method arguments of type {@link RedirectAttributes}. - * + * Resolves method arguments of type {@link RedirectAttributes}. + * *

    This resolver must be listed ahead of {@link org.springframework.web.method.annotation.ModelMethodProcessor} and * {@link org.springframework.web.method.annotation.MapMethodProcessor}, which support {@link Map} and {@link Model} * arguments both of which are "super" types of {@code RedirectAttributes} @@ -46,10 +46,11 @@ public class RedirectAttributesMethodArgumentResolver implements HandlerMethodAr return RedirectAttributes.class.isAssignableFrom(parameter.getParameterType()); } - public Object resolveArgument(MethodParameter parameter, - ModelAndViewContainer mavContainer, - NativeWebRequest webRequest, - WebDataBinderFactory binderFactory) throws Exception { + public Object resolveArgument( + MethodParameter parameter, ModelAndViewContainer mavContainer, + NativeWebRequest webRequest, WebDataBinderFactory binderFactory) + throws Exception { + DataBinder dataBinder = binderFactory.createBinder(webRequest, null, null); ModelMap redirectAttributes = new RedirectAttributesModelMap(dataBinder); mavContainer.setRedirectModel(redirectAttributes); diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ServletInvocableHandlerMethod.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ServletInvocableHandlerMethod.java index 348be0654ac..3eb3261fb7d 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ServletInvocableHandlerMethod.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ServletInvocableHandlerMethod.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2012 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. @@ -89,9 +89,9 @@ public class ServletInvocableHandlerMethod extends InvocableHandlerMethod { * @param mavContainer the {@link ModelAndViewContainer} for the current request * @param providedArgs argument values to try to use without the need for view resolution */ - public final void invokeAndHandle(NativeWebRequest request, - ModelAndViewContainer mavContainer, - Object...providedArgs) throws Exception { + public final void invokeAndHandle( + NativeWebRequest request, ModelAndViewContainer mavContainer, + Object... providedArgs) throws Exception { Object returnValue = invokeForRequest(request, mavContainer, providedArgs); @@ -124,7 +124,7 @@ public class ServletInvocableHandlerMethod extends InvocableHandlerMethod { sb.append("[value=" + returnValue + "]"); return getDetailedErrorMessage(sb.toString()); } - + /** * Set the response status according to the {@link ResponseStatus} annotation. */ @@ -157,4 +157,4 @@ public class ServletInvocableHandlerMethod extends InvocableHandlerMethod { private boolean hasResponseStatus() { return responseStatus != null; } -} \ No newline at end of file +} diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ServletRequestMethodArgumentResolver.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ServletRequestMethodArgumentResolver.java index 6976861adbf..a6e9f7f9333 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ServletRequestMethodArgumentResolver.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ServletRequestMethodArgumentResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2012 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. @@ -37,7 +37,7 @@ import org.springframework.web.multipart.MultipartRequest; import org.springframework.web.servlet.support.RequestContextUtils; /** - * Resolves request-related method argument values of the following types: + * Resolves request-related method argument values of the following types: *