From ca19b14f131584c28ea7be07c4e3a05671402f0a Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Wed, 8 Jun 2011 22:49:41 +0000 Subject: [PATCH] first bunch of backports from 3.1 M2 to 3.0.6 --- .../resources/changelog.txt | 19 +++ .../autoproxy/ProxyCreationContext.java | 9 +- .../factory/annotation/InjectionMetadata.java | 26 ++-- .../factory/config/BeanDefinitionVisitor.java | 4 +- .../AbstractAutowireCapableBeanFactory.java | 2 +- .../propertyeditors/CustomDateEditor.java | 7 +- .../ehcache/EhCacheManagerFactoryBean.java | 23 ++- .../quartz/LocalTaskExecutorThreadPool.java | 9 +- .../scheduling/quartz/SchedulerAccessor.java | 26 +++- .../quartz/SchedulerFactoryBean.java | 8 +- .../remoting/support/RemoteExporter.java | 3 +- .../validation/AbstractBindingResult.java | 23 +-- .../validation/BindingResult.java | 2 +- .../core/AttributeAccessorSupport.java | 4 +- .../org/springframework/util/StringUtils.java | 43 ++++-- .../orm/jpa/EntityManagerFactoryUtils.java | 2 +- .../bind/PortletRequestDataBinder.java | 7 +- .../portlet/context/PortletWebRequest.java | 37 +---- .../web/portlet/util/PortletUtils.java | 49 ++++++- .../servlet/i18n/LocaleChangeInterceptor.java | 10 +- .../resource/ResourceHttpRequestHandler.java | 8 +- .../org/springframework/http/HttpHeaders.java | 51 +++---- .../org/springframework/http/MediaType.java | 63 ++++---- .../converter/FormHttpMessageConverter.java | 136 ++++++++++-------- .../http/server/ServletServerHttpRequest.java | 79 ++++++++-- .../server/ServletServerHttpResponse.java | 13 +- .../web/bind/ServletRequestDataBinder.java | 7 +- .../context/request/ServletWebRequest.java | 49 ++----- .../web/filter/ShallowEtagHeaderFilter.java | 9 +- .../springframework/web/util/WebUtils.java | 47 +++++- .../FormHttpMessageConverterTests.java | 23 +-- 31 files changed, 498 insertions(+), 300 deletions(-) diff --git a/build-spring-framework/resources/changelog.txt b/build-spring-framework/resources/changelog.txt index 6930046f54..c8d613ac53 100644 --- a/build-spring-framework/resources/changelog.txt +++ b/build-spring-framework/resources/changelog.txt @@ -3,6 +3,25 @@ SPRING FRAMEWORK CHANGELOG http://www.springsource.org +Changes in version 3.0.6 (2011-06-15) +------------------------------------- + +* fixed aspects bundle to declare dependencies for @Async aspect as well +* fixed JPA 2.0 timeout hints to correctly specify milliseconds +* updated Quartz package to support Quartz 1.8 as well +* EhCacheManagerFactoryBean properly closes "ehcache.xml" input stream, if any +* ProxyCreationContext uses "ThreadLocal.remove()" over "ThreadLocal.set(null)" as well +* BeanDefinitionVisitor now actually visits factory method names +* fixed potential InjectionMetadata NPE when using SpringBeanAutowiringInterceptor +* fixed AbstractBindingResult to avoid NPE in "hashCode()" if target is null +* Servlet/PortletRequestDataBinder perform unwrapping for MultipartRequest as well +* fixed several HttpHeaders issues (charset handling, quoting/unquoting) +* FormHttpMessageConverter correctly processes POST requests +* ResourceHttpRequestHandler does not set Content-Length header for 304 response +* LocaleChangeInterceptor validates locale values in order to prevent XSS vulnerability +* RemoteExporter uses an opaque proxy for 'serviceInterface' (no AOP interfaces exposed) + + Changes in version 3.0.5 (2010-10-20) ------------------------------------- diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/framework/autoproxy/ProxyCreationContext.java b/org.springframework.aop/src/main/java/org/springframework/aop/framework/autoproxy/ProxyCreationContext.java index 58aefc2b30..244b1eadd5 100644 --- a/org.springframework.aop/src/main/java/org/springframework/aop/framework/autoproxy/ProxyCreationContext.java +++ b/org.springframework.aop/src/main/java/org/springframework/aop/framework/autoproxy/ProxyCreationContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2009 the original author or authors. + * Copyright 2002-2011 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. @@ -46,7 +46,12 @@ public class ProxyCreationContext { * @param beanName the name of the bean, or null to reset it */ static void setCurrentProxiedBeanName(String beanName) { - currentProxiedBeanName.set(beanName); + if (beanName != null) { + currentProxiedBeanName.set(beanName); + } + else { + currentProxiedBeanName.remove(); + } } } diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/annotation/InjectionMetadata.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/annotation/InjectionMetadata.java index ceffe5fc5a..227605b5a0 100644 --- a/org.springframework.beans/src/main/java/org/springframework/beans/factory/annotation/InjectionMetadata.java +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/annotation/InjectionMetadata.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2010 the original author or authors. + * Copyright 2002-2011 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. @@ -168,21 +168,23 @@ public class InjectionMetadata { */ protected boolean checkPropertySkipping(PropertyValues pvs) { if (this.skip == null) { - synchronized (pvs) { - if (this.skip == null) { - if (this.pd != null && pvs != null) { - if (pvs.contains(this.pd.getName())) { - // Explicit value provided as part of the bean definition. - this.skip = true; - return true; - } - else if (pvs instanceof MutablePropertyValues) { - ((MutablePropertyValues) pvs).registerProcessedProperty(this.pd.getName()); + if (pvs != null) { + synchronized (pvs) { + if (this.skip == null) { + if (this.pd != null) { + if (pvs.contains(this.pd.getName())) { + // Explicit value provided as part of the bean definition. + this.skip = true; + return true; + } + else if (pvs instanceof MutablePropertyValues) { + ((MutablePropertyValues) pvs).registerProcessedProperty(this.pd.getName()); + } } } - this.skip = false; } } + this.skip = false; } return this.skip; } diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/BeanDefinitionVisitor.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/BeanDefinitionVisitor.java index eb49eb7b02..27a95e9968 100644 --- a/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/BeanDefinitionVisitor.java +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/BeanDefinitionVisitor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2010 the original author or authors. + * Copyright 2002-2011 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. @@ -115,7 +115,7 @@ public class BeanDefinitionVisitor { } protected void visitFactoryMethodName(BeanDefinition beanDefinition) { - String factoryMethodName = beanDefinition.getFactoryBeanName(); + String factoryMethodName = beanDefinition.getFactoryMethodName(); if (factoryMethodName != null) { String resolvedName = resolveStringValue(factoryMethodName); if (!factoryMethodName.equals(resolvedName)) { diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java index 37e5d98fc6..efe8e7a21b 100644 --- a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java @@ -567,7 +567,7 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac } @Override - protected Class predictBeanType(String beanName, RootBeanDefinition mbd, Class[] typesToMatch) { + protected Class predictBeanType(String beanName, RootBeanDefinition mbd, Class... typesToMatch) { Class beanClass; if (mbd.getFactoryMethodName() != null) { beanClass = getTypeForFactoryMethod(beanName, mbd, typesToMatch); diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/propertyeditors/CustomDateEditor.java b/org.springframework.beans/src/main/java/org/springframework/beans/propertyeditors/CustomDateEditor.java index e5543b28d1..5f4acc9151 100644 --- a/org.springframework.beans/src/main/java/org/springframework/beans/propertyeditors/CustomDateEditor.java +++ b/org.springframework.beans/src/main/java/org/springframework/beans/propertyeditors/CustomDateEditor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2008 the original author or authors. + * Copyright 2002-2011 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. @@ -77,7 +77,10 @@ public class CustomDateEditor extends PropertyEditorSupport { * thrown if the String does not exactly match the length specified. This is useful * because SimpleDateFormat does not enforce strict parsing of the year part, * not even with setLenient(false). Without an "exactDateLength" - * specified, the "01/01/05" would get parsed to "01/01/0005". + * specified, the "01/01/05" would get parsed to "01/01/0005". However, even + * with an "exactDateLength" specified, prepended zeros in the day or month + * part may still allow for a shorter year part, so consider this as just + * one more assertion that gets you closer to the intended date format. * @param dateFormat DateFormat to use for parsing and rendering * @param allowEmpty if empty strings should be allowed * @param exactDateLength the exact expected length of the date String diff --git a/org.springframework.context.support/src/main/java/org/springframework/cache/ehcache/EhCacheManagerFactoryBean.java b/org.springframework.context.support/src/main/java/org/springframework/cache/ehcache/EhCacheManagerFactoryBean.java index 6c3ef49f14..8ae68321dc 100644 --- a/org.springframework.context.support/src/main/java/org/springframework/cache/ehcache/EhCacheManagerFactoryBean.java +++ b/org.springframework.context.support/src/main/java/org/springframework/cache/ehcache/EhCacheManagerFactoryBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2009 the original author or authors. + * Copyright 2002-2011 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. @@ -17,6 +17,7 @@ package org.springframework.cache.ehcache; import java.io.IOException; +import java.io.InputStream; import net.sf.ehcache.CacheException; import net.sf.ehcache.CacheManager; @@ -97,23 +98,17 @@ public class EhCacheManagerFactoryBean implements FactoryBean, Ini public void afterPropertiesSet() throws IOException, CacheException { logger.info("Initializing EHCache CacheManager"); - if (this.shared) { - // Shared CacheManager singleton at the VM level. - if (this.configLocation != null) { - this.cacheManager = CacheManager.create(this.configLocation.getInputStream()); + if (this.configLocation != null) { + InputStream is = this.configLocation.getInputStream(); + try { + this.cacheManager = (this.shared ? CacheManager.create(is) : new CacheManager(is)); } - else { - this.cacheManager = CacheManager.create(); + finally { + is.close(); } } else { - // Independent CacheManager instance (the default). - if (this.configLocation != null) { - this.cacheManager = new CacheManager(this.configLocation.getInputStream()); - } - else { - this.cacheManager = new CacheManager(); - } + this.cacheManager = (this.shared ? CacheManager.create() : new CacheManager()); } if (this.cacheManagerName != null) { this.cacheManager.setName(this.cacheManagerName); diff --git a/org.springframework.context.support/src/main/java/org/springframework/scheduling/quartz/LocalTaskExecutorThreadPool.java b/org.springframework.context.support/src/main/java/org/springframework/scheduling/quartz/LocalTaskExecutorThreadPool.java index 54f25a4d62..8738e21a51 100644 --- a/org.springframework.context.support/src/main/java/org/springframework/scheduling/quartz/LocalTaskExecutorThreadPool.java +++ b/org.springframework.context.support/src/main/java/org/springframework/scheduling/quartz/LocalTaskExecutorThreadPool.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2009 the original author or authors. + * Copyright 2002-2011 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. @@ -40,6 +40,13 @@ public class LocalTaskExecutorThreadPool implements ThreadPool { private Executor taskExecutor; + public void setInstanceId(String schedInstId) { + } + + public void setInstanceName(String schedName) { + } + + public void initialize() throws SchedulerConfigException { // Absolutely needs thread-bound TaskExecutor to initialize. this.taskExecutor = SchedulerFactoryBean.getConfigTimeTaskExecutor(); diff --git a/org.springframework.context.support/src/main/java/org/springframework/scheduling/quartz/SchedulerAccessor.java b/org.springframework.context.support/src/main/java/org/springframework/scheduling/quartz/SchedulerAccessor.java index e3473520fc..07ac232ea1 100644 --- a/org.springframework.context.support/src/main/java/org/springframework/scheduling/quartz/SchedulerAccessor.java +++ b/org.springframework.context.support/src/main/java/org/springframework/scheduling/quartz/SchedulerAccessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2008 the original author or authors. + * Copyright 2002-2011 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,6 +16,7 @@ package org.springframework.scheduling.quartz; +import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.LinkedList; @@ -34,7 +35,6 @@ import org.quartz.SchedulerListener; import org.quartz.Trigger; import org.quartz.TriggerListener; import org.quartz.spi.ClassLoadHelper; -import org.quartz.xml.JobSchedulingDataProcessor; import org.springframework.context.ResourceLoaderAware; import org.springframework.core.io.ResourceLoader; @@ -240,9 +240,25 @@ public abstract class SchedulerAccessor implements ResourceLoaderAware { if (this.jobSchedulingDataLocations != null) { ClassLoadHelper clh = new ResourceLoaderClassLoadHelper(this.resourceLoader); clh.initialize(); - JobSchedulingDataProcessor dataProcessor = new JobSchedulingDataProcessor(clh, true, true); - for (String location : this.jobSchedulingDataLocations) { - dataProcessor.processFileAndScheduleJobs(location, getScheduler(), this.overwriteExistingJobs); + try { + // Quartz 1.8 or higher? + Class dataProcessorClass = getClass().getClassLoader().loadClass("org.quartz.xml.XMLSchedulingDataProcessor"); + logger.debug("Using Quartz 1.8 XMLSchedulingDataProcessor"); + Object dataProcessor = dataProcessorClass.getConstructor(ClassLoadHelper.class).newInstance(clh); + Method processFileAndScheduleJobs = dataProcessorClass.getMethod("processFileAndScheduleJobs", String.class, Scheduler.class); + for (String location : this.jobSchedulingDataLocations) { + processFileAndScheduleJobs.invoke(dataProcessor, location, getScheduler()); + } + } + catch (ClassNotFoundException ex) { + // Quartz 1.6 + Class dataProcessorClass = getClass().getClassLoader().loadClass("org.quartz.xml.JobSchedulingDataProcessor"); + logger.debug("Using Quartz 1.6 JobSchedulingDataProcessor"); + Object dataProcessor = dataProcessorClass.getConstructor(ClassLoadHelper.class, boolean.class, boolean.class).newInstance(clh, true, true); + Method processFileAndScheduleJobs = dataProcessorClass.getMethod("processFileAndScheduleJobs", String.class, Scheduler.class, boolean.class); + for (String location : this.jobSchedulingDataLocations) { + processFileAndScheduleJobs.invoke(dataProcessor, location, getScheduler(), this.overwriteExistingJobs); + } } } diff --git a/org.springframework.context.support/src/main/java/org/springframework/scheduling/quartz/SchedulerFactoryBean.java b/org.springframework.context.support/src/main/java/org/springframework/scheduling/quartz/SchedulerFactoryBean.java index 24c42e341c..2abaab665d 100644 --- a/org.springframework.context.support/src/main/java/org/springframework/scheduling/quartz/SchedulerFactoryBean.java +++ b/org.springframework.context.support/src/main/java/org/springframework/scheduling/quartz/SchedulerFactoryBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2010 the original author or authors. + * Copyright 2002-2011 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. @@ -74,9 +74,9 @@ import org.springframework.util.CollectionUtils; * automatically apply to Scheduler operations performed within those scopes. * Alternatively, you may add transactional advice for the Scheduler itself. * - *

Note: This version of Spring's SchedulerFactoryBean requires - * Quartz 1.5.x or 1.6.x. The "jobSchedulingDataLocation" feature requires - * Quartz 1.6.1 or higher (as of Spring 2.5.5). + *

Note: This version of Spring's SchedulerFactoryBean supports Quartz 1.x, + * more specifically Quartz 1.5 or higher. The "jobSchedulingDataLocation" feature + * requires Quartz 1.6.1 or higher (as of Spring 2.5.5). * * @author Juergen Hoeller * @since 18.02.2004 diff --git a/org.springframework.context/src/main/java/org/springframework/remoting/support/RemoteExporter.java b/org.springframework.context/src/main/java/org/springframework/remoting/support/RemoteExporter.java index c4e64e90d9..79c7bda8c3 100644 --- a/org.springframework.context/src/main/java/org/springframework/remoting/support/RemoteExporter.java +++ b/org.springframework.context/src/main/java/org/springframework/remoting/support/RemoteExporter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2008 the original author or authors. + * Copyright 2002-2011 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. @@ -166,6 +166,7 @@ public abstract class RemoteExporter extends RemotingSupport { } } proxyFactory.setTarget(getService()); + proxyFactory.setOpaque(true); return proxyFactory.getProxy(getBeanClassLoader()); } diff --git a/org.springframework.context/src/main/java/org/springframework/validation/AbstractBindingResult.java b/org.springframework.context/src/main/java/org/springframework/validation/AbstractBindingResult.java index d6e061c597..20ddaf7969 100644 --- a/org.springframework.context/src/main/java/org/springframework/validation/AbstractBindingResult.java +++ b/org.springframework.context/src/main/java/org/springframework/validation/AbstractBindingResult.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2010 the original author or authors. + * Copyright 2002-2011 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. @@ -19,8 +19,8 @@ package org.springframework.validation; import java.beans.PropertyEditor; import java.io.Serializable; import java.util.Collections; -import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; @@ -28,6 +28,7 @@ import java.util.Set; import org.springframework.beans.PropertyEditorRegistry; import org.springframework.util.Assert; +import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; /** @@ -130,7 +131,7 @@ public abstract class AbstractBindingResult extends AbstractErrors implements Bi } public String[] resolveMessageCodes(String errorCode, String field) { - Class fieldType = getFieldType(field); + Class fieldType = getFieldType(field); return getMessageCodesResolver().resolveMessageCodes( errorCode, getObjectName(), fixedField(field), fieldType); } @@ -236,7 +237,7 @@ public abstract class AbstractBindingResult extends AbstractErrors implements Bi * @see #getActualFieldValue */ @Override - public Class getFieldType(String field) { + public Class getFieldType(String field) { Object value = getActualFieldValue(fixedField(field)); if (value != null) { return value.getClass(); @@ -268,11 +269,11 @@ public abstract class AbstractBindingResult extends AbstractErrors implements Bi * @see org.springframework.web.servlet.mvc.SimpleFormController */ public Map getModel() { - Map model = new HashMap(2); - // Errors instance, even if no errors. - model.put(MODEL_KEY_PREFIX + getObjectName(), this); + Map model = new LinkedHashMap(2); // Mapping from name to target object. model.put(getObjectName(), getTarget()); + // Errors instance, even if no errors. + model.put(MODEL_KEY_PREFIX + getObjectName(), this); return model; } @@ -285,10 +286,10 @@ public abstract class AbstractBindingResult extends AbstractErrors implements Bi * {@link #getPropertyEditorRegistry() PropertyEditorRegistry}'s * editor lookup facility, if available. */ - public PropertyEditor findEditor(String field, Class valueType) { + public PropertyEditor findEditor(String field, Class valueType) { PropertyEditorRegistry editorRegistry = getPropertyEditorRegistry(); if (editorRegistry != null) { - Class valueTypeToUse = valueType; + Class valueTypeToUse = valueType; if (valueTypeToUse == null) { valueTypeToUse = getFieldType(field); } @@ -337,13 +338,13 @@ public abstract class AbstractBindingResult extends AbstractErrors implements Bi } BindingResult otherResult = (BindingResult) other; return (getObjectName().equals(otherResult.getObjectName()) && - getTarget().equals(otherResult.getTarget()) && + ObjectUtils.nullSafeEquals(getTarget(), otherResult.getTarget()) && getAllErrors().equals(otherResult.getAllErrors())); } @Override public int hashCode() { - return getObjectName().hashCode() * 29 + getTarget().hashCode(); + return getObjectName().hashCode(); } diff --git a/org.springframework.context/src/main/java/org/springframework/validation/BindingResult.java b/org.springframework.context/src/main/java/org/springframework/validation/BindingResult.java index c681562165..848c3e0292 100644 --- a/org.springframework.context/src/main/java/org/springframework/validation/BindingResult.java +++ b/org.springframework.context/src/main/java/org/springframework/validation/BindingResult.java @@ -94,7 +94,7 @@ public interface BindingResult extends Errors { * is given but should be specified in any case for consistency checking) * @return the registered editor, or null if none */ - PropertyEditor findEditor(String field, Class valueType); + PropertyEditor findEditor(String field, Class valueType); /** * Return the underlying PropertyEditorRegistry. diff --git a/org.springframework.core/src/main/java/org/springframework/core/AttributeAccessorSupport.java b/org.springframework.core/src/main/java/org/springframework/core/AttributeAccessorSupport.java index a2424de60b..bd598c2f8a 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/AttributeAccessorSupport.java +++ b/org.springframework.core/src/main/java/org/springframework/core/AttributeAccessorSupport.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2008 the original author or authors. + * Copyright 2002-2011 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. @@ -35,7 +35,7 @@ import org.springframework.util.Assert; public abstract class AttributeAccessorSupport implements AttributeAccessor, Serializable { /** Map with String keys and Object values */ - private final Map attributes = new LinkedHashMap(); + private final Map attributes = new LinkedHashMap(0); public void setAttribute(String name, Object value) { diff --git a/org.springframework.core/src/main/java/org/springframework/util/StringUtils.java b/org.springframework.core/src/main/java/org/springframework/util/StringUtils.java index f2505f21c4..e86ab49793 100644 --- a/org.springframework.core/src/main/java/org/springframework/util/StringUtils.java +++ b/org.springframework.core/src/main/java/org/springframework/util/StringUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2010 the original author or authors. + * Copyright 2002-2011 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. @@ -529,8 +529,15 @@ public abstract class StringUtils { if (path == null) { return null; } - int sepIndex = path.lastIndexOf(EXTENSION_SEPARATOR); - return (sepIndex != -1 ? path.substring(sepIndex + 1) : null); + int extIndex = path.lastIndexOf(EXTENSION_SEPARATOR); + if (extIndex == -1) { + return null; + } + int folderIndex = path.lastIndexOf(FOLDER_SEPARATOR); + if (folderIndex > extIndex) { + return null; + } + return path.substring(extIndex + 1); } /** @@ -544,8 +551,15 @@ public abstract class StringUtils { if (path == null) { return null; } - int sepIndex = path.lastIndexOf(EXTENSION_SEPARATOR); - return (sepIndex != -1 ? path.substring(0, sepIndex) : path); + int extIndex = path.lastIndexOf(EXTENSION_SEPARATOR); + if (extIndex == -1) { + return path; + } + int folderIndex = path.lastIndexOf(FOLDER_SEPARATOR); + if (folderIndex > extIndex) { + return path; + } + return path.substring(0, extIndex); } /** @@ -643,7 +657,7 @@ public abstract class StringUtils { } /** - * Parse the given localeString into a {@link Locale}. + * Parse the given localeString value into a {@link Locale}. *

This is the inverse operation of {@link Locale#toString Locale's toString}. * @param localeString the locale string, following Locale's * toString() format ("en", "en_UK", etc); @@ -651,6 +665,13 @@ public abstract class StringUtils { * @return a corresponding Locale instance */ public static Locale parseLocaleString(String localeString) { + for (int i = 0; i < localeString.length(); i++) { + char ch = localeString.charAt(i); + if (ch != '_' && ch != ' ' && !Character.isLetterOrDigit(ch)) { + throw new IllegalArgumentException( + "Locale value \"" + localeString + "\" contains invalid characters"); + } + } String[] parts = tokenizeToStringArray(localeString, "_ ", false, false); String language = (parts.length > 0 ? parts[0] : ""); String country = (parts.length > 1 ? parts[1] : ""); @@ -726,7 +747,7 @@ public abstract class StringUtils { * array elements only included once. *

The order of elements in the original arrays is preserved * (with the exception of overlapping elements, which are only - * included on their first occurence). + * included on their first occurrence). * @param array1 the first array (can be null) * @param array2 the second array (can be null) * @return the new array (null if both given arrays were null) @@ -1043,12 +1064,12 @@ public abstract class StringUtils { * @param suffix the String to end each element with * @return the delimited String */ - public static String collectionToDelimitedString(Collection coll, String delim, String prefix, String suffix) { + public static String collectionToDelimitedString(Collection coll, String delim, String prefix, String suffix) { if (CollectionUtils.isEmpty(coll)) { return ""; } StringBuilder sb = new StringBuilder(); - Iterator it = coll.iterator(); + Iterator it = coll.iterator(); while (it.hasNext()) { sb.append(prefix).append(it.next()).append(suffix); if (it.hasNext()) { @@ -1065,7 +1086,7 @@ public abstract class StringUtils { * @param delim the delimiter to use (probably a ",") * @return the delimited String */ - public static String collectionToDelimitedString(Collection coll, String delim) { + public static String collectionToDelimitedString(Collection coll, String delim) { return collectionToDelimitedString(coll, delim, "", ""); } @@ -1075,7 +1096,7 @@ public abstract class StringUtils { * @param coll the Collection to display * @return the delimited String */ - public static String collectionToCommaDelimitedString(Collection coll) { + public static String collectionToCommaDelimitedString(Collection coll) { return collectionToDelimitedString(coll, ","); } diff --git a/org.springframework.orm/src/main/java/org/springframework/orm/jpa/EntityManagerFactoryUtils.java b/org.springframework.orm/src/main/java/org/springframework/orm/jpa/EntityManagerFactoryUtils.java index ecaca5890f..8e52e83683 100644 --- a/org.springframework.orm/src/main/java/org/springframework/orm/jpa/EntityManagerFactoryUtils.java +++ b/org.springframework.orm/src/main/java/org/springframework/orm/jpa/EntityManagerFactoryUtils.java @@ -257,7 +257,7 @@ public abstract class EntityManagerFactoryUtils { public static void applyTransactionTimeout(Query query, EntityManagerFactory emf) { EntityManagerHolder emHolder = (EntityManagerHolder) TransactionSynchronizationManager.getResource(emf); if (emHolder != null && emHolder.hasTimeout()) { - int timeoutValue = emHolder.getTimeToLiveInSeconds(); + int timeoutValue = (int) emHolder.getTimeToLiveInMillis(); query.setHint("javax.persistence.lock.timeout", timeoutValue); query.setHint("javax.persistence.query.timeout", timeoutValue); } diff --git a/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/bind/PortletRequestDataBinder.java b/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/bind/PortletRequestDataBinder.java index 26c3a5b55f..a63b894d17 100644 --- a/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/bind/PortletRequestDataBinder.java +++ b/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/bind/PortletRequestDataBinder.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2010 the original author or authors. + * Copyright 2002-2011 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. @@ -22,6 +22,7 @@ import org.springframework.beans.MutablePropertyValues; import org.springframework.validation.BindException; import org.springframework.web.bind.WebDataBinder; import org.springframework.web.multipart.MultipartRequest; +import org.springframework.web.portlet.util.PortletUtils; /** * Special {@link org.springframework.validation.DataBinder} to perform data binding @@ -105,8 +106,8 @@ public class PortletRequestDataBinder extends WebDataBinder { */ public void bind(PortletRequest request) { MutablePropertyValues mpvs = new PortletRequestParameterPropertyValues(request); - if (request instanceof MultipartRequest) { - MultipartRequest multipartRequest = (MultipartRequest) request; + MultipartRequest multipartRequest = PortletUtils.getNativeRequest(request, MultipartRequest.class); + if (multipartRequest != null) { bindMultipart(multipartRequest.getMultiFileMap(), mpvs); } doBind(mpvs); diff --git a/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/context/PortletWebRequest.java b/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/context/PortletWebRequest.java index 9be71736c4..52907bba74 100644 --- a/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/context/PortletWebRequest.java +++ b/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/context/PortletWebRequest.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2010 the original author or authors. + * Copyright 2002-2011 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,13 +23,12 @@ import java.util.Map; import javax.portlet.PortletRequest; import javax.portlet.PortletResponse; import javax.portlet.PortletSession; -import javax.portlet.filter.PortletRequestWrapper; -import javax.portlet.filter.PortletResponseWrapper; import org.springframework.util.CollectionUtils; import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; import org.springframework.web.context.request.NativeWebRequest; +import org.springframework.web.portlet.util.PortletUtils; /** * {@link org.springframework.web.context.request.WebRequest} adapter @@ -79,40 +78,12 @@ public class PortletWebRequest extends PortletRequestAttributes implements Nativ @SuppressWarnings("unchecked") public T getNativeRequest(Class requiredType) { - if (requiredType != null) { - PortletRequest request = getRequest(); - while (request != null) { - if (requiredType.isInstance(request)) { - return (T) request; - } - else if (request instanceof PortletRequestWrapper) { - request = ((PortletRequestWrapper) request).getRequest(); - } - else { - request = null; - } - } - } - return null; + return PortletUtils.getNativeRequest(getRequest(), requiredType); } @SuppressWarnings("unchecked") public T getNativeResponse(Class requiredType) { - if (requiredType != null) { - PortletResponse response = getResponse(); - while (response != null) { - if (requiredType.isInstance(response)) { - return (T) response; - } - else if (response instanceof PortletResponseWrapper) { - response = ((PortletResponseWrapper) response).getResponse(); - } - else { - response = null; - } - } - } - return null; + return PortletUtils.getNativeResponse(getResponse(), requiredType); } diff --git a/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/util/PortletUtils.java b/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/util/PortletUtils.java index cd9040b1b5..968229c753 100644 --- a/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/util/PortletUtils.java +++ b/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/util/PortletUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2009 the original author or authors. + * Copyright 2002-2011 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. @@ -32,6 +32,9 @@ import javax.portlet.PortletRequestDispatcher; import javax.portlet.PortletSession; import javax.portlet.ResourceRequest; import javax.portlet.ResourceResponse; +import javax.portlet.PortletResponse; +import javax.portlet.filter.PortletRequestWrapper; +import javax.portlet.filter.PortletResponseWrapper; import javax.servlet.http.Cookie; import org.springframework.util.Assert; @@ -276,6 +279,48 @@ public abstract class PortletUtils { } + /** + * Return an appropriate request object of the specified type, if available, + * unwrapping the given request as far as necessary. + * @param request the portlet request to introspect + * @param requiredType the desired type of request object + * @return the matching request object, or null if none + * of that type is available + */ + @SuppressWarnings("unchecked") + public static T getNativeRequest(PortletRequest request, Class requiredType) { + if (requiredType != null) { + if (requiredType.isInstance(request)) { + return (T) request; + } + else if (request instanceof PortletRequestWrapper) { + return getNativeRequest(((PortletRequestWrapper) request).getRequest(), requiredType); + } + } + return null; + } + + /** + * Return an appropriate response object of the specified type, if available, + * unwrapping the given response as far as necessary. + * @param response the portlet response to introspect + * @param requiredType the desired type of response object + * @return the matching response object, or null if none + * of that type is available + */ + @SuppressWarnings("unchecked") + public static T getNativeResponse(PortletResponse response, Class requiredType) { + if (requiredType != null) { + if (requiredType.isInstance(response)) { + return (T) response; + } + else if (response instanceof PortletResponseWrapper) { + return getNativeResponse(((PortletResponseWrapper) response).getResponse(), requiredType); + } + } + return null; + } + /** * Expose the given Map as request attributes, using the keys as attribute names * and the values as corresponding attribute values. Keys must be Strings. @@ -293,7 +338,7 @@ public abstract class PortletUtils { /** * Retrieve the first cookie with the given name. Note that multiple * cookies can have the same name but different paths or domains. - * @param request current servlet request + * @param request current portlet request * @param name cookie name * @return the first cookie with the given name, or null if none is found */ diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/i18n/LocaleChangeInterceptor.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/i18n/LocaleChangeInterceptor.java index d6b9a90e71..a39919c3e8 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/i18n/LocaleChangeInterceptor.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/i18n/LocaleChangeInterceptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2008 the original author or authors. + * Copyright 2002-2011 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,13 +16,11 @@ package org.springframework.web.servlet.i18n; -import java.util.Locale; - import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import org.springframework.beans.propertyeditors.LocaleEditor; +import org.springframework.util.StringUtils; import org.springframework.web.servlet.LocaleResolver; import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; import org.springframework.web.servlet.support.RequestContextUtils; @@ -72,9 +70,7 @@ public class LocaleChangeInterceptor extends HandlerInterceptorAdapter { if (localeResolver == null) { throw new IllegalStateException("No LocaleResolver found: not in a DispatcherServlet request?"); } - LocaleEditor localeEditor = new LocaleEditor(); - localeEditor.setAsText(newLocale); - localeResolver.setLocale(request, response, (Locale) localeEditor.getValue()); + localeResolver.setLocale(request, response, StringUtils.parseLocaleString(newLocale)); } // Proceed in any case. return true; diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandler.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandler.java index 07fe31be8c..8540de9c2e 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandler.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandler.java @@ -35,9 +35,9 @@ import org.springframework.web.servlet.support.WebContentGenerator; /** * {@link HttpRequestHandler} that serves static resources optimized for superior browser performance * (according to the guidelines of Page Speed, YSlow, etc.) by allowing for flexible cache settings - * ({@link #setCacheSeconds "cacheSeconds" property}, last-modified support). + * ({@linkplain #setCacheSeconds "cacheSeconds" property}, last-modified support). * - *

The {@link #setLocations "locations" property takes a list of Spring {@link Resource} locations + *

The {@linkplain #setLocations "locations" property} takes a list of Spring {@link Resource} locations * from which static resources are allowed to be served by this handler. For a given request, the * list of locations will be consulted in order for the presence of the requested resource, and the * first found match will be written to the response, with {@code Expires} and {@code Cache-Control} @@ -54,7 +54,7 @@ import org.springframework.web.servlet.support.WebContentGenerator; * using Spring EL. See the reference manual for further examples of this approach. * *

Rather than being directly configured as a bean, this handler will typically be configured - * through use of the <mvc:resources/> XML configuration element. + * through use of the {@code } XML configuration element. * * @author Keith Donald * @author Jeremy Grelle @@ -121,11 +121,11 @@ public class ResourceHttpRequestHandler extends WebContentGenerator implements H } // header phase - setHeaders(response, resource, mediaType); if (new ServletWebRequest(request, response).checkNotModified(resource.lastModified())) { logger.debug("Resource not modified - returning 304"); return; } + setHeaders(response, resource, mediaType); // content phase if (METHOD_HEAD.equals(request.getMethod())) { diff --git a/org.springframework.web/src/main/java/org/springframework/http/HttpHeaders.java b/org.springframework.web/src/main/java/org/springframework/http/HttpHeaders.java index c4ba00c4cf..800ededbaf 100644 --- a/org.springframework.web/src/main/java/org/springframework/http/HttpHeaders.java +++ b/org.springframework.web/src/main/java/org/springframework/http/HttpHeaders.java @@ -1,11 +1,11 @@ /* - * Copyright 2002-2010 the original author or authors. + * Copyright 2002-2011 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 + * 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, @@ -177,11 +177,15 @@ public class HttpHeaders implements MultiValueMap { String[] tokens = value.split(",\\s*"); for (String token : tokens) { int paramIdx = token.indexOf(';'); + String charsetName; if (paramIdx == -1) { - result.add(Charset.forName(token)); + charsetName = token; } else { - result.add(Charset.forName(token.substring(0, paramIdx))); + charsetName = token.substring(0, paramIdx); + } + if (!charsetName.equals("*")) { + result.add(Charset.forName(charsetName)); } } } @@ -310,7 +314,11 @@ public class HttpHeaders implements MultiValueMap { * @param eTag the new entity tag */ public void setETag(String eTag) { - set(ETAG, quote(eTag)); + if (eTag != null) { + Assert.isTrue(eTag.startsWith("\"") || eTag.startsWith("W/"), "Invalid eTag, does not start with W/ or \""); + Assert.isTrue(eTag.endsWith("\""), "Invalid eTag, does not end with \""); + } + set(ETAG, eTag); } /** @@ -318,7 +326,7 @@ public class HttpHeaders implements MultiValueMap { * @return the entity tag */ public String getETag() { - return unquote(getFirst(ETAG)); + return getFirst(ETAG); } /** @@ -362,7 +370,7 @@ public class HttpHeaders implements MultiValueMap { * @param ifNoneMatch the new value of the header */ public void setIfNoneMatch(String ifNoneMatch) { - set(IF_NONE_MATCH, quote(ifNoneMatch)); + set(IF_NONE_MATCH, ifNoneMatch); } /** @@ -373,7 +381,7 @@ public class HttpHeaders implements MultiValueMap { StringBuilder builder = new StringBuilder(); for (Iterator iterator = ifNoneMatchList.iterator(); iterator.hasNext();) { String ifNoneMatch = iterator.next(); - builder.append(quote(ifNoneMatch)); + builder.append(ifNoneMatch); if (iterator.hasNext()) { builder.append(", "); } @@ -392,7 +400,7 @@ public class HttpHeaders implements MultiValueMap { if (value != null) { String[] tokens = value.split(",\\s*"); for (String token : tokens) { - result.add(unquote(token)); + result.add(token); } } return result; @@ -452,31 +460,6 @@ public class HttpHeaders implements MultiValueMap { // Utility methods - private String quote(String s) { - Assert.notNull(s); - if (!s.startsWith("\"")) { - s = "\"" + s; - } - if (!s.endsWith("\"")) { - s = s + "\""; - } - return s; - } - - private String unquote(String s) { - if (s == null) { - return null; - } - if (s.startsWith("\"")) { - s = s.substring(1); - } - if (s.endsWith("\"")) { - s = s.substring(0, s.length() - 1); - } - return s; - } - - private long getFirstDate(String headerName) { String headerValue = getFirst(headerName); if (headerValue == null) { diff --git a/org.springframework.web/src/main/java/org/springframework/http/MediaType.java b/org.springframework.web/src/main/java/org/springframework/http/MediaType.java index 185e9e6cab..fa7886216a 100644 --- a/org.springframework.web/src/main/java/org/springframework/http/MediaType.java +++ b/org.springframework.web/src/main/java/org/springframework/http/MediaType.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2010 the original author or authors. + * Copyright 2002-2011 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. @@ -187,7 +187,7 @@ public class MediaType implements Comparable { /** - * Create a new {@link MediaType} for the given primary type. + * Create a new {@code MediaType} for the given primary type. *

The {@linkplain #getSubtype() subtype} is set to *, parameters empty. * @param type the primary type * @throws IllegalArgumentException if any of the parameters contain illegal characters @@ -197,7 +197,7 @@ public class MediaType implements Comparable { } /** - * Create a new {@link MediaType} for the given primary type and subtype. + * Create a new {@code MediaType} for the given primary type and subtype. *

The parameters are empty. * @param type the primary type * @param subtype the subtype @@ -208,18 +208,18 @@ public class MediaType implements Comparable { } /** - * Create a new {@link MediaType} for the given type, subtype, and character set. + * Create a new {@code MediaType} for the given type, subtype, and character set. * @param type the primary type * @param subtype the subtype * @param charSet the character set * @throws IllegalArgumentException if any of the parameters contain illegal characters */ public MediaType(String type, String subtype, Charset charSet) { - this(type, subtype, Collections.singletonMap(PARAM_CHARSET, charSet.toString())); + this(type, subtype, Collections.singletonMap(PARAM_CHARSET, charSet.name())); } /** - * Create a new {@link MediaType} for the given type, subtype, and quality value. + * Create a new {@code MediaType} for the given type, subtype, and quality value. * * @param type the primary type * @param subtype the subtype @@ -231,7 +231,7 @@ public class MediaType implements Comparable { } /** - * Copy-constructor that copies the type and subtype of the given {@link MediaType}, + * Copy-constructor that copies the type and subtype of the given {@code MediaType}, * and allows for different parameter. * @param other the other media type * @param parameters the parameters, may be null @@ -242,7 +242,7 @@ public class MediaType implements Comparable { } /** - * Create a new {@link MediaType} for the given type, subtype, and parameters. + * Create a new {@code MediaType} for the given type, subtype, and parameters. * @param type the primary type * @param subtype the subtype * @param parameters the parameters, may be null @@ -322,7 +322,7 @@ public class MediaType implements Comparable { } /** - * Indicate whether the {@linkplain #getType() type} is the wildcard character * or not. + * Indicates whether the {@linkplain #getType() type} is the wildcard character * or not. */ public boolean isWildcardType() { return WILDCARD_TYPE.equals(type); @@ -336,13 +336,22 @@ public class MediaType implements Comparable { } /** - * Indicate whether the {@linkplain #getSubtype() subtype} is the wildcard character * or not. + * Indicates whether the {@linkplain #getSubtype() subtype} is the wildcard character * or not. * @return whether the subtype is * */ public boolean isWildcardSubtype() { return WILDCARD_TYPE.equals(subtype); } + /** + * Indicates whether this media type is concrete, i.e. whether neither the type or subtype is a wildcard + * character *. + * @return whether this media type is concrete + */ + public boolean isConcrete() { + return !isWildcardType() && !isWildcardSubtype(); + } + /** * Return the character set, as indicated by a charset parameter, if any. * @return the character set; or null if not available @@ -372,9 +381,9 @@ public class MediaType implements Comparable { } /** - * Indicate whether this {@link MediaType} includes the given media type. - *

For instance, {@code text/*} includes {@code text/plain}, {@code text/html}, and {@code application/*+xml} - * includes {@code application/soap+xml}, etc. This method is non-symmetic. + * Indicate whether this {@code MediaType} includes the given media type. + *

For instance, {@code text/*} includes {@code text/plain} and {@code text/html}, and {@code application/*+xml} + * includes {@code application/soap+xml}, etc. This method is not symmetric. * @param other the reference media type with which to compare * @return true if this media type includes the given media type; false otherwise */ @@ -407,9 +416,9 @@ public class MediaType implements Comparable { } /** - * Indicate whether this {@link MediaType} is compatible with the given media type. + * Indicate whether this {@code MediaType} is compatible with the given media type. *

For instance, {@code text/*} is compatible with {@code text/plain}, {@code text/html}, and vice versa. - * In effect, this method is similar to {@link #includes(MediaType)}, except that it's symmetric. + * In effect, this method is similar to {@link #includes(MediaType)}, except that it is symmetric. * @param other the reference media type with which to compare * @return true if this media type is compatible with the given media type; false otherwise */ @@ -444,7 +453,7 @@ public class MediaType implements Comparable { } /** - * Compares this {@link MediaType} to another alphabetically. + * Compares this {@code MediaType} to another alphabetically. * @param other media type to compare to * @see #sortBySpecificity(List) */ @@ -533,7 +542,7 @@ public class MediaType implements Comparable { /** - * Parse the given String value into a {@link MediaType} object, + * Parse the given String value into a {@code MediaType} object, * with this method name following the 'valueOf' naming convention * (as supported by {@link org.springframework.core.convert.ConversionService}. * @see #parseMediaType(String) @@ -543,7 +552,7 @@ public class MediaType implements Comparable { } /** - * Parse the given String into a single {@link MediaType}. + * Parse the given String into a single {@code MediaType}. * @param mediaType the string to parse * @return the media type * @throws IllegalArgumentException if the string cannot be parsed @@ -586,7 +595,7 @@ public class MediaType implements Comparable { /** - * Parse the given, comma-seperated string into a list of {@link MediaType} objects. + * Parse the given, comma-separated string into a list of {@code MediaType} objects. *

This method can be used to parse an Accept or Content-Type header. * @param mediaTypes the string to parse * @return the list of media types @@ -605,7 +614,7 @@ public class MediaType implements Comparable { } /** - * Return a string representation of the given list of {@link MediaType} objects. + * Return a string representation of the given list of {@code MediaType} objects. *

This method can be used to for an {@code Accept} or {@code Content-Type} header. * @param mediaTypes the string to parse * @return the list of media types @@ -624,7 +633,7 @@ public class MediaType implements Comparable { } /** - * Sorts the given list of {@link MediaType} objects by specificity. + * Sorts the given list of {@code MediaType} objects by specificity. *

Given two media types: *

    *
  1. if either media type has a {@linkplain #isWildcardType() wildcard type}, then the media type without the @@ -657,7 +666,7 @@ public class MediaType implements Comparable { } /** - * Sorts the given list of {@link MediaType} objects by quality value. + * Sorts the given list of {@code MediaType} objects by quality value. *

    Given two media types: *

      *
    1. if the two media types have different {@linkplain #getQualityValue() quality value}, then the media type @@ -684,7 +693,10 @@ public class MediaType implements Comparable { } - static final Comparator SPECIFICITY_COMPARATOR = new Comparator() { + /** + * Comparator used by {@link #sortBySpecificity(List)}. + */ + public static final Comparator SPECIFICITY_COMPARATOR = new Comparator() { public int compare(MediaType mediaType1, MediaType mediaType2) { if (mediaType1.isWildcardType() && !mediaType2.isWildcardType()) { // */* < audio/* @@ -724,7 +736,10 @@ public class MediaType implements Comparable { }; - static final Comparator QUALITY_VALUE_COMPARATOR = new Comparator() { + /** + * Comparator used by {@link #sortByQualityValue(List)}. + */ + public static final Comparator QUALITY_VALUE_COMPARATOR = new Comparator() { public int compare(MediaType mediaType1, MediaType mediaType2) { double quality1 = mediaType1.getQualityValue(); diff --git a/org.springframework.web/src/main/java/org/springframework/http/converter/FormHttpMessageConverter.java b/org.springframework.web/src/main/java/org/springframework/http/converter/FormHttpMessageConverter.java index 7bcb4f2a75..609489af7f 100644 --- a/org.springframework.web/src/main/java/org/springframework/http/converter/FormHttpMessageConverter.java +++ b/org.springframework.web/src/main/java/org/springframework/http/converter/FormHttpMessageConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2010 the original author or authors. + * Copyright 2002-2011 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. @@ -19,13 +19,11 @@ package org.springframework.http.converter; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStream; -import java.io.OutputStreamWriter; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.net.URLEncoder; import java.nio.charset.Charset; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; @@ -43,38 +41,28 @@ import org.springframework.util.FileCopyUtils; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.util.StringUtils; -import org.springframework.web.util.WebUtils; /** - * Implementation of {@link HttpMessageConverter} that can handle form data, including multipart form data - * (i.e. file uploads). + * Implementation of {@link HttpMessageConverter} that can handle form data, including multipart form data (i.e. file + * uploads). * *

      This converter can write the {@code application/x-www-form-urlencoded} and {@code multipart/form-data} media * types, and read the {@code application/x-www-form-urlencoded}) media type (but not {@code multipart/form-data}). * - *

      In other words, this converter can read and write 'normal' HTML forms (as - * {@link MultiValueMap MultiValueMap<String, String>}), and it can write multipart form (as - * {@link MultiValueMap MultiValueMap<String, Object>}. When writing multipart, this converter uses other - * {@link HttpMessageConverter HttpMessageConverters} to write the respective MIME parts. By default, basic converters - * are registered (supporting {@code Strings} and {@code Resources}, for instance); these can be overridden by setting - * the {@link #setPartConverters(java.util.List) partConverters} property. + *

      In other words, this converter can read and write 'normal' HTML forms (as {@link MultiValueMap + * MultiValueMap<String, String>}), and it can write multipart form (as {@link MultiValueMap + * MultiValueMap<String, Object>}. When writing multipart, this converter uses other {@link HttpMessageConverter + * HttpMessageConverters} to write the respective MIME parts. By default, basic converters are registered (supporting + * {@code Strings} and {@code Resources}, for instance); these can be overridden by setting the {@link + * #setPartConverters(java.util.List) partConverters} property. * - *

      For example, the following snippet shows how to submit an HTML form: - *

      - * RestTemplate template = new RestTemplate(); // FormHttpMessageConverter is configured by default
      - * MultiValueMap<String, String> form = new LinkedMultiValueMap<String, String>();
      - * form.add("field 1", "value 1");
      - * form.add("field 2", "value 2");
      - * form.add("field 2", "value 3");
      - * template.postForLocation("http://example.com/myForm", form);
      - * 
      - *

      The following snippet shows how to do a file upload: - *

      - * MultiValueMap<String, Object> parts = new LinkedMultiValueMap<String, Object>();
      - * parts.add("field 1", "value 1");
      - * parts.add("file", new ClassPathResource("myFile.jpg"));
      - * template.postForLocation("http://example.com/myFileUpload", parts);
      - * 
      + *

      For example, the following snippet shows how to submit an HTML form:

       RestTemplate template =
      + * new RestTemplate(); // FormHttpMessageConverter is configured by default MultiValueMap<String, String> form =
      + * new LinkedMultiValueMap<String, String>(); form.add("field 1", "value 1"); form.add("field 2", "value 2");
      + * form.add("field 2", "value 3"); template.postForLocation("http://example.com/myForm", form); 

      The following + * snippet shows how to do a file upload:

       MultiValueMap<String, Object> parts = new
      + * LinkedMultiValueMap<String, Object>(); parts.add("field 1", "value 1"); parts.add("file", new
      + * ClassPathResource("myFile.jpg")); template.postForLocation("http://example.com/myFileUpload", parts); 
      * *

      Some methods in this class were inspired by {@link org.apache.commons.httpclient.methods.multipart.MultipartRequestEntity}. * @@ -88,16 +76,21 @@ public class FormHttpMessageConverter implements HttpMessageConverter supportedMediaTypes = new ArrayList(); private List> partConverters = new ArrayList>(); public FormHttpMessageConverter() { + this.supportedMediaTypes.add(MediaType.APPLICATION_FORM_URLENCODED); + this.supportedMediaTypes.add(MediaType.MULTIPART_FORM_DATA); + this.partConverters.add(new ByteArrayHttpMessageConverter()); StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter(); stringHttpMessageConverter.setWriteAcceptCharset(false); @@ -106,14 +99,6 @@ public class FormHttpMessageConverter implements HttpMessageConverter partConverter) { - Assert.notNull(partConverter, "'partConverter' must not be NULL"); - this.partConverters.add(partConverter); - } - /** * Set the message body converters to use. These converters are used to convert objects to MIME parts. */ @@ -122,6 +107,14 @@ public class FormHttpMessageConverter implements HttpMessageConverter partConverter) { + Assert.notNull(partConverter, "'partConverter' must not be NULL"); + this.partConverters.add(partConverter); + } + /** * Sets the character set used for writing form data. */ @@ -129,34 +122,47 @@ public class FormHttpMessageConverter implements HttpMessageConverter clazz, MediaType mediaType) { if (!MultiValueMap.class.isAssignableFrom(clazz)) { return false; } - if (mediaType != null) { - return MediaType.APPLICATION_FORM_URLENCODED.includes(mediaType); - } - else { + if (mediaType == null) { return true; } + for (MediaType supportedMediaType : getSupportedMediaTypes()) { + // we can't read multipart + if (!supportedMediaType.equals(MediaType.MULTIPART_FORM_DATA) && + supportedMediaType.includes(mediaType)) { + return true; + } + } + return false; } public boolean canWrite(Class clazz, MediaType mediaType) { if (!MultiValueMap.class.isAssignableFrom(clazz)) { return false; } - if (mediaType != null) { - return mediaType.isCompatibleWith(MediaType.APPLICATION_FORM_URLENCODED) || - mediaType.isCompatibleWith(MediaType.MULTIPART_FORM_DATA); - } - else { + if (mediaType == null || MediaType.ALL.equals(mediaType)) { return true; } + for (MediaType supportedMediaType : getSupportedMediaTypes()) { + if (supportedMediaType.isCompatibleWith(mediaType)) { + return true; + } + } + return false; + } + + /** + * Set the list of {@link MediaType} objects supported by this converter. + */ + public void setSupportedMediaTypes(List supportedMediaTypes) { + this.supportedMediaTypes = supportedMediaTypes; } public List getSupportedMediaTypes() { - return Arrays.asList(MediaType.APPLICATION_FORM_URLENCODED, MediaType.MULTIPART_FORM_DATA); + return Collections.unmodifiableList(this.supportedMediaTypes); } public MultiValueMap read(Class> clazz, @@ -188,7 +194,7 @@ public class FormHttpMessageConverter implements HttpMessageConverter map, MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException { if (!isMultipart(map, contentType)) { - writeForm((MultiValueMap) map, outputMessage); + writeForm((MultiValueMap) map, contentType, outputMessage); } else { writeMultipart((MultiValueMap) map, outputMessage); @@ -209,8 +215,17 @@ public class FormHttpMessageConverter implements HttpMessageConverter form, HttpOutputMessage outputMessage) throws IOException { - outputMessage.getHeaders().setContentType(MediaType.APPLICATION_FORM_URLENCODED); + private void writeForm(MultiValueMap form, MediaType contentType, HttpOutputMessage outputMessage) + throws IOException { + Charset charset; + if (contentType != null) { + outputMessage.getHeaders().setContentType(contentType); + charset = contentType.getCharSet() != null ? contentType.getCharSet() : this.charset; + } + else { + outputMessage.getHeaders().setContentType(MediaType.APPLICATION_FORM_URLENCODED); + charset = this.charset; + } StringBuilder builder = new StringBuilder(); for (Iterator nameIterator = form.keySet().iterator(); nameIterator.hasNext();) { String name = nameIterator.next(); @@ -229,10 +244,13 @@ public class FormHttpMessageConverter implements HttpMessageConverter parts, HttpOutputMessage outputMessage) throws IOException { + private void writeMultipart(MultiValueMap parts, HttpOutputMessage outputMessage) + throws IOException { byte[] boundary = generateMultipartBoundary(); Map parameters = Collections.singletonMap("boundary", new String(boundary, "US-ASCII")); @@ -310,7 +328,8 @@ public class FormHttpMessageConverter implements HttpMessageConverterDefault implementation returns a random boundary. Can be overridden in subclasses. + *

      The default implementation returns a random boundary. + * Can be overridden in subclasses. */ protected byte[] generateMultipartBoundary() { byte[] boundary = new byte[rnd.nextInt(11) + 30]; @@ -321,9 +340,10 @@ public class FormHttpMessageConverter implements HttpMessageConverterDefault implementation returns {@link Resource#getFilename()} if the part is a {@code Resource}, and - * {@code null} in other cases. Can be overridden in subclasses. + * Return the filename of the given multipart part. This value will be used for the + * {@code Content-Disposition} header. + *

      The default implementation returns {@link Resource#getFilename()} if the part is a + * {@code Resource}, and {@code null} in other cases. Can be overridden in subclasses. * @param part the part to determine the file name for * @return the filename, or {@code null} if not known */ diff --git a/org.springframework.web/src/main/java/org/springframework/http/server/ServletServerHttpRequest.java b/org.springframework.web/src/main/java/org/springframework/http/server/ServletServerHttpRequest.java index a3c6788594..28339d375e 100644 --- a/org.springframework.web/src/main/java/org/springframework/http/server/ServletServerHttpRequest.java +++ b/org.springframework.web/src/main/java/org/springframework/http/server/ServletServerHttpRequest.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2010 the original author or authors. + * Copyright 2002-2011 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,11 +16,20 @@ package org.springframework.http.server; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; +import java.io.OutputStreamWriter; +import java.io.Writer; import java.net.URI; import java.net.URISyntaxException; +import java.net.URLEncoder; +import java.util.Arrays; import java.util.Enumeration; +import java.util.Iterator; +import java.util.List; +import java.util.Map; import javax.servlet.http.HttpServletRequest; import org.springframework.http.HttpHeaders; @@ -35,14 +44,22 @@ import org.springframework.util.Assert; */ public class ServletServerHttpRequest implements ServerHttpRequest { + private static final String FORM_CONTENT_TYPE = "application/x-www-form-urlencoded"; + + private static final String POST_METHOD = "POST"; + + private static final String PUT_METHOD = "PUT"; + + private static final String FORM_CHARSET = "UTF-8"; + private final HttpServletRequest servletRequest; private HttpHeaders headers; /** - * Construct a new instance of the ServletServerHttpRequest based on the given {@link HttpServletRequest} - * @param servletRequest the HttpServletRequest + * Construct a new instance of the ServletServerHttpRequest based on the given {@link HttpServletRequest}. + * @param servletRequest the servlet request */ public ServletServerHttpRequest(HttpServletRequest servletRequest) { Assert.notNull(servletRequest, "'servletRequest' must not be null"); @@ -50,15 +67,22 @@ public class ServletServerHttpRequest implements ServerHttpRequest { } + /** + * Returns the {@code HttpServletRequest} this object is based on. + */ + public HttpServletRequest getServletRequest() { + return this.servletRequest; + } + public HttpMethod getMethod() { return HttpMethod.valueOf(this.servletRequest.getMethod()); } public URI getURI() { try { - return new URI(servletRequest.getScheme(), null, servletRequest.getServerName(), - servletRequest.getServerPort(), servletRequest.getRequestURI(), - servletRequest.getQueryString(), null); + return new URI(this.servletRequest.getScheme(), null, this.servletRequest.getServerName(), + this.servletRequest.getServerPort(), this.servletRequest.getRequestURI(), + this.servletRequest.getQueryString(), null); } catch (URISyntaxException ex) { throw new IllegalStateException("Could not get HttpServletRequest URI: " + ex.getMessage(), ex); @@ -70,7 +94,8 @@ public class ServletServerHttpRequest implements ServerHttpRequest { this.headers = new HttpHeaders(); for (Enumeration headerNames = this.servletRequest.getHeaderNames(); headerNames.hasMoreElements();) { String headerName = (String) headerNames.nextElement(); - for (Enumeration headerValues = this.servletRequest.getHeaders(headerName); headerValues.hasMoreElements();) { + for (Enumeration headerValues = this.servletRequest.getHeaders(headerName); + headerValues.hasMoreElements();) { String headerValue = (String) headerValues.nextElement(); this.headers.add(headerName, headerValue); } @@ -80,7 +105,45 @@ public class ServletServerHttpRequest implements ServerHttpRequest { } public InputStream getBody() throws IOException { - return this.servletRequest.getInputStream(); + if (isFormSubmittal(this.servletRequest)) { + return getFormBody(this.servletRequest); + } + else { + return this.servletRequest.getInputStream(); + } + } + + private boolean isFormSubmittal(HttpServletRequest request) { + return FORM_CONTENT_TYPE.equals(request.getContentType()) && + (POST_METHOD.equalsIgnoreCase(request.getMethod()) || PUT_METHOD.equalsIgnoreCase(request.getMethod())); + } + + private InputStream getFormBody(HttpServletRequest request) throws IOException { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + Writer writer = new OutputStreamWriter(bos, FORM_CHARSET); + + Map form = request.getParameterMap(); + for (Iterator nameIterator = form.keySet().iterator(); nameIterator.hasNext();) { + String name = nameIterator.next(); + List values = Arrays.asList(form.get(name)); + for (Iterator valueIterator = values.iterator(); valueIterator.hasNext();) { + String value = valueIterator.next(); + writer.write(URLEncoder.encode(name, FORM_CHARSET)); + if (value != null) { + writer.write('='); + writer.write(URLEncoder.encode(value, FORM_CHARSET)); + if (valueIterator.hasNext()) { + writer.write('&'); + } + } + } + if (nameIterator.hasNext()) { + writer.append('&'); + } + } + writer.flush(); + + return new ByteArrayInputStream(bos.toByteArray()); } } diff --git a/org.springframework.web/src/main/java/org/springframework/http/server/ServletServerHttpResponse.java b/org.springframework.web/src/main/java/org/springframework/http/server/ServletServerHttpResponse.java index af7ab7e449..9752c4259d 100644 --- a/org.springframework.web/src/main/java/org/springframework/http/server/ServletServerHttpResponse.java +++ b/org.springframework.web/src/main/java/org/springframework/http/server/ServletServerHttpResponse.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2010 the original author or authors. + * Copyright 2002-2011 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. @@ -43,7 +43,7 @@ public class ServletServerHttpResponse implements ServerHttpResponse { /** * Construct a new instance of the ServletServerHttpResponse based on the given {@link HttpServletResponse}. - * @param servletResponse the HTTP Servlet response + * @param servletResponse the servlet response */ public ServletServerHttpResponse(HttpServletResponse servletResponse) { Assert.notNull(servletResponse, "'servletResponse' must not be null"); @@ -51,12 +51,19 @@ public class ServletServerHttpResponse implements ServerHttpResponse { } + /** + * Return the {@code HttpServletResponse} this object is based on. + */ + public HttpServletResponse getServletResponse() { + return this.servletResponse; + } + public void setStatusCode(HttpStatus status) { this.servletResponse.setStatus(status.value()); } public HttpHeaders getHeaders() { - return headersWritten ? HttpHeaders.readOnlyHttpHeaders(headers) : this.headers; + return (this.headersWritten ? HttpHeaders.readOnlyHttpHeaders(this.headers) : this.headers); } public OutputStream getBody() throws IOException { diff --git a/org.springframework.web/src/main/java/org/springframework/web/bind/ServletRequestDataBinder.java b/org.springframework.web/src/main/java/org/springframework/web/bind/ServletRequestDataBinder.java index 2edc899cfa..f488024d9b 100644 --- a/org.springframework.web/src/main/java/org/springframework/web/bind/ServletRequestDataBinder.java +++ b/org.springframework.web/src/main/java/org/springframework/web/bind/ServletRequestDataBinder.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2010 the original author or authors. + * Copyright 2002-2011 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. @@ -21,6 +21,7 @@ import javax.servlet.ServletRequest; import org.springframework.beans.MutablePropertyValues; import org.springframework.validation.BindException; import org.springframework.web.multipart.MultipartRequest; +import org.springframework.web.util.WebUtils; /** * Special {@link org.springframework.validation.DataBinder} to perform data binding @@ -103,8 +104,8 @@ public class ServletRequestDataBinder extends WebDataBinder { */ public void bind(ServletRequest request) { MutablePropertyValues mpvs = new ServletRequestParameterPropertyValues(request); - if (request instanceof MultipartRequest) { - MultipartRequest multipartRequest = (MultipartRequest) request; + MultipartRequest multipartRequest = WebUtils.getNativeRequest(request, MultipartRequest.class); + if (multipartRequest != null) { bindMultipart(multipartRequest.getMultiFileMap(), mpvs); } doBind(mpvs); diff --git a/org.springframework.web/src/main/java/org/springframework/web/context/request/ServletWebRequest.java b/org.springframework.web/src/main/java/org/springframework/web/context/request/ServletWebRequest.java index b59909052b..d772877c74 100644 --- a/org.springframework.web/src/main/java/org/springframework/web/context/request/ServletWebRequest.java +++ b/org.springframework.web/src/main/java/org/springframework/web/context/request/ServletWebRequest.java @@ -1,11 +1,11 @@ /* - * Copyright 2002-2010 the original author or authors. + * Copyright 2002-2011 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 + * 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, @@ -20,10 +20,6 @@ import java.security.Principal; import java.util.Iterator; import java.util.Locale; import java.util.Map; -import javax.servlet.ServletRequest; -import javax.servlet.ServletRequestWrapper; -import javax.servlet.ServletResponse; -import javax.servlet.ServletResponseWrapper; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; @@ -31,6 +27,7 @@ import javax.servlet.http.HttpSession; import org.springframework.util.CollectionUtils; import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; +import org.springframework.web.util.WebUtils; /** * {@link WebRequest} adapter for an {@link javax.servlet.http.HttpServletRequest}. @@ -40,10 +37,16 @@ import org.springframework.util.StringUtils; */ public class ServletWebRequest extends ServletRequestAttributes implements NativeWebRequest { + private static final String HEADER_ETAG = "ETag"; + private static final String HEADER_IF_MODIFIED_SINCE = "If-Modified-Since"; + private static final String HEADER_IF_NONE_MATCH = "If-None-Match"; + private static final String HEADER_LAST_MODIFIED = "Last-Modified"; + private static final String METHOD_GET = "GET"; + private HttpServletResponse response; @@ -86,40 +89,12 @@ public class ServletWebRequest extends ServletRequestAttributes implements Nativ @SuppressWarnings("unchecked") public T getNativeRequest(Class requiredType) { - if (requiredType != null) { - ServletRequest request = getRequest(); - while (request != null) { - if (requiredType.isInstance(request)) { - return (T) request; - } - else if (request instanceof ServletRequestWrapper) { - request = ((ServletRequestWrapper) request).getRequest(); - } - else { - request = null; - } - } - } - return null; + return WebUtils.getNativeRequest(getRequest(), requiredType); } @SuppressWarnings("unchecked") public T getNativeResponse(Class requiredType) { - if (requiredType != null) { - ServletResponse response = getResponse(); - while (response != null) { - if (requiredType.isInstance(response)) { - return (T) response; - } - else if (response instanceof ServletResponseWrapper) { - response = ((ServletResponseWrapper) response).getResponse(); - } - else { - response = null; - } - } - } - return null; + return WebUtils.getNativeResponse(getResponse(), requiredType); } @@ -186,7 +161,7 @@ public class ServletWebRequest extends ServletRequestAttributes implements Nativ long ifModifiedSince = getRequest().getDateHeader(HEADER_IF_MODIFIED_SINCE); this.notModified = (ifModifiedSince >= (lastModifiedTimestamp / 1000 * 1000)); if (this.response != null) { - if (this.notModified && "GET".equals(getRequest().getMethod())) { + if (this.notModified && METHOD_GET.equals(getRequest().getMethod())) { this.response.setStatus(HttpServletResponse.SC_NOT_MODIFIED); } else { diff --git a/org.springframework.web/src/main/java/org/springframework/web/filter/ShallowEtagHeaderFilter.java b/org.springframework.web/src/main/java/org/springframework/web/filter/ShallowEtagHeaderFilter.java index 07269002c4..d214561959 100644 --- a/org.springframework.web/src/main/java/org/springframework/web/filter/ShallowEtagHeaderFilter.java +++ b/org.springframework.web/src/main/java/org/springframework/web/filter/ShallowEtagHeaderFilter.java @@ -69,7 +69,6 @@ public class ShallowEtagHeaderFilter extends OncePerRequestFilter { if (logger.isTraceEnabled()) { logger.trace("ETag [" + responseETag + "] equal to If-None-Match, sending 304"); } - response.setContentLength(0); response.setStatus(HttpServletResponse.SC_NOT_MODIFIED); } else { @@ -89,8 +88,8 @@ public class ShallowEtagHeaderFilter extends OncePerRequestFilter { } private void copyBodyToResponse(byte[] body, HttpServletResponse response) throws IOException { - response.setContentLength(body.length); if (body.length > 0) { + response.setContentLength(body.length); FileCopyUtils.copy(body, response.getOutputStream()); } } @@ -113,7 +112,7 @@ public class ShallowEtagHeaderFilter extends OncePerRequestFilter { /** * Generate the ETag header value from the given response body byte array. *

      The default implementation generates an MD5 hash. - * @param bytes the response bdoy as byte array + * @param bytes the response body as byte array * @return the ETag header value * @see org.springframework.util.DigestUtils */ @@ -168,6 +167,10 @@ public class ShallowEtagHeaderFilter extends OncePerRequestFilter { this.statusCode = sc; } + @Override + public void setContentLength(int len) { + } + @Override public ServletOutputStream getOutputStream() { return this.outputStream; diff --git a/org.springframework.web/src/main/java/org/springframework/web/util/WebUtils.java b/org.springframework.web/src/main/java/org/springframework/web/util/WebUtils.java index c11df93a05..8e19d70926 100644 --- a/org.springframework.web/src/main/java/org/springframework/web/util/WebUtils.java +++ b/org.springframework.web/src/main/java/org/springframework/web/util/WebUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2009 the original author or authors. + * Copyright 2002-2011 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,9 @@ import java.util.Map; import java.util.TreeMap; import javax.servlet.ServletContext; import javax.servlet.ServletRequest; +import javax.servlet.ServletRequestWrapper; +import javax.servlet.ServletResponse; +import javax.servlet.ServletResponseWrapper; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -366,6 +369,48 @@ public abstract class WebUtils { } + /** + * Return an appropriate request object of the specified type, if available, + * unwrapping the given request as far as necessary. + * @param request the servlet request to introspect + * @param requiredType the desired type of request object + * @return the matching request object, or null if none + * of that type is available + */ + @SuppressWarnings("unchecked") + public static T getNativeRequest(ServletRequest request, Class requiredType) { + if (requiredType != null) { + if (requiredType.isInstance(request)) { + return (T) request; + } + else if (request instanceof ServletRequestWrapper) { + return getNativeRequest(((ServletRequestWrapper) request).getRequest(), requiredType); + } + } + return null; + } + + /** + * Return an appropriate response object of the specified type, if available, + * unwrapping the given response as far as necessary. + * @param response the servlet response to introspect + * @param requiredType the desired type of response object + * @return the matching response object, or null if none + * of that type is available + */ + @SuppressWarnings("unchecked") + public static T getNativeResponse(ServletResponse response, Class requiredType) { + if (requiredType != null) { + if (requiredType.isInstance(response)) { + return (T) response; + } + else if (response instanceof ServletResponseWrapper) { + return getNativeResponse(((ServletResponseWrapper) response).getResponse(), requiredType); + } + } + return null; + } + /** * Determine whether the given request is an include request, * that is, not a top-level HTTP request coming in from the outside. diff --git a/org.springframework.web/src/test/java/org/springframework/http/converter/FormHttpMessageConverterTests.java b/org.springframework.web/src/test/java/org/springframework/http/converter/FormHttpMessageConverterTests.java index 75fbf892e3..83ffd70b42 100644 --- a/org.springframework.web/src/test/java/org/springframework/http/converter/FormHttpMessageConverterTests.java +++ b/org.springframework.web/src/test/java/org/springframework/http/converter/FormHttpMessageConverterTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2010 the original author or authors. + * Copyright 2002-2011 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,7 +30,6 @@ import org.apache.commons.fileupload.FileItemFactory; import org.apache.commons.fileupload.FileUpload; import org.apache.commons.fileupload.RequestContext; import org.apache.commons.fileupload.disk.DiskFileItemFactory; -import static org.junit.Assert.*; import org.junit.Before; import org.junit.Test; @@ -45,6 +44,8 @@ import org.springframework.http.converter.xml.XmlAwareFormHttpMessageConverter; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; +import static org.junit.Assert.*; + /** * @author Arjen Poutsma */ @@ -60,13 +61,13 @@ public class FormHttpMessageConverterTests { @Test public void canRead() { assertTrue(converter.canRead(MultiValueMap.class, new MediaType("application", "x-www-form-urlencoded"))); - assertFalse(converter.canRead(MultiValueMap.class, new MediaType("multipart","form-data"))); + assertFalse(converter.canRead(MultiValueMap.class, new MediaType("multipart", "form-data"))); } @Test public void canWrite() { assertTrue(converter.canWrite(MultiValueMap.class, new MediaType("application", "x-www-form-urlencoded"))); - assertTrue(converter.canWrite(MultiValueMap.class, new MediaType("multipart","form-data"))); + assertTrue(converter.canWrite(MultiValueMap.class, new MediaType("multipart", "form-data"))); assertTrue(converter.canWrite(MultiValueMap.class, MediaType.ALL)); } @@ -77,7 +78,7 @@ public class FormHttpMessageConverterTests { Charset iso88591 = Charset.forName("ISO-8859-1"); MockHttpInputMessage inputMessage = new MockHttpInputMessage(body.getBytes(iso88591)); inputMessage.getHeaders().setContentType(new MediaType("application", "x-www-form-urlencoded", iso88591)); - MultiValueMap result = (MultiValueMap) converter.read(null, inputMessage); + MultiValueMap result = converter.read(null, inputMessage); assertEquals("Invalid result", 3, result.size()); assertEquals("Invalid result", "value 1", result.getFirst("name 1")); @@ -97,19 +98,21 @@ public class FormHttpMessageConverterTests { body.add("name 3", null); MockHttpOutputMessage outputMessage = new MockHttpOutputMessage(); converter.write(body, MediaType.APPLICATION_FORM_URLENCODED, outputMessage); - Charset iso88591 = Charset.forName("ISO-8859-1"); assertEquals("Invalid result", "name+1=value+1&name+2=value+2%2B1&name+2=value+2%2B2&name+3", - outputMessage.getBodyAsString(iso88591)); + outputMessage.getBodyAsString(Charset.forName("UTF-8"))); assertEquals("Invalid content-type", new MediaType("application", "x-www-form-urlencoded"), outputMessage.getHeaders().getContentType()); + assertEquals("Invalid content-length", outputMessage.getBodyAsBytes().length, + outputMessage.getHeaders().getContentLength()); } - + @Test public void writeMultipart() throws Exception { MultiValueMap parts = new LinkedMultiValueMap(); parts.add("name 1", "value 1"); parts.add("name 2", "value 2+1"); parts.add("name 2", "value 2+2"); + Resource logo = new ClassPathResource("/org/springframework/http/converter/logo.jpg"); parts.add("logo", logo); Source xml = new StreamSource(new StringReader("")); @@ -122,7 +125,7 @@ public class FormHttpMessageConverterTests { converter.write(parts, MediaType.MULTIPART_FORM_DATA, outputMessage); final MediaType contentType = outputMessage.getHeaders().getContentType(); - assertNotNull(contentType.getParameter("boundary")); + assertNotNull("No boundary found", contentType.getParameter("boundary")); // see if Commons FileUpload can read what we wrote FileItemFactory fileItemFactory = new DiskFileItemFactory(); @@ -157,6 +160,7 @@ public class FormHttpMessageConverterTests { } private static class MockHttpOutputMessageRequestContext implements RequestContext { + private final MockHttpOutputMessage outputMessage; private MockHttpOutputMessageRequestContext(MockHttpOutputMessage outputMessage) { @@ -182,5 +186,4 @@ public class FormHttpMessageConverterTests { } } - }