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.ServletException;
59 import javax.servlet.http.HttpServletRequest;
60 import javax.servlet.http.HttpServletResponse;
61
62
68 public class MinifierFilter extends BasePortalFilter {
69
70 public void init(FilterConfig filterConfig) throws ServletException {
71 super.init(filterConfig);
72
73 _servletContext = filterConfig.getServletContext();
74 _servletContextName = GetterUtil.getString(
75 _servletContext.getServletContextName());
76
77 if (Validator.isNull(_servletContextName)) {
78 _tempDir += "/portal";
79 }
80 }
81
82 protected String aggregateCss(String dir, String content)
83 throws IOException {
84
85 StringBuilder sb = new StringBuilder(content.length());
86
87 int pos = 0;
88
89 while (true) {
90 int x = content.indexOf(_CSS_IMPORT_BEGIN, pos);
91 int y = content.indexOf(
92 _CSS_IMPORT_END, x + _CSS_IMPORT_BEGIN.length());
93
94 if ((x == -1) || (y == -1)) {
95 sb.append(content.substring(pos, content.length()));
96
97 break;
98 }
99 else {
100 sb.append(content.substring(pos, x));
101
102 String importFile = content.substring(
103 x + _CSS_IMPORT_BEGIN.length(), y);
104
105 String importContent = FileUtil.read(
106 dir + StringPool.SLASH + importFile);
107
108 String importFilePath = StringPool.BLANK;
109
110 if (importFile.lastIndexOf(StringPool.SLASH) != -1) {
111 importFilePath = StringPool.SLASH + importFile.substring(
112 0, importFile.lastIndexOf(StringPool.SLASH) + 1);
113 }
114
115 importContent = aggregateCss(
116 dir + importFilePath, importContent);
117
118 int importDepth = StringUtil.count(
119 importFile, StringPool.SLASH);
120
121
123 String relativePath = StringPool.BLANK;
124
125 for (int i = 0; i < importDepth; i++) {
126 relativePath += "../";
127 }
128
129 importContent = StringUtil.replace(
130 importContent,
131 new String[] {
132 "url('" + relativePath,
133 "url(\"" + relativePath,
134 "url(" + relativePath
135 },
136 new String[] {
137 "url('[$TEMP_RELATIVE_PATH$]",
138 "url(\"[$TEMP_RELATIVE_PATH$]",
139 "url([$TEMP_RELATIVE_PATH$]"
140 });
141
142 importContent = StringUtil.replace(
143 importContent, "[$TEMP_RELATIVE_PATH$]", StringPool.BLANK);
144
145 sb.append(importContent);
146
147 pos = y + _CSS_IMPORT_END.length();
148 }
149 }
150
151 return sb.toString();
152 }
153
154 protected String getMinifiedBundleContent(
155 HttpServletRequest request, HttpServletResponse response)
156 throws IOException {
157
158 String minifierType = ParamUtil.getString(request, "minifierType");
159 String minifierBundleId = ParamUtil.getString(
160 request, "minifierBundleId");
161
162 if (Validator.isNull(minifierType) ||
163 Validator.isNull(minifierBundleId) ||
164 !ArrayUtil.contains(
165 PropsValues.JAVASCRIPT_BUNDLE_IDS, minifierBundleId)) {
166
167 return null;
168 }
169
170 String minifierBundleDir = PropsUtil.get(
171 PropsKeys.JAVASCRIPT_BUNDLE_DIR, new Filter(minifierBundleId));
172
173 String bundleDirRealPath = ServletContextUtil.getRealPath(
174 _servletContext, minifierBundleDir);
175
176 if (bundleDirRealPath == null) {
177 return null;
178 }
179
180 String cacheFileName = _tempDir + request.getRequestURI();
181
182 String queryString = request.getQueryString();
183
184 if (queryString != null) {
185 cacheFileName += _QUESTION_SEPARATOR + queryString;
186 }
187
188 String[] fileNames = PropsUtil.getArray(minifierBundleId);
189
190 File cacheFile = new File(cacheFileName);
191
192 if (cacheFile.exists()) {
193 boolean staleCache = false;
194
195 for (String fileName : fileNames) {
196 File file = new File(
197 bundleDirRealPath + StringPool.SLASH + fileName);
198
199 if (file.lastModified() > cacheFile.lastModified()) {
200 staleCache = true;
201
202 break;
203 }
204 }
205
206 if (!staleCache) {
207 response.setContentType(ContentTypes.TEXT_JAVASCRIPT);
208
209 return FileUtil.read(cacheFile);
210 }
211 }
212
213 if (_log.isInfoEnabled()) {
214 _log.info("Minifying JavaScript bundle " + minifierBundleId);
215 }
216
217 StringBuilder sb = new StringBuilder();
218
219 for (String fileName : fileNames) {
220 String content = FileUtil.read(
221 bundleDirRealPath + StringPool.SLASH + fileName);
222
223 sb.append(content);
224 sb.append(StringPool.NEW_LINE);
225 }
226
227 String minifiedContent = minifyJavaScript(sb.toString());
228
229 response.setContentType(ContentTypes.TEXT_JAVASCRIPT);
230
231 FileUtil.write(cacheFile, minifiedContent);
232
233 return minifiedContent;
234 }
235
236 protected String getMinifiedContent(
237 HttpServletRequest request, HttpServletResponse response,
238 FilterChain filterChain)
239 throws IOException, ServletException {
240
241 String minifierType = ParamUtil.getString(request, "minifierType");
242 String minifierBundleId = ParamUtil.getString(
243 request, "minifierBundleId");
244 String minifierBundleDir = ParamUtil.getString(
245 request, "minifierBundleDir");
246
247 if (Validator.isNull(minifierType) ||
248 Validator.isNotNull(minifierBundleId) ||
249 Validator.isNotNull(minifierBundleDir)) {
250
251 return null;
252 }
253
254 String requestURI = request.getRequestURI();
255
256 String realPath = ServletContextUtil.getRealPath(
257 _servletContext, requestURI);
258
259 if (realPath == null) {
260 return null;
261 }
262
263 realPath = StringUtil.replace(
264 realPath, StringPool.BACK_SLASH, StringPool.SLASH);
265
266 File file = new File(realPath);
267
268 if (!file.exists()) {
269
270
275 if (Validator.isNotNull(_servletContextName)) {
276 realPath = StringUtil.replaceFirst(
277 realPath, StringPool.SLASH + _servletContextName,
278 StringPool.BLANK);
279
280 file = new File(realPath);
281 }
282 }
283
284 if (!file.exists()) {
285 return null;
286 }
287
288 String minifiedContent = null;
289
290 String cacheCommonFileName = _tempDir + requestURI;
291
292 String queryString = request.getQueryString();
293
294 if (queryString != null) {
295 cacheCommonFileName += _QUESTION_SEPARATOR + queryString;
296 }
297
298 File cacheContentTypeFile = new File(
299 cacheCommonFileName + "_E_CONTENT_TYPE");
300 File cacheDataFile = new File(cacheCommonFileName + "_E_DATA");
301
302 if ((cacheDataFile.exists()) &&
303 (cacheDataFile.lastModified() >= file.lastModified())) {
304
305 minifiedContent = FileUtil.read(cacheDataFile);
306
307 if (cacheContentTypeFile.exists()) {
308 String contentType = FileUtil.read(cacheContentTypeFile);
309
310 response.setContentType(contentType);
311 }
312 }
313 else {
314 if (realPath.endsWith(_CSS_EXTENSION)) {
315 if (_log.isInfoEnabled()) {
316 _log.info("Minifying CSS " + file);
317 }
318
319 minifiedContent = minifyCss(request, file);
320
321 response.setContentType(ContentTypes.TEXT_CSS);
322
323 FileUtil.write(cacheContentTypeFile, ContentTypes.TEXT_CSS);
324 }
325 else if (realPath.endsWith(_JAVASCRIPT_EXTENSION)) {
326 if (_log.isInfoEnabled()) {
327 _log.info("Minifying JavaScript " + file);
328 }
329
330 minifiedContent = minifyJavaScript(file);
331
332 response.setContentType(ContentTypes.TEXT_JAVASCRIPT);
333
334 FileUtil.write(
335 cacheContentTypeFile, ContentTypes.TEXT_JAVASCRIPT);
336 }
337 else if (realPath.endsWith(_JSP_EXTENSION)) {
338 if (_log.isInfoEnabled()) {
339 _log.info("Minifying JSP " + file);
340 }
341
342 CacheResponse cacheResponse = new CacheResponse(
343 response, StringPool.UTF8);
344
345 processFilter(
346 MinifierFilter.class, request, cacheResponse, filterChain);
347
348 CacheResponseUtil.addHeaders(
349 response, cacheResponse.getHeaders());
350
351 response.setContentType(cacheResponse.getContentType());
352
353 minifiedContent = new String(
354 cacheResponse.getData(), StringPool.UTF8);
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, cacheResponse.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 IOException, ServletException {
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 if (!ETagUtil.processETag(request, response, minifiedContent)) {
425 ServletResponseUtil.write(response, minifiedContent);
426 }
427 }
428 }
429
430 private static final String _CSS_IMPORT_BEGIN = "@import url(";
431
432 private static final String _CSS_IMPORT_END = ");";
433
434 private static final String _CSS_EXTENSION = ".css";
435
436 private static final String _JAVASCRIPT_EXTENSION = ".js";
437
438 private static final String _JSP_EXTENSION = ".jsp";
439
440 private static final String _QUESTION_SEPARATOR = "_Q_";
441
442 private static final String _TEMP_DIR =
443 SystemProperties.get(SystemProperties.TMP_DIR) + "/liferay/minifier";
444
445 private static Log _log = LogFactoryUtil.getLog(MinifierFilter.class);
446
447 private static Pattern _pattern = Pattern.compile(
448 "^(\\.ie|\\.js\\.ie)([^}]*)}", Pattern.MULTILINE);
449
450 private ServletContext _servletContext;
451 private String _servletContextName;
452 private String _tempDir = _TEMP_DIR;
453
454 }