diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/AnnotationDrivenBeanDefinitionParser.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/AnnotationDrivenBeanDefinitionParser.java index d274a3a04f594a1a594e8ba319ac2248982b66d5..f344d96138a9c9303dad9726e3134f44c1a8fed0 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/AnnotationDrivenBeanDefinitionParser.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/AnnotationDrivenBeanDefinitionParser.java @@ -30,7 +30,10 @@ import org.springframework.core.convert.ConversionService; import org.springframework.format.support.FormattingConversionServiceFactoryBean; import org.springframework.http.converter.ByteArrayHttpMessageConverter; import org.springframework.http.converter.FormHttpMessageConverter; +import org.springframework.http.converter.ResourceHttpMessageConverter; import org.springframework.http.converter.StringHttpMessageConverter; +import org.springframework.http.converter.feed.AtomFeedHttpMessageConverter; +import org.springframework.http.converter.feed.RssChannelHttpMessageConverter; import org.springframework.http.converter.json.MappingJacksonHttpMessageConverter; import org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter; import org.springframework.http.converter.xml.SourceHttpMessageConverter; @@ -82,6 +85,8 @@ class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser { ClassUtils.isPresent("org.codehaus.jackson.map.ObjectMapper", AnnotationDrivenBeanDefinitionParser.class.getClassLoader()) && ClassUtils.isPresent("org.codehaus.jackson.JsonGenerator", AnnotationDrivenBeanDefinitionParser.class.getClassLoader()); + private static boolean romePresent = + ClassUtils.isPresent("com.sun.syndication.feed.WireFeed", AnnotationDrivenBeanDefinitionParser.class.getClassLoader()); public BeanDefinition parse(Element element, ParserContext parserContext) { @@ -167,6 +172,7 @@ class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser { messageConverters.setSource(source); messageConverters.add(new RootBeanDefinition(ByteArrayHttpMessageConverter.class)); messageConverters.add(new RootBeanDefinition(StringHttpMessageConverter.class)); + messageConverters.add(new RootBeanDefinition(ResourceHttpMessageConverter.class)); messageConverters.add(new RootBeanDefinition(FormHttpMessageConverter.class)); messageConverters.add(new RootBeanDefinition(SourceHttpMessageConverter.class)); if (jaxb2Present) { @@ -175,6 +181,10 @@ class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser { if (jacksonPresent) { messageConverters.add(new RootBeanDefinition(MappingJacksonHttpMessageConverter.class)); } + if (romePresent) { + messageConverters.add(new RootBeanDefinition(AtomFeedHttpMessageConverter.class)); + messageConverters.add(new RootBeanDefinition(RssChannelHttpMessageConverter.class)); + } return messageConverters; } 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 76fca3346eb8309d2ea287a7fb39e314a3eb8498..a1199c5be42eb74d9db5caaf33396bbdc90b5774 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 @@ -64,6 +64,8 @@ public class HttpHeaders implements MultiValueMap { private static final String CACHE_CONTROL = "Cache-Control"; + private static final String CONTENT_DISPOSITION = "Content-Disposition"; + private static final String CONTENT_LENGTH = "Content-Length"; private static final String CONTENT_TYPE = "Content-Type"; @@ -95,6 +97,7 @@ public class HttpHeaders implements MultiValueMap { private final Map> headers; + /** * Private constructor that can create read-only {@code HttpHeader} instances. */ @@ -229,6 +232,22 @@ public class HttpHeaders implements MultiValueMap { return getFirst(CACHE_CONTROL); } + /** + * Sets the (new) value of the {@code Content-Disposition} header for {@code form-data}. + * @param name the control name + * @param filename the filename, may be {@code null} + */ + public void setContentDispositionFormData(String name, String filename) { + Assert.notNull(name, "'name' must not be null"); + StringBuilder builder = new StringBuilder("form-data; name=\""); + builder.append(name).append('\"'); + if (filename != null) { + builder.append("; filename=\""); + builder.append(filename).append('\"'); + } + set(CONTENT_DISPOSITION, builder.toString()); + } + /** * Set the length of the body in bytes, as specified by the {@code Content-Length} header. * @param contentLength the content length 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 c92d71815cca17318111277368de35cf230c126a..e85b8ff91817a927bc4a0e4b678c8c1248b6c8fa 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 @@ -49,10 +49,75 @@ import org.springframework.util.StringUtils; public class MediaType implements Comparable { /** - * Public constant that includes all media ranges (i.e. */*). + * Public constant media type that includes all media ranges (i.e. */*). */ public static final MediaType ALL; + /** + * Public constant media type for {@code application/atom+xml}. + */ + public final static MediaType APPLICATION_ATOM_XML; + + /** + * Public constant media type for {@code application/x-www-form-urlencoded}. + * */ + public final static MediaType APPLICATION_FORM_URLENCODED; + + /** + * Public constant media type for {@code application/json}. + * */ + public final static MediaType APPLICATION_JSON; + + /** + * Public constant media type for {@code application/octet-stream}. + * */ + public final static MediaType APPLICATION_OCTET_STREAM; + + /** + * Public constant media type for {@code application/xhtml+xml}. + * */ + public final static MediaType APPLICATION_XHTML_XML; + + /** + * Public constant media type for {@code image/gif}. + */ + public final static MediaType IMAGE_GIF; + + /** + * Public constant media type for {@code image/jpeg}. + */ + public final static MediaType IMAGE_JPEG; + + /** + * Public constant media type for {@code image/png}. + */ + public final static MediaType IMAGE_PNG; + + /** + * Public constant media type for {@code image/xml}. + */ + public final static MediaType APPLICATION_XML; + + /** + * Public constant media type for {@code multipart/form-data}. + * */ + public final static MediaType MULTIPART_FORM_DATA; + + /** + * Public constant media type for {@code text/html}. + * */ + public final static MediaType TEXT_HTML; + + /** + * Public constant media type for {@code text/plain}. + * */ + public final static MediaType TEXT_PLAIN; + + /** + * Public constant media type for {@code text/xml}. + * */ + public final static MediaType TEXT_XML; + private static final BitSet TOKEN; private static final String WILDCARD_TYPE = "*"; @@ -104,6 +169,19 @@ public class MediaType implements Comparable { TOKEN.andNot(separators); ALL = new MediaType("*", "*"); + APPLICATION_ATOM_XML = new MediaType("application","atom+xml"); + APPLICATION_FORM_URLENCODED = new MediaType("application","x-www-form-urlencoded"); + APPLICATION_JSON = new MediaType("application","json"); + APPLICATION_OCTET_STREAM = new MediaType("application","octet-stream"); + APPLICATION_XHTML_XML = new MediaType("application","xhtml+xml"); + APPLICATION_XML = new MediaType("application","xml"); + IMAGE_GIF = new MediaType("image", "gif"); + IMAGE_JPEG = new MediaType("image", "jpeg"); + IMAGE_PNG = new MediaType("image", "png"); + MULTIPART_FORM_DATA = new MediaType("multipart","form-data"); + TEXT_HTML = new MediaType("text","html"); + TEXT_PLAIN = new MediaType("text","plain"); + TEXT_XML = new MediaType("text","xml"); } /** @@ -153,12 +231,24 @@ public class MediaType implements Comparable { this(type, subtype, Collections.singletonMap(PARAM_QUALITY_FACTOR, Double.toString(qualityValue))); } + /** + * Copy-constructor that copies the type and subtype of the given {@link MediaType}, and allows for different + * parameter. + * + * @param other the other media type + * @param parameters the parameters, may be null + * @throws IllegalArgumentException if any of the parameters contain illegal characters + */ + public MediaType(MediaType other, Map parameters) { + this(other.getType(), other.getSubtype(), parameters); + } + /** * Create a new {@link MediaType} for the given type, subtype, and parameters. * * @param type the primary type * @param subtype the subtype - * @param parameters the parameters, mat be null + * @param parameters the parameters, may be null * @throws IllegalArgumentException if any of the parameters contain illegal characters */ public MediaType(String type, String subtype, Map parameters) { diff --git a/org.springframework.web/src/main/java/org/springframework/http/converter/ResourceHttpMessageConverter.java b/org.springframework.web/src/main/java/org/springframework/http/converter/ResourceHttpMessageConverter.java new file mode 100644 index 0000000000000000000000000000000000000000..5be9ef939a5d6e8c554032e8b9b7b3765c2992f3 --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/http/converter/ResourceHttpMessageConverter.java @@ -0,0 +1,147 @@ +/* + * Copyright 2002-2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.http.converter; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Collections; +import java.util.List; +import javax.activation.FileTypeMap; +import javax.activation.MimetypesFileTypeMap; + +import org.springframework.core.io.ByteArrayResource; +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.Resource; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpInputMessage; +import org.springframework.http.HttpOutputMessage; +import org.springframework.http.MediaType; +import org.springframework.util.ClassUtils; +import org.springframework.util.FileCopyUtils; +import org.springframework.util.StringUtils; + +/** + * Implementation of {@link HttpMessageConverter} that can read and write {@link Resource Resources}. + * + *

By default, this converter can read all media types. The Java Activation Framework (JAF) - if available - is used + * to determine the {@code Content-Type} of written resources. If JAF is not available, {@code application/octet-stream} + * is used. + * + * @author Arjen Poutsma + * @since 3.0.2 + */ +public class ResourceHttpMessageConverter implements HttpMessageConverter { + + private static final boolean jafPresent = + ClassUtils.isPresent("javax.activation.FileTypeMap", ResourceHttpMessageConverter.class.getClassLoader()); + + public boolean canRead(Class clazz, MediaType mediaType) { + return Resource.class.isAssignableFrom(clazz); + } + + public boolean canWrite(Class clazz, MediaType mediaType) { + return Resource.class.isAssignableFrom(clazz); + } + + public List getSupportedMediaTypes() { + return Collections.singletonList(MediaType.ALL); + } + + public Resource read(Class clazz, HttpInputMessage inputMessage) + throws IOException, HttpMessageNotReadableException { + byte[] body = FileCopyUtils.copyToByteArray(inputMessage.getBody()); + return new ByteArrayResource(body); + } + + public void write(Resource resource, MediaType contentType, HttpOutputMessage outputMessage) + throws IOException, HttpMessageNotWritableException { + HttpHeaders headers = outputMessage.getHeaders(); + if (contentType == null || contentType.isWildcardType() || contentType.isWildcardSubtype()) { + contentType = getContentType(resource); + } + if (contentType != null) { + headers.setContentType(contentType); + } + Long contentLength = getContentLength(resource, contentType); + if (contentLength != null) { + headers.setContentLength(contentLength); + } + FileCopyUtils.copy(resource.getInputStream(), outputMessage.getBody()); + outputMessage.getBody().flush(); + } + + private MediaType getContentType(Resource resource) { + if (jafPresent) { + return ActivationMediaTypeFactory.getMediaType(resource); + } else { + return MediaType.APPLICATION_OCTET_STREAM; + } + } + + protected Long getContentLength(Resource resource, MediaType contentType) { + try { + return resource.getFile().length(); + } + catch (IOException e) { + return null; + } + } + + /** + * Inner class to avoid hard-coded JAF dependency. + */ + private static class ActivationMediaTypeFactory { + + private static final FileTypeMap fileTypeMap; + + static { + fileTypeMap = loadFileTypeMapFromContextSupportModule(); + } + + private static FileTypeMap loadFileTypeMapFromContextSupportModule() { + // see if we can find the extended mime.types from the context-support module + Resource mappingLocation = new ClassPathResource("org/springframework/mail/javamail/mime.types"); + if (mappingLocation.exists()) { + InputStream inputStream = null; + try { + inputStream = mappingLocation.getInputStream(); + return new MimetypesFileTypeMap(inputStream); + } + catch (IOException ex) { + // ignore + } + finally { + if (inputStream != null) { + try { + inputStream.close(); + } + catch (IOException ex) { + // ignore + } + } + } + } + return FileTypeMap.getDefaultFileTypeMap(); + } + + public static MediaType getMediaType(Resource resource) { + String mediaType = fileTypeMap.getContentType(resource.getFilename()); + return StringUtils.hasText(mediaType) ? MediaType.parseMediaType(mediaType) : null; + } + } + +} diff --git a/org.springframework.web/src/main/java/org/springframework/http/converter/StringHttpMessageConverter.java b/org.springframework.web/src/main/java/org/springframework/http/converter/StringHttpMessageConverter.java index a71c88ce3e468c7de4e2ab44688920e93595d357..2b2ba6f7f761f47c8cc74ae37dcc718287a96af5 100644 --- a/org.springframework.web/src/main/java/org/springframework/http/converter/StringHttpMessageConverter.java +++ b/org.springframework.web/src/main/java/org/springframework/http/converter/StringHttpMessageConverter.java @@ -45,11 +45,21 @@ public class StringHttpMessageConverter extends AbstractHttpMessageConverter availableCharsets; + private boolean writeAcceptCharset = true; + public StringHttpMessageConverter() { super(new MediaType("text", "plain", DEFAULT_CHARSET), MediaType.ALL); this.availableCharsets = new ArrayList(Charset.availableCharsets().values()); } + /** + * Indicates whether the {@code Accept-Charset} should be written to any outgoing request. + *

Default is {@code true}. + */ + public void setWriteAcceptCharset(boolean writeAcceptCharset) { + this.writeAcceptCharset = writeAcceptCharset; + } + @Override public boolean supports(Class clazz) { return String.class.equals(clazz); @@ -81,7 +91,9 @@ public class StringHttpMessageConverter extends AbstractHttpMessageConverterInspired by {@link org.apache.commons.httpclient.methods.multipart.MultipartRequestEntity}. - * - * @author Arjen Poutsma - * @since 3.0.2 - */ -abstract class AbstractPart implements Part { - - private static final byte[] CONTENT_DISPOSITION = - new byte[]{'C', 'o', 'n', 't', 'e', 'n', 't', '-', 'D', 'i', 's', 'p', 'o', 's', 'i', 't', 'i', 'o', 'n', - ':', ' ', 'f', 'o', 'r', 'm', '-', 'd', 'a', 't', 'a', ';', ' ', 'n', 'a', 'm', 'e', '='}; - - private static final byte[] CONTENT_TYPE = - new byte[]{'C', 'o', 'n', 't', 'e', 'n', 't', '-', 'T', 'y', 'p', 'e', ':', ' '}; - - private final MediaType contentType; - - protected AbstractPart(MediaType contentType) { - Assert.notNull(contentType, "'contentType' must not be null"); - this.contentType = contentType; - } - - public final void write(byte[] boundary, String name, OutputStream os) throws IOException { - writeBoundary(boundary, os); - writeContentDisposition(name, os); - writeContentType(os); - writeEndOfHeader(os); - writeData(os); - writeEnd(os); - } - - protected void writeBoundary(byte[] boundary, OutputStream os) throws IOException { - os.write('-'); - os.write('-'); - os.write(boundary); - writeNewLine(os); - } - - protected void writeContentDisposition(String name, OutputStream os) throws IOException { - os.write(CONTENT_DISPOSITION); - os.write('"'); - os.write(getAsciiBytes(name)); - os.write('"'); - } - - protected void writeContentType(OutputStream os) throws IOException { - writeNewLine(os); - os.write(CONTENT_TYPE); - os.write(getAsciiBytes(contentType.toString())); - } - - protected byte[] getAsciiBytes(String name) { - try { - return name.getBytes("US-ASCII"); - } - catch (UnsupportedEncodingException ex) { - // should not happen, US-ASCII is always supported - throw new IllegalStateException(ex); - } - } - - protected void writeEndOfHeader(OutputStream os) throws IOException { - writeNewLine(os); - writeNewLine(os); - } - - protected void writeEnd(OutputStream os) throws IOException { - writeNewLine(os); - } - - private void writeNewLine(OutputStream os) throws IOException { - os.write('\r'); - os.write('\n'); - } - - protected abstract void writeData(OutputStream os) throws IOException; - -} diff --git a/org.springframework.web/src/main/java/org/springframework/http/converter/multipart/ByteArrayPart.java b/org.springframework.web/src/main/java/org/springframework/http/converter/multipart/ByteArrayPart.java deleted file mode 100644 index d1d81f5f4d7df1074d2f0a7d2a27c5d35d84ba5e..0000000000000000000000000000000000000000 --- a/org.springframework.web/src/main/java/org/springframework/http/converter/multipart/ByteArrayPart.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2002-2010 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.http.converter.multipart; - -import java.io.IOException; -import java.io.OutputStream; - -import org.springframework.http.MediaType; -import org.springframework.util.Assert; -import org.springframework.util.FileCopyUtils; - -/** - * @author Arjen Poutsma - * @since 3.0.2 - */ -class ByteArrayPart extends AbstractPart { - - private final byte[] value; - - public ByteArrayPart(byte[] value, MediaType contentType) { - super(contentType); - Assert.isTrue(value != null && value.length != 0, "'value' must not be null"); - this.value = value; - } - - @Override - protected void writeData(OutputStream os) throws IOException { - FileCopyUtils.copy(value, os); - } -} diff --git a/org.springframework.web/src/main/java/org/springframework/http/converter/multipart/MultipartHttpMessageConverter.java b/org.springframework.web/src/main/java/org/springframework/http/converter/multipart/MultipartHttpMessageConverter.java index edcd7737d5c8c6d202e13873fa0312950728b373..8caabe740cab41f4d64b7423aff51c3237e88605 100644 --- a/org.springframework.web/src/main/java/org/springframework/http/converter/multipart/MultipartHttpMessageConverter.java +++ b/org.springframework.web/src/main/java/org/springframework/http/converter/multipart/MultipartHttpMessageConverter.java @@ -18,26 +18,41 @@ package org.springframework.http.converter.multipart; import java.io.IOException; import java.io.OutputStream; +import java.io.UnsupportedEncodingException; +import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Random; +import org.springframework.core.io.Resource; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpInputMessage; import org.springframework.http.HttpOutputMessage; import org.springframework.http.MediaType; -import org.springframework.http.converter.AbstractHttpMessageConverter; +import org.springframework.http.converter.ByteArrayHttpMessageConverter; +import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.HttpMessageNotReadableException; import org.springframework.http.converter.HttpMessageNotWritableException; +import org.springframework.http.converter.ResourceHttpMessageConverter; +import org.springframework.http.converter.StringHttpMessageConverter; +import org.springframework.http.converter.xml.SourceHttpMessageConverter; +import org.springframework.util.Assert; /** + * Implementation of {@link org.springframework.http.converter.HttpMessageConverter} that can write multipart form data + * (i.e. file uploads). + * + *

This converter writes the media type ({@code multipart/form-data}). Multipart form data is provided as + * a {@link MultipartMap}. + * *

Inspired by {@link org.apache.commons.httpclient.methods.multipart.MultipartRequestEntity}. * * @author Arjen Poutsma + * @see MultipartMap * @since 3.0.2 */ -public class MultipartHttpMessageConverter extends AbstractHttpMessageConverter { +public class MultipartHttpMessageConverter implements HttpMessageConverter { private static final byte[] BOUNDARY_CHARS = new byte[]{'-', '_', @@ -47,39 +62,126 @@ public class MultipartHttpMessageConverter extends AbstractHttpMessageConverter< private final Random rnd = new Random(); + private List> partConverters = new ArrayList>(); + public MultipartHttpMessageConverter() { - super(new MediaType("multipart", "form-data")); + this.partConverters.add(new ByteArrayHttpMessageConverter()); + StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter(); + stringHttpMessageConverter.setWriteAcceptCharset(false); + this.partConverters.add(stringHttpMessageConverter); + this.partConverters.add(new ResourceHttpMessageConverter()); + this.partConverters.add(new SourceHttpMessageConverter()); + } + + /** + * Set the message body converters to use. These converters are used to convert to MIME parts. + */ + public void setPartConverters(List> partConverters) { + Assert.notEmpty(partConverters, "'messageConverters' must not be empty"); + this.partConverters = partConverters; } - @Override - protected boolean supports(Class clazz) { - return MultipartMap.class.isAssignableFrom(clazz); + /** + * Returns {@code false}, as reading multipart data is not supported. + */ + public boolean canRead(Class clazz, MediaType mediaType) { + return false; } - @Override - protected void writeInternal(MultipartMap map, HttpOutputMessage outputMessage) + public boolean canWrite(Class clazz, MediaType mediaType) { + if (!MultipartMap.class.isAssignableFrom(clazz)) { + return false; + } + if (mediaType != null) { + return mediaType.includes(MediaType.MULTIPART_FORM_DATA); + } else { + return true; + } + } + + public List getSupportedMediaTypes() { + return Collections.singletonList(MediaType.MULTIPART_FORM_DATA); + } + + public MultipartMap read(Class clazz, HttpInputMessage inputMessage) + throws IOException, HttpMessageNotReadableException { + throw new UnsupportedOperationException(); + } + + public void write(MultipartMap map, MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException { + byte[] boundary = generateBoundary(); + HttpHeaders headers = outputMessage.getHeaders(); - MediaType contentType = headers.getContentType(); - if (contentType != null) { - String boundaryString = new String(boundary, "US-ASCII"); - Map params = Collections.singletonMap("boundary", boundaryString); - contentType = new MediaType(contentType.getType(), contentType.getSubtype(), params); - headers.setContentType(contentType); - } OutputStream os = outputMessage.getBody(); - for (Map.Entry> entry : map.entrySet()) { + + setContentType(headers, boundary); + writeParts(os, map, boundary); + writeEnd(boundary, os); + } + + private void setContentType(HttpHeaders headers, byte[] boundary) throws UnsupportedEncodingException { + Map parameters = Collections.singletonMap("boundary", new String(boundary, "US-ASCII")); + MediaType contentType = new MediaType(MediaType.MULTIPART_FORM_DATA, parameters); + headers.setContentType(contentType); + } + + private void writeParts(OutputStream os, MultipartMap map, byte[] boundary) + throws IOException { + for (Map.Entry> entry : map.entrySet()) { String name = entry.getKey(); - for (Part part : entry.getValue()) { - part.write(boundary, name, os); + for (Object part : entry.getValue()) { + writeBoundary(boundary, os); + writePart(name, part, os); + writeNewLine(os); } } + } + + private void writeBoundary(byte[] boundary, OutputStream os) throws IOException { + os.write('-'); + os.write('-'); + os.write(boundary); + writeNewLine(os); + } + + @SuppressWarnings("unchecked") + private void writePart(String name, Object part, OutputStream os) throws IOException { + Class partType = part.getClass(); + for (HttpMessageConverter messageConverter : partConverters) { + if (messageConverter.canWrite(partType, null)) { + HttpOutputMessage multipartOutputMessage = new MultipartHttpOutputMessage(os); + multipartOutputMessage.getHeaders().setContentDispositionFormData(name, getFileName(part)); + messageConverter.write(part, null, multipartOutputMessage); + return; + } + } + throw new HttpMessageNotWritableException( + "Could not write request: no suitable HttpMessageConverter found for request type [" + + partType.getName() + "]"); + } + + protected String getFileName(Object part) { + if (part instanceof Resource) { + Resource resource = (Resource) part; + return resource.getFilename(); + } + else { + return null; + } + } + + private void writeEnd(byte[] boundary, OutputStream os) throws IOException { os.write('-'); os.write('-'); os.write(boundary); os.write('-'); os.write('-'); + writeNewLine(os); + } + + private void writeNewLine(OutputStream os) throws IOException { os.write('\r'); os.write('\n'); } @@ -99,15 +201,4 @@ public class MultipartHttpMessageConverter extends AbstractHttpMessageConverter< return boundary; } - @Override - public boolean canRead(Class clazz, MediaType mediaType) { - // reading not supported yet - return false; - } - - @Override - protected MultipartMap readInternal(Class clazz, HttpInputMessage inputMessage) - throws IOException, HttpMessageNotReadableException { - throw new UnsupportedOperationException(); - } } diff --git a/org.springframework.web/src/main/java/org/springframework/http/converter/multipart/MultipartHttpOutputMessage.java b/org.springframework.web/src/main/java/org/springframework/http/converter/multipart/MultipartHttpOutputMessage.java new file mode 100644 index 0000000000000000000000000000000000000000..31bdc8b799a23d5968598a78fdb6be54a29540f5 --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/http/converter/multipart/MultipartHttpOutputMessage.java @@ -0,0 +1,89 @@ +/* + * Copyright 2002-2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.http.converter.multipart; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; +import java.util.List; +import java.util.Map; + +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpOutputMessage; + +/** + * Implementation of {@link HttpOutputMessage} used for writing multipart data. + * + * @author Arjen Poutsma + * @since 3.0.2 + */ +class MultipartHttpOutputMessage implements HttpOutputMessage { + + private final HttpHeaders headers = new HttpHeaders(); + + private final OutputStream os; + + private boolean headersWritten = false; + + public MultipartHttpOutputMessage(OutputStream os) { + this.os = os; + } + + public HttpHeaders getHeaders() { + return headersWritten ? HttpHeaders.readOnlyHttpHeaders(headers) : this.headers; + } + + public OutputStream getBody() throws IOException { + writeHeaders(); + return this.os; + } + + private void writeHeaders() throws IOException { + if (!this.headersWritten) { + for (Map.Entry> entry : this.headers.entrySet()) { + byte[] headerName = getAsciiBytes(entry.getKey()); + for (String headerValueString : entry.getValue()) { + byte[] headerValue = getAsciiBytes(headerValueString); + os.write(headerName); + os.write(':'); + os.write(' '); + os.write(headerValue); + writeNewLine(os); + } + } + writeNewLine(os); + this.headersWritten = true; + } + } + + private void writeNewLine(OutputStream os) throws IOException { + os.write('\r'); + os.write('\n'); + } + + protected byte[] getAsciiBytes(String name) { + try { + return name.getBytes("US-ASCII"); + } + catch (UnsupportedEncodingException ex) { + // should not happen, US-ASCII is always supported + throw new IllegalStateException(ex); + } + } + + +} diff --git a/org.springframework.web/src/main/java/org/springframework/http/converter/multipart/MultipartMap.java b/org.springframework.web/src/main/java/org/springframework/http/converter/multipart/MultipartMap.java index 40231cd6a8efcdd5e7f1aee874ed771ca6742508..ed2c889848c87b8379a6bc4d266bf2e616414375 100644 --- a/org.springframework.web/src/main/java/org/springframework/http/converter/multipart/MultipartMap.java +++ b/org.springframework.web/src/main/java/org/springframework/http/converter/multipart/MultipartMap.java @@ -16,52 +16,22 @@ package org.springframework.http.converter.multipart; -import java.io.File; -import java.nio.charset.Charset; - -import org.springframework.core.io.FileSystemResource; -import org.springframework.core.io.Resource; -import org.springframework.http.MediaType; -import org.springframework.util.Assert; import org.springframework.util.LinkedMultiValueMap; /** + * Represents HTTP multipart form data, mapping names to parts. + * + *

In addition to the normal methods defined by {@link org.springframework.util.MultiValueMap}, this class offers + * the following convenience methods: + *

    + *
  • {@link #addTextPart} to add a text part (i.e. a form field)
  • + *
  • {@link #addBinaryPart} to add a binary part (i.e. a file)
  • + *
  • {@link #addPart} to add a custom part
  • + *
+ * * @author Arjen Poutsma * @since 3.0.2 */ -public class MultipartMap extends LinkedMultiValueMap { - - public void addTextPart(String name, String value) { - Assert.hasText(name, "'name' must not be empty"); - add(name, new StringPart(value)); - } - - public void addTextPart(String name, String value, Charset charset) { - Assert.hasText(name, "'name' must not be empty"); - add(name, new StringPart(value, charset)); - } - - public void addBinaryPart(String name, Resource resource) { - Assert.hasText(name, "'name' must not be empty"); - add(name, new ResourcePart(resource)); - } - - public void addBinaryPart(Resource resource) { - Assert.notNull(resource, "'resource' must not be null"); - addBinaryPart(resource.getFilename(), resource); - } - - public void addBinaryPart(String name, File file) { - addBinaryPart(name, new FileSystemResource(file)); - } - - public void addBinaryPart(File file) { - addBinaryPart(new FileSystemResource(file)); - } - - public void addPart(String name, byte[] value, MediaType contentType) { - Assert.hasText(name, "'name' must not be empty"); - add(name, new ByteArrayPart(value, contentType)); - } +public class MultipartMap extends LinkedMultiValueMap { } diff --git a/org.springframework.web/src/main/java/org/springframework/http/converter/multipart/Part.java b/org.springframework.web/src/main/java/org/springframework/http/converter/multipart/Part.java deleted file mode 100644 index f60788d99118fb9c16d6cf2efbd8f3728017a71e..0000000000000000000000000000000000000000 --- a/org.springframework.web/src/main/java/org/springframework/http/converter/multipart/Part.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright 2002-2010 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.http.converter.multipart; - -import java.io.IOException; -import java.io.OutputStream; - -/** - * @author Arjen Poutsma - * @since 3.0.2 - */ -public interface Part { - - void write(byte[] boundary, String name, OutputStream os) throws IOException; - -} diff --git a/org.springframework.web/src/main/java/org/springframework/http/converter/multipart/ResourcePart.java b/org.springframework.web/src/main/java/org/springframework/http/converter/multipart/ResourcePart.java deleted file mode 100644 index a6ae5ed61b80029c3448e0c48982449b1608f403..0000000000000000000000000000000000000000 --- a/org.springframework.web/src/main/java/org/springframework/http/converter/multipart/ResourcePart.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2002-2010 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.http.converter.multipart; - -import java.io.IOException; -import java.io.OutputStream; - -import org.springframework.core.io.Resource; -import org.springframework.http.MediaType; -import org.springframework.util.Assert; -import org.springframework.util.FileCopyUtils; -import org.springframework.util.StringUtils; - -/** @author Arjen Poutsma */ -class ResourcePart extends AbstractPart { - - private static final byte[] FILE_NAME = new byte[]{';', ' ', 'f', 'i', 'l', 'e', 'n', 'a', 'm', 'e', '='}; - - private final Resource resource; - - public ResourcePart(Resource resource) { - super(new MediaType("application", "octet-stream")); - Assert.notNull(resource, "'resource' must not be null"); - Assert.isTrue(resource.exists(), "'" + resource + "' does not exist"); - this.resource = resource; - } - - @Override - protected void writeContentDisposition(String name, OutputStream os) throws IOException { - super.writeContentDisposition(name, os); - String filename = resource.getFilename(); - if (StringUtils.hasLength(filename)) { - os.write(FILE_NAME); - os.write('"'); - os.write(getAsciiBytes(filename)); - os.write('"'); - } - } - - @Override - protected void writeData(OutputStream os) throws IOException { - FileCopyUtils.copy(resource.getInputStream(), os); - } -} diff --git a/org.springframework.web/src/main/java/org/springframework/http/converter/multipart/StringPart.java b/org.springframework.web/src/main/java/org/springframework/http/converter/multipart/StringPart.java deleted file mode 100644 index 3e5f223c37d85322327bd387e59355b65ff534ce..0000000000000000000000000000000000000000 --- a/org.springframework.web/src/main/java/org/springframework/http/converter/multipart/StringPart.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2002-2010 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.http.converter.multipart; - -import java.io.IOException; -import java.io.OutputStream; -import java.io.OutputStreamWriter; -import java.nio.charset.Charset; - -import org.springframework.http.MediaType; -import org.springframework.util.Assert; -import org.springframework.util.FileCopyUtils; - -/** @author Arjen Poutsma */ -class StringPart extends AbstractPart { - - private static final Charset DEFAULT_CHARSET = Charset.forName("ISO-8859-1"); - - private final String value; - - private final Charset charset; - - public StringPart(String value) { - this(value, DEFAULT_CHARSET); - } - - public StringPart(String value, Charset charset) { - super(new MediaType("text", "plain", charset)); - Assert.hasText(value, "'value' must not be null"); - Assert.notNull(charset, "'charset' must not be null"); - this.value = value; - this.charset = charset; - } - - @Override - protected void writeData(OutputStream os) throws IOException { - FileCopyUtils.copy(value, new OutputStreamWriter(os, charset)); - } - -} diff --git a/org.springframework.web/src/main/java/org/springframework/web/client/RestTemplate.java b/org.springframework.web/src/main/java/org/springframework/web/client/RestTemplate.java index 5b1fa0f0b92f7656f7cada3598fc327518d4dabe..bb46d53c69d6e514e1d7ddc72428d1e96f278a62 100644 --- a/org.springframework.web/src/main/java/org/springframework/web/client/RestTemplate.java +++ b/org.springframework.web/src/main/java/org/springframework/web/client/RestTemplate.java @@ -35,7 +35,10 @@ import org.springframework.http.client.support.HttpAccessor; import org.springframework.http.converter.ByteArrayHttpMessageConverter; import org.springframework.http.converter.FormHttpMessageConverter; import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.http.converter.ResourceHttpMessageConverter; import org.springframework.http.converter.StringHttpMessageConverter; +import org.springframework.http.converter.feed.AtomFeedHttpMessageConverter; +import org.springframework.http.converter.feed.RssChannelHttpMessageConverter; import org.springframework.http.converter.json.MappingJacksonHttpMessageConverter; import org.springframework.http.converter.multipart.MultipartHttpMessageConverter; import org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter; @@ -111,6 +114,9 @@ public class RestTemplate extends HttpAccessor implements RestOperations { ClassUtils.isPresent("org.codehaus.jackson.map.ObjectMapper", RestTemplate.class.getClassLoader()) && ClassUtils.isPresent("org.codehaus.jackson.JsonGenerator", RestTemplate.class.getClassLoader()); + private static boolean romePresent = + ClassUtils.isPresent("com.sun.syndication.feed.WireFeed", RestTemplate.class.getClassLoader()); + private final ResponseExtractor headersExtractor = new HeadersExtractor(); @@ -123,6 +129,7 @@ public class RestTemplate extends HttpAccessor implements RestOperations { public RestTemplate() { this.messageConverters.add(new ByteArrayHttpMessageConverter()); this.messageConverters.add(new StringHttpMessageConverter()); + this.messageConverters.add(new ResourceHttpMessageConverter()); this.messageConverters.add(new MultipartHttpMessageConverter()); this.messageConverters.add(new FormHttpMessageConverter()); this.messageConverters.add(new SourceHttpMessageConverter()); @@ -132,6 +139,10 @@ public class RestTemplate extends HttpAccessor implements RestOperations { if (jacksonPresent) { this.messageConverters.add(new MappingJacksonHttpMessageConverter()); } + if (romePresent) { + this.messageConverters.add(new AtomFeedHttpMessageConverter()); + this.messageConverters.add(new RssChannelHttpMessageConverter()); + } } /** diff --git a/org.springframework.web/src/test/java/org/springframework/http/HttpHeadersTests.java b/org.springframework.web/src/test/java/org/springframework/http/HttpHeadersTests.java index d322adad923fd67078441c92177df43352ae21f9..6fe9ed0d9dc336a4af8f67046707699f825c3347 100644 --- a/org.springframework.web/src/test/java/org/springframework/http/HttpHeadersTests.java +++ b/org.springframework.web/src/test/java/org/springframework/http/HttpHeadersTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2009 the original author or authors. + * Copyright 2002-2010 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. @@ -210,5 +210,15 @@ public class HttpHeadersTests { assertEquals("Invalid Cache-Control header", "no-cache", headers.getFirst("cache-control")); } + @Test + public void contentDisposition() { + headers.setContentDispositionFormData("name", null); + assertEquals("Invalid Content-Disposition header", "form-data; name=\"name\"", headers.getFirst("Content-Disposition")); + + + headers.setContentDispositionFormData("name", "filename"); + assertEquals("Invalid Content-Disposition header", "form-data; name=\"name\"; filename=\"filename\"", headers.getFirst("Content-Disposition")); + } + } diff --git a/org.springframework.web/src/test/java/org/springframework/http/MediaTypeTests.java b/org.springframework.web/src/test/java/org/springframework/http/MediaTypeTests.java index c5f6db33a80eba6ed5c3eaaaec505c0e13668e1f..2d4d3658000e4cf693369c31844aa1f9670dec63 100644 --- a/org.springframework.web/src/test/java/org/springframework/http/MediaTypeTests.java +++ b/org.springframework.web/src/test/java/org/springframework/http/MediaTypeTests.java @@ -31,7 +31,7 @@ public class MediaTypeTests { @Test public void includes() throws Exception { - MediaType textPlain = new MediaType("text", "plain"); + MediaType textPlain = MediaType.TEXT_PLAIN; assertTrue("Equal types is not inclusive", textPlain.includes(textPlain)); MediaType allText = new MediaType("text"); assertTrue("All subtypes is not inclusive", allText.includes(textPlain)); diff --git a/org.springframework.web/src/test/java/org/springframework/http/converter/ResourceHttpMessageConverterTests.java b/org.springframework.web/src/test/java/org/springframework/http/converter/ResourceHttpMessageConverterTests.java new file mode 100644 index 0000000000000000000000000000000000000000..8d8f38516bf7bba4456cca571d158a7c9d683408 --- /dev/null +++ b/org.springframework.web/src/test/java/org/springframework/http/converter/ResourceHttpMessageConverterTests.java @@ -0,0 +1,73 @@ +/* + * Copyright 2002-2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.http.converter; + +import java.io.IOException; + +import static org.junit.Assert.*; +import org.junit.Before; +import org.junit.Test; + +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.Resource; +import org.springframework.http.MediaType; +import org.springframework.http.MockHttpInputMessage; +import org.springframework.http.MockHttpOutputMessage; +import org.springframework.util.FileCopyUtils; + +/** + * @author Arjen Poutsma + */ +public class ResourceHttpMessageConverterTests { + + private ResourceHttpMessageConverter converter; + + @Before + public void setUp() { + converter = new ResourceHttpMessageConverter(); + } + + @Test + public void canRead() { + assertTrue(converter.canRead(Resource.class, new MediaType("application", "octet-stream"))); + } + + @Test + public void canWrite() { + assertTrue(converter.canWrite(Resource.class, new MediaType("application", "octet-stream"))); + assertTrue(converter.canWrite(Resource.class, MediaType.ALL)); + } + + @Test + public void read() throws IOException { + byte[] body = FileCopyUtils.copyToByteArray(getClass().getResourceAsStream("logo.jpg")); + MockHttpInputMessage inputMessage = new MockHttpInputMessage(body); + inputMessage.getHeaders().setContentType(MediaType.IMAGE_JPEG); + converter.read(Resource.class, inputMessage); + } + + @Test + public void write() throws IOException { + MockHttpOutputMessage outputMessage = new MockHttpOutputMessage(); + Resource body = new ClassPathResource("logo.jpg", getClass()); + converter.write(body, null, outputMessage); + assertEquals("Invalid content-type", MediaType.IMAGE_JPEG, + outputMessage.getHeaders().getContentType()); + assertEquals("Invalid content-length", body.getFile().length(), outputMessage.getHeaders().getContentLength()); + } + +} diff --git a/org.springframework.web/src/test/java/org/springframework/http/converter/multipart/MultipartHttpMessageConverterTest.java b/org.springframework.web/src/test/java/org/springframework/http/converter/multipart/MultipartHttpMessageConverterTest.java index 76613d21fdfd5aa325b06adaa04433375b62def7..7185ebe20636b92bacb2a257949caccb83641e8e 100644 --- a/org.springframework.web/src/test/java/org/springframework/http/converter/multipart/MultipartHttpMessageConverterTest.java +++ b/org.springframework.web/src/test/java/org/springframework/http/converter/multipart/MultipartHttpMessageConverterTest.java @@ -19,7 +19,10 @@ package org.springframework.http.converter.multipart; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; +import java.io.StringReader; import java.util.List; +import javax.xml.transform.Source; +import javax.xml.transform.stream.StreamSource; import org.apache.commons.fileupload.FileItem; import org.apache.commons.fileupload.FileItemFactory; @@ -59,18 +62,19 @@ public class MultipartHttpMessageConverterTest { @Test public void write() throws Exception { MultipartMap body = new MultipartMap(); - body.addTextPart("name 1", "value 1"); - body.addTextPart("name 2", "value 2+1"); - body.addTextPart("name 2", "value 2+2"); + body.add("name 1", "value 1"); + body.add("name 2", "value 2+1"); + body.add("name 2", "value 2+2"); Resource logo = new ClassPathResource("/org/springframework/http/converter/logo.jpg"); - body.addBinaryPart("logo", logo); - byte[] xml = "".getBytes("UTF-8"); - body.addPart("xml", xml, new MediaType("application", "xml")); + body.add("logo", logo); + Source xml = new StreamSource(new StringReader("")); + body.add("xml", xml); MockHttpOutputMessage outputMessage = new MockHttpOutputMessage(); converter.write(body, null, outputMessage); final MediaType contentType = outputMessage.getHeaders().getContentType(); final byte[] result = outputMessage.getBodyAsBytes(); + System.out.println(new String(result)); assertNotNull(contentType); assertNotNull(contentType.getParameter("boundary")); @@ -114,13 +118,12 @@ public class MultipartHttpMessageConverterTest { assertFalse(item.isFormField()); assertEquals("logo", item.getFieldName()); assertEquals("logo.jpg", item.getName()); - assertEquals("application/octet-stream", item.getContentType()); + assertEquals("image/jpeg", item.getContentType()); assertEquals(logo.getFile().length(), item.getSize()); item = (FileItem) items.get(4); assertEquals("xml", item.getFieldName()); assertEquals("application/xml", item.getContentType()); - assertEquals(xml.length, item.getSize()); } diff --git a/org.springframework.web/src/test/java/org/springframework/web/client/RestTemplateIntegrationTests.java b/org.springframework.web/src/test/java/org/springframework/web/client/RestTemplateIntegrationTests.java index a0c47767756d4c76c2932bf9807b160cc7421d70..3061a062b8a25f25adfa5c23ffef76ad5195e8bd 100644 --- a/org.springframework.web/src/test/java/org/springframework/web/client/RestTemplateIntegrationTests.java +++ b/org.springframework.web/src/test/java/org/springframework/web/client/RestTemplateIntegrationTests.java @@ -146,11 +146,11 @@ public class RestTemplateIntegrationTests { @Test public void multipart() throws UnsupportedEncodingException { MultipartMap body = new MultipartMap(); - body.addTextPart("name 1", "value 1"); - body.addTextPart("name 2", "value 2+1"); - body.addTextPart("name 2", "value 2+2"); + body.add("name 1", "value 1"); + body.add("name 2", "value 2+1"); + body.add("name 2", "value 2+2"); Resource logo = new ClassPathResource("/org/springframework/http/converter/logo.jpg"); - body.addBinaryPart("logo", logo); + body.add("logo", logo); template.postForLocation(URI + "/multipart", body); } @@ -261,7 +261,7 @@ public class RestTemplateIntegrationTests { assertFalse(item.isFormField()); assertEquals("logo", item.getFieldName()); assertEquals("logo.jpg", item.getName()); - assertEquals("application/octet-stream", item.getContentType()); + assertEquals("image/jpeg", item.getContentType()); } catch (FileUploadException ex) { throw new ServletException(ex); diff --git a/org.springframework.web/template.mf b/org.springframework.web/template.mf index 52fa9ef22ec4106c1eb635bafc5562dff3a01423..77dde13dd4f9258e2b30ccd76af57c38a5cb8fba 100644 --- a/org.springframework.web/template.mf +++ b/org.springframework.web/template.mf @@ -7,6 +7,7 @@ Import-Template: com.sun.syndication.*;version="[1.0.0, 2.0.0)";resolution:=optional, org.codehaus.jackson.*;version="[1.0.0, 2.0.0)";resolution:=optional, com.sun.net.*;version="0";resolution:=optional, + javax.activation.*;version="0";resolution:=optional, javax.el.*;version="[1.0.0, 3.0.0)";resolution:=optional, javax.faces.*;version="[1.1.0, 3.0.0)";resolution:=optional, javax.imageio.*;version="0";resolution:=optional,