001
014
015 package com.liferay.portal.servlet.filters.aggregate;
016
017 import com.liferay.portal.kernel.cache.key.CacheKeyGenerator;
018 import com.liferay.portal.kernel.cache.key.CacheKeyGeneratorUtil;
019 import com.liferay.portal.kernel.configuration.Filter;
020 import com.liferay.portal.kernel.log.Log;
021 import com.liferay.portal.kernel.log.LogFactoryUtil;
022 import com.liferay.portal.kernel.servlet.BrowserSniffer;
023 import com.liferay.portal.kernel.servlet.BufferCacheServletResponse;
024 import com.liferay.portal.kernel.servlet.HttpHeaders;
025 import com.liferay.portal.kernel.servlet.PortalWebResourceConstants;
026 import com.liferay.portal.kernel.servlet.PortalWebResourcesUtil;
027 import com.liferay.portal.kernel.servlet.ServletResponseUtil;
028 import com.liferay.portal.kernel.util.ArrayUtil;
029 import com.liferay.portal.kernel.util.CharPool;
030 import com.liferay.portal.kernel.util.ContentTypes;
031 import com.liferay.portal.kernel.util.FileUtil;
032 import com.liferay.portal.kernel.util.HttpUtil;
033 import com.liferay.portal.kernel.util.JavaConstants;
034 import com.liferay.portal.kernel.util.ParamUtil;
035 import com.liferay.portal.kernel.util.PropsKeys;
036 import com.liferay.portal.kernel.util.StringBundler;
037 import com.liferay.portal.kernel.util.StringPool;
038 import com.liferay.portal.kernel.util.StringUtil;
039 import com.liferay.portal.kernel.util.URLUtil;
040 import com.liferay.portal.kernel.util.Validator;
041 import com.liferay.portal.minifier.MinifierUtil;
042 import com.liferay.portal.servlet.filters.IgnoreModuleRequestFilter;
043 import com.liferay.portal.servlet.filters.dynamiccss.DynamicCSSUtil;
044 import com.liferay.portal.util.AggregateUtil;
045 import com.liferay.portal.util.JavaScriptBundleUtil;
046 import com.liferay.portal.util.PropsUtil;
047 import com.liferay.portal.util.PropsValues;
048
049 import java.io.File;
050 import java.io.IOException;
051
052 import java.net.URL;
053 import java.net.URLConnection;
054
055 import java.util.regex.Matcher;
056 import java.util.regex.Pattern;
057
058 import javax.servlet.FilterChain;
059 import javax.servlet.FilterConfig;
060 import javax.servlet.ServletContext;
061 import javax.servlet.http.HttpServletRequest;
062 import javax.servlet.http.HttpServletResponse;
063
064
069 public class AggregateFilter extends IgnoreModuleRequestFilter {
070
071
074 public static String aggregateCss(ServletPaths servletPaths, String content)
075 throws IOException {
076
077 StringBundler sb = new StringBundler();
078
079 int pos = 0;
080
081 while (true) {
082 int commentX = content.indexOf(_CSS_COMMENT_BEGIN, pos);
083 int commentY = content.indexOf(
084 _CSS_COMMENT_END, commentX + _CSS_COMMENT_BEGIN.length());
085
086 int importX = content.indexOf(_CSS_IMPORT_BEGIN, pos);
087 int importY = content.indexOf(
088 _CSS_IMPORT_END, importX + _CSS_IMPORT_BEGIN.length());
089
090 if ((importX == -1) || (importY == -1)) {
091 sb.append(content.substring(pos));
092
093 break;
094 }
095 else if ((commentX != -1) && (commentY != -1) &&
096 (commentX < importX) && (commentY > importX)) {
097
098 commentY += _CSS_COMMENT_END.length();
099
100 sb.append(content.substring(pos, commentY));
101
102 pos = commentY;
103 }
104 else {
105 sb.append(content.substring(pos, importX));
106
107 String mediaQuery = StringPool.BLANK;
108
109 int mediaQueryImportX = content.indexOf(
110 CharPool.CLOSE_PARENTHESIS,
111 importX + _CSS_IMPORT_BEGIN.length());
112 int mediaQueryImportY = content.indexOf(
113 CharPool.SEMICOLON, importX + _CSS_IMPORT_BEGIN.length());
114
115 String importFileName = null;
116
117 if (importY != mediaQueryImportX) {
118 mediaQuery = content.substring(
119 mediaQueryImportX + 1, mediaQueryImportY);
120
121 importFileName = content.substring(
122 importX + _CSS_IMPORT_BEGIN.length(),
123 mediaQueryImportX);
124 }
125 else {
126 importFileName = content.substring(
127 importX + _CSS_IMPORT_BEGIN.length(), importY);
128 }
129
130 String importContent = null;
131
132 if (Validator.isUrl(importFileName)) {
133 URL url = new URL(importFileName);
134
135 URLConnection urlConnection = url.openConnection();
136
137 importContent = StringUtil.read(
138 urlConnection.getInputStream());
139 }
140 else {
141 ServletPaths importFileServletPaths = servletPaths.down(
142 importFileName);
143
144 importContent = importFileServletPaths.getContent();
145
146 if (importContent == null) {
147 if (_log.isWarnEnabled()) {
148 _log.warn(
149 "File " +
150 importFileServletPaths.getResourcePath() +
151 " does not exist");
152 }
153
154 importContent = StringPool.BLANK;
155 }
156
157 String importDirName = StringPool.BLANK;
158
159 int slashPos = importFileName.lastIndexOf(CharPool.SLASH);
160
161 if (slashPos != -1) {
162 importDirName = importFileName.substring(
163 0, slashPos + 1);
164 }
165
166 ServletPaths importDirServletPaths = servletPaths.down(
167 importDirName);
168
169 importContent = aggregateCss(
170 importDirServletPaths, importContent);
171
172
173
174 String baseURL = _BASE_URL.concat(
175 importDirServletPaths.getResourcePath());
176
177 if (!baseURL.endsWith(StringPool.SLASH)) {
178 baseURL += StringPool.SLASH;
179 }
180
181 importContent = AggregateUtil.updateRelativeURLs(
182 importContent, baseURL);
183 }
184
185 if (Validator.isNotNull(mediaQuery)) {
186 sb.append(_CSS_MEDIA_QUERY);
187 sb.append(CharPool.SPACE);
188 sb.append(mediaQuery);
189 sb.append(CharPool.OPEN_CURLY_BRACE);
190 sb.append(importContent);
191 sb.append(CharPool.CLOSE_CURLY_BRACE);
192
193 pos = mediaQueryImportY + 1;
194 }
195 else {
196 sb.append(importContent);
197
198 pos = importY + _CSS_IMPORT_END.length();
199 }
200 }
201 }
202
203 return sb.toString();
204 }
205
206 public static String aggregateJavaScript(
207 ServletPaths servletPaths, String[] fileNames) {
208
209 StringBundler sb = new StringBundler(fileNames.length * 2);
210
211 for (String fileName : fileNames) {
212 ServletPaths fileServletPaths = servletPaths.down(fileName);
213
214 String content = fileServletPaths.getContent();
215
216 if (Validator.isNull(content)) {
217 continue;
218 }
219
220 sb.append(content);
221 sb.append(StringPool.NEW_LINE);
222 }
223
224 return getJavaScriptContent(
225 StringUtil.merge(fileNames, "+"), sb.toString());
226 }
227
228 @Override
229 public void init(FilterConfig filterConfig) {
230 super.init(filterConfig);
231
232 _servletContext = filterConfig.getServletContext();
233
234 File tempDir = (File)_servletContext.getAttribute(
235 JavaConstants.JAVAX_SERVLET_CONTEXT_TEMPDIR);
236
237 _tempDir = new File(tempDir, _TEMP_DIR);
238
239 _tempDir.mkdirs();
240 }
241
242 protected static String getJavaScriptContent(
243 String resourceName, String content) {
244
245 return MinifierUtil.minifyJavaScript(resourceName, content);
246 }
247
248 protected Object getBundleContent(
249 HttpServletRequest request, HttpServletResponse response)
250 throws IOException {
251
252 String minifierType = ParamUtil.getString(request, "minifierType");
253 String bundleId = ParamUtil.getString(
254 request, "bundleId",
255 ParamUtil.getString(request, "minifierBundleId"));
256
257 if (Validator.isNull(minifierType) || Validator.isNull(bundleId) ||
258 !ArrayUtil.contains(PropsValues.JAVASCRIPT_BUNDLE_IDS, bundleId)) {
259
260 return null;
261 }
262
263 String bundleDirName = PropsUtil.get(
264 PropsKeys.JAVASCRIPT_BUNDLE_DIR, new Filter(bundleId));
265
266 ServletContext jsServletContext =
267 PortalWebResourcesUtil.getServletContext(
268 PortalWebResourceConstants.RESOURCE_TYPE_JS);
269
270 URL bundleDirURL = jsServletContext.getResource(bundleDirName);
271
272 if (bundleDirURL == null) {
273 return null;
274 }
275
276 String cacheFileName = bundleId;
277
278 String[] fileNames = JavaScriptBundleUtil.getFileNames(bundleId);
279
280 File cacheFile = new File(_tempDir, cacheFileName);
281
282 if (cacheFile.exists()) {
283 long lastModified = PortalWebResourcesUtil.getLastModified(
284 PortalWebResourceConstants.RESOURCE_TYPE_JS);
285
286 if (lastModified <= cacheFile.lastModified()) {
287 response.setContentType(ContentTypes.TEXT_JAVASCRIPT);
288
289 return cacheFile;
290 }
291 }
292
293 if (_log.isInfoEnabled()) {
294 _log.info("Aggregating JavaScript bundle " + bundleId);
295 }
296
297 String content = null;
298
299 if (fileNames.length == 0) {
300 content = StringPool.BLANK;
301 }
302 else {
303 content = aggregateJavaScript(
304 new ServletPaths(jsServletContext, bundleDirName), fileNames);
305 }
306
307 response.setContentType(ContentTypes.TEXT_JAVASCRIPT);
308
309 FileUtil.write(cacheFile, content);
310
311 return content;
312 }
313
314 protected String getCacheFileName(HttpServletRequest request) {
315 CacheKeyGenerator cacheKeyGenerator =
316 CacheKeyGeneratorUtil.getCacheKeyGenerator(
317 AggregateFilter.class.getName());
318
319 cacheKeyGenerator.append(HttpUtil.getProtocol(request.isSecure()));
320 cacheKeyGenerator.append(StringPool.UNDERLINE);
321 cacheKeyGenerator.append(request.getRequestURI());
322
323 String queryString = request.getQueryString();
324
325 if (queryString != null) {
326 cacheKeyGenerator.append(sterilizeQueryString(queryString));
327 }
328
329 return String.valueOf(cacheKeyGenerator.finish());
330 }
331
332 protected Object getContent(
333 HttpServletRequest request, HttpServletResponse response,
334 FilterChain filterChain)
335 throws Exception {
336
337 String minifierType = ParamUtil.getString(request, "minifierType");
338 String minifierBundleId = ParamUtil.getString(
339 request, "minifierBundleId");
340 String minifierBundleDirName = ParamUtil.getString(
341 request, "minifierBundleDir");
342
343 if (Validator.isNull(minifierType) ||
344 Validator.isNotNull(minifierBundleId) ||
345 Validator.isNotNull(minifierBundleDirName)) {
346
347 return null;
348 }
349
350 String requestURI = request.getRequestURI();
351
352 String resourcePath = requestURI;
353
354 String contextPath = request.getContextPath();
355
356 if (!contextPath.equals(StringPool.SLASH)) {
357 resourcePath = resourcePath.substring(contextPath.length());
358 }
359
360 URL resourceURL = _servletContext.getResource(resourcePath);
361
362 if (resourceURL == null) {
363 resourceURL = PortalWebResourcesUtil.getServletContextResource(
364 resourcePath);
365
366 if (resourceURL == null) {
367 return null;
368 }
369 }
370
371 String cacheCommonFileName = getCacheFileName(request);
372
373 File cacheContentTypeFile = new File(
374 _tempDir, cacheCommonFileName + "_E_CONTENT_TYPE");
375 File cacheDataFile = new File(
376 _tempDir, cacheCommonFileName + "_E_DATA");
377
378 if (cacheDataFile.exists() &&
379 (cacheDataFile.lastModified() >=
380 URLUtil.getLastModifiedTime(resourceURL))) {
381
382 if (cacheContentTypeFile.exists()) {
383 String contentType = FileUtil.read(cacheContentTypeFile);
384
385 response.setContentType(contentType);
386 }
387
388 return cacheDataFile;
389 }
390
391 String content = null;
392
393 if (resourcePath.endsWith(_CSS_EXTENSION)) {
394 if (_log.isInfoEnabled()) {
395 _log.info("Minifying CSS " + resourcePath);
396 }
397
398 content = getCssContent(
399 request, response, resourceURL, resourcePath);
400
401 response.setContentType(ContentTypes.TEXT_CSS);
402
403 FileUtil.write(cacheContentTypeFile, ContentTypes.TEXT_CSS);
404 }
405 else if (resourcePath.endsWith(_JAVASCRIPT_EXTENSION)) {
406 if (_log.isInfoEnabled()) {
407 _log.info("Minifying JavaScript " + resourcePath);
408 }
409
410 content = getJavaScriptContent(resourceURL);
411
412 response.setContentType(ContentTypes.TEXT_JAVASCRIPT);
413
414 FileUtil.write(cacheContentTypeFile, ContentTypes.TEXT_JAVASCRIPT);
415 }
416 else if (resourcePath.endsWith(_JSP_EXTENSION)) {
417 if (_log.isInfoEnabled()) {
418 _log.info("Minifying JSP " + resourcePath);
419 }
420
421 BufferCacheServletResponse bufferCacheServletResponse =
422 new BufferCacheServletResponse(response);
423
424 processFilter(
425 AggregateFilter.class, request, bufferCacheServletResponse,
426 filterChain);
427
428 bufferCacheServletResponse.finishResponse(false);
429
430 content = bufferCacheServletResponse.getString();
431
432 if (minifierType.equals("css")) {
433 content = getCssContent(
434 request, response, resourcePath, content);
435 }
436 else if (minifierType.equals("js")) {
437 content = getJavaScriptContent(resourcePath, content);
438 }
439
440 FileUtil.write(
441 cacheContentTypeFile,
442 bufferCacheServletResponse.getContentType());
443 }
444 else {
445 return null;
446 }
447
448 FileUtil.write(cacheDataFile, content);
449
450 return content;
451 }
452
453 protected String getCssContent(
454 HttpServletRequest request, HttpServletResponse response,
455 String resourcePath, String content) {
456
457 try {
458 ServletContext cssServletContext = null;
459
460 String requestURI = request.getRequestURI();
461
462 if (PortalWebResourcesUtil.isResourceContextPath(requestURI)) {
463 cssServletContext = PortalWebResourcesUtil.getServletContext(
464 PortalWebResourceConstants.RESOURCE_TYPE_CSS);
465 }
466 else {
467 cssServletContext = _servletContext;
468 }
469
470 content = DynamicCSSUtil.parseSass(
471 cssServletContext, request, resourcePath, content);
472 }
473 catch (Exception e) {
474 _log.error("Unable to parse SASS on CSS " + resourcePath, e);
475
476 if (_log.isDebugEnabled()) {
477 _log.debug(content);
478 }
479
480 response.setHeader(
481 HttpHeaders.CACHE_CONTROL,
482 HttpHeaders.CACHE_CONTROL_NO_CACHE_VALUE);
483 }
484
485 String browserId = ParamUtil.getString(request, "browserId");
486
487 if (!browserId.equals(BrowserSniffer.BROWSER_ID_IE)) {
488 Matcher matcher = _pattern.matcher(content);
489
490 content = matcher.replaceAll(StringPool.BLANK);
491 }
492
493 return MinifierUtil.minifyCss(content);
494 }
495
496 protected String getCssContent(
497 HttpServletRequest request, HttpServletResponse response,
498 URL resourceURL, String resourcePath)
499 throws IOException {
500
501 URLConnection urlConnection = resourceURL.openConnection();
502
503 String content = StringUtil.read(urlConnection.getInputStream());
504
505 content = aggregateCss(
506 new ServletPaths(
507 _servletContext, ServletPaths.getParentPath(resourcePath)),
508 content);
509
510 return getCssContent(request, response, resourcePath, content);
511 }
512
513 protected String getJavaScriptContent(URL resourceURL) throws IOException {
514 URLConnection urlConnection = resourceURL.openConnection();
515
516 String content = StringUtil.read(urlConnection.getInputStream());
517
518 return getJavaScriptContent(resourceURL.toString(), content);
519 }
520
521 @Override
522 protected boolean isModuleRequest(HttpServletRequest request) {
523 String requestURI = request.getRequestURI();
524
525 if (PortalWebResourcesUtil.isResourceContextPath(requestURI)) {
526 return false;
527 }
528
529 return super.isModuleRequest(request);
530 }
531
532 @Override
533 protected void processFilter(
534 HttpServletRequest request, HttpServletResponse response,
535 FilterChain filterChain)
536 throws Exception {
537
538 Object minifiedContent = getContent(request, response, filterChain);
539
540 if (minifiedContent == null) {
541 minifiedContent = getBundleContent(request, response);
542 }
543
544 if (minifiedContent == null) {
545 processFilter(
546 AggregateFilter.class, request, response, filterChain);
547 }
548 else {
549 if (minifiedContent instanceof File) {
550 ServletResponseUtil.write(response, (File)minifiedContent);
551 }
552 else if (minifiedContent instanceof String) {
553 ServletResponseUtil.write(response, (String)minifiedContent);
554 }
555 }
556 }
557
558 protected String sterilizeQueryString(String queryString) {
559 return StringUtil.replace(
560 queryString, new String[] {StringPool.SLASH, StringPool.BACK_SLASH},
561 new String[] {StringPool.UNDERLINE, StringPool.UNDERLINE});
562 }
563
564 private static final String _BASE_URL = "@base_url@";
565
566 private static final String _CSS_COMMENT_BEGIN = "";
569
570 private static final String _CSS_EXTENSION = ".css";
571
572 private static final String _CSS_IMPORT_BEGIN = "@import url(";
573
574 private static final String _CSS_IMPORT_END = ");";
575
576 private static final String _CSS_MEDIA_QUERY = "@media";
577
578 private static final String _JAVASCRIPT_EXTENSION = ".js";
579
580 private static final String _JSP_EXTENSION = ".jsp";
581
582 private static final String _TEMP_DIR = "aggregate";
583
584 private static final Log _log = LogFactoryUtil.getLog(
585 AggregateFilter.class);
586
587 private static final Pattern _pattern = Pattern.compile(
588 "^(\\.ie|\\.js\\.ie)([^}]*)}", Pattern.MULTILINE);
589
590 private ServletContext _servletContext;
591 private File _tempDir;
592
593 }