001    /**
002     * Copyright (c) 2000-2010 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.servlet.ServletContextUtil;
018    import com.liferay.portal.kernel.util.CharPool;
019    import com.liferay.portal.kernel.util.ContentTypes;
020    import com.liferay.portal.kernel.util.FileUtil;
021    import com.liferay.portal.kernel.util.ParamUtil;
022    import com.liferay.portal.kernel.util.StringPool;
023    import com.liferay.portal.kernel.util.StringUtil;
024    import com.liferay.portal.kernel.util.Validator;
025    import com.liferay.portal.util.MinifierUtil;
026    import com.liferay.portal.util.PortalUtil;
027    import com.liferay.portal.util.PropsValues;
028    import com.liferay.util.servlet.ServletResponseUtil;
029    
030    import java.io.File;
031    import java.io.IOException;
032    
033    import java.util.concurrent.ConcurrentHashMap;
034    import java.util.concurrent.ConcurrentMap;
035    
036    import javax.servlet.ServletContext;
037    import javax.servlet.http.HttpServlet;
038    import javax.servlet.http.HttpServletRequest;
039    import javax.servlet.http.HttpServletResponse;
040    
041    /**
042     * @author Eduardo Lundgren
043     */
044    public class ComboServlet extends HttpServlet {
045    
046            public void service(
047                            HttpServletRequest request, HttpServletResponse response)
048                    throws IOException {
049    
050                    String contextPath = PortalUtil.getPathContext();
051    
052                    String[] modulePaths = request.getParameterValues("m");
053    
054                    if ((modulePaths == null) || (modulePaths.length == 0)) {
055                            response.sendError(HttpServletResponse.SC_BAD_REQUEST);
056    
057                            return;
058                    }
059    
060                    String p = ParamUtil.getString(request, "p");
061                    String minifierType = ParamUtil.getString(request, "minifierType");
062    
063                    int length = modulePaths.length;
064    
065                    byte[][] bytesArray = new byte[length][];
066    
067                    for (String modulePath : modulePaths) {
068                            byte[] bytes = new byte[0];
069    
070                            if (Validator.isNotNull(modulePath)) {
071                                    modulePath = StringUtil.replaceFirst(
072                                            p.concat(modulePath), contextPath, StringPool.BLANK);
073    
074                                    bytes = getFileContent(modulePath, minifierType);
075                            }
076    
077                            bytesArray[--length] = bytes;
078                    }
079    
080                    String contentType = ContentTypes.TEXT_JAVASCRIPT;
081    
082                    String firstModulePath =
083                            (String)request.getParameterNames().nextElement();
084    
085                    String extension = FileUtil.getExtension(firstModulePath);
086    
087                    if (extension.equalsIgnoreCase(_CSS_EXTENSION)) {
088                            contentType = ContentTypes.TEXT_CSS;
089                    }
090    
091                    response.setContentType(contentType);
092    
093                    ServletResponseUtil.write(response, bytesArray);
094            }
095    
096            protected File getFile(String path) throws IOException {
097                    ServletContext servletContext = getServletContext();
098    
099                    String basePath = ServletContextUtil.getRealPath(
100                            servletContext, _JAVASCRIPT_DIR);
101    
102                    if (basePath == null) {
103                            return null;
104                    }
105    
106                    basePath = StringUtil.replace(
107                            basePath, CharPool.BACK_SLASH, CharPool.SLASH);
108    
109                    File baseDir = new File(basePath);
110    
111                    if (!baseDir.exists()) {
112                            return null;
113                    }
114    
115                    String filePath = ServletContextUtil.getRealPath(servletContext, path);
116    
117                    if (filePath == null) {
118                            return null;
119                    }
120    
121                    filePath = StringUtil.replace(
122                            filePath, CharPool.BACK_SLASH, CharPool.SLASH);
123    
124                    File file = new File(filePath);
125    
126                    if (!file.exists()) {
127                            return null;
128                    }
129    
130                    String baseCanonicalPath = baseDir.getCanonicalPath();
131                    String fileCanonicalPath = file.getCanonicalPath();
132    
133                    if (fileCanonicalPath.indexOf(baseCanonicalPath) == 0) {
134                            return file;
135                    }
136    
137                    return null;
138            }
139    
140            protected byte[] getFileContent(String path, String minifierType)
141                    throws IOException {
142    
143                    String fileContentKey = path.concat(StringPool.QUESTION).concat(
144                            minifierType);
145    
146                    FileContentBag fileContentBag = _fileContents.get(fileContentKey);
147    
148                    if ((fileContentBag != null) &&
149                            !PropsValues.COMBO_CHECK_TIMESTAMP) {
150    
151                            return fileContentBag._fileContent;
152                    }
153    
154                    File file = getFile(path);
155    
156                    if ((fileContentBag != null) && PropsValues.COMBO_CHECK_TIMESTAMP) {
157                            if ((file != null) &&
158                                    (file.lastModified() == fileContentBag._lastModified)) {
159    
160                                    return fileContentBag._fileContent;
161                            }
162                            else {
163                                    _fileContents.remove(fileContentKey, fileContentBag);
164                            }
165                    }
166    
167                    if (file == null) {
168                            fileContentBag = _EMPTY_FILE_CONTENT_BAG;
169                    }
170                    else {
171                            String stringFileContent = FileUtil.read(file);
172    
173                            if (!StringUtil.endsWith(path, _CSS_MINIFIED_SUFFIX) &&
174                                    !StringUtil.endsWith(path, _JAVASCRIPT_MINIFIED_SUFFIX)) {
175    
176                                    if (minifierType.equals("css")) {
177                                            stringFileContent = MinifierUtil.minifyCss(
178                                                    stringFileContent);
179                                    }
180                                    else if (minifierType.equals("js")) {
181                                            stringFileContent = MinifierUtil.minifyJavaScript(
182                                                    stringFileContent);
183                                    }
184                            }
185    
186                            fileContentBag = new FileContentBag(
187                                    stringFileContent.getBytes(StringPool.UTF8),
188                                    file.lastModified());
189                    }
190    
191                    FileContentBag oldFileContentBag = _fileContents.putIfAbsent(
192                            fileContentKey, fileContentBag);
193    
194                    if (oldFileContentBag != null) {
195                            fileContentBag = oldFileContentBag;
196                    }
197    
198                    return fileContentBag._fileContent;
199            }
200    
201            private static final String _CSS_EXTENSION = "css";
202    
203            private static final String _CSS_MINIFIED_SUFFIX = "-min.css";
204    
205            private static final FileContentBag _EMPTY_FILE_CONTENT_BAG =
206                    new FileContentBag(new byte[0], 0);
207    
208            private static final String _JAVASCRIPT_DIR = "html/js";
209    
210            private static final String _JAVASCRIPT_MINIFIED_SUFFIX = "-min.js";
211    
212            private ConcurrentMap<String, FileContentBag> _fileContents =
213                    new ConcurrentHashMap<String, FileContentBag>();
214    
215            private static class FileContentBag {
216    
217                    public FileContentBag(byte[] fileContent, long lastModifiedTime) {
218                            _fileContent = fileContent;
219                            _lastModified = lastModifiedTime;
220                    }
221    
222                    private byte[] _fileContent;
223                    private long _lastModified;
224    
225            }
226    
227    }