001/*
002 * Copyright 2002-2017 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.http.converter;
018
019import java.io.FileNotFoundException;
020import java.io.IOException;
021import java.io.InputStream;
022
023import org.springframework.core.io.ByteArrayResource;
024import org.springframework.core.io.InputStreamResource;
025import org.springframework.core.io.Resource;
026import org.springframework.http.HttpInputMessage;
027import org.springframework.http.HttpOutputMessage;
028import org.springframework.http.MediaType;
029import org.springframework.util.ClassUtils;
030import org.springframework.util.StreamUtils;
031
032/**
033 * Implementation of {@link HttpMessageConverter} that can read/write {@link Resource Resources}
034 * and supports byte range requests.
035 *
036 * <p>By default, this converter can read all media types. The Java Activation Framework (JAF) -
037 * if available - is used to determine the {@code Content-Type} of written resources.
038 * If JAF is not available, {@code application/octet-stream} is used.
039 *
040 * @author Arjen Poutsma
041 * @author Juergen Hoeller
042 * @author Kazuki Shimizu
043 * @since 3.0.2
044 */
045public class ResourceHttpMessageConverter extends AbstractHttpMessageConverter<Resource> {
046
047        private static final boolean jafPresent = ClassUtils.isPresent(
048                        "javax.activation.FileTypeMap", ResourceHttpMessageConverter.class.getClassLoader());
049
050
051        public ResourceHttpMessageConverter() {
052                super(MediaType.ALL);
053        }
054
055
056        @Override
057        protected boolean supports(Class<?> clazz) {
058                return Resource.class.isAssignableFrom(clazz);
059        }
060
061        @Override
062        protected Resource readInternal(Class<? extends Resource> clazz, HttpInputMessage inputMessage)
063                        throws IOException, HttpMessageNotReadableException {
064
065                if (InputStreamResource.class == clazz) {
066                        return new InputStreamResource(inputMessage.getBody());
067                }
068                else if (clazz.isAssignableFrom(ByteArrayResource.class)) {
069                        byte[] body = StreamUtils.copyToByteArray(inputMessage.getBody());
070                        return new ByteArrayResource(body);
071                }
072                else {
073                        throw new IllegalStateException("Unsupported resource class: " + clazz);
074                }
075        }
076
077        @Override
078        protected MediaType getDefaultContentType(Resource resource) {
079                if (jafPresent) {
080                        return ActivationMediaTypeFactory.getMediaType(resource);
081                }
082                else {
083                        return MediaType.APPLICATION_OCTET_STREAM;
084                }
085        }
086
087        @Override
088        protected Long getContentLength(Resource resource, MediaType contentType) throws IOException {
089                // Don't try to determine contentLength on InputStreamResource - cannot be read afterwards...
090                // Note: custom InputStreamResource subclasses could provide a pre-calculated content length!
091                if (InputStreamResource.class == resource.getClass()) {
092                        return null;
093                }
094                long contentLength = resource.contentLength();
095                return (contentLength < 0 ? null : contentLength);
096        }
097
098        @Override
099        protected void writeInternal(Resource resource, HttpOutputMessage outputMessage)
100                        throws IOException, HttpMessageNotWritableException {
101
102                writeContent(resource, outputMessage);
103        }
104
105        protected void writeContent(Resource resource, HttpOutputMessage outputMessage)
106                        throws IOException, HttpMessageNotWritableException {
107                try {
108                        InputStream in = resource.getInputStream();
109                        try {
110                                StreamUtils.copy(in, outputMessage.getBody());
111                        }
112                        catch (NullPointerException ex) {
113                                // ignore, see SPR-13620
114                        }
115                        finally {
116                                try {
117                                        in.close();
118                                }
119                                catch (Throwable ex) {
120                                        // ignore, see SPR-12999
121                                }
122                        }
123                }
124                catch (FileNotFoundException ex) {
125                        // ignore, see SPR-12999
126                }
127        }
128
129}