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