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