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