001
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.PropsKeys;
027 import com.liferay.portal.kernel.util.StringPool;
028 import com.liferay.portal.kernel.util.StringUtil;
029 import com.liferay.portal.kernel.util.Validator;
030 import com.liferay.portal.servlet.filters.dynamiccss.DynamicCSSUtil;
031 import com.liferay.portal.util.MinifierUtil;
032 import com.liferay.portal.util.PortalUtil;
033 import com.liferay.portal.util.PrefsPropsUtil;
034 import com.liferay.portal.util.PropsValues;
035
036 import java.io.File;
037 import java.io.IOException;
038
039 import java.util.Arrays;
040 import java.util.concurrent.ConcurrentHashMap;
041 import java.util.concurrent.ConcurrentMap;
042
043 import javax.servlet.ServletContext;
044 import javax.servlet.ServletException;
045 import javax.servlet.http.HttpServlet;
046 import javax.servlet.http.HttpServletRequest;
047 import javax.servlet.http.HttpServletResponse;
048
049
054 public class ComboServlet extends HttpServlet {
055
056 @Override
057 public void service(
058 HttpServletRequest request, HttpServletResponse response)
059 throws IOException, ServletException {
060
061 try {
062 doService(request, response);
063 }
064 catch (Exception e) {
065 _log.error(e, e);
066
067 PortalUtil.sendError(
068 HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e, request,
069 response);
070 }
071 }
072
073 protected void doService(
074 HttpServletRequest request, HttpServletResponse response)
075 throws Exception {
076
077 String contextPath = PortalUtil.getPathContext();
078
079 String[] modulePaths = request.getParameterValues("m");
080
081 if ((modulePaths == null) || (modulePaths.length == 0)) {
082 response.sendError(HttpServletResponse.SC_BAD_REQUEST);
083
084 return;
085 }
086
087 Arrays.sort(modulePaths);
088
089 String modulePathsString = null;
090
091 byte[][] bytesArray = null;
092
093 if (!PropsValues.COMBO_CHECK_TIMESTAMP) {
094 modulePathsString = Arrays.toString(modulePaths);
095
096 bytesArray = _byteArrays.get(modulePathsString);
097 }
098
099 String firstModulePath = modulePaths[0];
100
101 String extension = FileUtil.getExtension(firstModulePath);
102
103 if (bytesArray == null) {
104 String p = ParamUtil.getString(request, "p");
105
106 String minifierType = ParamUtil.getString(request, "minifierType");
107
108 if (Validator.isNull(minifierType)) {
109 minifierType = "js";
110
111 if (extension.equalsIgnoreCase(_CSS_EXTENSION)) {
112 minifierType = "css";
113 }
114 }
115
116 int length = modulePaths.length;
117
118 bytesArray = new byte[length][];
119
120 for (String modulePath : modulePaths) {
121 if (!validateModuleExtension(modulePath)) {
122 PortalUtil.sendError(
123 HttpServletResponse.SC_NOT_FOUND, new IOException(),
124 request, response);
125
126 return;
127 }
128
129 byte[] bytes = new byte[0];
130
131 if (Validator.isNotNull(modulePath)) {
132 modulePath = StringUtil.replaceFirst(
133 p.concat(modulePath), contextPath, StringPool.BLANK);
134
135 bytes = getFileContent(
136 request, response, modulePath, minifierType);
137 }
138
139 bytesArray[--length] = bytes;
140 }
141
142 if (modulePathsString != null) {
143 _byteArrays.put(modulePathsString, bytesArray);
144 }
145 }
146
147 String contentType = ContentTypes.TEXT_JAVASCRIPT;
148
149 if (extension.equalsIgnoreCase(_CSS_EXTENSION)) {
150 contentType = ContentTypes.TEXT_CSS;
151 }
152
153 response.setContentType(contentType);
154
155 ServletResponseUtil.write(response, bytesArray);
156 }
157
158 protected File getFile(String path) throws IOException {
159 ServletContext servletContext = getServletContext();
160
161 String basePath = ServletContextUtil.getRealPath(
162 servletContext, _JAVASCRIPT_DIR);
163
164 if (basePath == null) {
165 return null;
166 }
167
168 basePath = StringUtil.replace(
169 basePath, CharPool.BACK_SLASH, CharPool.SLASH);
170
171 File baseDir = new File(basePath);
172
173 if (!baseDir.exists()) {
174 return null;
175 }
176
177 String filePath = ServletContextUtil.getRealPath(servletContext, path);
178
179 if (filePath == null) {
180 return null;
181 }
182
183 filePath = StringUtil.replace(
184 filePath, CharPool.BACK_SLASH, CharPool.SLASH);
185
186 File file = new File(filePath);
187
188 if (!file.exists()) {
189 return null;
190 }
191
192 String baseCanonicalPath = baseDir.getCanonicalPath();
193 String fileCanonicalPath = file.getCanonicalPath();
194
195 if (fileCanonicalPath.indexOf(baseCanonicalPath) == 0) {
196 return file;
197 }
198
199 return null;
200 }
201
202 protected byte[] getFileContent(
203 HttpServletRequest request, HttpServletResponse response,
204 String path, String minifierType)
205 throws IOException {
206
207 String fileContentKey = path.concat(StringPool.QUESTION).concat(
208 minifierType);
209
210 FileContentBag fileContentBag = _fileContentBags.get(fileContentKey);
211
212 if ((fileContentBag != null) &&
213 !PropsValues.COMBO_CHECK_TIMESTAMP) {
214
215 return fileContentBag._fileContent;
216 }
217
218 File file = getFile(path);
219
220 if ((fileContentBag != null) && PropsValues.COMBO_CHECK_TIMESTAMP) {
221 long elapsedTime =
222 System.currentTimeMillis() - fileContentBag._lastModified;
223
224 if ((file != null) &&
225 (elapsedTime <= PropsValues.COMBO_CHECK_TIMESTAMP_INTERVAL) &&
226 (file.lastModified() == fileContentBag._lastModified)) {
227
228 return fileContentBag._fileContent;
229 }
230 else {
231 _fileContentBags.remove(fileContentKey, fileContentBag);
232 }
233 }
234
235 if (file == null) {
236 fileContentBag = _EMPTY_FILE_CONTENT_BAG;
237 }
238 else {
239 String stringFileContent = FileUtil.read(file);
240
241 if (!StringUtil.endsWith(path, _CSS_MINIFIED_SUFFIX) &&
242 !StringUtil.endsWith(path, _JAVASCRIPT_MINIFIED_SUFFIX)) {
243
244 if (minifierType.equals("css")) {
245 String cssRealPath = file.getAbsolutePath();
246
247 try {
248 stringFileContent = DynamicCSSUtil.parseSass(
249 request, cssRealPath, stringFileContent);
250 }
251 catch (Exception e) {
252 _log.error(
253 "Unable to parse SASS on CSS " + cssRealPath, e);
254
255 if (_log.isDebugEnabled()) {
256 _log.debug(stringFileContent);
257 }
258
259 response.setHeader(
260 HttpHeaders.CACHE_CONTROL,
261 HttpHeaders.CACHE_CONTROL_NO_CACHE_VALUE);
262 }
263
264 stringFileContent = MinifierUtil.minifyCss(
265 stringFileContent);
266 }
267 else if (minifierType.equals("js")) {
268 stringFileContent = MinifierUtil.minifyJavaScript(
269 stringFileContent);
270 }
271 }
272
273 fileContentBag = new FileContentBag(
274 stringFileContent.getBytes(StringPool.UTF8),
275 file.lastModified());
276 }
277
278 FileContentBag oldFileContentBag = _fileContentBags.putIfAbsent(
279 fileContentKey, fileContentBag);
280
281 if (oldFileContentBag != null) {
282 fileContentBag = oldFileContentBag;
283 }
284
285 return fileContentBag._fileContent;
286 }
287
288 protected boolean validateModuleExtension(String moduleName)
289 throws Exception {
290
291 boolean validModuleExtension = false;
292
293 String[] fileExtensions = PrefsPropsUtil.getStringArray(
294 PropsKeys.COMBO_ALLOWED_FILE_EXTENSIONS, StringPool.COMMA);
295
296 for (int i = 0; i < fileExtensions.length; i++) {
297 if (StringPool.STAR.equals(fileExtensions[i]) ||
298 StringUtil.endsWith(moduleName, fileExtensions[i])) {
299
300 validModuleExtension = true;
301
302 break;
303 }
304 }
305
306 return validModuleExtension;
307 }
308
309 private static final String _CSS_EXTENSION = "css";
310
311 private static final String _CSS_MINIFIED_SUFFIX = "-min.css";
312
313 private static final FileContentBag _EMPTY_FILE_CONTENT_BAG =
314 new FileContentBag(new byte[0], 0);
315
316 private static final String _JAVASCRIPT_DIR = "html/js";
317
318 private static final String _JAVASCRIPT_MINIFIED_SUFFIX = "-min.js";
319
320 private static Log _log = LogFactoryUtil.getLog(ComboServlet.class);
321
322 private static class FileContentBag {
323
324 public FileContentBag(byte[] fileContent, long lastModifiedTime) {
325 _fileContent = fileContent;
326 _lastModified = lastModifiedTime;
327 }
328
329 private byte[] _fileContent;
330 private long _lastModified;
331
332 }
333
334 private ConcurrentMap<String, byte[][]> _byteArrays =
335 new ConcurrentHashMap<String, byte[][]>();
336 private ConcurrentMap<String, FileContentBag> _fileContentBags =
337 new ConcurrentHashMap<String, FileContentBag>();
338
339 }