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