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
66 public class MinifierFilter extends BasePortalFilter {
67
68 public void init(FilterConfig filterConfig) {
69 super.init(filterConfig);
70
71 _servletContext = filterConfig.getServletContext();
72 _servletContextName = GetterUtil.getString(
73 _servletContext.getServletContextName());
74
75 if (Validator.isNull(_servletContextName)) {
76 _tempDir += "/portal";
77 }
78 }
79
80 protected String aggregateCss(String dir, String content)
81 throws IOException {
82
83 StringBuilder sb = new StringBuilder(content.length());
84
85 int pos = 0;
86
87 while (true) {
88 int x = content.indexOf(_CSS_IMPORT_BEGIN, pos);
89 int y = content.indexOf(
90 _CSS_IMPORT_END, x + _CSS_IMPORT_BEGIN.length());
91
92 if ((x == -1) || (y == -1)) {
93 sb.append(content.substring(pos, content.length()));
94
95 break;
96 }
97 else {
98 sb.append(content.substring(pos, x));
99
100 String importFile = content.substring(
101 x + _CSS_IMPORT_BEGIN.length(), y);
102
103 String importContent = FileUtil.read(
104 dir + StringPool.SLASH + importFile);
105
106 String importFilePath = StringPool.BLANK;
107
108 if (importFile.lastIndexOf(StringPool.SLASH) != -1) {
109 importFilePath = StringPool.SLASH + importFile.substring(
110 0, importFile.lastIndexOf(StringPool.SLASH) + 1);
111 }
112
113 importContent = aggregateCss(
114 dir + importFilePath, importContent);
115
116 int importDepth = StringUtil.count(
117 importFile, StringPool.SLASH);
118
119
121 String relativePath = StringPool.BLANK;
122
123 for (int i = 0; i < importDepth; i++) {
124 relativePath += "../";
125 }
126
127 importContent = StringUtil.replace(
128 importContent,
129 new String[] {
130 "url('" + relativePath,
131 "url(\"" + relativePath,
132 "url(" + relativePath
133 },
134 new String[] {
135 "url('[$TEMP_RELATIVE_PATH$]",
136 "url(\"[$TEMP_RELATIVE_PATH$]",
137 "url([$TEMP_RELATIVE_PATH$]"
138 });
139
140 importContent = StringUtil.replace(
141 importContent, "[$TEMP_RELATIVE_PATH$]", StringPool.BLANK);
142
143 sb.append(importContent);
144
145 pos = y + _CSS_IMPORT_END.length();
146 }
147 }
148
149 return sb.toString();
150 }
151
152 protected String getMinifiedBundleContent(
153 HttpServletRequest request, HttpServletResponse response)
154 throws IOException {
155
156 String minifierType = ParamUtil.getString(request, "minifierType");
157 String minifierBundleId = ParamUtil.getString(
158 request, "minifierBundleId");
159
160 if (Validator.isNull(minifierType) ||
161 Validator.isNull(minifierBundleId) ||
162 !ArrayUtil.contains(
163 PropsValues.JAVASCRIPT_BUNDLE_IDS, minifierBundleId)) {
164
165 return null;
166 }
167
168 String minifierBundleDir = PropsUtil.get(
169 PropsKeys.JAVASCRIPT_BUNDLE_DIR, new Filter(minifierBundleId));
170
171 String bundleDirRealPath = ServletContextUtil.getRealPath(
172 _servletContext, minifierBundleDir);
173
174 if (bundleDirRealPath == null) {
175 return null;
176 }
177
178 String cacheFileName = _tempDir + request.getRequestURI();
179
180 String queryString = request.getQueryString();
181
182 if (queryString != null) {
183 cacheFileName += _QUESTION_SEPARATOR + queryString;
184 }
185
186 String[] fileNames = PropsUtil.getArray(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 StringBuilder sb = new StringBuilder();
216
217 for (String fileName : fileNames) {
218 String content = FileUtil.read(
219 bundleDirRealPath + StringPool.SLASH + fileName);
220
221 sb.append(content);
222 sb.append(StringPool.NEW_LINE);
223 }
224
225 String minifiedContent = minifyJavaScript(sb.toString());
226
227 response.setContentType(ContentTypes.TEXT_JAVASCRIPT);
228
229 FileUtil.write(cacheFile, minifiedContent);
230
231 return minifiedContent;
232 }
233
234 protected String getMinifiedContent(
235 HttpServletRequest request, HttpServletResponse response,
236 FilterChain filterChain)
237 throws Exception {
238
239 String minifierType = ParamUtil.getString(request, "minifierType");
240 String minifierBundleId = ParamUtil.getString(
241 request, "minifierBundleId");
242 String minifierBundleDir = ParamUtil.getString(
243 request, "minifierBundleDir");
244
245 if (Validator.isNull(minifierType) ||
246 Validator.isNotNull(minifierBundleId) ||
247 Validator.isNotNull(minifierBundleDir)) {
248
249 return null;
250 }
251
252 String requestURI = request.getRequestURI();
253
254 String realPath = ServletContextUtil.getRealPath(
255 _servletContext, requestURI);
256
257 if (realPath == null) {
258 return null;
259 }
260
261 realPath = StringUtil.replace(
262 realPath, StringPool.BACK_SLASH, StringPool.SLASH);
263
264 File file = new File(realPath);
265
266 if (!file.exists()) {
267 if (Validator.isNotNull(_servletContextName)) {
268
269
274 int lastIndex = realPath.lastIndexOf(
275 StringPool.SLASH + _servletContextName);
276
277 if (lastIndex > -1) {
278 realPath = StringUtil.replace(
279 realPath, StringPool.SLASH + _servletContextName,
280 StringPool.BLANK, lastIndex);
281 }
282
283 file = new File(realPath);
284 }
285 }
286
287 if (!file.exists()) {
288 return null;
289 }
290
291 String minifiedContent = null;
292
293 String cacheCommonFileName = _tempDir + requestURI;
294
295 String queryString = request.getQueryString();
296
297 if (queryString != null) {
298 cacheCommonFileName += _QUESTION_SEPARATOR + queryString;
299 }
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 CacheResponse cacheResponse = new CacheResponse(
346 response, StringPool.UTF8);
347
348 processFilter(
349 MinifierFilter.class, request, cacheResponse, filterChain);
350
351 CacheResponseUtil.addHeaders(
352 response, cacheResponse.getHeaders());
353
354 response.setContentType(cacheResponse.getContentType());
355
356 minifiedContent = new String(
357 cacheResponse.getData(), 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 private static final String _CSS_IMPORT_BEGIN = "@import url(";
434
435 private static final String _CSS_IMPORT_END = ");";
436
437 private static final String _CSS_EXTENSION = ".css";
438
439 private static final String _JAVASCRIPT_EXTENSION = ".js";
440
441 private static final String _JSP_EXTENSION = ".jsp";
442
443 private static final String _QUESTION_SEPARATOR = "_Q_";
444
445 private static final String _TEMP_DIR =
446 SystemProperties.get(SystemProperties.TMP_DIR) + "/liferay/minifier";
447
448 private static Log _log = LogFactoryUtil.getLog(MinifierFilter.class);
449
450 private static Pattern _pattern = Pattern.compile(
451 "^(\\.ie|\\.js\\.ie)([^}]*)}", Pattern.MULTILINE);
452
453 private ServletContext _servletContext;
454 private String _servletContextName;
455 private String _tempDir = _TEMP_DIR;
456
457 }