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