001/*
002 * Copyright 2002-2018 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.mail.javamail;
018
019import java.io.File;
020import java.io.IOException;
021import java.io.InputStream;
022
023import javax.activation.FileTypeMap;
024import javax.activation.MimetypesFileTypeMap;
025
026import org.springframework.beans.factory.InitializingBean;
027import org.springframework.core.io.ClassPathResource;
028import org.springframework.core.io.Resource;
029import org.springframework.lang.Nullable;
030
031/**
032 * Spring-configurable {@code FileTypeMap} implementation that will read
033 * MIME type to file extension mappings from a standard JavaMail MIME type
034 * mapping file, using a standard {@code MimetypesFileTypeMap} underneath.
035 *
036 * <p>The mapping file should be in the following format, as specified by the
037 * Java Activation Framework:
038 *
039 * <pre class="code">
040 * # map text/html to .htm and .html files
041 * text/html  html htm HTML HTM</pre>
042 *
043 * Lines starting with {@code #} are treated as comments and are ignored. All
044 * other lines are treated as mappings. Each mapping line should contain the MIME
045 * type as the first entry and then each file extension to map to that MIME type
046 * as subsequent entries. Each entry is separated by spaces or tabs.
047 *
048 * <p>By default, the mappings in the {@code mime.types} file located in the
049 * same package as this class are used, which cover many common file extensions
050 * (in contrast to the out-of-the-box mappings in {@code activation.jar}).
051 * This can be overridden using the {@code mappingLocation} property.
052 *
053 * <p>Additional mappings can be added via the {@code mappings} bean property,
054 * as lines that follow the {@code mime.types} file format.
055 *
056 * @author Rob Harrop
057 * @author Juergen Hoeller
058 * @since 1.2
059 * @see #setMappingLocation
060 * @see #setMappings
061 * @see javax.activation.MimetypesFileTypeMap
062 */
063public class ConfigurableMimeFileTypeMap extends FileTypeMap implements InitializingBean {
064
065        /**
066         * The {@code Resource} to load the mapping file from.
067         */
068        private Resource mappingLocation = new ClassPathResource("mime.types", getClass());
069
070        /**
071         * Used to configure additional mappings.
072         */
073        @Nullable
074        private String[] mappings;
075
076        /**
077         * The delegate FileTypeMap, compiled from the mappings in the mapping file
078         * and the entries in the {@code mappings} property.
079         */
080        @Nullable
081        private FileTypeMap fileTypeMap;
082
083
084        /**
085         * Specify the {@code Resource} from which mappings are loaded.
086         * <p>Needs to follow the {@code mime.types} file format, as specified
087         * by the Java Activation Framework, containing lines such as:<br>
088         * {@code text/html  html htm HTML HTM}
089         */
090        public void setMappingLocation(Resource mappingLocation) {
091                this.mappingLocation = mappingLocation;
092        }
093
094        /**
095         * Specify additional MIME type mappings as lines that follow the
096         * {@code mime.types} file format, as specified by the
097         * Java Activation Framework. For example:<br>
098         * {@code text/html  html htm HTML HTM}
099         */
100        public void setMappings(String... mappings) {
101                this.mappings = mappings;
102        }
103
104
105        /**
106         * Creates the final merged mapping set.
107         */
108        @Override
109        public void afterPropertiesSet() {
110                getFileTypeMap();
111        }
112
113        /**
114         * Return the delegate FileTypeMap, compiled from the mappings in the mapping file
115         * and the entries in the {@code mappings} property.
116         * @see #setMappingLocation
117         * @see #setMappings
118         * @see #createFileTypeMap
119         */
120        protected final FileTypeMap getFileTypeMap() {
121                if (this.fileTypeMap == null) {
122                        try {
123                                this.fileTypeMap = createFileTypeMap(this.mappingLocation, this.mappings);
124                        }
125                        catch (IOException ex) {
126                                throw new IllegalStateException(
127                                                "Could not load specified MIME type mapping file: " + this.mappingLocation, ex);
128                        }
129                }
130                return this.fileTypeMap;
131        }
132
133        /**
134         * Compile a {@link FileTypeMap} from the mappings in the given mapping file
135         * and the given mapping entries.
136         * <p>The default implementation creates an Activation Framework {@link MimetypesFileTypeMap},
137         * passing in an InputStream from the mapping resource (if any) and registering
138         * the mapping lines programmatically.
139         * @param mappingLocation a {@code mime.types} mapping resource (can be {@code null})
140         * @param mappings an array of MIME type mapping lines (can be {@code null})
141         * @return the compiled FileTypeMap
142         * @throws IOException if resource access failed
143         * @see javax.activation.MimetypesFileTypeMap#MimetypesFileTypeMap(java.io.InputStream)
144         * @see javax.activation.MimetypesFileTypeMap#addMimeTypes(String)
145         */
146        protected FileTypeMap createFileTypeMap(@Nullable Resource mappingLocation, @Nullable String[] mappings) throws IOException {
147                MimetypesFileTypeMap fileTypeMap = null;
148                if (mappingLocation != null) {
149                        InputStream is = mappingLocation.getInputStream();
150                        try {
151                                fileTypeMap = new MimetypesFileTypeMap(is);
152                        }
153                        finally {
154                                is.close();
155                        }
156                }
157                else {
158                        fileTypeMap = new MimetypesFileTypeMap();
159                }
160                if (mappings != null) {
161                        for (String mapping : mappings) {
162                                fileTypeMap.addMimeTypes(mapping);
163                        }
164                }
165                return fileTypeMap;
166        }
167
168
169        /**
170         * Delegates to the underlying FileTypeMap.
171         * @see #getFileTypeMap()
172         */
173        @Override
174        public String getContentType(File file) {
175                return getFileTypeMap().getContentType(file);
176        }
177
178        /**
179         * Delegates to the underlying FileTypeMap.
180         * @see #getFileTypeMap()
181         */
182        @Override
183        public String getContentType(String fileName) {
184                return getFileTypeMap().getContentType(fileName);
185        }
186
187}