001    /**
002     * Copyright (c) 2000-2013 Liferay, Inc. All rights reserved.
003     *
004     * This library is free software; you can redistribute it and/or modify it under
005     * the terms of the GNU Lesser General Public License as published by the Free
006     * Software Foundation; either version 2.1 of the License, or (at your option)
007     * any later version.
008     *
009     * This library is distributed in the hope that it will be useful, but WITHOUT
010     * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
011     * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
012     * details.
013     */
014    
015    package com.liferay.portal.servlet;
016    
017    import com.liferay.portal.kernel.cache.PortalCache;
018    import com.liferay.portal.kernel.cache.SingleVMPoolUtil;
019    import com.liferay.portal.kernel.log.Log;
020    import com.liferay.portal.kernel.log.LogFactoryUtil;
021    import com.liferay.portal.kernel.servlet.HttpHeaders;
022    import com.liferay.portal.kernel.servlet.ServletContextUtil;
023    import com.liferay.portal.kernel.servlet.ServletResponseUtil;
024    import com.liferay.portal.kernel.util.ContentTypes;
025    import com.liferay.portal.kernel.util.FileUtil;
026    import com.liferay.portal.kernel.util.ParamUtil;
027    import com.liferay.portal.kernel.util.PropsKeys;
028    import com.liferay.portal.kernel.util.SetUtil;
029    import com.liferay.portal.kernel.util.StringPool;
030    import com.liferay.portal.kernel.util.StringUtil;
031    import com.liferay.portal.kernel.util.Time;
032    import com.liferay.portal.kernel.util.Validator;
033    import com.liferay.portal.servlet.filters.dynamiccss.DynamicCSSUtil;
034    import com.liferay.portal.util.MinifierUtil;
035    import com.liferay.portal.util.PortalUtil;
036    import com.liferay.portal.util.PrefsPropsUtil;
037    import com.liferay.portal.util.PropsValues;
038    
039    import java.io.IOException;
040    import java.io.Serializable;
041    
042    import java.net.URL;
043    import java.net.URLConnection;
044    
045    import java.util.Arrays;
046    import java.util.Enumeration;
047    import java.util.LinkedHashSet;
048    import java.util.Set;
049    
050    import javax.servlet.ServletContext;
051    import javax.servlet.ServletException;
052    import javax.servlet.http.HttpServlet;
053    import javax.servlet.http.HttpServletRequest;
054    import javax.servlet.http.HttpServletResponse;
055    
056    /**
057     * @author Eduardo Lundgren
058     * @author Edward Han
059     * @author Zsigmond Rab
060     * @author Raymond Aug??
061     */
062    public class ComboServlet extends HttpServlet {
063    
064            @Override
065            public void service(
066                            HttpServletRequest request, HttpServletResponse response)
067                    throws IOException, ServletException {
068    
069                    try {
070                            doService(request, response);
071                    }
072                    catch (Exception e) {
073                            _log.error(e, e);
074    
075                            PortalUtil.sendError(
076                                    HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e, request,
077                                    response);
078                    }
079            }
080    
081            protected void doService(
082                            HttpServletRequest request, HttpServletResponse response)
083                    throws Exception {
084    
085                    Set<String> modulePathsSet = new LinkedHashSet<String>();
086    
087                    Enumeration<String> enu = request.getParameterNames();
088    
089                    while (enu.hasMoreElements()) {
090                            String name = enu.nextElement();
091    
092                            if (_protectedParameters.contains(name)) {
093                                    continue;
094                            }
095    
096                            modulePathsSet.add(name);
097                    }
098    
099                    if (modulePathsSet.size() == 0) {
100                            response.sendError(HttpServletResponse.SC_BAD_REQUEST);
101    
102                            return;
103                    }
104    
105                    String[] modulePaths = modulePathsSet.toArray(
106                            new String[modulePathsSet.size()]);
107    
108                    String modulePathsString = null;
109    
110                    byte[][] bytesArray = null;
111    
112                    if (!PropsValues.COMBO_CHECK_TIMESTAMP) {
113                            modulePathsString = Arrays.toString(modulePaths);
114    
115                            bytesArray = _bytesArrayPortalCache.get(modulePathsString);
116                    }
117    
118                    String firstModulePath = modulePaths[0];
119    
120                    String extension = FileUtil.getExtension(firstModulePath);
121    
122                    if (bytesArray == null) {
123                            ServletContext servletContext = getServletContext();
124    
125                            String rootPath = ServletContextUtil.getRootPath(servletContext);
126    
127                            String minifierType = ParamUtil.getString(request, "minifierType");
128    
129                            if (Validator.isNull(minifierType)) {
130                                    minifierType = "js";
131    
132                                    if (extension.equalsIgnoreCase(_CSS_EXTENSION)) {
133                                            minifierType = "css";
134                                    }
135                            }
136    
137                            if (!minifierType.equals("css") && !minifierType.equals("js")) {
138                                    minifierType = "js";
139                            }
140    
141                            bytesArray = new byte[modulePaths.length][];
142    
143                            for (int i = 0; i < modulePaths.length; i++) {
144                                    String modulePath = modulePaths[i];
145    
146                                    if (!validateModuleExtension(modulePath)) {
147                                            response.setHeader(
148                                                    HttpHeaders.CACHE_CONTROL,
149                                                    HttpHeaders.CACHE_CONTROL_NO_CACHE_VALUE);
150                                            response.setStatus(HttpServletResponse.SC_NOT_FOUND);
151    
152                                            return;
153                                    }
154    
155                                    byte[] bytes = new byte[0];
156    
157                                    if (Validator.isNotNull(modulePath)) {
158                                            modulePath = StringUtil.replaceFirst(
159                                                    modulePath, PortalUtil.getPathContext(),
160                                                    StringPool.BLANK);
161    
162                                            URL url = getResourceURL(
163                                                    servletContext, rootPath, modulePath);
164    
165                                            if (url == null) {
166                                                    response.setHeader(
167                                                            HttpHeaders.CACHE_CONTROL,
168                                                            HttpHeaders.CACHE_CONTROL_NO_CACHE_VALUE);
169                                                    response.setStatus(HttpServletResponse.SC_NOT_FOUND);
170    
171                                                    return;
172                                            }
173    
174                                            bytes = getResourceContent(
175                                                    request, response, url, modulePath, minifierType);
176                                    }
177    
178                                    bytesArray[i] = bytes;
179                            }
180    
181                            if ((modulePathsString != null) &&
182                                    !PropsValues.COMBO_CHECK_TIMESTAMP) {
183    
184                                    _bytesArrayPortalCache.put(modulePathsString, bytesArray);
185                            }
186                    }
187    
188                    String contentType = ContentTypes.TEXT_JAVASCRIPT;
189    
190                    if (extension.equalsIgnoreCase(_CSS_EXTENSION)) {
191                            contentType = ContentTypes.TEXT_CSS;
192                    }
193    
194                    response.setContentType(contentType);
195    
196                    ServletResponseUtil.write(response, bytesArray);
197            }
198    
199            protected byte[] getResourceContent(
200                            HttpServletRequest request, HttpServletResponse response,
201                            URL resourceURL, String resourcePath, String minifierType)
202                    throws IOException {
203    
204                    String fileContentKey = resourcePath.concat(StringPool.QUESTION).concat(
205                            minifierType);
206    
207                    FileContentBag fileContentBag = _fileContentBagPortalCache.get(
208                            fileContentKey);
209    
210                    if ((fileContentBag != null) && !PropsValues.COMBO_CHECK_TIMESTAMP) {
211                            return fileContentBag._fileContent;
212                    }
213    
214                    URLConnection urlConnection = null;
215    
216                    if (resourceURL != null) {
217                            urlConnection = resourceURL.openConnection();
218                    }
219    
220                    if ((fileContentBag != null) && PropsValues.COMBO_CHECK_TIMESTAMP) {
221                            long elapsedTime =
222                                    System.currentTimeMillis() - fileContentBag._lastModified;
223    
224                            if ((urlConnection != null) &&
225                                    (elapsedTime <= PropsValues.COMBO_CHECK_TIMESTAMP_INTERVAL) &&
226                                    (urlConnection.getLastModified() ==
227                                            fileContentBag._lastModified)) {
228    
229                                    return fileContentBag._fileContent;
230                            }
231    
232                            _fileContentBagPortalCache.remove(fileContentKey);
233                    }
234    
235                    if (resourceURL == null) {
236                            fileContentBag = _EMPTY_FILE_CONTENT_BAG;
237                    }
238                    else {
239                            String stringFileContent = StringUtil.read(
240                                    urlConnection.getInputStream());
241    
242                            if (!StringUtil.endsWith(resourcePath, _CSS_MINIFIED_SUFFIX) &&
243                                    !StringUtil.endsWith(
244                                            resourcePath, _JAVASCRIPT_MINIFIED_SUFFIX)) {
245    
246                                    if (minifierType.equals("css")) {
247                                            try {
248                                                    stringFileContent = DynamicCSSUtil.parseSass(
249                                                            getServletContext(), request, resourcePath,
250                                                            stringFileContent);
251                                            }
252                                            catch (Exception e) {
253                                                    _log.error(
254                                                            "Unable to parse SASS on CSS " +
255                                                                    resourceURL.getPath(), e);
256    
257                                                    if (_log.isDebugEnabled()) {
258                                                            _log.debug(stringFileContent);
259                                                    }
260    
261                                                    response.setHeader(
262                                                            HttpHeaders.CACHE_CONTROL,
263                                                            HttpHeaders.CACHE_CONTROL_NO_CACHE_VALUE);
264                                            }
265    
266                                            stringFileContent = MinifierUtil.minifyCss(
267                                                    stringFileContent);
268                                    }
269                                    else if (minifierType.equals("js")) {
270                                            stringFileContent = MinifierUtil.minifyJavaScript(
271                                                    stringFileContent);
272                                    }
273                            }
274    
275                            fileContentBag = new FileContentBag(
276                                    stringFileContent.getBytes(StringPool.UTF8),
277                                    urlConnection.getLastModified());
278                    }
279    
280                    if (PropsValues.COMBO_CHECK_TIMESTAMP) {
281                            int timeToLive =
282                                    (int)(PropsValues.COMBO_CHECK_TIMESTAMP_INTERVAL / Time.SECOND);
283    
284                            _fileContentBagPortalCache.put(
285                                    fileContentKey, fileContentBag, timeToLive);
286                    }
287    
288                    return fileContentBag._fileContent;
289            }
290    
291            protected URL getResourceURL(
292                            ServletContext servletContext, String rootPath, String path)
293                    throws Exception {
294    
295                    URL url = servletContext.getResource(path);
296    
297                    if (url == null) {
298                            return null;
299                    }
300    
301                    String filePath = ServletContextUtil.getResourcePath(url);
302    
303                    int pos = filePath.indexOf(
304                            rootPath.concat(StringPool.SLASH).concat(_JAVASCRIPT_DIR));
305    
306                    if (pos == 0) {
307                            return url;
308                    }
309    
310                    return null;
311            }
312    
313            protected boolean validateModuleExtension(String moduleName)
314                    throws Exception {
315    
316                    boolean validModuleExtension = false;
317    
318                    String[] fileExtensions = PrefsPropsUtil.getStringArray(
319                            PropsKeys.COMBO_ALLOWED_FILE_EXTENSIONS, StringPool.COMMA);
320    
321                    for (String fileExtension : fileExtensions) {
322                            if (StringPool.STAR.equals(fileExtension) ||
323                                    StringUtil.endsWith(moduleName, fileExtension)) {
324    
325                                    validModuleExtension = true;
326    
327                                    break;
328                            }
329                    }
330    
331                    return validModuleExtension;
332            }
333    
334            private static final String _CSS_EXTENSION = "css";
335    
336            private static final String _CSS_MINIFIED_SUFFIX = "-min.css";
337    
338            private static final FileContentBag _EMPTY_FILE_CONTENT_BAG =
339                    new FileContentBag(new byte[0], 0);
340    
341            private static final String _JAVASCRIPT_DIR = "html/js";
342    
343            private static final String _JAVASCRIPT_MINIFIED_SUFFIX = "-min.js";
344    
345            private static Log _log = LogFactoryUtil.getLog(ComboServlet.class);
346    
347            private PortalCache<String, byte[][]> _bytesArrayPortalCache =
348                    SingleVMPoolUtil.getCache(ComboServlet.class.getName());
349            private PortalCache<String, FileContentBag> _fileContentBagPortalCache =
350                    SingleVMPoolUtil.getCache(FileContentBag.class.getName());
351            private Set<String> _protectedParameters = SetUtil.fromArray(
352                    new String[] {"b", "browserId", "minifierType", "languageId", "t"});
353    
354            private static class FileContentBag implements Serializable {
355    
356                    public FileContentBag(byte[] fileContent, long lastModifiedTime) {
357                            _fileContent = fileContent;
358                            _lastModified = lastModifiedTime;
359                    }
360    
361                    private byte[] _fileContent;
362                    private long _lastModified;
363    
364            }
365    
366    }