diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/SpelNodeImpl.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/SpelNodeImpl.java index 5b0e2138c16..6a0b3159747 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/SpelNodeImpl.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/SpelNodeImpl.java @@ -62,7 +62,7 @@ public abstract class SpelNodeImpl implements SpelNode { SpelNodeImpl result = null; if (this.parent != null) { for (SpelNodeImpl child : this.parent.children) { - if (this==child) { + if (this == child) { break; } result = child; @@ -78,18 +78,16 @@ public abstract class SpelNodeImpl implements SpelNode { if (this.parent != null) { SpelNodeImpl[] peers = this.parent.children; for (int i = 0, max = peers.length; i < max; i++) { - if (peers[i] == this) { - if ((i + 1) >= max) { + if (this == peers[i]) { + if (i + 1 >= max) { return false; } - Class clazz = peers[i + 1].getClass(); for (Class desiredClazz : clazzes) { if (clazz.equals(desiredClazz)) { return true; } } - return false; } } diff --git a/spring-jms/src/main/java/org/springframework/jms/listener/DefaultMessageListenerContainer.java b/spring-jms/src/main/java/org/springframework/jms/listener/DefaultMessageListenerContainer.java index baa9666da53..7862530dfc6 100644 --- a/spring-jms/src/main/java/org/springframework/jms/listener/DefaultMessageListenerContainer.java +++ b/spring-jms/src/main/java/org/springframework/jms/listener/DefaultMessageListenerContainer.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2014 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,7 +40,7 @@ import org.springframework.util.ClassUtils; * Message listener container variant that uses plain JMS client APIs, specifically * a loop of {@code MessageConsumer.receive()} calls that also allow for * transactional reception of messages (registering them with XA transactions). - * Designed to work in a native JMS environment as well as in a J2EE environment, + * Designed to work in a native JMS environment as well as in a Java EE environment, * with only minimal differences in configuration. * *

This is a simple but nevertheless powerful form of message listener container. @@ -57,7 +57,7 @@ import org.springframework.util.ClassUtils; * abstraction. By default, the specified number of invoker tasks will be created * on startup, according to the {@link #setConcurrentConsumers "concurrentConsumers"} * setting. Specify an alternative {@code TaskExecutor} to integrate with an existing - * thread pool facility (such as a J2EE server's), for example using a + * thread pool facility (such as a Java EE server's), for example using a * {@link org.springframework.scheduling.commonj.WorkManagerTaskExecutor CommonJ WorkManager}. * With a native JMS setup, each of those listener threads is going to use a * cached JMS {@code Session} and {@code MessageConsumer} (only refreshed in case @@ -68,11 +68,11 @@ import org.springframework.util.ClassUtils; * {@link org.springframework.transaction.PlatformTransactionManager} into the * {@link #setTransactionManager "transactionManager"} property. This will usually * be a {@link org.springframework.transaction.jta.JtaTransactionManager} in a - * J2EE environment, in combination with a JTA-aware JMS {@code ConnectionFactory} - * obtained from JNDI (check your J2EE server's documentation). Note that this + * Java EE environment, in combination with a JTA-aware JMS {@code ConnectionFactory} + * obtained from JNDI (check your Java EE server's documentation). Note that this * listener container will automatically reobtain all JMS handles for each transaction * in case an external transaction manager is specified, for compatibility with - * all J2EE servers (in particular JBoss). This non-caching behavior can be + * all Java EE servers (in particular JBoss). This non-caching behavior can be * overridden through the {@link #setCacheLevel "cacheLevel"} / * {@link #setCacheLevelName "cacheLevelName"} property, enforcing caching of * the {@code Connection} (or also {@code Session} and {@code MessageConsumer}) @@ -206,7 +206,7 @@ public class DefaultMessageListenerContainer extends AbstractPollingMessageListe * of concurrent consumers. *

Specify an alternative {@code TaskExecutor} for integration with an existing * thread pool. Note that this really only adds value if the threads are - * managed in a specific fashion, for example within a J2EE environment. + * managed in a specific fashion, for example within a Java EE environment. * A plain thread pool does not add much value, as this listener container * will occupy a number of threads for its entire lifetime. * @see #setConcurrentConsumers @@ -243,12 +243,13 @@ public class DefaultMessageListenerContainer extends AbstractPollingMessageListe *

Default is {@link #CACHE_NONE} if an external transaction manager has been specified * (to reobtain all resources freshly within the scope of the external transaction), * and {@link #CACHE_CONSUMER} otherwise (operating with local JMS resources). - *

Some J2EE servers only register their JMS resources with an ongoing XA + *

Some Java EE servers only register their JMS resources with an ongoing XA * transaction in case of a freshly obtained JMS {@code Connection} and {@code Session}, * which is why this listener container by default does not cache any of those. - * However, if you want to optimize for a specific server, consider switching - * this setting to at least {@link #CACHE_CONNECTION} or {@link #CACHE_SESSION} - * even in conjunction with an external transaction manager. + * However, depending on the rules of your server with respect to the caching + * of transactional resources, consider switching this setting to at least + * {@link #CACHE_CONNECTION} or {@link #CACHE_SESSION} even in conjunction with an + * external transaction manager. * @see #CACHE_NONE * @see #CACHE_CONNECTION * @see #CACHE_SESSION diff --git a/spring-web/src/main/java/org/springframework/http/converter/FormHttpMessageConverter.java b/spring-web/src/main/java/org/springframework/http/converter/FormHttpMessageConverter.java index a881340cad1..e1815538496 100644 --- a/spring-web/src/main/java/org/springframework/http/converter/FormHttpMessageConverter.java +++ b/spring-web/src/main/java/org/springframework/http/converter/FormHttpMessageConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2014 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. @@ -42,42 +42,55 @@ import org.springframework.util.StreamUtils; import org.springframework.util.StringUtils; /** - * Implementation of {@link HttpMessageConverter} that can handle form data, including multipart form data (i.e. file - * uploads). + * Implementation of {@link HttpMessageConverter} to read and write 'normal' HTML + * forms and also to write (but not read) multipart data (e.g. 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 the + * {@code "application/x-www-form-urlencoded"} media type as + * {@link MultiValueMap MultiValueMap<String, String>} and it can also + * write (but not read) the {@code "multipart/form-data"} media type as + * {@link MultiValueMap MultiValueMap<String, Object>}. * - *

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. + *

When writing multipart data, this converter uses other + * {@link HttpMessageConverter HttpMessageConverters} to write the respective + * MIME parts. By default, basic converters are registered (for {@code Strings} + * and {@code Resources}). These can be overridden through the + * {@link #setPartConverters 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);
+ * 
* - *

Some methods in this class were inspired by {@code org.apache.commons.httpclient.methods.multipart.MultipartRequestEntity}. + *

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 + * {@code org.apache.commons.httpclient.methods.multipart.MultipartRequestEntity}. * * @author Arjen Poutsma - * @see MultiValueMap + * @author Rossen Stoyanchev * @since 3.0 + * @see MultiValueMap */ public class FormHttpMessageConverter implements HttpMessageConverter> { private static final byte[] BOUNDARY_CHARS = - new byte[]{'-', '_', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', 'a', 'b', 'c', 'd', 'e', 'f', 'g', + 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'}; - private final Random rnd = new Random(); private Charset charset = Charset.forName("UTF-8"); @@ -85,6 +98,8 @@ public class FormHttpMessageConverter implements HttpMessageConverter> partConverters = new ArrayList>(); + private final Random random = new Random(); + public FormHttpMessageConverter() { this.supportedMediaTypes.add(MediaType.APPLICATION_FORM_URLENCODED); @@ -99,28 +114,45 @@ public class FormHttpMessageConverter implements HttpMessageConverterBy default this is set to "UTF-8". */ - public final void setPartConverters(List> partConverters) { - Assert.notEmpty(partConverters, "'partConverters' must not be empty"); - this.partConverters = partConverters; + public void setCharset(Charset charset) { + this.charset = charset; } /** - * Add a message body converter. Such a converters is used to convert objects to MIME parts. + * Set the list of {@link MediaType} objects supported by this converter. */ - public final void addPartConverter(HttpMessageConverter partConverter) { - Assert.notNull(partConverter, "'partConverter' must not be NULL"); - this.partConverters.add(partConverter); + public void setSupportedMediaTypes(List supportedMediaTypes) { + this.supportedMediaTypes = supportedMediaTypes; + } + + @Override + public List getSupportedMediaTypes() { + return Collections.unmodifiableList(this.supportedMediaTypes); } /** - * Sets the character set used for writing form data. + * Set the message body converters to use. These converters are used to + * convert objects to MIME parts. */ - public void setCharset(Charset charset) { - this.charset = charset; + public void setPartConverters(List> partConverters) { + Assert.notEmpty(partConverters, "'partConverters' must not be empty"); + this.partConverters = partConverters; + } + + /** + * Add a message body converter. Such a converter is used to convert objects + * to MIME parts. + */ + public void addPartConverter(HttpMessageConverter partConverter) { + Assert.notNull(partConverter, "'partConverter' must not be null"); + this.partConverters.add(partConverter); } + @Override public boolean canRead(Class clazz, MediaType mediaType) { if (!MultiValueMap.class.isAssignableFrom(clazz)) { @@ -130,9 +162,8 @@ public class FormHttpMessageConverter implements HttpMessageConverter supportedMediaTypes) { - this.supportedMediaTypes = supportedMediaTypes; - } - - @Override - public List getSupportedMediaTypes() { - return Collections.unmodifiableList(this.supportedMediaTypes); - } - @Override public MultiValueMap read(Class> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException { MediaType contentType = inputMessage.getHeaders().getContentType(); - Charset charset = contentType.getCharSet() != null ? contentType.getCharSet() : this.charset; + Charset charset = (contentType.getCharSet() != null ? contentType.getCharSet() : this.charset); String body = StreamUtils.copyToString(inputMessage.getBody(), charset); String[] pairs = StringUtils.tokenizeToStringArray(body, "&"); - MultiValueMap result = new LinkedMultiValueMap(pairs.length); - for (String pair : pairs) { int idx = pair.indexOf('='); if (idx == -1) { @@ -197,6 +214,7 @@ public class FormHttpMessageConverter implements HttpMessageConverter map, MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException { + if (!isMultipart(map, contentType)) { writeForm((MultiValueMap) map, contentType, outputMessage); } @@ -205,6 +223,7 @@ public class FormHttpMessageConverter implements HttpMessageConverter map, MediaType contentType) { if (contentType != null) { return MediaType.MULTIPART_FORM_DATA.equals(contentType); @@ -221,6 +240,7 @@ public class FormHttpMessageConverter implements HttpMessageConverter form, MediaType contentType, HttpOutputMessage outputMessage) throws IOException { + Charset charset; if (contentType != null) { outputMessage.getHeaders().setContentType(contentType); @@ -253,16 +273,15 @@ 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")); + MediaType contentType = new MediaType(MediaType.MULTIPART_FORM_DATA, parameters); outputMessage.getHeaders().setContentType(contentType); writeParts(outputMessage.getBody(), parts, boundary); - writeEnd(boundary, outputMessage.getBody()); + writeEnd(outputMessage.getBody(), boundary); } private void writeParts(OutputStream os, MultiValueMap parts, byte[] boundary) throws IOException { @@ -270,66 +289,35 @@ public class FormHttpMessageConverter implements HttpMessageConverter entity = getEntity(part); - writePart(name, entity, os); + writeBoundary(os, boundary); + writePart(name, getHttpEntity(part), os); writeNewLine(os); } } } } - private void writeBoundary(byte[] boundary, OutputStream os) throws IOException { - os.write('-'); - os.write('-'); - os.write(boundary); - writeNewLine(os); - } - - private HttpEntity getEntity(Object part) { - if (part instanceof HttpEntity) { - return (HttpEntity) part; - } - else { - return new HttpEntity(part); - } - } - @SuppressWarnings("unchecked") private void writePart(String name, HttpEntity partEntity, OutputStream os) throws IOException { Object partBody = partEntity.getBody(); Class partType = partBody.getClass(); HttpHeaders partHeaders = partEntity.getHeaders(); MediaType partContentType = partHeaders.getContentType(); - for (HttpMessageConverter messageConverter : partConverters) { + for (HttpMessageConverter messageConverter : this.partConverters) { if (messageConverter.canWrite(partType, partContentType)) { - HttpOutputMessage multipartOutputMessage = new MultipartHttpOutputMessage(os); - multipartOutputMessage.getHeaders().setContentDispositionFormData(name, getFilename(partBody)); + HttpOutputMessage multipartMessage = new MultipartHttpOutputMessage(os); + multipartMessage.getHeaders().setContentDispositionFormData(name, getFilename(partBody)); if (!partHeaders.isEmpty()) { - multipartOutputMessage.getHeaders().putAll(partHeaders); + multipartMessage.getHeaders().putAll(partHeaders); } - ((HttpMessageConverter) messageConverter).write(partBody, partContentType, multipartOutputMessage); + ((HttpMessageConverter) messageConverter).write(partBody, partContentType, multipartMessage); return; } } - throw new HttpMessageNotWritableException( - "Could not write request: no suitable HttpMessageConverter found for request type [" + - partType.getName() + "]"); - } - - private void writeEnd(byte[] boundary, OutputStream os) throws IOException { - os.write('-'); - os.write('-'); - os.write(boundary); - os.write('-'); - os.write('-'); - writeNewLine(os); + throw new HttpMessageNotWritableException("Could not write request: no suitable HttpMessageConverter " + + "found for request type [" + partType.getName() + "]"); } - private void writeNewLine(OutputStream os) throws IOException { - os.write('\r'); - os.write('\n'); - } /** * Generate a multipart boundary. @@ -337,13 +325,28 @@ public class FormHttpMessageConverter implements HttpMessageConverter getHttpEntity(Object part) { + if (part instanceof HttpEntity) { + return (HttpEntity) part; + } + else { + return new HttpEntity(part); + } + } + /** * Return the filename of the given multipart part. This value will be used for the * {@code Content-Disposition} header. @@ -363,30 +366,53 @@ public class FormHttpMessageConverter implements HttpMessageConverter