diff --git a/org.springframework.web/src/main/java/org/springframework/http/converter/BufferedImageHttpMessageConverter.java b/org.springframework.web/src/main/java/org/springframework/http/converter/BufferedImageHttpMessageConverter.java new file mode 100644 index 00000000000..9880e6ae2c7 --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/http/converter/BufferedImageHttpMessageConverter.java @@ -0,0 +1,230 @@ +/* + * Copyright 2002-2009 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.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import javax.imageio.IIOImage; +import javax.imageio.ImageIO; +import javax.imageio.ImageReadParam; +import javax.imageio.ImageReader; +import javax.imageio.ImageWriteParam; +import javax.imageio.ImageWriter; +import javax.imageio.stream.FileCacheImageInputStream; +import javax.imageio.stream.FileCacheImageOutputStream; +import javax.imageio.stream.ImageInputStream; +import javax.imageio.stream.ImageOutputStream; +import javax.imageio.stream.MemoryCacheImageInputStream; +import javax.imageio.stream.MemoryCacheImageOutputStream; + +import org.springframework.http.HttpInputMessage; +import org.springframework.http.HttpOutputMessage; +import org.springframework.http.MediaType; +import org.springframework.util.Assert; + +/** + * Implementation of {@link HttpMessageConverter} that can read and write {@link BufferedImage BufferedImages}. + * + *

By default, this converter can read all media types that are supported by the {@linkplain + * ImageIO#getReaderMIMETypes() registered image readers}, and writes using the media type of the first available + * {@linkplain javax.imageio.ImageIO#getWriterMIMETypes() registered image writer}. This behavior can be overriden by + * setting the {@link #setSupportedMediaTypes(java.util.List) supportedMediaTypes} and {@link + * #setContentType(org.springframework.http.MediaType) contentType} properties respectively. + * + *

If the {@link #setCacheDir(java.io.File) cacheDir} property is set to an existing directory, this converter will + * cache image data. + * + *

The {@link #process(ImageReadParam)} and {@link #process(ImageWriteParam)} template methods allow subclasses to + * override Image I/O parameters. + * + * @author Arjen Poutsma + * @since 3.0 + */ +public class BufferedImageHttpMessageConverter extends AbstractHttpMessageConverter { + + private MediaType contentType; + + private File cacheDir; + + public BufferedImageHttpMessageConverter() { + String[] readerMediaTypes = ImageIO.getReaderMIMETypes(); + List supportedMediaTypes = new ArrayList(readerMediaTypes.length); + for (String mediaType : readerMediaTypes) { + supportedMediaTypes.add(MediaType.parseMediaType(mediaType)); + } + setSupportedMediaTypes(supportedMediaTypes); + String[] writerMediaTypes = ImageIO.getWriterMIMETypes(); + if (writerMediaTypes.length > 0) { + contentType = MediaType.parseMediaType(writerMediaTypes[0]); + } + } + + /** + * Sets the {@link MediaType MediaTypes} supported for reading. + * + * @throws IllegalArgumentException if the given media type is not supported by the Java Image I/O API + */ + @Override + public void setSupportedMediaTypes(List supportedMediaTypes) { + Assert.notEmpty(supportedMediaTypes, "'supportedMediaTypes' must not be empty"); + for (MediaType supportedMediaType : supportedMediaTypes) { + Iterator imageReaders = ImageIO.getImageReadersByMIMEType(supportedMediaType.toString()); + if (!imageReaders.hasNext()) { + throw new IllegalArgumentException( + "MediaType [" + supportedMediaType + "] is not supported by the Java Image I/O API"); + } + } + super.setSupportedMediaTypes(supportedMediaTypes); + } + + /** + * Sets the {@code Content-Type} to be used for writing. + * + * @throws IllegalArgumentException if the given content type is not supported by the Java Image I/O API + */ + public void setContentType(MediaType contentType) { + Assert.notNull(contentType, "'contentType' must not be null"); + Iterator imageWriters = ImageIO.getImageWritersByMIMEType(contentType.toString()); + if (!imageWriters.hasNext()) { + throw new IllegalArgumentException( + "ContentType [" + contentType + "] is not supported by the Java Image I/O API"); + } + + this.contentType = contentType; + } + + /** Sets the cache directory. If this property is set to an existing directory, this converter will cache image data. */ + public void setCacheDir(File cacheDir) { + Assert.notNull(cacheDir, "'cacheDir' must not be null"); + Assert.isTrue(cacheDir.isDirectory(), "'cacheDir' is not a valid directory"); + this.cacheDir = cacheDir; + } + + public boolean supports(Class clazz) { + return BufferedImage.class.equals(clazz); + } + + @Override + public BufferedImage readInternal(Class clazz, HttpInputMessage inputMessage) throws IOException { + ImageInputStream imageInputStream = null; + ImageReader imageReader = null; + try { + imageInputStream = createImageInputStream(inputMessage.getBody()); + MediaType contentType = inputMessage.getHeaders().getContentType(); + Iterator imageReaders = ImageIO.getImageReadersByMIMEType(contentType.toString()); + if (imageReaders.hasNext()) { + imageReader = imageReaders.next(); + ImageReadParam irp = imageReader.getDefaultReadParam(); + process(irp); + imageReader.setInput(imageInputStream, true); + return imageReader.read(0, irp); + } + else { + throw new HttpMessageNotReadableException( + "Could not find javax.imageio.ImageReader for Content-Type [" + contentType + "]"); + } + } + finally { + if (imageReader != null) { + imageReader.dispose(); + } + if (imageInputStream != null) { + try { + imageInputStream.close(); + } + catch (IOException ex) { + // ignore + } + } + } + } + + private ImageInputStream createImageInputStream(InputStream is) throws IOException { + if (cacheDir != null) { + return new FileCacheImageInputStream(is, cacheDir); + } + else { + return new MemoryCacheImageInputStream(is); + } + } + + @Override + protected MediaType getContentType(BufferedImage image) { + return contentType; + } + + @Override + protected void writeInternal(BufferedImage image, HttpOutputMessage outputMessage) throws IOException { + ImageOutputStream imageOutputStream = null; + ImageWriter imageWriter = null; + try { + imageOutputStream = createImageOutputStream(outputMessage.getBody()); + Iterator imageWriters = ImageIO.getImageWritersByMIMEType(contentType.toString()); + if (imageWriters.hasNext()) { + imageWriter = imageWriters.next(); + ImageWriteParam iwp = imageWriter.getDefaultWriteParam(); + process(iwp); + imageWriter.setOutput(imageOutputStream); + imageWriter.write(null, new IIOImage(image, null, null), iwp); + } + } + finally { + if (imageWriter != null) { + imageWriter.dispose(); + } + if (imageOutputStream != null) { + try { + imageOutputStream.close(); + } + catch (IOException ex) { + // ignore + } + } + } + } + + private ImageOutputStream createImageOutputStream(OutputStream os) throws IOException { + if (cacheDir != null) { + return new FileCacheImageOutputStream(os, cacheDir); + } + else { + return new MemoryCacheImageOutputStream(os); + } + } + + /** + * Template method that allows for manipulating the {@link ImageReadParam} before it is used to read an image. + * + *

Default implementation is empty. + */ + protected void process(ImageReadParam irp) { + } + + /** + * Template method that allows for manipulating the {@link ImageWriteParam} before it is used to write an image. + * + *

Default implementation is empty. + */ + protected void process(ImageWriteParam iwp) { + } +} diff --git a/org.springframework.web/src/test/java/org/springframework/http/converter/BufferedImageHttpMessageConverterTests.java b/org.springframework.web/src/test/java/org/springframework/http/converter/BufferedImageHttpMessageConverterTests.java new file mode 100644 index 00000000000..e8df5930e55 --- /dev/null +++ b/org.springframework.web/src/test/java/org/springframework/http/converter/BufferedImageHttpMessageConverterTests.java @@ -0,0 +1,75 @@ +/* + * Copyright 2002-2009 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.awt.image.BufferedImage; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import javax.imageio.ImageIO; + +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; + +public class BufferedImageHttpMessageConverterTests { + + private BufferedImageHttpMessageConverter converter; + + @Before + public void setUp() { + converter = new BufferedImageHttpMessageConverter(); + } + + @Test + public void supports() { + assertTrue("Image not supported", converter.supports(BufferedImage.class)); + } + + @Test + public void read() throws IOException { + Resource logo = new ClassPathResource("logo.jpg", BufferedImageHttpMessageConverterTests.class); + byte[] body = FileCopyUtils.copyToByteArray(logo.getInputStream()); + MockHttpInputMessage inputMessage = new MockHttpInputMessage(body); + inputMessage.getHeaders().setContentType(new MediaType("image", "jpeg")); + BufferedImage result = converter.read(BufferedImage.class, inputMessage); + assertEquals("Invalid height", 500, result.getHeight()); + assertEquals("Invalid width", 750, result.getWidth()); + } + + @Test + public void write() throws IOException { + Resource logo = new ClassPathResource("logo.jpg", BufferedImageHttpMessageConverterTests.class); + MediaType contentType = new MediaType("image", "png"); + converter.setContentType(contentType); + BufferedImage body = ImageIO.read(logo.getFile()); + MockHttpOutputMessage outputMessage = new MockHttpOutputMessage(); + converter.write(body, outputMessage); + assertEquals("Invalid content type", contentType, outputMessage.getHeaders().getContentType()); + assertTrue("Invalid size", outputMessage.getBodyAsBytes().length > 0); + BufferedImage result = ImageIO.read(new ByteArrayInputStream(outputMessage.getBodyAsBytes())); + assertEquals("Invalid height", 500, result.getHeight()); + assertEquals("Invalid width", 750, result.getWidth()); + } + +} diff --git a/org.springframework.web/src/test/resources/org/springframework/http/converter/logo.jpg b/org.springframework.web/src/test/resources/org/springframework/http/converter/logo.jpg new file mode 100644 index 00000000000..8a70e6af172 Binary files /dev/null and b/org.springframework.web/src/test/resources/org/springframework/http/converter/logo.jpg differ