View Javadoc
1   /**
2    * This file Copyright (c) 2003-2015 Magnolia International
3    * Ltd.  (http://www.magnolia-cms.com). All rights reserved.
4    *
5    *
6    * This file is dual-licensed under both the Magnolia
7    * Network Agreement and the GNU General Public License.
8    * You may elect to use one or the other of these licenses.
9    *
10   * This file is distributed in the hope that it will be
11   * useful, but AS-IS and WITHOUT ANY WARRANTY; without even the
12   * implied warranty of MERCHANTABILITY or FITNESS FOR A
13   * PARTICULAR PURPOSE, TITLE, or NONINFRINGEMENT.
14   * Redistribution, except as permitted by whichever of the GPL
15   * or MNA you select, is prohibited.
16   *
17   * 1. For the GPL license (GPL), you can redistribute and/or
18   * modify this file under the terms of the GNU General
19   * Public License, Version 3, as published by the Free Software
20   * Foundation.  You should have received a copy of the GNU
21   * General Public License, Version 3 along with this program;
22   * if not, write to the Free Software Foundation, Inc., 51
23   * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
24   *
25   * 2. For the Magnolia Network Agreement (MNA), this file
26   * and the accompanying materials are made available under the
27   * terms of the MNA which accompanies this distribution, and
28   * is available at http://www.magnolia-cms.com/mna.html
29   *
30   * Any modifications to this file must keep this entire header
31   * intact.
32   *
33   */
34  package info.magnolia.cms.beans.config;
35  
36  import info.magnolia.cms.core.Content;
37  import info.magnolia.cms.core.HierarchyManager;
38  import info.magnolia.cms.core.ItemType;
39  import info.magnolia.cms.util.NodeDataUtil;
40  import info.magnolia.cms.util.ObservationUtil;
41  import info.magnolia.context.MgnlContext;
42  import info.magnolia.repository.RepositoryConstants;
43  
44  import java.util.Collection;
45  import java.util.Hashtable;
46  import java.util.Iterator;
47  import java.util.Map;
48  
49  import javax.jcr.PathNotFoundException;
50  import javax.jcr.RepositoryException;
51  import javax.jcr.observation.EventIterator;
52  import javax.jcr.observation.EventListener;
53  
54  import org.apache.commons.lang3.StringUtils;
55  import org.slf4j.Logger;
56  import org.slf4j.LoggerFactory;
57  
58  /**
59   * Manages mappings of file extensions with their MIME types and icon.
60   */
61  public class MIMEMapping {
62      private static final Logger log = LoggerFactory.getLogger(MIMEMapping.class);
63  
64      public static final String ICONS_PATH = "/.resources/file-icons/";
65      public static final String DEFAULT_ICON = ICONS_PATH + "general.png";
66      private static final String NODEPATH = "/server/MIMEMapping";
67      public static final String DEFAULT_ICON_STYLE = "icon-file";
68  
69      private static Map<String, MIMEMappingItem> cachedContent = new Hashtable<String, MIMEMappingItem>();
70      public static final String DEFAULT_CHAR_ENCODING = "UTF-8";
71      public static final String DEFAULT_EXTENSION = "html";
72  
73      /**
74       * Used to keep the configuration in memory.
75       */
76      protected static class MIMEMappingItem {
77  
78          protected String ext;
79  
80          protected String mime;
81  
82          protected String icon;
83  
84          protected String iconStyle;
85      }
86  
87  
88      /**
89       * Utility class, don't instantiate.
90       */
91      private MIMEMapping() {
92          // unused
93      }
94  
95      /**
96       * Reads all configured mime mapping (config/server/MIMEMapping).
97       */
98      public static void init() {
99          log.info("Initializing MIMEMapping from {}", NODEPATH);
100         load();
101         registerEventListener();
102     }
103 
104     /**
105      * Reads all configured mime mapping (config/server/MIMEMapping).
106      */
107     public static void load() {
108         MIMEMapping.cachedContent.clear();
109         try {
110             final HierarchyManager hm = MgnlContext.getSystemContext().getHierarchyManager(RepositoryConstants.CONFIG);
111 
112             Collection<Content> mimeList = hm.getContent(NODEPATH).getChildren(ItemType.CONTENTNODE);
113             MIMEMapping.cacheContent(mimeList);
114             log.debug("MIMEMapping loaded from {}", NODEPATH);
115         } catch (PathNotFoundException e) {
116             log.warn("No MIMEMapping info configured at {}", NODEPATH);
117         } catch (RepositoryException e) {
118             log.error("Failed to load MIMEMapping: {}", e.getMessage(), e);
119         }
120     }
121 
122     public static void reload() {
123         log.info("Reloading MIMEMapping from {}", NODEPATH);
124         MIMEMapping.load();
125     }
126 
127     /**
128      * Register an event listener: reload cache configuration when something changes.
129      */
130     private static void registerEventListener() {
131         log.info("Registering event listener for MIMEMapping");
132 
133         ObservationUtil.registerChangeListener(RepositoryConstants.CONFIG, NODEPATH, new EventListener() {
134             @Override
135             public void onEvent(EventIterator iterator) {
136                 // reload everything
137                 reload();
138             }
139         });
140     }
141 
142     /**
143      * Cache all MIME types configured.
144      */
145     private static void cacheContent(Collection<Content> mimeList) {
146         Iterator<Content> iterator = mimeList.iterator();
147         while (iterator.hasNext()) {
148             Content c = iterator.next();
149             try {
150                 MIMEMappingItem item = new MIMEMappingItem();
151                 item.ext = NodeDataUtil.getString(c, "extension", c.getName());
152                 item.mime = c.getNodeData("mime-type").getString();
153                 item.icon = NodeDataUtil.getString(c, "icon");
154                 item.iconStyle = NodeDataUtil.getString(c, "iconStyle");
155 
156                 MIMEMapping.cachedContent.put(item.ext, item);
157             } catch (Exception e) {
158                 log.error("Failed to cache MIMEMapping");
159             }
160         }
161     }
162 
163     /**
164      * Get MIME type String.
165      *
166      * @param key extension for which MIME type is requested
167      * @return MIME type
168      */
169     public static String getMIMEType(String key) {
170         if (StringUtils.isEmpty(key)) {
171             return StringUtils.EMPTY;
172         }
173         // check that the cached content contains the key first to avoid NPE when accessing 'mime'
174         String loweredKey = key.toLowerCase();
175         if (MIMEMapping.cachedContent.containsKey(loweredKey)) {
176             return MIMEMapping.cachedContent.get(loweredKey).mime;
177         }
178 
179         // this is expected by the caller
180         return null;
181 
182     }
183 
184     /**
185      * Returns the mime-type associated with this extension, or the server's default.
186      */
187     public static String getMIMETypeOrDefault(String extension) {
188         String mimeType = getMIMEType(extension);
189 
190         if (StringUtils.isNotEmpty(mimeType)) {
191             return mimeType;
192         }
193 
194         if (StringUtils.isNotEmpty(extension)) {
195             log.info("Cannot find MIME type for extension \"{}\"", extension);
196         }
197 
198         String defaultExtension = ServerConfiguration.getInstance().getDefaultExtension();
199         if (StringUtils.isBlank(defaultExtension)) {
200             defaultExtension = DEFAULT_EXTENSION;
201         }
202         return getMIMEType(defaultExtension);
203     }
204 
205     public static String getContentEncoding(String contentType) {
206         if (contentType != null) {
207             int index = contentType.lastIndexOf(";");
208             if (index > -1) {
209                 String encoding = contentType.substring(index + 1).toLowerCase().trim();
210                 encoding = encoding.replaceAll("charset=", StringUtils.EMPTY);
211                 return encoding;
212             }
213         }
214         return StringUtils.EMPTY;
215     }
216 
217     public static String getContentEncodingOrDefault(String contentType) {
218         final String characterEncoding = getContentEncoding(contentType);
219 
220         return (StringUtils.isEmpty(characterEncoding)) ? DEFAULT_CHAR_ENCODING : characterEncoding;
221     }
222 
223     /**
224      * Returns the icon used for rendering this type.
225      *
226      * @return the icon name
227      */
228     public static String getMIMETypeIcon(String extension) {
229         MIMEMappingItem item = MIMEMapping.cachedContent.get(extension.toLowerCase());
230         if (item != null) {
231             return StringUtils.defaultIfEmpty(item.icon, DEFAULT_ICON);
232         }
233 
234         return DEFAULT_ICON;
235 
236     }
237 
238     /**
239      * Returns the icon css style name.
240      *
241      * @return icon css style name.
242      */
243     public static String getMIMETypeIconStyle(String mimeType) {
244         String genericMimeType = StringUtils.substringBefore(mimeType, "/");
245         MIMEMappingItem mimeMappingItem = null;
246         for (MIMEMappingItem item : cachedContent.values()) {
247             if (item.mime != null && item.mime.equals(mimeType) && StringUtils.isNotBlank(item.iconStyle)) {
248                 mimeMappingItem = item;
249                 break;
250             }
251             if (item.mime != null && item.mime.equals(genericMimeType) && StringUtils.isNotBlank(item.iconStyle)) {
252                 mimeMappingItem = item;
253             }
254         }
255         if (mimeMappingItem != null && StringUtils.isNotBlank(mimeMappingItem.iconStyle)) {
256             return mimeMappingItem.iconStyle;
257         }
258         return DEFAULT_ICON_STYLE;
259     }
260 }