1   /**
2    * Copyright (c) 2000-2010 Liferay, Inc. All rights reserved.
3    *
4    * The contents of this file are subject to the terms of the Liferay Enterprise
5    * Subscription License ("License"). You may not use this file except in
6    * compliance with the License. You can obtain a copy of the License by
7    * contacting Liferay, Inc. See the License for the specific language governing
8    * permissions and limitations under the License, including but not limited to
9    * distribution rights of the Software.
10   *
11   *
12   *
13   */
14  
15  package com.liferay.portal.servlet.filters.cache;
16  
17  import com.liferay.portal.NoSuchLayoutException;
18  import com.liferay.portal.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.struts.LastPath;
25  import com.liferay.portal.kernel.util.CharPool;
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.Portlet;
39  import com.liferay.portal.model.PortletConstants;
40  import com.liferay.portal.model.impl.LayoutTypePortletImpl;
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.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(CharPool.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.isTypePortlet()) {
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                     LayoutTypePortletImpl.NESTED_COLUMN_IDS)) {
293 
294                 String[] columnIds = StringUtil.split(
295                     properties.get(LayoutTypePortletImpl.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(CacheResponse cacheResponse) {
342         if (cacheResponse.getStatus() == HttpServletResponse.SC_OK) {
343             return true;
344         }
345         else {
346             return false;
347         }
348     }
349 
350     protected boolean isInclude(HttpServletRequest request) {
351         String uri = (String)request.getAttribute(
352             JavaConstants.JAVAX_SERVLET_INCLUDE_REQUEST_URI);
353 
354         if (uri == null) {
355             return false;
356         }
357         else {
358             return true;
359         }
360     }
361 
362     protected void processFilter(
363             HttpServletRequest request, HttpServletResponse response,
364             FilterChain filterChain)
365         throws Exception {
366 
367         if (isCacheableRequest(request) && !isInclude(request) &&
368             !isAlreadyFiltered(request)) {
369 
370             request.setAttribute(SKIP_FILTER, Boolean.TRUE);
371 
372             String key = getCacheKey(request);
373 
374             long companyId = PortalInstances.getCompanyId(request);
375 
376             CacheResponseData cacheResponseData =
377                 CacheUtil.getCacheResponseData(companyId, key);
378 
379             if (cacheResponseData == null) {
380                 if (!isCacheableData(companyId, request)) {
381                     if (_log.isDebugEnabled()) {
382                         _log.debug("Request is not cacheable " + key);
383                     }
384 
385                     processFilter(
386                         CacheFilter.class, request, response, filterChain);
387 
388                     return;
389                 }
390 
391                 if (_log.isInfoEnabled()) {
392                     _log.info("Caching request " + key);
393                 }
394 
395                 CacheResponse cacheResponse = new CacheResponse(
396                     response, StringPool.UTF8);
397 
398                 processFilter(
399                     CacheFilter.class, request, cacheResponse, filterChain);
400 
401                 cacheResponseData = new CacheResponseData(
402                     cacheResponse.unsafeGetData(),
403                     cacheResponse.getContentLength(),
404                     cacheResponse.getContentType(), cacheResponse.getHeaders());
405 
406                 LastPath lastPath = (LastPath)request.getAttribute(
407                     WebKeys.LAST_PATH);
408 
409                 if (lastPath != null) {
410                     cacheResponseData.setAttribute(WebKeys.LAST_PATH, lastPath);
411                 }
412 
413                 // Cache the result if and only if there is a result and the
414                 // request is cacheable. We have to test the cacheability of a
415                 // request twice because the user could have been authenticated
416                 // after the initial test.
417 
418                 if (isCacheableRequest(request) &&
419                     isCacheableResponse(cacheResponse)) {
420 
421                     CacheUtil.putCacheResponseData(
422                         companyId, key, cacheResponseData);
423                 }
424             }
425             else {
426                 LastPath lastPath = (LastPath)cacheResponseData.getAttribute(
427                     WebKeys.LAST_PATH);
428 
429                 if (lastPath != null) {
430                     HttpSession session = request.getSession();
431 
432                     session.setAttribute(WebKeys.LAST_PATH, lastPath);
433                 }
434             }
435 
436             CacheResponseUtil.write(response, cacheResponseData);
437         }
438         else {
439             if (_log.isDebugEnabled()) {
440                 _log.debug("Request is not cacheable");
441             }
442 
443             processFilter(CacheFilter.class, request, response, filterChain);
444         }
445     }
446 
447     private static final int _PATTERN_FRIENDLY = 0;
448 
449     private static final int _PATTERN_LAYOUT = 1;
450 
451     private static final int _PATTERN_RESOURCE = 2;
452 
453     private static Log _log = LogFactoryUtil.getLog(CacheFilter.class);
454 
455     private int _pattern;
456 
457 }