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