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