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.servlet.StringServletResponse;
23 import com.liferay.portal.kernel.util.ArrayUtil;
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.util.JavaScriptBundleUtil;
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.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 = JavaScriptBundleUtil.getFileNames(
186 minifierBundleId);
187
188 File cacheFile = new File(cacheFileName);
189
190 if (cacheFile.exists()) {
191 boolean staleCache = false;
192
193 for (String fileName : fileNames) {
194 File file = new File(
195 bundleDirRealPath + StringPool.SLASH + fileName);
196
197 if (file.lastModified() > cacheFile.lastModified()) {
198 staleCache = true;
199
200 break;
201 }
202 }
203
204 if (!staleCache) {
205 response.setContentType(ContentTypes.TEXT_JAVASCRIPT);
206
207 return FileUtil.read(cacheFile);
208 }
209 }
210
211 if (_log.isInfoEnabled()) {
212 _log.info("Minifying JavaScript bundle " + minifierBundleId);
213 }
214
215 String minifiedContent = null;
216
217 if (fileNames.length == 0) {
218 minifiedContent = StringPool.BLANK;
219 }
220 else {
221 sb = new StringBundler(fileNames.length * 2);
222
223 for (String fileName : fileNames) {
224 String content = FileUtil.read(
225 bundleDirRealPath + StringPool.SLASH + fileName);
226
227 sb.append(content);
228 sb.append(StringPool.NEW_LINE);
229 }
230
231 minifiedContent = minifyJavaScript(sb.toString());
232 }
233
234 response.setContentType(ContentTypes.TEXT_JAVASCRIPT);
235
236 FileUtil.write(cacheFile, minifiedContent);
237
238 return minifiedContent;
239 }
240
241 protected String getMinifiedContent(
242 HttpServletRequest request, HttpServletResponse response,
243 FilterChain filterChain)
244 throws Exception {
245
246 String minifierType = ParamUtil.getString(request, "minifierType");
247 String minifierBundleId = ParamUtil.getString(
248 request, "minifierBundleId");
249 String minifierBundleDir = ParamUtil.getString(
250 request, "minifierBundleDir");
251
252 if (Validator.isNull(minifierType) ||
253 Validator.isNotNull(minifierBundleId) ||
254 Validator.isNotNull(minifierBundleDir)) {
255
256 return null;
257 }
258
259 String requestURI = request.getRequestURI();
260
261 String requestPath = requestURI;
262
263 String contextPath = request.getContextPath();
264
265 if (!contextPath.equals(StringPool.SLASH)) {
266 requestPath = requestPath.substring(contextPath.length());
267 }
268
269 String realPath = ServletContextUtil.getRealPath(
270 _servletContext, requestPath);
271
272 if (realPath == null) {
273 return null;
274 }
275
276 realPath = StringUtil.replace(
277 realPath, StringPool.BACK_SLASH, StringPool.SLASH);
278
279 File file = new File(realPath);
280
281 if (!file.exists()) {
282 return null;
283 }
284
285 String minifiedContent = null;
286
287 StringBundler sb = new StringBundler(4);
288
289 sb.append(_tempDir);
290 sb.append(requestURI);
291
292 String queryString = request.getQueryString();
293
294 if (queryString != null) {
295 sb.append(_QUESTION_SEPARATOR);
296 sb.append(sterilizeQueryString(queryString));
297 }
298
299 String cacheCommonFileName = sb.toString();
300
301 File cacheContentTypeFile = new File(
302 cacheCommonFileName + "_E_CONTENT_TYPE");
303 File cacheDataFile = new File(cacheCommonFileName + "_E_DATA");
304
305 if ((cacheDataFile.exists()) &&
306 (cacheDataFile.lastModified() >= file.lastModified())) {
307
308 minifiedContent = FileUtil.read(cacheDataFile);
309
310 if (cacheContentTypeFile.exists()) {
311 String contentType = FileUtil.read(cacheContentTypeFile);
312
313 response.setContentType(contentType);
314 }
315 }
316 else {
317 if (realPath.endsWith(_CSS_EXTENSION)) {
318 if (_log.isInfoEnabled()) {
319 _log.info("Minifying CSS " + file);
320 }
321
322 minifiedContent = minifyCss(request, file);
323
324 response.setContentType(ContentTypes.TEXT_CSS);
325
326 FileUtil.write(cacheContentTypeFile, ContentTypes.TEXT_CSS);
327 }
328 else if (realPath.endsWith(_JAVASCRIPT_EXTENSION)) {
329 if (_log.isInfoEnabled()) {
330 _log.info("Minifying JavaScript " + file);
331 }
332
333 minifiedContent = minifyJavaScript(file);
334
335 response.setContentType(ContentTypes.TEXT_JAVASCRIPT);
336
337 FileUtil.write(
338 cacheContentTypeFile, ContentTypes.TEXT_JAVASCRIPT);
339 }
340 else if (realPath.endsWith(_JSP_EXTENSION)) {
341 if (_log.isInfoEnabled()) {
342 _log.info("Minifying JSP " + file);
343 }
344
345 StringServletResponse stringResponse =
346 new StringServletResponse(response);
347
348 processFilter(
349 MinifierFilter.class, request, stringResponse, filterChain);
350
351 CacheResponseUtil.setHeaders(
352 response, stringResponse.getHeaders());
353
354 response.setContentType(stringResponse.getContentType());
355
356 minifiedContent = stringResponse.getString();
357
358 if (minifierType.equals("css")) {
359 minifiedContent = minifyCss(request, minifiedContent);
360 }
361 else if (minifierType.equals("js")) {
362 minifiedContent = minifyJavaScript(minifiedContent);
363 }
364
365 FileUtil.write(
366 cacheContentTypeFile, stringResponse.getContentType());
367 }
368 else {
369 return null;
370 }
371
372 FileUtil.write(cacheDataFile, minifiedContent);
373 }
374
375 return minifiedContent;
376 }
377
378 protected String minifyCss(HttpServletRequest request, File file)
379 throws IOException {
380
381 String content = FileUtil.read(file);
382
383 content = aggregateCss(file.getParent(), content);
384
385 return minifyCss(request, content);
386 }
387
388 protected String minifyCss(HttpServletRequest request, String content) {
389 String browserId = ParamUtil.getString(request, "browserId");
390
391 if (!browserId.equals(BrowserSniffer.BROWSER_ID_IE)) {
392 Matcher matcher = _pattern.matcher(content);
393
394 content = matcher.replaceAll(StringPool.BLANK);
395 }
396
397 return MinifierUtil.minifyCss(content);
398 }
399
400 protected String minifyJavaScript(File file) throws IOException {
401 String content = FileUtil.read(file);
402
403 return minifyJavaScript(content);
404 }
405
406 protected String minifyJavaScript(String content) {
407 return MinifierUtil.minifyJavaScript(content);
408 }
409
410 protected void processFilter(
411 HttpServletRequest request, HttpServletResponse response,
412 FilterChain filterChain)
413 throws Exception {
414
415 String minifiedContent = getMinifiedContent(
416 request, response, filterChain);
417
418 if (Validator.isNull(minifiedContent)) {
419 minifiedContent = getMinifiedBundleContent(request, response);
420 }
421
422 if (Validator.isNull(minifiedContent)) {
423 processFilter(MinifierFilter.class, request, response, filterChain);
424 }
425 else {
426 ServletResponseUtil.write(response, minifiedContent);
427 }
428 }
429
430 protected String sterilizeQueryString(String queryString) {
431 return StringUtil.replace(
432 queryString,
433 new String[] {StringPool.SLASH, StringPool.BACK_SLASH},
434 new String[] {StringPool.UNDERLINE, StringPool.UNDERLINE});
435 }
436
437 private static final String _CSS_IMPORT_BEGIN = "@import url(";
438
439 private static final String _CSS_IMPORT_END = ");";
440
441 private static final String _CSS_EXTENSION = ".css";
442
443 private static final String _JAVASCRIPT_EXTENSION = ".js";
444
445 private static final String _JSP_EXTENSION = ".jsp";
446
447 private static final String _QUESTION_SEPARATOR = "_Q_";
448
449 private static final String _TEMP_DIR =
450 SystemProperties.get(SystemProperties.TMP_DIR) + "/liferay/minifier";
451
452 private static Log _log = LogFactoryUtil.getLog(MinifierFilter.class);
453
454 private static Pattern _pattern = Pattern.compile(
455 "^(\\.ie|\\.js\\.ie)([^}]*)}", Pattern.MULTILINE);
456
457 private ServletContext _servletContext;
458 private String _servletContextName;
459 private String _tempDir = _TEMP_DIR;
460
461 }