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