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