001
014
015 package com.liferay.portal.servlet.filters.minifier;
016
017 import com.liferay.portal.kernel.configuration.Filter;
018 import com.liferay.portal.kernel.log.Log;
019 import com.liferay.portal.kernel.log.LogFactoryUtil;
020 import com.liferay.portal.kernel.servlet.BrowserSniffer;
021 import com.liferay.portal.kernel.servlet.ServletContextUtil;
022 import com.liferay.portal.kernel.servlet.StringServletResponse;
023 import com.liferay.portal.kernel.util.ArrayUtil;
024 import com.liferay.portal.kernel.util.CharPool;
025 import com.liferay.portal.kernel.util.ContentTypes;
026 import com.liferay.portal.kernel.util.FileUtil;
027 import com.liferay.portal.kernel.util.GetterUtil;
028 import com.liferay.portal.kernel.util.ParamUtil;
029 import com.liferay.portal.kernel.util.PropsKeys;
030 import com.liferay.portal.kernel.util.StringBundler;
031 import com.liferay.portal.kernel.util.StringPool;
032 import com.liferay.portal.kernel.util.StringUtil;
033 import com.liferay.portal.kernel.util.Validator;
034 import com.liferay.portal.servlet.filters.BasePortalFilter;
035 import com.liferay.portal.util.JavaScriptBundleUtil;
036 import com.liferay.portal.util.MinifierUtil;
037 import com.liferay.portal.util.PropsUtil;
038 import com.liferay.portal.util.PropsValues;
039 import com.liferay.util.SystemProperties;
040 import com.liferay.util.servlet.ServletResponseUtil;
041 import com.liferay.util.servlet.filters.CacheResponseUtil;
042
043 import java.io.File;
044 import java.io.IOException;
045
046 import java.util.regex.Matcher;
047 import java.util.regex.Pattern;
048
049 import javax.servlet.FilterChain;
050 import javax.servlet.FilterConfig;
051 import javax.servlet.ServletContext;
052 import javax.servlet.http.HttpServletRequest;
053 import javax.servlet.http.HttpServletResponse;
054
055
058 public class MinifierFilter extends BasePortalFilter {
059
060 public void init(FilterConfig filterConfig) {
061 super.init(filterConfig);
062
063 _servletContext = filterConfig.getServletContext();
064 _servletContextName = GetterUtil.getString(
065 _servletContext.getServletContextName());
066
067 if (Validator.isNull(_servletContextName)) {
068 _tempDir += "/portal";
069 }
070 }
071
072 protected String aggregateCss(String dir, String content)
073 throws IOException {
074
075 StringBuilder sb = new StringBuilder(content.length());
076
077 int pos = 0;
078
079 while (true) {
080 int x = content.indexOf(_CSS_IMPORT_BEGIN, pos);
081 int y = content.indexOf(
082 _CSS_IMPORT_END, x + _CSS_IMPORT_BEGIN.length());
083
084 if ((x == -1) || (y == -1)) {
085 sb.append(content.substring(pos, content.length()));
086
087 break;
088 }
089 else {
090 sb.append(content.substring(pos, x));
091
092 String importFileName = content.substring(
093 x + _CSS_IMPORT_BEGIN.length(), y);
094
095 String importFullFileName = dir.concat(StringPool.SLASH).concat(
096 importFileName);
097
098 String importContent = FileUtil.read(importFullFileName);
099
100 if (importContent == null) {
101 if (_log.isWarnEnabled()) {
102 _log.warn(
103 "File " + importFullFileName + " does not exist");
104 }
105
106 importContent = StringPool.BLANK;
107 }
108
109 String importDir = StringPool.BLANK;
110
111 int slashPos = importFileName.lastIndexOf(CharPool.SLASH);
112
113 if (slashPos != -1) {
114 importDir = StringPool.SLASH.concat(
115 importFileName.substring(0, slashPos + 1));
116 }
117
118 importContent = aggregateCss(dir + importDir, importContent);
119
120 int importDepth = StringUtil.count(
121 importFileName, StringPool.SLASH);
122
123
124
125 String relativePath = StringPool.BLANK;
126
127 for (int i = 0; i < importDepth; i++) {
128 relativePath += "../";
129 }
130
131 importContent = StringUtil.replace(
132 importContent,
133 new String[] {
134 "url('" + relativePath,
135 "url(\"" + relativePath,
136 "url(" + relativePath
137 },
138 new String[] {
139 "url('[$TEMP_RELATIVE_PATH$]",
140 "url(\"[$TEMP_RELATIVE_PATH$]",
141 "url([$TEMP_RELATIVE_PATH$]"
142 });
143
144 importContent = StringUtil.replace(
145 importContent, "[$TEMP_RELATIVE_PATH$]", StringPool.BLANK);
146
147 sb.append(importContent);
148
149 pos = y + _CSS_IMPORT_END.length();
150 }
151 }
152
153 return sb.toString();
154 }
155
156 protected String getMinifiedBundleContent(
157 HttpServletRequest request, HttpServletResponse response)
158 throws IOException {
159
160 String minifierType = ParamUtil.getString(request, "minifierType");
161 String minifierBundleId = ParamUtil.getString(
162 request, "minifierBundleId");
163
164 if (Validator.isNull(minifierType) ||
165 Validator.isNull(minifierBundleId) ||
166 !ArrayUtil.contains(
167 PropsValues.JAVASCRIPT_BUNDLE_IDS, minifierBundleId)) {
168
169 return null;
170 }
171
172 String minifierBundleDir = PropsUtil.get(
173 PropsKeys.JAVASCRIPT_BUNDLE_DIR, new Filter(minifierBundleId));
174
175 String bundleDirRealPath = ServletContextUtil.getRealPath(
176 _servletContext, minifierBundleDir);
177
178 if (bundleDirRealPath == null) {
179 return null;
180 }
181
182 StringBundler sb = new StringBundler(4);
183
184 sb.append(_tempDir);
185 sb.append(request.getRequestURI());
186
187 String queryString = request.getQueryString();
188
189 if (queryString != null) {
190 sb.append(_QUESTION_SEPARATOR);
191 sb.append(sterilizeQueryString(queryString));
192 }
193
194 String cacheFileName = sb.toString();
195
196 String[] fileNames = JavaScriptBundleUtil.getFileNames(
197 minifierBundleId);
198
199 File cacheFile = new File(cacheFileName);
200
201 if (cacheFile.exists()) {
202 boolean staleCache = false;
203
204 for (String fileName : fileNames) {
205 File file = new File(
206 bundleDirRealPath + StringPool.SLASH + fileName);
207
208 if (file.lastModified() > cacheFile.lastModified()) {
209 staleCache = true;
210
211 break;
212 }
213 }
214
215 if (!staleCache) {
216 response.setContentType(ContentTypes.TEXT_JAVASCRIPT);
217
218 return FileUtil.read(cacheFile);
219 }
220 }
221
222 if (_log.isInfoEnabled()) {
223 _log.info("Minifying JavaScript bundle " + minifierBundleId);
224 }
225
226 String minifiedContent = null;
227
228 if (fileNames.length == 0) {
229 minifiedContent = StringPool.BLANK;
230 }
231 else {
232 sb = new StringBundler(fileNames.length * 2);
233
234 for (String fileName : fileNames) {
235 String content = FileUtil.read(
236 bundleDirRealPath + StringPool.SLASH + fileName);
237
238 sb.append(content);
239 sb.append(StringPool.NEW_LINE);
240 }
241
242 minifiedContent = minifyJavaScript(sb.toString());
243 }
244
245 response.setContentType(ContentTypes.TEXT_JAVASCRIPT);
246
247 FileUtil.write(cacheFile, minifiedContent);
248
249 return minifiedContent;
250 }
251
252 protected String getMinifiedContent(
253 HttpServletRequest request, HttpServletResponse response,
254 FilterChain filterChain)
255 throws Exception {
256
257 String minifierType = ParamUtil.getString(request, "minifierType");
258 String minifierBundleId = ParamUtil.getString(
259 request, "minifierBundleId");
260 String minifierBundleDir = ParamUtil.getString(
261 request, "minifierBundleDir");
262
263 if (Validator.isNull(minifierType) ||
264 Validator.isNotNull(minifierBundleId) ||
265 Validator.isNotNull(minifierBundleDir)) {
266
267 return null;
268 }
269
270 String requestURI = request.getRequestURI();
271
272 String requestPath = requestURI;
273
274 String contextPath = request.getContextPath();
275
276 if (!contextPath.equals(StringPool.SLASH)) {
277 requestPath = requestPath.substring(contextPath.length());
278 }
279
280 String realPath = ServletContextUtil.getRealPath(
281 _servletContext, requestPath);
282
283 if (realPath == null) {
284 return null;
285 }
286
287 realPath = StringUtil.replace(
288 realPath, CharPool.BACK_SLASH, CharPool.SLASH);
289
290 File file = new File(realPath);
291
292 if (!file.exists()) {
293 return null;
294 }
295
296 String minifiedContent = null;
297
298 StringBundler sb = new StringBundler(4);
299
300 sb.append(_tempDir);
301 sb.append(requestURI);
302
303 String queryString = request.getQueryString();
304
305 if (queryString != null) {
306 sb.append(_QUESTION_SEPARATOR);
307 sb.append(sterilizeQueryString(queryString));
308 }
309
310 String cacheCommonFileName = sb.toString();
311
312 File cacheContentTypeFile = new File(
313 cacheCommonFileName + "_E_CONTENT_TYPE");
314 File cacheDataFile = new File(cacheCommonFileName + "_E_DATA");
315
316 if ((cacheDataFile.exists()) &&
317 (cacheDataFile.lastModified() >= file.lastModified())) {
318
319 minifiedContent = FileUtil.read(cacheDataFile);
320
321 if (cacheContentTypeFile.exists()) {
322 String contentType = FileUtil.read(cacheContentTypeFile);
323
324 response.setContentType(contentType);
325 }
326 }
327 else {
328 if (realPath.endsWith(_CSS_EXTENSION)) {
329 if (_log.isInfoEnabled()) {
330 _log.info("Minifying CSS " + file);
331 }
332
333 minifiedContent = minifyCss(request, file);
334
335 response.setContentType(ContentTypes.TEXT_CSS);
336
337 FileUtil.write(cacheContentTypeFile, ContentTypes.TEXT_CSS);
338 }
339 else if (realPath.endsWith(_JAVASCRIPT_EXTENSION)) {
340 if (_log.isInfoEnabled()) {
341 _log.info("Minifying JavaScript " + file);
342 }
343
344 minifiedContent = minifyJavaScript(file);
345
346 response.setContentType(ContentTypes.TEXT_JAVASCRIPT);
347
348 FileUtil.write(
349 cacheContentTypeFile, ContentTypes.TEXT_JAVASCRIPT);
350 }
351 else if (realPath.endsWith(_JSP_EXTENSION)) {
352 if (_log.isInfoEnabled()) {
353 _log.info("Minifying JSP " + file);
354 }
355
356 StringServletResponse stringResponse =
357 new StringServletResponse(response);
358
359 processFilter(
360 MinifierFilter.class, request, stringResponse, filterChain);
361
362 CacheResponseUtil.setHeaders(
363 response, stringResponse.getHeaders());
364
365 response.setContentType(stringResponse.getContentType());
366
367 minifiedContent = stringResponse.getString();
368
369 if (minifierType.equals("css")) {
370 minifiedContent = minifyCss(request, minifiedContent);
371 }
372 else if (minifierType.equals("js")) {
373 minifiedContent = minifyJavaScript(minifiedContent);
374 }
375
376 FileUtil.write(
377 cacheContentTypeFile, stringResponse.getContentType());
378 }
379 else {
380 return null;
381 }
382
383 FileUtil.write(cacheDataFile, minifiedContent);
384 }
385
386 return minifiedContent;
387 }
388
389 protected String minifyCss(HttpServletRequest request, File file)
390 throws IOException {
391
392 String content = FileUtil.read(file);
393
394 content = aggregateCss(file.getParent(), content);
395
396 return minifyCss(request, content);
397 }
398
399 protected String minifyCss(HttpServletRequest request, String content) {
400 String browserId = ParamUtil.getString(request, "browserId");
401
402 if (!browserId.equals(BrowserSniffer.BROWSER_ID_IE)) {
403 Matcher matcher = _pattern.matcher(content);
404
405 content = matcher.replaceAll(StringPool.BLANK);
406 }
407
408 return MinifierUtil.minifyCss(content);
409 }
410
411 protected String minifyJavaScript(File file) throws IOException {
412 String content = FileUtil.read(file);
413
414 return minifyJavaScript(content);
415 }
416
417 protected String minifyJavaScript(String content) {
418 return MinifierUtil.minifyJavaScript(content);
419 }
420
421 protected void processFilter(
422 HttpServletRequest request, HttpServletResponse response,
423 FilterChain filterChain)
424 throws Exception {
425
426 String minifiedContent = getMinifiedContent(
427 request, response, filterChain);
428
429 if (Validator.isNull(minifiedContent)) {
430 minifiedContent = getMinifiedBundleContent(request, response);
431 }
432
433 if (Validator.isNull(minifiedContent)) {
434 processFilter(MinifierFilter.class, request, response, filterChain);
435 }
436 else {
437 ServletResponseUtil.write(response, minifiedContent);
438 }
439 }
440
441 protected String sterilizeQueryString(String queryString) {
442 return StringUtil.replace(
443 queryString,
444 new String[] {StringPool.SLASH, StringPool.BACK_SLASH},
445 new String[] {StringPool.UNDERLINE, StringPool.UNDERLINE});
446 }
447
448 private static final String _CSS_IMPORT_BEGIN = "@import url(";
449
450 private static final String _CSS_IMPORT_END = ");";
451
452 private static final String _CSS_EXTENSION = ".css";
453
454 private static final String _JAVASCRIPT_EXTENSION = ".js";
455
456 private static final String _JSP_EXTENSION = ".jsp";
457
458 private static final String _QUESTION_SEPARATOR = "_Q_";
459
460 private static final String _TEMP_DIR =
461 SystemProperties.get(SystemProperties.TMP_DIR) + "/liferay/minifier";
462
463 private static Log _log = LogFactoryUtil.getLog(MinifierFilter.class);
464
465 private static Pattern _pattern = Pattern.compile(
466 "^(\\.ie|\\.js\\.ie)([^}]*)}", Pattern.MULTILINE);
467
468 private ServletContext _servletContext;
469 private String _servletContextName;
470 private String _tempDir = _TEMP_DIR;
471
472 }