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