1 /*
2  * Copyright 2002-2010 the original author or authors.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package org.springframework.http.converter;
18 
19 import java.awt.image.BufferedImage;
20 import java.io.File;
21 import java.io.IOException;
22 import java.io.InputStream;
23 import java.io.OutputStream;
24 import java.util.ArrayList;
25 import java.util.Collections;
26 import java.util.Iterator;
27 import java.util.List;
28 import javax.imageio.IIOImage;
29 import javax.imageio.ImageIO;
30 import javax.imageio.ImageReadParam;
31 import javax.imageio.ImageReader;
32 import javax.imageio.ImageWriteParam;
33 import javax.imageio.ImageWriter;
34 import javax.imageio.stream.FileCacheImageInputStream;
35 import javax.imageio.stream.FileCacheImageOutputStream;
36 import javax.imageio.stream.ImageInputStream;
37 import javax.imageio.stream.ImageOutputStream;
38 import javax.imageio.stream.MemoryCacheImageInputStream;
39 import javax.imageio.stream.MemoryCacheImageOutputStream;
40 
41 import org.springframework.http.HttpInputMessage;
42 import org.springframework.http.HttpOutputMessage;
43 import org.springframework.http.MediaType;
44 import org.springframework.util.Assert;
45 
46 /**
47  * Implementation of {@link HttpMessageConverter} that can read and write {@link BufferedImage BufferedImages}.
48  *
49  * <p>By default, this converter can read all media types that are supported by the {@linkplain
50  * ImageIO#getReaderMIMETypes() registered image readers}, and writes using the media type of the first available
51  * {@linkplain javax.imageio.ImageIO#getWriterMIMETypes() registered image writer}. This behavior can be overriden by
52  * setting the #setContentType(org.springframework.http.MediaType) contentType} properties.
53  *
54  * <p>If the {@link #setCacheDir(java.io.File) cacheDir} property is set to an existing directory, this converter will
55  * cache image data.
56  *
57  * <p>The {@link #process(ImageReadParam)} and {@link #process(ImageWriteParam)} template methods allow subclasses to
58  * override Image I/O parameters.
59  *
60  * @author Arjen Poutsma
61  * @since 3.0
62  */
63 public class BufferedImageHttpMessageConverter implements HttpMessageConverter<BufferedImage> {
64 
65 	private final List<MediaType> readableMediaTypes = new ArrayList<MediaType>();
66 
67 	private MediaType defaultContentType;
68 
69 	private File cacheDir;
70 
71 
BufferedImageHttpMessageConverter()72 	public BufferedImageHttpMessageConverter() {
73 		String[] readerMediaTypes = ImageIO.getReaderMIMETypes();
74 		for (String mediaType : readerMediaTypes) {
75 			this.readableMediaTypes.add(MediaType.parseMediaType(mediaType));
76 		}
77 
78 		String[] writerMediaTypes = ImageIO.getWriterMIMETypes();
79 		if (writerMediaTypes.length > 0) {
80 			this.defaultContentType = MediaType.parseMediaType(writerMediaTypes[0]);
81 		}
82 	}
83 
84 	/**
85 	 * Sets the default {@code Content-Type} to be used for writing.
86 	 * @throws IllegalArgumentException if the given content type is not supported by the Java Image I/O API
87 	 */
setDefaultContentType(MediaType defaultContentType)88 	public void setDefaultContentType(MediaType defaultContentType) {
89 		Assert.notNull(defaultContentType, "'contentType' must not be null");
90 		Iterator<ImageWriter> imageWriters = ImageIO.getImageWritersByMIMEType(defaultContentType.toString());
91 		if (!imageWriters.hasNext()) {
92 			throw new IllegalArgumentException(
93 					"ContentType [" + defaultContentType + "] is not supported by the Java Image I/O API");
94 		}
95 
96 		this.defaultContentType = defaultContentType;
97 	}
98 
99 	/**
100 	 * Returns the default {@code Content-Type} to be used for writing.
101 	 * Called when {@link #write} is invoked without a specified content type parameter.
102 	 */
getDefaultContentType()103 	public MediaType getDefaultContentType() {
104 		return this.defaultContentType;
105 	}
106 
107 	/**
108 	 * Sets the cache directory. If this property is set to an existing directory,
109 	 * this converter will cache image data.
110 	 */
setCacheDir(File cacheDir)111 	public void setCacheDir(File cacheDir) {
112 		Assert.notNull(cacheDir, "'cacheDir' must not be null");
113 		Assert.isTrue(cacheDir.isDirectory(), "'cacheDir' is not a directory");
114 		this.cacheDir = cacheDir;
115 	}
116 
117 
canRead(Class<?> clazz, MediaType mediaType)118 	public boolean canRead(Class<?> clazz, MediaType mediaType) {
119 		return (BufferedImage.class.equals(clazz) && isReadable(mediaType));
120 	}
121 
isReadable(MediaType mediaType)122 	private boolean isReadable(MediaType mediaType) {
123 		if (mediaType == null) {
124 			return true;
125 		}
126 		Iterator<ImageReader> imageReaders = ImageIO.getImageReadersByMIMEType(mediaType.toString());
127 		return imageReaders.hasNext();
128 	}
129 
canWrite(Class<?> clazz, MediaType mediaType)130 	public boolean canWrite(Class<?> clazz, MediaType mediaType) {
131 		return (BufferedImage.class.equals(clazz) && isWritable(mediaType));
132 	}
133 
isWritable(MediaType mediaType)134 	private boolean isWritable(MediaType mediaType) {
135 		if (mediaType == null) {
136 			return true;
137 		}
138 		Iterator<ImageWriter> imageWriters = ImageIO.getImageWritersByMIMEType(mediaType.toString());
139 		return imageWriters.hasNext();
140 	}
141 
getSupportedMediaTypes()142 	public List<MediaType> getSupportedMediaTypes() {
143 		return Collections.unmodifiableList(this.readableMediaTypes);
144 	}
145 
read(Class<? extends BufferedImage> clazz, HttpInputMessage inputMessage)146 	public BufferedImage read(Class<? extends BufferedImage> clazz, HttpInputMessage inputMessage)
147 			throws IOException, HttpMessageNotReadableException {
148 
149 		ImageInputStream imageInputStream = null;
150 		ImageReader imageReader = null;
151 		try {
152 			imageInputStream = createImageInputStream(inputMessage.getBody());
153 			MediaType contentType = inputMessage.getHeaders().getContentType();
154 			Iterator<ImageReader> imageReaders = ImageIO.getImageReadersByMIMEType(contentType.toString());
155 			if (imageReaders.hasNext()) {
156 				imageReader = imageReaders.next();
157 				ImageReadParam irp = imageReader.getDefaultReadParam();
158 				process(irp);
159 				imageReader.setInput(imageInputStream, true);
160 				return imageReader.read(0, irp);
161 			}
162 			else {
163 				throw new HttpMessageNotReadableException(
164 						"Could not find javax.imageio.ImageReader for Content-Type [" + contentType + "]");
165 			}
166 		}
167 		finally {
168 			if (imageReader != null) {
169 				imageReader.dispose();
170 			}
171 			if (imageInputStream != null) {
172 				try {
173 					imageInputStream.close();
174 				}
175 				catch (IOException ex) {
176 					// ignore
177 				}
178 			}
179 		}
180 	}
181 
createImageInputStream(InputStream is)182 	private ImageInputStream createImageInputStream(InputStream is) throws IOException {
183 		if (this.cacheDir != null) {
184 			return new FileCacheImageInputStream(is, cacheDir);
185 		}
186 		else {
187 			return new MemoryCacheImageInputStream(is);
188 		}
189 	}
190 
write(BufferedImage image, MediaType contentType, HttpOutputMessage outputMessage)191 	public void write(BufferedImage image, MediaType contentType, HttpOutputMessage outputMessage)
192 			throws IOException, HttpMessageNotWritableException {
193 
194 		if (contentType == null) {
195 			contentType = getDefaultContentType();
196 		}
197 		Assert.notNull(contentType,
198 				"Count not determine Content-Type, set one using the 'defaultContentType' property");
199 		outputMessage.getHeaders().setContentType(contentType);
200 		ImageOutputStream imageOutputStream = null;
201 		ImageWriter imageWriter = null;
202 		try {
203 			imageOutputStream = createImageOutputStream(outputMessage.getBody());
204 			Iterator<ImageWriter> imageWriters = ImageIO.getImageWritersByMIMEType(contentType.toString());
205 			if (imageWriters.hasNext()) {
206 				imageWriter = imageWriters.next();
207 				ImageWriteParam iwp = imageWriter.getDefaultWriteParam();
208 				process(iwp);
209 				imageWriter.setOutput(imageOutputStream);
210 				imageWriter.write(null, new IIOImage(image, null, null), iwp);
211 			}
212 			else {
213 				throw new HttpMessageNotWritableException(
214 						"Could not find javax.imageio.ImageWriter for Content-Type [" + contentType + "]");
215 			}
216 		}
217 		finally {
218 			if (imageWriter != null) {
219 				imageWriter.dispose();
220 			}
221 			if (imageOutputStream != null) {
222 				try {
223 					imageOutputStream.close();
224 				}
225 				catch (IOException ex) {
226 					// ignore
227 				}
228 			}
229 		}
230 	}
231 
createImageOutputStream(OutputStream os)232 	private ImageOutputStream createImageOutputStream(OutputStream os) throws IOException {
233 		if (this.cacheDir != null) {
234 			return new FileCacheImageOutputStream(os, this.cacheDir);
235 		}
236 		else {
237 			return new MemoryCacheImageOutputStream(os);
238 		}
239 	}
240 
241 
242 	/**
243 	 * Template method that allows for manipulating the {@link ImageReadParam} before it is used to read an image.
244 	 * <p>Default implementation is empty.
245 	 */
process(ImageReadParam irp)246 	protected void process(ImageReadParam irp) {
247 	}
248 
249 	/**
250 	 * Template method that allows for manipulating the {@link ImageWriteParam} before it is used to write an image.
251 	 * <p>Default implementation is empty.
252 	 */
process(ImageWriteParam iwp)253 	protected void process(ImageWriteParam iwp) {
254 	}
255 
256 }
257