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.PortalUtil;
036 import com.liferay.portal.kernel.util.PropsKeys;
037 import com.liferay.portal.kernel.util.StringBundler;
038 import com.liferay.portal.kernel.util.StringPool;
039 import com.liferay.portal.kernel.util.StringUtil;
040 import com.liferay.portal.kernel.util.URLUtil;
041 import com.liferay.portal.kernel.util.Validator;
042 import com.liferay.portal.minifier.MinifierUtil;
043 import com.liferay.portal.servlet.filters.IgnoreModuleRequestFilter;
044 import com.liferay.portal.servlet.filters.dynamiccss.DynamicCSSUtil;
045 import com.liferay.portal.util.AggregateUtil;
046 import com.liferay.portal.util.JavaScriptBundleUtil;
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 requestURL = String.valueOf(request.getRequestURL());
325
326 if (requestURL != null) {
327 requestURL = HttpUtil.removeParameter(requestURL, "zx");
328
329 String queryString = HttpUtil.getQueryString(requestURL);
330
331 if (queryString != null) {
332 cacheKeyGenerator.append(sterilizeQueryString(queryString));
333 }
334 }
335
336 return String.valueOf(cacheKeyGenerator.finish());
337 }
338
339 protected Object getContent(
340 HttpServletRequest request, HttpServletResponse response,
341 FilterChain filterChain)
342 throws Exception {
343
344 String minifierType = ParamUtil.getString(request, "minifierType");
345 String minifierBundleId = ParamUtil.getString(
346 request, "minifierBundleId");
347 String minifierBundleDirName = ParamUtil.getString(
348 request, "minifierBundleDir");
349
350 if (Validator.isNull(minifierType) ||
351 Validator.isNotNull(minifierBundleId) ||
352 Validator.isNotNull(minifierBundleDirName)) {
353
354 return null;
355 }
356
357 String requestURI = request.getRequestURI();
358
359 String resourcePath = requestURI;
360
361 String contextPath = request.getContextPath();
362
363 if (!contextPath.equals(StringPool.SLASH)) {
364 resourcePath = resourcePath.substring(contextPath.length());
365 }
366
367 if (resourcePath.endsWith(_CSS_EXTENSION) &&
368 PortalUtil.isRightToLeft(request)) {
369
370 int pos = resourcePath.lastIndexOf(StringPool.PERIOD);
371
372 resourcePath =
373 resourcePath.substring(0, pos) + "_rtl" +
374 resourcePath.substring(pos);
375 }
376
377 URL resourceURL = _servletContext.getResource(resourcePath);
378
379 if (resourceURL == null) {
380 resourceURL = PortalWebResourcesUtil.getResource(resourcePath);
381
382 if (resourceURL == null) {
383 return null;
384 }
385 }
386
387 String cacheCommonFileName = getCacheFileName(request);
388
389 File cacheContentTypeFile = new File(
390 _tempDir, cacheCommonFileName + "_E_CONTENT_TYPE");
391 File cacheDataFile = new File(
392 _tempDir, cacheCommonFileName + "_E_DATA");
393
394 if (cacheDataFile.exists() &&
395 (cacheDataFile.lastModified() >=
396 URLUtil.getLastModifiedTime(resourceURL))) {
397
398 if (cacheContentTypeFile.exists()) {
399 String contentType = FileUtil.read(cacheContentTypeFile);
400
401 response.setContentType(contentType);
402 }
403
404 return cacheDataFile;
405 }
406
407 String content = null;
408
409 if (resourcePath.endsWith(_CSS_EXTENSION)) {
410 if (_log.isInfoEnabled()) {
411 _log.info("Minifying CSS " + resourcePath);
412 }
413
414 content = getCssContent(
415 request, response, resourceURL, resourcePath);
416
417 response.setContentType(ContentTypes.TEXT_CSS);
418
419 FileUtil.write(cacheContentTypeFile, ContentTypes.TEXT_CSS);
420 }
421 else if (resourcePath.endsWith(_JAVASCRIPT_EXTENSION)) {
422 if (_log.isInfoEnabled()) {
423 _log.info("Minifying JavaScript " + resourcePath);
424 }
425
426 content = getJavaScriptContent(resourceURL);
427
428 response.setContentType(ContentTypes.TEXT_JAVASCRIPT);
429
430 FileUtil.write(cacheContentTypeFile, ContentTypes.TEXT_JAVASCRIPT);
431 }
432 else if (resourcePath.endsWith(_JSP_EXTENSION)) {
433 if (_log.isInfoEnabled()) {
434 _log.info("Minifying JSP " + resourcePath);
435 }
436
437 BufferCacheServletResponse bufferCacheServletResponse =
438 new BufferCacheServletResponse(response);
439
440 processFilter(
441 AggregateFilter.class.getName(), request,
442 bufferCacheServletResponse, filterChain);
443
444 bufferCacheServletResponse.finishResponse(false);
445
446 content = bufferCacheServletResponse.getString();
447
448 if (minifierType.equals("css")) {
449 content = getCssContent(
450 request, response, resourcePath, content);
451 }
452 else if (minifierType.equals("js")) {
453 content = getJavaScriptContent(resourcePath, content);
454 }
455
456 FileUtil.write(
457 cacheContentTypeFile,
458 bufferCacheServletResponse.getContentType());
459 }
460 else {
461 return null;
462 }
463
464 FileUtil.write(cacheDataFile, content);
465
466 return content;
467 }
468
469 protected String getCssContent(
470 HttpServletRequest request, HttpServletResponse response,
471 String resourcePath, String content) {
472
473 try {
474 ServletContext cssServletContext = null;
475
476 String requestURI = request.getRequestURI();
477
478 if (PortalWebResourcesUtil.hasContextPath(requestURI)) {
479 cssServletContext =
480 PortalWebResourcesUtil.getPathServletContext(requestURI);
481 }
482
483 if (cssServletContext == null) {
484 cssServletContext = _servletContext;
485 }
486
487 content = DynamicCSSUtil.replaceToken(
488 cssServletContext, request, content);
489 }
490 catch (Exception e) {
491 _log.error("Unable to replace tokens in CSS " + resourcePath, e);
492
493 if (_log.isDebugEnabled()) {
494 _log.debug(content);
495 }
496
497 response.setHeader(
498 HttpHeaders.CACHE_CONTROL,
499 HttpHeaders.CACHE_CONTROL_NO_CACHE_VALUE);
500 }
501
502 String browserId = ParamUtil.getString(request, "browserId");
503
504 if (!browserId.equals(BrowserSniffer.BROWSER_ID_IE)) {
505 Matcher matcher = _pattern.matcher(content);
506
507 content = matcher.replaceAll(StringPool.BLANK);
508 }
509
510 return MinifierUtil.minifyCss(content);
511 }
512
513 protected String getCssContent(
514 HttpServletRequest request, HttpServletResponse response,
515 URL resourceURL, String resourcePath)
516 throws IOException {
517
518 ServletContext cssServletContext = null;
519 String resourcePathRoot = null;
520
521 String requestURI = request.getRequestURI();
522
523 if (PortalWebResourcesUtil.hasContextPath(requestURI)) {
524 cssServletContext = PortalWebResourcesUtil.getPathServletContext(
525 requestURI);
526 resourcePathRoot = "/";
527 }
528
529 if (cssServletContext == null) {
530 cssServletContext = _servletContext;
531 resourcePathRoot = ServletPaths.getParentPath(resourcePath);
532 }
533
534 URLConnection urlConnection = resourceURL.openConnection();
535
536 String content = StringUtil.read(urlConnection.getInputStream());
537
538 content = aggregateCss(
539 new ServletPaths(cssServletContext, resourcePathRoot), content);
540
541 return getCssContent(request, response, resourcePath, content);
542 }
543
544 protected String getJavaScriptContent(URL resourceURL) throws IOException {
545 URLConnection urlConnection = resourceURL.openConnection();
546
547 String content = StringUtil.read(urlConnection.getInputStream());
548
549 return getJavaScriptContent(resourceURL.toString(), content);
550 }
551
552 @Override
553 protected boolean isModuleRequest(HttpServletRequest request) {
554 String requestURI = request.getRequestURI();
555
556 if (PortalWebResourcesUtil.hasContextPath(requestURI)) {
557 return false;
558 }
559
560 return super.isModuleRequest(request);
561 }
562
563 @Override
564 protected void processFilter(
565 HttpServletRequest request, HttpServletResponse response,
566 FilterChain filterChain)
567 throws Exception {
568
569 Object minifiedContent = getContent(request, response, filterChain);
570
571 if (minifiedContent == null) {
572 minifiedContent = getBundleContent(request, response);
573 }
574
575 if (minifiedContent == null) {
576 processFilter(
577 AggregateFilter.class.getName(), request, response,
578 filterChain);
579 }
580 else {
581 if (minifiedContent instanceof File) {
582 ServletResponseUtil.write(response, (File)minifiedContent);
583 }
584 else if (minifiedContent instanceof String) {
585 ServletResponseUtil.write(response, (String)minifiedContent);
586 }
587 }
588 }
589
590 protected String sterilizeQueryString(String queryString) {
591 return StringUtil.replace(
592 queryString, new char[] {CharPool.SLASH, CharPool.BACK_SLASH},
593 new char[] {CharPool.UNDERLINE, CharPool.UNDERLINE});
594 }
595
596 private static final String _BASE_URL = "@base_url@";
597
598 private static final String _CSS_COMMENT_BEGIN = "";
601
602 private static final String _CSS_EXTENSION = ".css";
603
604 private static final String _CSS_IMPORT_BEGIN = "@import url(";
605
606 private static final String _CSS_IMPORT_END = ");";
607
608 private static final String _CSS_MEDIA_QUERY = "@media";
609
610 private static final String _JAVASCRIPT_EXTENSION = ".js";
611
612 private static final String _JSP_EXTENSION = ".jsp";
613
614 private static final String _TEMP_DIR = "aggregate";
615
616 private static final Log _log = LogFactoryUtil.getLog(
617 AggregateFilter.class);
618
619 private static final Pattern _pattern = Pattern.compile(
620 "^(\\.ie|\\.js\\.ie)([^}]*)}", Pattern.MULTILINE);
621
622 private ServletContext _servletContext;
623 private File _tempDir;
624
625 }