1
14
15 package com.liferay.portal.servlet.filters.minifier;
16
17 import com.liferay.portal.kernel.configuration.Filter;
18 import com.liferay.portal.kernel.log.Log;
19 import com.liferay.portal.kernel.log.LogFactoryUtil;
20 import com.liferay.portal.kernel.servlet.BrowserSniffer;
21 import com.liferay.portal.kernel.servlet.ServletContextUtil;
22 import com.liferay.portal.kernel.util.ArrayUtil;
23 import com.liferay.portal.kernel.util.CharPool;
24 import com.liferay.portal.kernel.util.ContentTypes;
25 import com.liferay.portal.kernel.util.FileUtil;
26 import com.liferay.portal.kernel.util.GetterUtil;
27 import com.liferay.portal.kernel.util.ParamUtil;
28 import com.liferay.portal.kernel.util.PropsKeys;
29 import com.liferay.portal.kernel.util.StringBundler;
30 import com.liferay.portal.kernel.util.StringPool;
31 import com.liferay.portal.kernel.util.StringUtil;
32 import com.liferay.portal.kernel.util.Validator;
33 import com.liferay.portal.servlet.filters.BasePortalFilter;
34 import com.liferay.portal.servlet.filters.etag.ETagUtil;
35 import com.liferay.portal.util.MinifierUtil;
36 import com.liferay.portal.util.PropsUtil;
37 import com.liferay.portal.util.PropsValues;
38 import com.liferay.util.SystemProperties;
39 import com.liferay.util.servlet.ServletResponseUtil;
40 import com.liferay.util.servlet.filters.CacheResponse;
41 import com.liferay.util.servlet.filters.CacheResponseUtil;
42
43 import java.io.File;
44 import java.io.IOException;
45
46 import java.util.regex.Matcher;
47 import java.util.regex.Pattern;
48
49 import javax.servlet.FilterChain;
50 import javax.servlet.FilterConfig;
51 import javax.servlet.ServletContext;
52 import javax.servlet.http.HttpServletRequest;
53 import javax.servlet.http.HttpServletResponse;
54
55
60 public class MinifierFilter extends BasePortalFilter {
61
62 public void init(FilterConfig filterConfig) {
63 super.init(filterConfig);
64
65 _servletContext = filterConfig.getServletContext();
66 _servletContextName = GetterUtil.getString(
67 _servletContext.getServletContextName());
68
69 if (Validator.isNull(_servletContextName)) {
70 _tempDir += "/portal";
71 }
72 }
73
74 protected String aggregateCss(String dir, String content)
75 throws IOException {
76
77 StringBuilder sb = new StringBuilder(content.length());
78
79 int pos = 0;
80
81 while (true) {
82 int x = content.indexOf(_CSS_IMPORT_BEGIN, pos);
83 int y = content.indexOf(
84 _CSS_IMPORT_END, x + _CSS_IMPORT_BEGIN.length());
85
86 if ((x == -1) || (y == -1)) {
87 sb.append(content.substring(pos, content.length()));
88
89 break;
90 }
91 else {
92 sb.append(content.substring(pos, x));
93
94 String importFileName = content.substring(
95 x + _CSS_IMPORT_BEGIN.length(), y);
96
97 String importFullFileName = dir.concat(StringPool.SLASH).concat(
98 importFileName);
99
100 String importContent = FileUtil.read(importFullFileName);
101
102 if (importContent == null) {
103 if (_log.isWarnEnabled()) {
104 _log.warn(
105 "File " + importFullFileName + " does not exist");
106 }
107
108 importContent = StringPool.BLANK;
109 }
110
111 String importDir = StringPool.BLANK;
112
113 int slashPos = importFileName.lastIndexOf(CharPool.SLASH);
114
115 if (slashPos != -1) {
116 importDir = StringPool.SLASH.concat(
117 importFileName.substring(0, slashPos + 1));
118 }
119
120 importContent = aggregateCss(dir + importDir, importContent);
121
122 int importDepth = StringUtil.count(
123 importFileName, StringPool.SLASH);
124
125
127 String relativePath = StringPool.BLANK;
128
129 for (int i = 0; i < importDepth; i++) {
130 relativePath += "../";
131 }
132
133 importContent = StringUtil.replace(
134 importContent,
135 new String[] {
136 "url('" + relativePath,
137 "url(\"" + relativePath,
138 "url(" + relativePath
139 },
140 new String[] {
141 "url('[$TEMP_RELATIVE_PATH$]",
142 "url(\"[$TEMP_RELATIVE_PATH$]",
143 "url([$TEMP_RELATIVE_PATH$]"
144 });
145
146 importContent = StringUtil.replace(
147 importContent, "[$TEMP_RELATIVE_PATH$]", StringPool.BLANK);
148
149 sb.append(importContent);
150
151 pos = y + _CSS_IMPORT_END.length();
152 }
153 }
154
155 return sb.toString();
156 }
157
158 protected String getMinifiedBundleContent(
159 HttpServletRequest request, HttpServletResponse response)
160 throws IOException {
161
162 String minifierType = ParamUtil.getString(request, "minifierType");
163 String minifierBundleId = ParamUtil.getString(
164 request, "minifierBundleId");
165
166 if (Validator.isNull(minifierType) ||
167 Validator.isNull(minifierBundleId) ||
168 !ArrayUtil.contains(
169 PropsValues.JAVASCRIPT_BUNDLE_IDS, minifierBundleId)) {
170
171 return null;
172 }
173
174 String minifierBundleDir = PropsUtil.get(
175 PropsKeys.JAVASCRIPT_BUNDLE_DIR, new Filter(minifierBundleId));
176
177 String bundleDirRealPath = ServletContextUtil.getRealPath(
178 _servletContext, minifierBundleDir);
179
180 if (bundleDirRealPath == null) {
181 return null;
182 }
183
184 StringBundler sb = new StringBundler(4);
185
186 sb.append(_tempDir);
187 sb.append(request.getRequestURI());
188
189 String queryString = request.getQueryString();
190
191 if (queryString != null) {
192 sb.append(_QUESTION_SEPARATOR);
193 sb.append(sterilizeQueryString(queryString));
194 }
195
196 String cacheFileName = sb.toString();
197
198 String[] fileNames = PropsUtil.getArray(minifierBundleId);
199
200 File cacheFile = new File(cacheFileName);
201
202 if (cacheFile.exists()) {
203 boolean staleCache = false;
204
205 for (String fileName : fileNames) {
206 File file = new File(
207 bundleDirRealPath + StringPool.SLASH + fileName);
208
209 if (file.lastModified() > cacheFile.lastModified()) {
210 staleCache = true;
211
212 break;
213 }
214 }
215
216 if (!staleCache) {
217 response.setContentType(ContentTypes.TEXT_JAVASCRIPT);
218
219 return FileUtil.read(cacheFile);
220 }
221 }
222
223 if (_log.isInfoEnabled()) {
224 _log.info("Minifying JavaScript bundle " + minifierBundleId);
225 }
226
227 String minifiedContent = null;
228
229 if (fileNames.length == 0) {
230 minifiedContent = StringPool.BLANK;
231 }
232 else {
233 sb = new StringBundler(fileNames.length * 2);
234
235 for (String fileName : fileNames) {
236 String content = FileUtil.read(
237 bundleDirRealPath + StringPool.SLASH + fileName);
238
239 sb.append(content);
240 sb.append(StringPool.NEW_LINE);
241 }
242
243 minifiedContent = minifyJavaScript(sb.toString());
244 }
245
246 response.setContentType(ContentTypes.TEXT_JAVASCRIPT);
247
248 FileUtil.write(cacheFile, minifiedContent);
249
250 return minifiedContent;
251 }
252
253 protected String getMinifiedContent(
254 HttpServletRequest request, HttpServletResponse response,
255 FilterChain filterChain)
256 throws Exception {
257
258 String minifierType = ParamUtil.getString(request, "minifierType");
259 String minifierBundleId = ParamUtil.getString(
260 request, "minifierBundleId");
261 String minifierBundleDir = ParamUtil.getString(
262 request, "minifierBundleDir");
263
264 if (Validator.isNull(minifierType) ||
265 Validator.isNotNull(minifierBundleId) ||
266 Validator.isNotNull(minifierBundleDir)) {
267
268 return null;
269 }
270
271 String requestURI = request.getRequestURI();
272
273 String requestPath = requestURI;
274
275 String contextPath = request.getContextPath();
276
277 if (!contextPath.equals(StringPool.SLASH)) {
278 requestPath = requestPath.substring(contextPath.length());
279 }
280
281 String realPath = ServletContextUtil.getRealPath(
282 _servletContext, requestPath);
283
284 if (realPath == null) {
285 return null;
286 }
287
288 realPath = StringUtil.replace(
289 realPath, CharPool.BACK_SLASH, CharPool.SLASH);
290
291 File file = new File(realPath);
292
293 if (!file.exists()) {
294 return null;
295 }
296
297 String minifiedContent = null;
298
299 StringBundler sb = new StringBundler(4);
300
301 sb.append(_tempDir);
302 sb.append(requestURI);
303
304 String queryString = request.getQueryString();
305
306 if (queryString != null) {
307 sb.append(_QUESTION_SEPARATOR);
308 sb.append(sterilizeQueryString(queryString));
309 }
310
311 String cacheCommonFileName = sb.toString();
312
313 File cacheContentTypeFile = new File(
314 cacheCommonFileName + "_E_CONTENT_TYPE");
315 File cacheDataFile = new File(cacheCommonFileName + "_E_DATA");
316
317 if ((cacheDataFile.exists()) &&
318 (cacheDataFile.lastModified() >= file.lastModified())) {
319
320 minifiedContent = FileUtil.read(cacheDataFile);
321
322 if (cacheContentTypeFile.exists()) {
323 String contentType = FileUtil.read(cacheContentTypeFile);
324
325 response.setContentType(contentType);
326 }
327 }
328 else {
329 if (realPath.endsWith(_CSS_EXTENSION)) {
330 if (_log.isInfoEnabled()) {
331 _log.info("Minifying CSS " + file);
332 }
333
334 minifiedContent = minifyCss(request, file);
335
336 response.setContentType(ContentTypes.TEXT_CSS);
337
338 FileUtil.write(cacheContentTypeFile, ContentTypes.TEXT_CSS);
339 }
340 else if (realPath.endsWith(_JAVASCRIPT_EXTENSION)) {
341 if (_log.isInfoEnabled()) {
342 _log.info("Minifying JavaScript " + file);
343 }
344
345 minifiedContent = minifyJavaScript(file);
346
347 response.setContentType(ContentTypes.TEXT_JAVASCRIPT);
348
349 FileUtil.write(
350 cacheContentTypeFile, ContentTypes.TEXT_JAVASCRIPT);
351 }
352 else if (realPath.endsWith(_JSP_EXTENSION)) {
353 if (_log.isInfoEnabled()) {
354 _log.info("Minifying JSP " + file);
355 }
356
357 CacheResponse cacheResponse = new CacheResponse(
358 response, StringPool.UTF8);
359
360 processFilter(
361 MinifierFilter.class, request, cacheResponse, filterChain);
362
363 CacheResponseUtil.addHeaders(
364 response, cacheResponse.getHeaders());
365
366 response.setContentType(cacheResponse.getContentType());
367
368 minifiedContent = new String(
369 cacheResponse.unsafeGetData(), 0,
370 cacheResponse.getContentLength(), StringPool.UTF8);
371
372 if (minifierType.equals("css")) {
373 minifiedContent = minifyCss(request, minifiedContent);
374 }
375 else if (minifierType.equals("js")) {
376 minifiedContent = minifyJavaScript(minifiedContent);
377 }
378
379 FileUtil.write(
380 cacheContentTypeFile, cacheResponse.getContentType());
381 }
382 else {
383 return null;
384 }
385
386 FileUtil.write(cacheDataFile, minifiedContent);
387 }
388
389 return minifiedContent;
390 }
391
392 protected String minifyCss(HttpServletRequest request, File file)
393 throws IOException {
394
395 String content = FileUtil.read(file);
396
397 content = aggregateCss(file.getParent(), content);
398
399 return minifyCss(request, content);
400 }
401
402 protected String minifyCss(HttpServletRequest request, String content) {
403 String browserId = ParamUtil.getString(request, "browserId");
404
405 if (!browserId.equals(BrowserSniffer.BROWSER_ID_IE)) {
406 Matcher matcher = _pattern.matcher(content);
407
408 content = matcher.replaceAll(StringPool.BLANK);
409 }
410
411 return MinifierUtil.minifyCss(content);
412 }
413
414 protected String minifyJavaScript(File file) throws IOException {
415 String content = FileUtil.read(file);
416
417 return minifyJavaScript(content);
418 }
419
420 protected String minifyJavaScript(String content) {
421 return MinifierUtil.minifyJavaScript(content);
422 }
423
424 protected void processFilter(
425 HttpServletRequest request, HttpServletResponse response,
426 FilterChain filterChain)
427 throws Exception {
428
429 String minifiedContent = getMinifiedContent(
430 request, response, filterChain);
431
432 if (Validator.isNull(minifiedContent)) {
433 minifiedContent = getMinifiedBundleContent(request, response);
434 }
435
436 if (Validator.isNull(minifiedContent)) {
437 processFilter(MinifierFilter.class, request, response, filterChain);
438 }
439 else {
440 if (!ETagUtil.processETag(request, response, minifiedContent)) {
441 ServletResponseUtil.write(response, minifiedContent);
442 }
443 }
444 }
445
446 protected String sterilizeQueryString(String queryString) {
447 return StringUtil.replace(
448 queryString,
449 new String[] {StringPool.SLASH, StringPool.BACK_SLASH},
450 new String[] {StringPool.UNDERLINE, StringPool.UNDERLINE});
451 }
452
453 private static final String _CSS_IMPORT_BEGIN = "@import url(";
454
455 private static final String _CSS_IMPORT_END = ");";
456
457 private static final String _CSS_EXTENSION = ".css";
458
459 private static final String _JAVASCRIPT_EXTENSION = ".js";
460
461 private static final String _JSP_EXTENSION = ".jsp";
462
463 private static final String _QUESTION_SEPARATOR = "_Q_";
464
465 private static final String _TEMP_DIR =
466 SystemProperties.get(SystemProperties.TMP_DIR) + "/liferay/minifier";
467
468 private static Log _log = LogFactoryUtil.getLog(MinifierFilter.class);
469
470 private static Pattern _pattern = Pattern.compile(
471 "^(\\.ie|\\.js\\.ie)([^}]*)}", Pattern.MULTILINE);
472
473 private ServletContext _servletContext;
474 private String _servletContextName;
475 private String _tempDir = _TEMP_DIR;
476
477 }