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      private static final String DEFAULT_CHAR_ENCODING = "UTF-8";
71  
72      /**
73       * Used to keep the configuration in memory.
74       */
75      protected static class MIMEMappingItem {
76  
77          protected String ext;
78  
79          protected String mime;
80  
81          protected String icon;
82  
83          protected String iconStyle;
84      }
85  
86  
87      /**
88       * Utility class, don't instantiate.
89       */
90      private MIMEMapping() {
91          // unused
92      }
93  
94      /**
95       * Reads all configured mime mapping (config/server/MIMEMapping).
96       */
97      public static void init() {
98          log.info("Initializing MIMEMapping from {}", NODEPATH);
99          load();
100         registerEventListener();
101     }
102 
103     /**
104      * Reads all configured mime mapping (config/server/MIMEMapping).
105      */
106     public static void load() {
107         MIMEMapping.cachedContent.clear();
108         try {
109             final HierarchyManager hm = MgnlContext.getSystemContext().getHierarchyManager(RepositoryConstants.CONFIG);
110 
111             Collection<Content> mimeList = hm.getContent(NODEPATH).getChildren(ItemType.CONTENTNODE);
112             MIMEMapping.cacheContent(mimeList);
113             log.debug("MIMEMapping loaded from {}", NODEPATH);
114         } catch (PathNotFoundException e) {
115             log.warn("No MIMEMapping info configured at {}", NODEPATH);
116         } catch (RepositoryException e) {
117             log.error("Failed to load MIMEMapping: {}", e.getMessage(), e);
118         }
119     }
120 
121     public static void reload() {
122         log.info("Reloading MIMEMapping from {}", NODEPATH);
123         MIMEMapping.load();
124     }
125 
126     /**
127      * Register an event listener: reload cache configuration when something changes.
128      */
129     private static void registerEventListener() {
130         log.info("Registering event listener for MIMEMapping");
131 
132         ObservationUtil.registerChangeListener(RepositoryConstants.CONFIG, NODEPATH, new EventListener() {
133             @Override
134             public void onEvent(EventIterator iterator) {
135                 // reload everything
136                 reload();
137             }
138         });
139     }
140 
141     /**
142      * Cache all MIME types configured.
143      */
144     private static void cacheContent(Collection<Content> mimeList) {
145         Iterator<Content> iterator = mimeList.iterator();
146         while (iterator.hasNext()) {
147             Content c = iterator.next();
148             try {
149                 MIMEMappingItem item = new MIMEMappingItem();
150                 item.ext = NodeDataUtil.getString(c, "extension", c.getName());
151                 item.mime = c.getNodeData("mime-type").getString();
152                 item.icon = NodeDataUtil.getString(c, "icon");
153                 item.iconStyle = NodeDataUtil.getString(c, "iconStyle");
154 
155                 MIMEMapping.cachedContent.put(item.ext, item);
156             } catch (Exception e) {
157                 log.error("Failed to cache MIMEMapping");
158             }
159         }
160     }
161 
162     /**
163      * Get MIME type String.
164      *
165      * @param key extension for which MIME type is requested
166      * @return MIME type
167      */
168     public static String getMIMEType(String key) {
169         if (StringUtils.isEmpty(key)) {
170             return StringUtils.EMPTY;
171         }
172         // check that the cached content contains the key first to avoid NPE when accessing 'mime'
173         String loweredKey = key.toLowerCase();
174         if (MIMEMapping.cachedContent.containsKey(loweredKey)) {
175             return MIMEMapping.cachedContent.get(loweredKey).mime;
176         }
177 
178         // this is expected by the caller
179         return null;
180 
181     }
182 
183     /**
184      * Returns the mime-type associated with this extension, or the server's default.
185      */
186     public static String getMIMETypeOrDefault(String extension) {
187         String mimeType = getMIMEType(extension);
188 
189         if (StringUtils.isNotEmpty(mimeType)) {
190             return mimeType;
191         }
192 
193         if (StringUtils.isNotEmpty(extension)) {
194             log.info("Cannot find MIME type for extension \"{}\"", extension);
195         }
196 
197         String defaultExtension = ServerConfiguration.getInstance().getDefaultExtension();
198         if (StringUtils.isBlank(defaultExtension)) {
199             defaultExtension = "html";
200         }
201         return getMIMEType(defaultExtension);
202     }
203 
204     public static String getContentEncoding(String contentType) {
205         if (contentType != null) {
206             int index = contentType.lastIndexOf(";");
207             if (index > -1) {
208                 String encoding = contentType.substring(index + 1).toLowerCase().trim();
209                 encoding = encoding.replaceAll("charset=", StringUtils.EMPTY);
210                 return encoding;
211             }
212         }
213         return StringUtils.EMPTY;
214     }
215 
216     public static String getContentEncodingOrDefault(String contentType) {
217         final String characterEncoding = getContentEncoding(contentType);
218 
219         return (StringUtils.isEmpty(characterEncoding)) ? DEFAULT_CHAR_ENCODING : characterEncoding;
220     }
221 
222     /**
223      * Returns the icon used for rendering this type.
224      *
225      * @return the icon name
226      */
227     public static String getMIMETypeIcon(String extension) {
228         MIMEMappingItem item = MIMEMapping.cachedContent.get(extension.toLowerCase());
229         if (item != null) {
230             return StringUtils.defaultIfEmpty(item.icon, DEFAULT_ICON);
231         }
232 
233         return DEFAULT_ICON;
234 
235     }
236 
237     /**
238      * Returns the icon css style name.
239      *
240      * @return icon css style name.
241      */
242     public static String getMIMETypeIconStyle(String mimeType) {
243         String genericMimeType = StringUtils.substringBefore(mimeType, "/");
244         MIMEMappingItem mimeMappingItem = null;
245         for (MIMEMappingItem item : cachedContent.values()) {
246             if (item.mime != null && item.mime.equals(mimeType) && StringUtils.isNotBlank(item.iconStyle)) {
247                 mimeMappingItem = item;
248                 break;
249             }
250             if (item.mime != null && item.mime.equals(genericMimeType) && StringUtils.isNotBlank(item.iconStyle)) {
251                 mimeMappingItem = item;
252             }
253         }
254         if (mimeMappingItem != null && StringUtils.isNotBlank(mimeMappingItem.iconStyle)) {
255             return mimeMappingItem.iconStyle;
256         }
257         return DEFAULT_ICON_STYLE;
258     }
259 }