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