001    /**
002     * Copyright (c) 2000-2011 Liferay, Inc. All rights reserved.
003     *
004     * The contents of this file are subject to the terms of the Liferay Enterprise
005     * Subscription License ("License"). You may not use this file except in
006     * compliance with the License. You can obtain a copy of the License by
007     * contacting Liferay, Inc. See the License for the specific language governing
008     * permissions and limitations under the License, including but not limited to
009     * distribution rights of the Software.
010     *
011     *
012     *
013     */
014    
015    package com.liferay.portal.servlet;
016    
017    import com.liferay.portal.kernel.log.Log;
018    import com.liferay.portal.kernel.log.LogFactoryUtil;
019    import com.liferay.portal.kernel.servlet.HttpHeaders;
020    import com.liferay.portal.kernel.servlet.ServletContextUtil;
021    import com.liferay.portal.kernel.servlet.ServletResponseUtil;
022    import com.liferay.portal.kernel.util.CharPool;
023    import com.liferay.portal.kernel.util.ContentTypes;
024    import com.liferay.portal.kernel.util.FileUtil;
025    import com.liferay.portal.kernel.util.ParamUtil;
026    import com.liferay.portal.kernel.util.StringPool;
027    import com.liferay.portal.kernel.util.StringUtil;
028    import com.liferay.portal.kernel.util.Validator;
029    import com.liferay.portal.servlet.filters.dynamiccss.DynamicCSSUtil;
030    import com.liferay.portal.util.MinifierUtil;
031    import com.liferay.portal.util.PortalUtil;
032    import com.liferay.portal.util.PropsValues;
033    
034    import java.io.File;
035    import java.io.IOException;
036    
037    import java.util.Arrays;
038    import java.util.concurrent.ConcurrentHashMap;
039    import java.util.concurrent.ConcurrentMap;
040    
041    import javax.servlet.ServletContext;
042    import javax.servlet.http.HttpServlet;
043    import javax.servlet.http.HttpServletRequest;
044    import javax.servlet.http.HttpServletResponse;
045    
046    /**
047     * @author Eduardo Lundgren
048     * @author Edward Han
049     */
050    public class ComboServlet extends HttpServlet {
051    
052            @Override
053            public void service(
054                            HttpServletRequest request, HttpServletResponse response)
055                    throws IOException {
056    
057                    String contextPath = PortalUtil.getPathContext();
058    
059                    String[] modulePaths = request.getParameterValues("m");
060    
061                    if ((modulePaths == null) || (modulePaths.length == 0)) {
062                            response.sendError(HttpServletResponse.SC_BAD_REQUEST);
063    
064                            return;
065                    }
066    
067                    Arrays.sort(modulePaths);
068    
069                    String modulePathsString = null;
070    
071                    byte[][] bytesArray = null;
072    
073                    if (!PropsValues.COMBO_CHECK_TIMESTAMP) {
074                            modulePathsString = Arrays.toString(modulePaths);
075    
076                            bytesArray = _byteArrays.get(modulePathsString);
077                    }
078    
079                    String firstModulePath = modulePaths[0];
080    
081                    String extension = FileUtil.getExtension(firstModulePath);
082    
083                    if (bytesArray == null) {
084                            String p = ParamUtil.getString(request, "p");
085    
086                            String minifierType = ParamUtil.getString(request, "minifierType");
087    
088                            if (Validator.isNull(minifierType)) {
089                                    minifierType = "js";
090    
091                                    if (extension.equalsIgnoreCase(_CSS_EXTENSION)) {
092                                            minifierType = "css";
093                                    }
094                            }
095    
096                            int length = modulePaths.length;
097    
098                            bytesArray = new byte[length][];
099    
100                            for (String modulePath : modulePaths) {
101                                    byte[] bytes = new byte[0];
102    
103                                    if (Validator.isNotNull(modulePath)) {
104                                            modulePath = StringUtil.replaceFirst(
105                                                    p.concat(modulePath), contextPath, StringPool.BLANK);
106    
107                                            bytes = getFileContent(
108                                                    request, response, modulePath, minifierType);
109                                    }
110    
111                                    bytesArray[--length] = bytes;
112                            }
113    
114                            if (modulePathsString != null) {
115                                    _byteArrays.put(modulePathsString, bytesArray);
116                            }
117                    }
118    
119                    String contentType = ContentTypes.TEXT_JAVASCRIPT;
120    
121                    if (extension.equalsIgnoreCase(_CSS_EXTENSION)) {
122                            contentType = ContentTypes.TEXT_CSS;
123                    }
124    
125                    response.setContentType(contentType);
126    
127                    ServletResponseUtil.write(response, bytesArray);
128            }
129    
130            protected File getFile(String path) throws IOException {
131                    ServletContext servletContext = getServletContext();
132    
133                    String basePath = ServletContextUtil.getRealPath(
134                            servletContext, _JAVASCRIPT_DIR);
135    
136                    if (basePath == null) {
137                            return null;
138                    }
139    
140                    basePath = StringUtil.replace(
141                            basePath, CharPool.BACK_SLASH, CharPool.SLASH);
142    
143                    File baseDir = new File(basePath);
144    
145                    if (!baseDir.exists()) {
146                            return null;
147                    }
148    
149                    String filePath = ServletContextUtil.getRealPath(servletContext, path);
150    
151                    if (filePath == null) {
152                            return null;
153                    }
154    
155                    filePath = StringUtil.replace(
156                            filePath, CharPool.BACK_SLASH, CharPool.SLASH);
157    
158                    File file = new File(filePath);
159    
160                    if (!file.exists()) {
161                            return null;
162                    }
163    
164                    String baseCanonicalPath = baseDir.getCanonicalPath();
165                    String fileCanonicalPath = file.getCanonicalPath();
166    
167                    if (fileCanonicalPath.indexOf(baseCanonicalPath) == 0) {
168                            return file;
169                    }
170    
171                    return null;
172            }
173    
174            protected byte[] getFileContent(
175                            HttpServletRequest request, HttpServletResponse response,
176                            String path, String minifierType)
177                    throws IOException {
178    
179                    String fileContentKey = path.concat(StringPool.QUESTION).concat(
180                            minifierType);
181    
182                    FileContentBag fileContentBag = _fileContentBags.get(fileContentKey);
183    
184                    if ((fileContentBag != null) &&
185                            !PropsValues.COMBO_CHECK_TIMESTAMP) {
186    
187                            return fileContentBag._fileContent;
188                    }
189    
190                    File file = getFile(path);
191    
192                    if ((fileContentBag != null) && PropsValues.COMBO_CHECK_TIMESTAMP) {
193                            long elapsedTime =
194                                    System.currentTimeMillis() - fileContentBag._lastModified;
195    
196                            if ((file != null) &&
197                                    (elapsedTime <= PropsValues.COMBO_CHECK_TIMESTAMP_INTERVAL) &&
198                                    (file.lastModified() == fileContentBag._lastModified)) {
199    
200                                    return fileContentBag._fileContent;
201                            }
202                            else {
203                                    _fileContentBags.remove(fileContentKey, fileContentBag);
204                            }
205                    }
206    
207                    if (file == null) {
208                            fileContentBag = _EMPTY_FILE_CONTENT_BAG;
209                    }
210                    else {
211                            String stringFileContent = FileUtil.read(file);
212    
213                            if (!StringUtil.endsWith(path, _CSS_MINIFIED_SUFFIX) &&
214                                    !StringUtil.endsWith(path, _JAVASCRIPT_MINIFIED_SUFFIX)) {
215    
216                                    if (minifierType.equals("css")) {
217                                            String cssRealPath = file.getAbsolutePath();
218    
219                                            try {
220                                                    stringFileContent = DynamicCSSUtil.parseSass(
221                                                            request, cssRealPath, stringFileContent);
222                                            }
223                                            catch (Exception e) {
224                                                    _log.error(
225                                                            "Unable to parse SASS on CSS " + cssRealPath, e);
226    
227                                                    if (_log.isDebugEnabled()) {
228                                                            _log.debug(stringFileContent);
229                                                    }
230    
231                                                    response.setHeader(
232                                                            HttpHeaders.CACHE_CONTROL,
233                                                            HttpHeaders.CACHE_CONTROL_NO_CACHE_VALUE);
234                                            }
235    
236                                            stringFileContent = MinifierUtil.minifyCss(
237                                                    stringFileContent);
238                                    }
239                                    else if (minifierType.equals("js")) {
240                                            stringFileContent = MinifierUtil.minifyJavaScript(
241                                                    stringFileContent);
242                                    }
243                            }
244    
245                            fileContentBag = new FileContentBag(
246                                    stringFileContent.getBytes(StringPool.UTF8),
247                                    file.lastModified());
248                    }
249    
250                    FileContentBag oldFileContentBag = _fileContentBags.putIfAbsent(
251                            fileContentKey, fileContentBag);
252    
253                    if (oldFileContentBag != null) {
254                            fileContentBag = oldFileContentBag;
255                    }
256    
257                    return fileContentBag._fileContent;
258            }
259    
260            private static final String _CSS_EXTENSION = "css";
261    
262            private static final String _CSS_MINIFIED_SUFFIX = "-min.css";
263    
264            private static final FileContentBag _EMPTY_FILE_CONTENT_BAG =
265                    new FileContentBag(new byte[0], 0);
266    
267            private static final String _JAVASCRIPT_DIR = "html/js";
268    
269            private static final String _JAVASCRIPT_MINIFIED_SUFFIX = "-min.js";
270    
271            private static Log _log = LogFactoryUtil.getLog(ComboServlet.class);
272    
273            private static class FileContentBag {
274    
275                    public FileContentBag(byte[] fileContent, long lastModifiedTime) {
276                            _fileContent = fileContent;
277                            _lastModified = lastModifiedTime;
278                    }
279    
280                    private byte[] _fileContent;
281                    private long _lastModified;
282    
283            }
284    
285            private ConcurrentMap<String, byte[][]> _byteArrays =
286                    new ConcurrentHashMap<String, byte[][]>();
287            private ConcurrentMap<String, FileContentBag> _fileContentBags =
288                    new ConcurrentHashMap<String, FileContentBag>();
289    
290    }