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