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