first bunch of backports from 3.1 M2 to 3.0.6

This commit is contained in:
Juergen Hoeller
2011-06-08 22:49:41 +00:00
parent 175f6d4bc5
commit ca19b14f13
31 changed files with 498 additions and 300 deletions

View File

@@ -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)
-------------------------------------

View File

@@ -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 <code>null</code> to reset it
*/
static void setCurrentProxiedBeanName(String beanName) {
currentProxiedBeanName.set(beanName);
if (beanName != null) {
currentProxiedBeanName.set(beanName);
}
else {
currentProxiedBeanName.remove();
}
}
}

View File

@@ -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;
}

View File

@@ -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)) {

View File

@@ -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);

View File

@@ -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 <code>setLenient(false)</code>. 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

View File

@@ -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<CacheManager>, 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);

View File

@@ -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();

View File

@@ -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);
}
}
}

View File

@@ -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.
*
* <p><b>Note:</b> 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).
* <p><b>Note:</b> 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

View File

@@ -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());
}

View File

@@ -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<String, Object> getModel() {
Map<String, Object> model = new HashMap<String, Object>(2);
// Errors instance, even if no errors.
model.put(MODEL_KEY_PREFIX + getObjectName(), this);
Map<String, Object> model = new LinkedHashMap<String, Object>(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();
}

View File

@@ -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 <code>null</code> if none
*/
PropertyEditor findEditor(String field, Class valueType);
PropertyEditor findEditor(String field, Class<?> valueType);
/**
* Return the underlying PropertyEditorRegistry.

View File

@@ -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<String, Object> attributes = new LinkedHashMap<String, Object>();
private final Map<String, Object> attributes = new LinkedHashMap<String, Object>(0);
public void setAttribute(String name, Object value) {

View File

@@ -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 <code>localeString</code> into a {@link Locale}.
* Parse the given <code>localeString</code> value into a {@link Locale}.
* <p>This is the inverse operation of {@link Locale#toString Locale's toString}.
* @param localeString the locale string, following <code>Locale's</code>
* <code>toString()</code> format ("en", "en_UK", etc);
@@ -651,6 +665,13 @@ public abstract class StringUtils {
* @return a corresponding <code>Locale</code> 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.
* <p>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 <code>null</code>)
* @param array2 the second array (can be <code>null</code>)
* @return the new array (<code>null</code> if both given arrays were <code>null</code>)
@@ -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, ",");
}

View File

@@ -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);
}

View File

@@ -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);

View File

@@ -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> T getNativeRequest(Class<T> 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> T getNativeResponse(Class<T> 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);
}

View File

@@ -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 <code>null</code> if none
* of that type is available
*/
@SuppressWarnings("unchecked")
public static <T> T getNativeRequest(PortletRequest request, Class<T> 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 <code>null</code> if none
* of that type is available
*/
@SuppressWarnings("unchecked")
public static <T> T getNativeResponse(PortletResponse response, Class<T> 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 <code>null</code> if none is found
*/

View File

@@ -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;

View File

@@ -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).
*
* <p>The {@link #setLocations "locations" property takes a list of Spring {@link Resource} locations
* <p>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.
*
* <p>Rather than being directly configured as a bean, this handler will typically be configured
* through use of the <code>&lt;mvc:resources/&gt;</code> XML configuration element.
* through use of the {@code <mvc:resources/>} 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())) {

View File

@@ -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, String> {
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<String, String> {
* @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<String, String> {
* @return the entity tag
*/
public String getETag() {
return unquote(getFirst(ETAG));
return getFirst(ETAG);
}
/**
@@ -362,7 +370,7 @@ public class HttpHeaders implements MultiValueMap<String, String> {
* @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<String, String> {
StringBuilder builder = new StringBuilder();
for (Iterator<String> 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<String, String> {
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<String, String> {
// 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) {

View File

@@ -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<MediaType> {
/**
* Create a new {@link MediaType} for the given primary type.
* Create a new {@code MediaType} for the given primary type.
* <p>The {@linkplain #getSubtype() subtype} is set to <code>&#42;</code>, 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<MediaType> {
}
/**
* Create a new {@link MediaType} for the given primary type and subtype.
* Create a new {@code MediaType} for the given primary type and subtype.
* <p>The parameters are empty.
* @param type the primary type
* @param subtype the subtype
@@ -208,18 +208,18 @@ public class MediaType implements Comparable<MediaType> {
}
/**
* 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<MediaType> {
}
/**
* 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 <code>null</code>
@@ -242,7 +242,7 @@ public class MediaType implements Comparable<MediaType> {
}
/**
* 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 <code>null</code>
@@ -322,7 +322,7 @@ public class MediaType implements Comparable<MediaType> {
}
/**
* Indicate whether the {@linkplain #getType() type} is the wildcard character <code>&#42;</code> or not.
* Indicates whether the {@linkplain #getType() type} is the wildcard character <code>&#42;</code> or not.
*/
public boolean isWildcardType() {
return WILDCARD_TYPE.equals(type);
@@ -336,13 +336,22 @@ public class MediaType implements Comparable<MediaType> {
}
/**
* Indicate whether the {@linkplain #getSubtype() subtype} is the wildcard character <code>&#42;</code> or not.
* Indicates whether the {@linkplain #getSubtype() subtype} is the wildcard character <code>&#42;</code> or not.
* @return whether the subtype is <code>&#42;</code>
*/
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 <code>&#42;</code>.
* @return whether this media type is concrete
*/
public boolean isConcrete() {
return !isWildcardType() && !isWildcardSubtype();
}
/**
* Return the character set, as indicated by a <code>charset</code> parameter, if any.
* @return the character set; or <code>null</code> if not available
@@ -372,9 +381,9 @@ public class MediaType implements Comparable<MediaType> {
}
/**
* Indicate whether this {@link MediaType} includes the given media type.
* <p>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.
* <p>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 <b>not</b> symmetric.
* @param other the reference media type with which to compare
* @return <code>true</code> if this media type includes the given media type; <code>false</code> otherwise
*/
@@ -407,9 +416,9 @@ public class MediaType implements Comparable<MediaType> {
}
/**
* Indicate whether this {@link MediaType} is compatible with the given media type.
* Indicate whether this {@code MediaType} is compatible with the given media type.
* <p>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 <b>is</b> symmetric.
* @param other the reference media type with which to compare
* @return <code>true</code> if this media type is compatible with the given media type; <code>false</code> otherwise
*/
@@ -444,7 +453,7 @@ public class MediaType implements Comparable<MediaType> {
}
/**
* 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<MediaType> {
/**
* 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<MediaType> {
}
/**
* 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<MediaType> {
/**
* 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.
* <p>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<MediaType> {
}
/**
* Return a string representation of the given list of {@link MediaType} objects.
* Return a string representation of the given list of {@code MediaType} objects.
* <p>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<MediaType> {
}
/**
* Sorts the given list of {@link MediaType} objects by specificity.
* Sorts the given list of {@code MediaType} objects by specificity.
* <p>Given two media types:
* <ol>
* <li>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<MediaType> {
}
/**
* Sorts the given list of {@link MediaType} objects by quality value.
* Sorts the given list of {@code MediaType} objects by quality value.
* <p>Given two media types:
* <ol>
* <li>if the two media types have different {@linkplain #getQualityValue() quality value}, then the media type
@@ -684,7 +693,10 @@ public class MediaType implements Comparable<MediaType> {
}
static final Comparator<MediaType> SPECIFICITY_COMPARATOR = new Comparator<MediaType>() {
/**
* Comparator used by {@link #sortBySpecificity(List)}.
*/
public static final Comparator<MediaType> SPECIFICITY_COMPARATOR = new Comparator<MediaType>() {
public int compare(MediaType mediaType1, MediaType mediaType2) {
if (mediaType1.isWildcardType() && !mediaType2.isWildcardType()) { // */* < audio/*
@@ -724,7 +736,10 @@ public class MediaType implements Comparable<MediaType> {
};
static final Comparator<MediaType> QUALITY_VALUE_COMPARATOR = new Comparator<MediaType>() {
/**
* Comparator used by {@link #sortByQualityValue(List)}.
*/
public static final Comparator<MediaType> QUALITY_VALUE_COMPARATOR = new Comparator<MediaType>() {
public int compare(MediaType mediaType1, MediaType mediaType2) {
double quality1 = mediaType1.getQualityValue();

View File

@@ -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).
*
* <p>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}).
*
* <p>In other words, this converter can read and write 'normal' HTML forms (as
* {@link MultiValueMap MultiValueMap&lt;String, String&gt;}), and it can write multipart form (as
* {@link MultiValueMap MultiValueMap&lt;String, Object&gt;}. 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.
* <p>In other words, this converter can read and write 'normal' HTML forms (as {@link MultiValueMap
* MultiValueMap&lt;String, String&gt;}), and it can write multipart form (as {@link MultiValueMap
* MultiValueMap&lt;String, Object&gt;}. 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.
*
* <p>For example, the following snippet shows how to submit an HTML form:
* <pre class="code">
* RestTemplate template = new RestTemplate(); // FormHttpMessageConverter is configured by default
* MultiValueMap&lt;String, String&gt; form = new LinkedMultiValueMap&lt;String, String&gt;();
* 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);
* </pre>
* <p>The following snippet shows how to do a file upload:
* <pre class="code">
* MultiValueMap&lt;String, Object&gt; parts = new LinkedMultiValueMap&lt;String, Object&gt;();
* parts.add("field 1", "value 1");
* parts.add("file", new ClassPathResource("myFile.jpg"));
* template.postForLocation("http://example.com/myFileUpload", parts);
* </pre>
* <p>For example, the following snippet shows how to submit an HTML form: <pre class="code"> RestTemplate template =
* new RestTemplate(); // FormHttpMessageConverter is configured by default MultiValueMap&lt;String, String&gt; form =
* new LinkedMultiValueMap&lt;String, String&gt;(); 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); </pre> <p>The following
* snippet shows how to do a file upload: <pre class="code"> MultiValueMap&lt;String, Object&gt; parts = new
* LinkedMultiValueMap&lt;String, Object&gt;(); parts.add("field 1", "value 1"); parts.add("file", new
* ClassPathResource("myFile.jpg")); template.postForLocation("http://example.com/myFileUpload", parts); </pre>
*
* <p>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<MultiValue
new byte[]{'-', '_', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A',
'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U',
'V', 'W', 'X', 'Y', 'Z'};
'V', 'W', 'X', 'Y', 'Z'};
private final Random rnd = new Random();
private Charset charset = Charset.forName(WebUtils.DEFAULT_CHARACTER_ENCODING);
private Charset charset = Charset.forName("UTF-8");
private List<MediaType> supportedMediaTypes = new ArrayList<MediaType>();
private List<HttpMessageConverter<?>> partConverters = new ArrayList<HttpMessageConverter<?>>();
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<MultiValue
}
/**
* Add a message body converter. Such a converters is used to convert objects to MIME parts.
*/
public final void addPartConverter(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<MultiValue
this.partConverters = partConverters;
}
/**
* Add a message body converter. Such a converters is used to convert objects to MIME parts.
*/
public final void addPartConverter(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<MultiValue
this.charset = charset;
}
public boolean canRead(Class<?> 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<MediaType> supportedMediaTypes) {
this.supportedMediaTypes = supportedMediaTypes;
}
public List<MediaType> getSupportedMediaTypes() {
return Arrays.asList(MediaType.APPLICATION_FORM_URLENCODED, MediaType.MULTIPART_FORM_DATA);
return Collections.unmodifiableList(this.supportedMediaTypes);
}
public MultiValueMap<String, String> read(Class<? extends MultiValueMap<String, ?>> clazz,
@@ -188,7 +194,7 @@ public class FormHttpMessageConverter implements HttpMessageConverter<MultiValue
public void write(MultiValueMap<String, ?> map, MediaType contentType, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException {
if (!isMultipart(map, contentType)) {
writeForm((MultiValueMap<String, String>) map, outputMessage);
writeForm((MultiValueMap<String, String>) map, contentType, outputMessage);
}
else {
writeMultipart((MultiValueMap<String, Object>) map, outputMessage);
@@ -209,8 +215,17 @@ public class FormHttpMessageConverter implements HttpMessageConverter<MultiValue
return false;
}
private void writeForm(MultiValueMap<String, String> form, HttpOutputMessage outputMessage) throws IOException {
outputMessage.getHeaders().setContentType(MediaType.APPLICATION_FORM_URLENCODED);
private void writeForm(MultiValueMap<String, String> 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<String> nameIterator = form.keySet().iterator(); nameIterator.hasNext();) {
String name = nameIterator.next();
@@ -229,10 +244,13 @@ public class FormHttpMessageConverter implements HttpMessageConverter<MultiValue
builder.append('&');
}
}
FileCopyUtils.copy(builder.toString(), new OutputStreamWriter(outputMessage.getBody(), charset));
byte[] bytes = builder.toString().getBytes(charset.name());
outputMessage.getHeaders().setContentLength(bytes.length);
FileCopyUtils.copy(bytes, outputMessage.getBody());
}
private void writeMultipart(MultiValueMap<String, Object> parts, HttpOutputMessage outputMessage) throws IOException {
private void writeMultipart(MultiValueMap<String, Object> parts, HttpOutputMessage outputMessage)
throws IOException {
byte[] boundary = generateMultipartBoundary();
Map<String, String> parameters = Collections.singletonMap("boundary", new String(boundary, "US-ASCII"));
@@ -310,7 +328,8 @@ public class FormHttpMessageConverter implements HttpMessageConverter<MultiValue
/**
* Generate a multipart boundary.
* <p>Default implementation returns a random boundary. Can be overridden in subclasses.
* <p>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 HttpMessageConverter<MultiValue
}
/**
* Return the filename of the given multipart part. This value will be used for the {@code Content-Disposition} header.
* <p>Default 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.
* <p>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
*/

View File

@@ -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<String, String[]> form = request.getParameterMap();
for (Iterator<String> nameIterator = form.keySet().iterator(); nameIterator.hasNext();) {
String name = nameIterator.next();
List<String> values = Arrays.asList(form.get(name));
for (Iterator<String> 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());
}
}

View File

@@ -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 {

View File

@@ -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);

View File

@@ -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> T getNativeRequest(Class<T> 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> T getNativeResponse(Class<T> 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 {

View File

@@ -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.
* <p>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;

View File

@@ -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 <code>null</code> if none
* of that type is available
*/
@SuppressWarnings("unchecked")
public static <T> T getNativeRequest(ServletRequest request, Class<T> 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 <code>null</code> if none
* of that type is available
*/
@SuppressWarnings("unchecked")
public static <T> T getNativeResponse(ServletResponse response, Class<T> 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.

View File

@@ -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<String, String> result = (MultiValueMap<String, String>) converter.read(null, inputMessage);
MultiValueMap<String, String> 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<String, Object> parts = new LinkedMultiValueMap<String, Object>();
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("<root><child/></root>"));
@@ -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 {
}
}
}