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