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