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