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) throws ServletException {
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.getContextPath());
113         sb.append(request.getServletPath());
114         sb.append(request.getPathInfo());
115         sb.append(StringPool.QUESTION);
116         sb.append(request.getQueryString());
117 
118         // Language
119 
120         sb.append(StringPool.POUND);
121 
122         String languageId = (String)request.getAttribute(
123             WebKeys.I18N_LANGUAGE_ID);
124 
125         if (Validator.isNull(languageId)) {
126             languageId = LanguageUtil.getLanguageId(request);
127         }
128 
129         sb.append(languageId);
130 
131         // Browser type
132 
133         sb.append(StringPool.POUND);
134         sb.append(getBrowserType(request));
135 
136         // Gzip compression
137 
138         sb.append(StringPool.POUND);
139         sb.append(BrowserSnifferUtil.acceptsGzip(request));
140 
141         return sb.toString().trim().toUpperCase();
142     }
143 
144     protected long getPlid(
145         long companyId, String pathInfo, String servletPath, long defaultPlid) {
146 
147         if (_pattern == _PATTERN_LAYOUT) {
148             return defaultPlid;
149         }
150 
151         if (Validator.isNull(pathInfo) ||
152             !pathInfo.startsWith(StringPool.SLASH)) {
153 
154             return 0;
155         }
156 
157         // Group friendly URL
158 
159         String friendlyURL = null;
160 
161         int pos = pathInfo.indexOf(StringPool.SLASH, 1);
162 
163         if (pos != -1) {
164             friendlyURL = pathInfo.substring(0, pos);
165         }
166         else {
167             if (pathInfo.length() > 1) {
168                 friendlyURL = pathInfo.substring(0, pathInfo.length());
169             }
170         }
171 
172         if (Validator.isNull(friendlyURL)) {
173             return 0;
174         }
175 
176         long groupId = 0;
177         boolean privateLayout = false;
178 
179         try {
180             Group group = GroupLocalServiceUtil.getFriendlyURLGroup(
181                 companyId, friendlyURL);
182 
183             groupId = group.getGroupId();
184 
185             if (servletPath.startsWith(
186                     PropsValues.
187                         LAYOUT_FRIENDLY_URL_PRIVATE_GROUP_SERVLET_MAPPING) ||
188                 servletPath.startsWith(
189                     PropsValues.
190                         LAYOUT_FRIENDLY_URL_PRIVATE_USER_SERVLET_MAPPING)) {
191 
192                 privateLayout = true;
193             }
194             else if (servletPath.startsWith(
195                         PropsValues.
196                             LAYOUT_FRIENDLY_URL_PUBLIC_SERVLET_MAPPING)) {
197 
198                 privateLayout = false;
199             }
200         }
201         catch (NoSuchLayoutException nsle) {
202             if (_log.isWarnEnabled()) {
203                 _log.warn(nsle);
204             }
205         }
206         catch (Exception e) {
207             if (_log.isWarnEnabled()) {
208                 _log.error(e);
209             }
210 
211             return 0;
212         }
213 
214         // Layout friendly URL
215 
216         friendlyURL = null;
217 
218         if ((pos != -1) && ((pos + 1) != pathInfo.length())) {
219             friendlyURL = pathInfo.substring(pos, pathInfo.length());
220         }
221 
222         if (Validator.isNull(friendlyURL)) {
223             return 0;
224         }
225 
226         // If there is no layout path take the first from the group or user
227 
228         try {
229             Layout layout = LayoutLocalServiceUtil.getFriendlyURLLayout(
230                 groupId, privateLayout, friendlyURL);
231 
232             return layout.getPlid();
233         }
234         catch (NoSuchLayoutException nsle) {
235             _log.warn(nsle);
236 
237             return 0;
238         }
239         catch (Exception e) {
240             _log.error(e);
241 
242             return 0;
243         }
244     }
245 
246     protected boolean isAlreadyFiltered(HttpServletRequest request) {
247         if (request.getAttribute(SKIP_FILTER) != null) {
248             return true;
249         }
250         else {
251             return false;
252         }
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.getType().equals(LayoutConstants.TYPE_PORTLET)) {
274                 return false;
275             }
276 
277             UnicodeProperties props = layout.getTypeSettingsProperties();
278 
279             for (int i = 0; i < 10; i++) {
280                 String columnId = "column-" + i;
281 
282                 String settings = props.getProperty(columnId, StringPool.BLANK);
283 
284                 String[] portlets = StringUtil.split(settings);
285 
286                 for (int j = 0; j < portlets.length; j++) {
287                     String portletId = StringUtil.extractFirst(
288                         portlets[j], PortletConstants.INSTANCE_SEPARATOR);
289 
290                     Portlet portlet = PortletLocalServiceUtil.getPortletById(
291                         companyId, portletId);
292 
293                     if (!portlet.isLayoutCacheable()) {
294                         return false;
295                     }
296                 }
297             }
298 
299             return true;
300         }
301         catch (Exception e) {
302             return false;
303         }
304     }
305 
306     protected boolean isCacheableRequest(HttpServletRequest request) {
307         String portletId = ParamUtil.getString(request, "p_p_id");
308 
309         if (Validator.isNotNull(portletId)) {
310             return false;
311         }
312 
313         if ((_pattern == _PATTERN_FRIENDLY) || (_pattern == _PATTERN_LAYOUT)) {
314             long userId = PortalUtil.getUserId(request);
315             String remoteUser = request.getRemoteUser();
316 
317             if ((userId > 0) || Validator.isNotNull(remoteUser)) {
318                 return false;
319             }
320         }
321 
322         if (_pattern == _PATTERN_LAYOUT) {
323             String plid = ParamUtil.getString(request, "p_l_id");
324 
325             if (Validator.isNull(plid)) {
326                 return false;
327             }
328         }
329 
330         return true;
331     }
332 
333     protected boolean isInclude(HttpServletRequest request) {
334         String uri = (String)request.getAttribute(
335             JavaConstants.JAVAX_SERVLET_INCLUDE_REQUEST_URI);
336 
337         if (uri == null) {
338             return false;
339         }
340         else {
341             return true;
342         }
343     }
344 
345     protected void processFilter(
346             HttpServletRequest request, HttpServletResponse response,
347             FilterChain filterChain)
348         throws IOException, ServletException {
349 
350         if (isCacheableRequest(request) && !isInclude(request) &&
351             !isAlreadyFiltered(request)) {
352 
353             request.setAttribute(SKIP_FILTER, Boolean.TRUE);
354 
355             String key = getCacheKey(request);
356 
357             long companyId = PortalInstances.getCompanyId(request);
358 
359             CacheResponseData cacheResponseData =
360                 CacheUtil.getCacheResponseData(companyId, key);
361 
362             if (cacheResponseData == null) {
363                 if (!isCacheableData(companyId, request)) {
364                     if (_log.isDebugEnabled()) {
365                         _log.debug("Request is not cacheable " + key);
366                     }
367 
368                     processFilter(
369                         CacheFilter.class, request, response, filterChain);
370 
371                     return;
372                 }
373 
374                 if (_log.isInfoEnabled()) {
375                     _log.info("Caching request " + key);
376                 }
377 
378                 CacheResponse cacheResponse = new CacheResponse(
379                     response, StringPool.UTF8);
380 
381                 processFilter(
382                     CacheFilter.class, request, cacheResponse, filterChain);
383 
384                 cacheResponseData = new CacheResponseData(
385                     cacheResponse.getData(), cacheResponse.getContentType(),
386                     cacheResponse.getHeaders());
387 
388                 LastPath lastPath = (LastPath)request.getAttribute(
389                     WebKeys.LAST_PATH);
390 
391                 if (lastPath != null) {
392                     cacheResponseData.setAttribute(WebKeys.LAST_PATH, lastPath);
393                 }
394 
395                 // Cache the result if and only if there is a result and the
396                 // request is cacheable. We have to test the cacheability of a
397                 // request twice because the user could have been authenticated
398                 // after the initial test.
399 
400                 if ((cacheResponseData.getData().length > 0) &&
401                     (isCacheableRequest(request))) {
402 
403                     CacheUtil.putCacheResponseData(
404                         companyId, key, cacheResponseData);
405                 }
406             }
407             else {
408                 LastPath lastPath = (LastPath)cacheResponseData.getAttribute(
409                     WebKeys.LAST_PATH);
410 
411                 if (lastPath != null) {
412                     HttpSession session = request.getSession();
413 
414                     session.setAttribute(WebKeys.LAST_PATH, lastPath);
415                 }
416             }
417 
418             CacheResponseUtil.write(response, cacheResponseData);
419         }
420         else {
421             if (_log.isDebugEnabled()) {
422                 _log.debug("Request is not cacheable");
423             }
424 
425             processFilter(CacheFilter.class, request, response, filterChain);
426         }
427     }
428 
429     private static final int _PATTERN_FRIENDLY = 0;
430 
431     private static final int _PATTERN_LAYOUT = 1;
432 
433     private static final int _PATTERN_RESOURCE = 2;
434 
435     private static final String _BROWSER_TYPE_IE_7 = "ie_7";
436 
437     private static final String _BROWSER_TYPE_IE = "ie";
438 
439     private static final String _BROWSER_TYPE_OTHER = "other";
440 
441     private static Log _log = LogFactoryUtil.getLog(CacheFilter.class);
442 
443     private int _pattern;
444 
445 }