001
014
015 package com.liferay.portal.servlet.filters.minifier;
016
017 import com.liferay.portal.kernel.configuration.Filter;
018 import com.liferay.portal.kernel.log.Log;
019 import com.liferay.portal.kernel.log.LogFactoryUtil;
020 import com.liferay.portal.kernel.servlet.BrowserSniffer;
021 import com.liferay.portal.kernel.servlet.ServletContextUtil;
022 import com.liferay.portal.kernel.servlet.StringServletResponse;
023 import com.liferay.portal.kernel.util.ArrayUtil;
024 import com.liferay.portal.kernel.util.ContentTypes;
025 import com.liferay.portal.kernel.util.FileUtil;
026 import com.liferay.portal.kernel.util.GetterUtil;
027 import com.liferay.portal.kernel.util.ParamUtil;
028 import com.liferay.portal.kernel.util.PropsKeys;
029 import com.liferay.portal.kernel.util.StringBundler;
030 import com.liferay.portal.kernel.util.StringPool;
031 import com.liferay.portal.kernel.util.StringUtil;
032 import com.liferay.portal.kernel.util.Validator;
033 import com.liferay.portal.servlet.filters.BasePortalFilter;
034 import com.liferay.portal.util.JavaScriptBundleUtil;
035 import com.liferay.portal.util.MinifierUtil;
036 import com.liferay.portal.util.PropsUtil;
037 import com.liferay.portal.util.PropsValues;
038 import com.liferay.util.SystemProperties;
039 import com.liferay.util.servlet.ServletResponseUtil;
040 import com.liferay.util.servlet.filters.CacheResponseUtil;
041
042 import java.io.File;
043 import java.io.IOException;
044
045 import java.util.regex.Matcher;
046 import java.util.regex.Pattern;
047
048 import javax.servlet.FilterChain;
049 import javax.servlet.FilterConfig;
050 import javax.servlet.ServletContext;
051 import javax.servlet.http.HttpServletRequest;
052 import javax.servlet.http.HttpServletResponse;
053
054
057 public class MinifierFilter extends BasePortalFilter {
058
059 public void init(FilterConfig filterConfig) {
060 super.init(filterConfig);
061
062 _servletContext = filterConfig.getServletContext();
063 _servletContextName = GetterUtil.getString(
064 _servletContext.getServletContextName());
065
066 if (Validator.isNull(_servletContextName)) {
067 _tempDir += "/portal";
068 }
069 }
070
071 protected String aggregateCss(String dir, String content)
072 throws IOException {
073
074 StringBuilder sb = new StringBuilder(content.length());
075
076 int pos = 0;
077
078 while (true) {
079 int x = content.indexOf(_CSS_IMPORT_BEGIN, pos);
080 int y = content.indexOf(
081 _CSS_IMPORT_END, x + _CSS_IMPORT_BEGIN.length());
082
083 if ((x == -1) || (y == -1)) {
084 sb.append(content.substring(pos, content.length()));
085
086 break;
087 }
088 else {
089 sb.append(content.substring(pos, x));
090
091 String importFileName = content.substring(
092 x + _CSS_IMPORT_BEGIN.length(), y);
093
094 String importFullFileName = dir.concat(StringPool.SLASH).concat(
095 importFileName);
096
097 String importContent = FileUtil.read(importFullFileName);
098
099 if (importContent == null) {
100 if (_log.isWarnEnabled()) {
101 _log.warn(
102 "File " + importFullFileName + " does not exist");
103 }
104
105 importContent = StringPool.BLANK;
106 }
107
108 String importDir = StringPool.BLANK;
109
110 int slashPos = importFileName.lastIndexOf(StringPool.SLASH);
111
112 if (slashPos != -1) {
113 importDir = StringPool.SLASH.concat(
114 importFileName.substring(0, slashPos + 1));
115 }
116
117 importContent = aggregateCss(dir + importDir, importContent);
118
119 int importDepth = StringUtil.count(
120 importFileName, StringPool.SLASH);
121
122
123
124 String relativePath = StringPool.BLANK;
125
126 for (int i = 0; i < importDepth; i++) {
127 relativePath += "../";
128 }
129
130 importContent = StringUtil.replace(
131 importContent,
132 new String[] {
133 "url('" + relativePath,
134 "url(\"" + relativePath,
135 "url(" + relativePath
136 },
137 new String[] {
138 "url('[$TEMP_RELATIVE_PATH$]",
139 "url(\"[$TEMP_RELATIVE_PATH$]",
140 "url([$TEMP_RELATIVE_PATH$]"
141 });
142
143 importContent = StringUtil.replace(
144 importContent, "[$TEMP_RELATIVE_PATH$]", StringPool.BLANK);
145
146 sb.append(importContent);
147
148 pos = y + _CSS_IMPORT_END.length();
149 }
150 }
151
152 return sb.toString();
153 }
154
155 protected String getMinifiedBundleContent(
156 HttpServletRequest request, HttpServletResponse response)
157 throws IOException {
158
159 String minifierType = ParamUtil.getString(request, "minifierType");
160 String minifierBundleId = ParamUtil.getString(
161 request, "minifierBundleId");
162
163 if (Validator.isNull(minifierType) ||
164 Validator.isNull(minifierBundleId) ||
165 !ArrayUtil.contains(
166 PropsValues.JAVASCRIPT_BUNDLE_IDS, minifierBundleId)) {
167
168 return null;
169 }
170
171 String minifierBundleDir = PropsUtil.get(
172 PropsKeys.JAVASCRIPT_BUNDLE_DIR, new Filter(minifierBundleId));
173
174 String bundleDirRealPath = ServletContextUtil.getRealPath(
175 _servletContext, minifierBundleDir);
176
177 if (bundleDirRealPath == null) {
178 return null;
179 }
180
181 StringBundler sb = new StringBundler(4);
182
183 sb.append(_tempDir);
184 sb.append(request.getRequestURI());
185
186 String queryString = request.getQueryString();
187
188 if (queryString != null) {
189 sb.append(_QUESTION_SEPARATOR);
190 sb.append(sterilizeQueryString(queryString));
191 }
192
193 String cacheFileName = sb.toString();
194
195 String[] fileNames = JavaScriptBundleUtil.getFileNames(
196 minifierBundleId);
197
198 File cacheFile = new File(cacheFileName);
199
200 if (cacheFile.exists()) {
201 boolean staleCache = false;
202
203 for (String fileName : fileNames) {
204 File file = new File(
205 bundleDirRealPath + StringPool.SLASH + fileName);
206
207 if (file.lastModified() > cacheFile.lastModified()) {
208 staleCache = true;
209
210 break;
211 }
212 }
213
214 if (!staleCache) {
215 response.setContentType(ContentTypes.TEXT_JAVASCRIPT);
216
217 return FileUtil.read(cacheFile);
218 }
219 }
220
221 if (_log.isInfoEnabled()) {
222 _log.info("Minifying JavaScript bundle " + minifierBundleId);
223 }
224
225 String minifiedContent = null;
226
227 if (fileNames.length == 0) {
228 minifiedContent = StringPool.BLANK;
229 }
230 else {
231 sb = new StringBundler(fileNames.length * 2);
232
233 for (String fileName : fileNames) {
234 String content = FileUtil.read(
235 bundleDirRealPath + StringPool.SLASH + fileName);
236
237 sb.append(content);
238 sb.append(StringPool.NEW_LINE);
239 }
240
241 minifiedContent = minifyJavaScript(sb.toString());
242 }
243
244 response.setContentType(ContentTypes.TEXT_JAVASCRIPT);
245
246 FileUtil.write(cacheFile, minifiedContent);
247
248 return minifiedContent;
249 }
250
251 protected String getMinifiedContent(
252 HttpServletRequest request, HttpServletResponse response,
253 FilterChain filterChain)
254 throws Exception {
255
256 String minifierType = ParamUtil.getString(request, "minifierType");
257 String minifierBundleId = ParamUtil.getString(
258 request, "minifierBundleId");
259 String minifierBundleDir = ParamUtil.getString(
260 request, "minifierBundleDir");
261
262 if (Validator.isNull(minifierType) ||
263 Validator.isNotNull(minifierBundleId) ||
264 Validator.isNotNull(minifierBundleDir)) {
265
266 return null;
267 }
268
269 String requestURI = request.getRequestURI();
270
271 String requestPath = requestURI;
272
273 String contextPath = request.getContextPath();
274
275 if (!contextPath.equals(StringPool.SLASH)) {
276 requestPath = requestPath.substring(contextPath.length());
277 }
278
279 String realPath = ServletContextUtil.getRealPath(
280 _servletContext, requestPath);
281
282 if (realPath == null) {
283 return null;
284 }
285
286 realPath = StringUtil.replace(
287 realPath, StringPool.BACK_SLASH, StringPool.SLASH);
288
289 File file = new File(realPath);
290
291 if (!file.exists()) {
292 return null;
293 }
294
295 String minifiedContent = null;
296
297 StringBundler sb = new StringBundler(4);
298
299 sb.append(_tempDir);
300 sb.append(requestURI);
301
302 String queryString = request.getQueryString();
303
304 if (queryString != null) {
305 sb.append(_QUESTION_SEPARATOR);
306 sb.append(sterilizeQueryString(queryString));
307 }
308
309 String cacheCommonFileName = sb.toString();
310
311 File cacheContentTypeFile = new File(
312 cacheCommonFileName + "_E_CONTENT_TYPE");
313 File cacheDataFile = new File(cacheCommonFileName + "_E_DATA");
314
315 if ((cacheDataFile.exists()) &&
316 (cacheDataFile.lastModified() >= file.lastModified())) {
317
318 minifiedContent = FileUtil.read(cacheDataFile);
319
320 if (cacheContentTypeFile.exists()) {
321 String contentType = FileUtil.read(cacheContentTypeFile);
322
323 response.setContentType(contentType);
324 }
325 }
326 else {
327 if (realPath.endsWith(_CSS_EXTENSION)) {
328 if (_log.isInfoEnabled()) {
329 _log.info("Minifying CSS " + file);
330 }
331
332 minifiedContent = minifyCss(request, file);
333
334 response.setContentType(ContentTypes.TEXT_CSS);
335
336 FileUtil.write(cacheContentTypeFile, ContentTypes.TEXT_CSS);
337 }
338 else if (realPath.endsWith(_JAVASCRIPT_EXTENSION)) {
339 if (_log.isInfoEnabled()) {
340 _log.info("Minifying JavaScript " + file);
341 }
342
343 minifiedContent = minifyJavaScript(file);
344
345 response.setContentType(ContentTypes.TEXT_JAVASCRIPT);
346
347 FileUtil.write(
348 cacheContentTypeFile, ContentTypes.TEXT_JAVASCRIPT);
349 }
350 else if (realPath.endsWith(_JSP_EXTENSION)) {
351 if (_log.isInfoEnabled()) {
352 _log.info("Minifying JSP " + file);
353 }
354
355 StringServletResponse stringResponse =
356 new StringServletResponse(response);
357
358 processFilter(
359 MinifierFilter.class, request, stringResponse, filterChain);
360
361 CacheResponseUtil.setHeaders(
362 response, stringResponse.getHeaders());
363
364 response.setContentType(stringResponse.getContentType());
365
366 minifiedContent = stringResponse.getString();
367
368 if (minifierType.equals("css")) {
369 minifiedContent = minifyCss(request, minifiedContent);
370 }
371 else if (minifierType.equals("js")) {
372 minifiedContent = minifyJavaScript(minifiedContent);
373 }
374
375 FileUtil.write(
376 cacheContentTypeFile, stringResponse.getContentType());
377 }
378 else {
379 return null;
380 }
381
382 FileUtil.write(cacheDataFile, minifiedContent);
383 }
384
385 return minifiedContent;
386 }
387
388 protected String minifyCss(HttpServletRequest request, File file)
389 throws IOException {
390
391 String content = FileUtil.read(file);
392
393 content = aggregateCss(file.getParent(), content);
394
395 return minifyCss(request, content);
396 }
397
398 protected String minifyCss(HttpServletRequest request, String content) {
399 String browserId = ParamUtil.getString(request, "browserId");
400
401 if (!browserId.equals(BrowserSniffer.BROWSER_ID_IE)) {
402 Matcher matcher = _pattern.matcher(content);
403
404 content = matcher.replaceAll(StringPool.BLANK);
405 }
406
407 return MinifierUtil.minifyCss(content);
408 }
409
410 protected String minifyJavaScript(File file) throws IOException {
411 String content = FileUtil.read(file);
412
413 return minifyJavaScript(content);
414 }
415
416 protected String minifyJavaScript(String content) {
417 return MinifierUtil.minifyJavaScript(content);
418 }
419
420 protected void processFilter(
421 HttpServletRequest request, HttpServletResponse response,
422 FilterChain filterChain)
423 throws Exception {
424
425 String minifiedContent = getMinifiedContent(
426 request, response, filterChain);
427
428 if (Validator.isNull(minifiedContent)) {
429 minifiedContent = getMinifiedBundleContent(request, response);
430 }
431
432 if (Validator.isNull(minifiedContent)) {
433 processFilter(MinifierFilter.class, request, response, filterChain);
434 }
435 else {
436 ServletResponseUtil.write(response, minifiedContent);
437 }
438 }
439
440 protected String sterilizeQueryString(String queryString) {
441 return StringUtil.replace(
442 queryString,
443 new String[] {StringPool.SLASH, StringPool.BACK_SLASH},
444 new String[] {StringPool.UNDERLINE, StringPool.UNDERLINE});
445 }
446
447 private static final String _CSS_IMPORT_BEGIN = "@import url(";
448
449 private static final String _CSS_IMPORT_END = ");";
450
451 private static final String _CSS_EXTENSION = ".css";
452
453 private static final String _JAVASCRIPT_EXTENSION = ".js";
454
455 private static final String _JSP_EXTENSION = ".jsp";
456
457 private static final String _QUESTION_SEPARATOR = "_Q_";
458
459 private static final String _TEMP_DIR =
460 SystemProperties.get(SystemProperties.TMP_DIR) + "/liferay/minifier";
461
462 private static Log _log = LogFactoryUtil.getLog(MinifierFilter.class);
463
464 private static Pattern _pattern = Pattern.compile(
465 "^(\\.ie|\\.js\\.ie)([^}]*)}", Pattern.MULTILINE);
466
467 private ServletContext _servletContext;
468 private String _servletContextName;
469 private String _tempDir = _TEMP_DIR;
470
471 }