1   /**
2    * Copyright (c) 2000-2010 Liferay, Inc. All rights reserved.
3    *
4    * This library is free software; you can redistribute it and/or modify it under
5    * the terms of the GNU Lesser General Public License as published by the Free
6    * Software Foundation; either version 2.1 of the License, or (at your option)
7    * any later version.
8    *
9    * This library is distributed in the hope that it will be useful, but WITHOUT
10   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
11   * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
12   * details.
13   */
14  
15  package com.liferay.portal.servlet.filters.cache;
16  
17  import com.liferay.portal.NoSuchLayoutException;
18  import com.liferay.portal.kernel.exception.SystemException;
19  import com.liferay.portal.kernel.language.LanguageUtil;
20  import com.liferay.portal.kernel.log.Log;
21  import com.liferay.portal.kernel.log.LogFactoryUtil;
22  import com.liferay.portal.kernel.servlet.BrowserSnifferUtil;
23  import com.liferay.portal.kernel.servlet.HttpHeaders;
24  import com.liferay.portal.kernel.util.GetterUtil;
25  import com.liferay.portal.kernel.util.Http;
26  import com.liferay.portal.kernel.util.HttpUtil;
27  import com.liferay.portal.kernel.util.JavaConstants;
28  import com.liferay.portal.kernel.util.ParamUtil;
29  import com.liferay.portal.kernel.util.StringBundler;
30  import com.liferay.portal.kernel.util.StringPool;
31  import com.liferay.portal.kernel.util.StringUtil;
32  import com.liferay.portal.kernel.util.UnicodeProperties;
33  import com.liferay.portal.kernel.util.Validator;
34  import com.liferay.portal.model.Group;
35  import com.liferay.portal.model.Layout;
36  import com.liferay.portal.model.LayoutConstants;
37  import com.liferay.portal.model.LayoutTypePortletConstants;
38  import com.liferay.portal.model.Portlet;
39  import com.liferay.portal.model.PortletConstants;
40  import com.liferay.portal.service.GroupLocalServiceUtil;
41  import com.liferay.portal.service.LayoutLocalServiceUtil;
42  import com.liferay.portal.service.PortletLocalServiceUtil;
43  import com.liferay.portal.servlet.filters.BasePortalFilter;
44  import com.liferay.portal.struts.LastPath;
45  import com.liferay.portal.util.PortalInstances;
46  import com.liferay.portal.util.PortalUtil;
47  import com.liferay.portal.util.PropsValues;
48  import com.liferay.portal.util.WebKeys;
49  import com.liferay.util.servlet.filters.CacheResponse;
50  import com.liferay.util.servlet.filters.CacheResponseData;
51  import com.liferay.util.servlet.filters.CacheResponseUtil;
52  
53  import javax.servlet.FilterChain;
54  import javax.servlet.FilterConfig;
55  import javax.servlet.http.HttpServletRequest;
56  import javax.servlet.http.HttpServletResponse;
57  import javax.servlet.http.HttpSession;
58  
59  /**
60   * <a href="CacheFilter.java.html"><b><i>View Source</i></b></a>
61   *
62   * @author Alexander Chow
63   * @author Javier de Ros
64   * @author Raymond Augé
65   */
66  public class CacheFilter extends BasePortalFilter {
67  
68      public static final String SKIP_FILTER = CacheFilter.class + "SKIP_FILTER";
69  
70      public void init(FilterConfig filterConfig) {
71          super.init(filterConfig);
72  
73          _pattern = GetterUtil.getInteger(
74              filterConfig.getInitParameter("pattern"));
75  
76          if ((_pattern != _PATTERN_FRIENDLY) &&
77              (_pattern != _PATTERN_LAYOUT) &&
78              (_pattern != _PATTERN_RESOURCE)) {
79  
80              _log.error("Cache pattern is invalid");
81          }
82      }
83  
84      protected String getCacheKey(HttpServletRequest request) {
85          StringBundler sb = new StringBundler(13);
86  
87          // Url
88  
89          sb.append(HttpUtil.getProtocol(request));
90          sb.append(Http.PROTOCOL_DELIMITER);
91          sb.append(request.getContextPath());
92          sb.append(request.getServletPath());
93          sb.append(request.getPathInfo());
94          sb.append(StringPool.QUESTION);
95          sb.append(request.getQueryString());
96  
97          // Language
98  
99          sb.append(StringPool.POUND);
100 
101         String languageId = (String)request.getAttribute(
102             WebKeys.I18N_LANGUAGE_ID);
103 
104         if (Validator.isNull(languageId)) {
105             languageId = LanguageUtil.getLanguageId(request);
106         }
107 
108         sb.append(languageId);
109 
110         // User agent
111 
112         String userAgent = GetterUtil.getString(
113             request.getHeader(HttpHeaders.USER_AGENT));
114 
115         sb.append(StringPool.POUND);
116         sb.append(userAgent.toLowerCase().hashCode());
117 
118         // Gzip compression
119 
120         sb.append(StringPool.POUND);
121         sb.append(BrowserSnifferUtil.acceptsGzip(request));
122 
123         return sb.toString().trim().toUpperCase();
124     }
125 
126     protected long getPlid(
127         long companyId, String pathInfo, String servletPath, long defaultPlid) {
128 
129         if (_pattern == _PATTERN_LAYOUT) {
130             return defaultPlid;
131         }
132 
133         if (Validator.isNull(pathInfo) ||
134             !pathInfo.startsWith(StringPool.SLASH)) {
135 
136             return 0;
137         }
138 
139         // Group friendly URL
140 
141         String friendlyURL = null;
142 
143         int pos = pathInfo.indexOf(StringPool.SLASH, 1);
144 
145         if (pos != -1) {
146             friendlyURL = pathInfo.substring(0, pos);
147         }
148         else {
149             if (pathInfo.length() > 1) {
150                 friendlyURL = pathInfo.substring(0, pathInfo.length());
151             }
152         }
153 
154         if (Validator.isNull(friendlyURL)) {
155             return 0;
156         }
157 
158         long groupId = 0;
159         boolean privateLayout = false;
160 
161         try {
162             Group group = GroupLocalServiceUtil.getFriendlyURLGroup(
163                 companyId, friendlyURL);
164 
165             groupId = group.getGroupId();
166 
167             if (servletPath.startsWith(
168                     PropsValues.
169                         LAYOUT_FRIENDLY_URL_PRIVATE_GROUP_SERVLET_MAPPING) ||
170                 servletPath.startsWith(
171                     PropsValues.
172                         LAYOUT_FRIENDLY_URL_PRIVATE_USER_SERVLET_MAPPING)) {
173 
174                 privateLayout = true;
175             }
176             else if (servletPath.startsWith(
177                         PropsValues.
178                             LAYOUT_FRIENDLY_URL_PUBLIC_SERVLET_MAPPING)) {
179 
180                 privateLayout = false;
181             }
182         }
183         catch (NoSuchLayoutException nsle) {
184             if (_log.isWarnEnabled()) {
185                 _log.warn(nsle);
186             }
187         }
188         catch (Exception e) {
189             if (_log.isWarnEnabled()) {
190                 _log.error(e);
191             }
192 
193             return 0;
194         }
195 
196         // Layout friendly URL
197 
198         friendlyURL = null;
199 
200         if ((pos != -1) && ((pos + 1) != pathInfo.length())) {
201             friendlyURL = pathInfo.substring(pos, pathInfo.length());
202         }
203 
204         if (Validator.isNull(friendlyURL)) {
205             return 0;
206         }
207 
208         // If there is no layout path take the first from the group or user
209 
210         try {
211             Layout layout = LayoutLocalServiceUtil.getFriendlyURLLayout(
212                 groupId, privateLayout, friendlyURL);
213 
214             return layout.getPlid();
215         }
216         catch (NoSuchLayoutException nsle) {
217             _log.warn(nsle);
218 
219             return 0;
220         }
221         catch (Exception e) {
222             _log.error(e);
223 
224             return 0;
225         }
226     }
227 
228     protected boolean isAlreadyFiltered(HttpServletRequest request) {
229         if (request.getAttribute(SKIP_FILTER) != null) {
230             return true;
231         }
232         else {
233             return false;
234         }
235     }
236 
237     protected boolean isCacheableColumn(long companyId, String columnSettings)
238         throws SystemException {
239 
240         String[] portletIds = StringUtil.split(columnSettings);
241 
242         for (String portletId : portletIds) {
243             portletId = PortletConstants.getRootPortletId(portletId);
244 
245             Portlet portlet = PortletLocalServiceUtil.getPortletById(
246                 companyId, portletId);
247 
248             if (!portlet.isLayoutCacheable()) {
249                 return false;
250             }
251         }
252 
253         return true;
254     }
255 
256     protected boolean isCacheableData(
257         long companyId, HttpServletRequest request) {
258 
259         try {
260             if (_pattern == _PATTERN_RESOURCE) {
261                 return true;
262             }
263 
264             long plid = getPlid(
265                 companyId, request.getPathInfo(), request.getServletPath(),
266                 ParamUtil.getLong(request, "p_l_id"));
267 
268             if (plid <= 0) {
269                 return false;
270             }
271 
272             Layout layout = LayoutLocalServiceUtil.getLayout(plid);
273 
274             if (!layout.getType().equals(LayoutConstants.TYPE_PORTLET)) {
275                 return false;
276             }
277 
278             UnicodeProperties properties = layout.getTypeSettingsProperties();
279 
280             for (int i = 0; i < 10; i++) {
281                 String columnId = "column-" + i;
282 
283                 String settings = properties.getProperty(
284                     columnId, StringPool.BLANK);
285 
286                 if (!isCacheableColumn(companyId, settings)) {
287                     return false;
288                 }
289             }
290 
291             if (properties.containsKey(
292                     LayoutTypePortletConstants.NESTED_COLUMN_IDS)) {
293 
294                 String[] columnIds = StringUtil.split(
295                     properties.get(
296                         LayoutTypePortletConstants.NESTED_COLUMN_IDS));
297 
298                 for (String columnId : columnIds) {
299                     String settings = properties.getProperty(
300                         columnId, StringPool.BLANK);
301 
302                     if (!isCacheableColumn(companyId, settings)) {
303                         return false;
304                     }
305                 }
306             }
307 
308             return true;
309         }
310         catch (Exception e) {
311             return false;
312         }
313     }
314 
315     protected boolean isCacheableRequest(HttpServletRequest request) {
316         String portletId = ParamUtil.getString(request, "p_p_id");
317 
318         if (Validator.isNotNull(portletId)) {
319             return false;
320         }
321 
322         if ((_pattern == _PATTERN_FRIENDLY) || (_pattern == _PATTERN_LAYOUT)) {
323             long userId = PortalUtil.getUserId(request);
324             String remoteUser = request.getRemoteUser();
325 
326             if ((userId > 0) || Validator.isNotNull(remoteUser)) {
327                 return false;
328             }
329         }
330 
331         if (_pattern == _PATTERN_LAYOUT) {
332             String plid = ParamUtil.getString(request, "p_l_id");
333 
334             if (Validator.isNull(plid)) {
335                 return false;
336             }
337         }
338 
339         return true;
340     }
341 
342     protected boolean isCacheableResponse(CacheResponse cacheResponse) {
343         if (cacheResponse.getStatus() == HttpServletResponse.SC_OK) {
344             return true;
345         }
346         else {
347             return false;
348         }
349     }
350 
351     protected boolean isInclude(HttpServletRequest request) {
352         String uri = (String)request.getAttribute(
353             JavaConstants.JAVAX_SERVLET_INCLUDE_REQUEST_URI);
354 
355         if (uri == null) {
356             return false;
357         }
358         else {
359             return true;
360         }
361     }
362 
363     protected void processFilter(
364             HttpServletRequest request, HttpServletResponse response,
365             FilterChain filterChain)
366         throws Exception {
367 
368         if (isCacheableRequest(request) && !isInclude(request) &&
369             !isAlreadyFiltered(request)) {
370 
371             request.setAttribute(SKIP_FILTER, Boolean.TRUE);
372 
373             String key = getCacheKey(request);
374 
375             long companyId = PortalInstances.getCompanyId(request);
376 
377             CacheResponseData cacheResponseData =
378                 CacheUtil.getCacheResponseData(companyId, key);
379 
380             if (cacheResponseData == null) {
381                 if (!isCacheableData(companyId, request)) {
382                     if (_log.isDebugEnabled()) {
383                         _log.debug("Request is not cacheable " + key);
384                     }
385 
386                     processFilter(
387                         CacheFilter.class, request, response, filterChain);
388 
389                     return;
390                 }
391 
392                 if (_log.isInfoEnabled()) {
393                     _log.info("Caching request " + key);
394                 }
395 
396                 CacheResponse cacheResponse = new CacheResponse(
397                     response, StringPool.UTF8);
398 
399                 processFilter(
400                     CacheFilter.class, request, cacheResponse, filterChain);
401 
402                 cacheResponseData = new CacheResponseData(
403                     cacheResponse.unsafeGetData(),
404                     cacheResponse.getContentLength(),
405                     cacheResponse.getContentType(), cacheResponse.getHeaders());
406 
407                 LastPath lastPath = (LastPath)request.getAttribute(
408                     WebKeys.LAST_PATH);
409 
410                 if (lastPath != null) {
411                     cacheResponseData.setAttribute(WebKeys.LAST_PATH, lastPath);
412                 }
413 
414                 // Cache the result if and only if there is a result and the
415                 // request is cacheable. We have to test the cacheability of a
416                 // request twice because the user could have been authenticated
417                 // after the initial test.
418 
419                 if (isCacheableRequest(request) &&
420                     isCacheableResponse(cacheResponse)) {
421 
422                     CacheUtil.putCacheResponseData(
423                         companyId, key, cacheResponseData);
424                 }
425             }
426             else {
427                 LastPath lastPath = (LastPath)cacheResponseData.getAttribute(
428                     WebKeys.LAST_PATH);
429 
430                 if (lastPath != null) {
431                     HttpSession session = request.getSession();
432 
433                     session.setAttribute(WebKeys.LAST_PATH, lastPath);
434                 }
435             }
436 
437             CacheResponseUtil.write(response, cacheResponseData);
438         }
439         else {
440             if (_log.isDebugEnabled()) {
441                 _log.debug("Request is not cacheable");
442             }
443 
444             processFilter(CacheFilter.class, request, response, filterChain);
445         }
446     }
447 
448     private static final int _PATTERN_FRIENDLY = 0;
449 
450     private static final int _PATTERN_LAYOUT = 1;
451 
452     private static final int _PATTERN_RESOURCE = 2;
453 
454     private static Log _log = LogFactoryUtil.getLog(CacheFilter.class);
455 
456     private int _pattern;
457 
458 }