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.HttpUtil;
33  import com.liferay.portal.kernel.util.JavaConstants;
34  import com.liferay.portal.kernel.util.ParamUtil;
35  import com.liferay.portal.kernel.util.PortalInitable;
36  import com.liferay.portal.kernel.util.PortalInitableUtil;
37  import com.liferay.portal.kernel.util.StringMaker;
38  import com.liferay.portal.kernel.util.StringPool;
39  import com.liferay.portal.kernel.util.StringUtil;
40  import com.liferay.portal.kernel.util.Validator;
41  import com.liferay.portal.model.Group;
42  import com.liferay.portal.model.Layout;
43  import com.liferay.portal.model.Portlet;
44  import com.liferay.portal.model.impl.LayoutImpl;
45  import com.liferay.portal.model.impl.PortletImpl;
46  import com.liferay.portal.service.GroupLocalServiceUtil;
47  import com.liferay.portal.service.LayoutLocalServiceUtil;
48  import com.liferay.portal.service.PortletLocalServiceUtil;
49  import com.liferay.portal.struts.LastPath;
50  import com.liferay.portal.util.PortalInstances;
51  import com.liferay.portal.util.PortalUtil;
52  import com.liferay.portal.util.PropsUtil;
53  import com.liferay.portal.util.PropsValues;
54  import com.liferay.portal.util.WebKeys;
55  import com.liferay.util.SystemProperties;
56  import com.liferay.util.servlet.filters.CacheResponse;
57  import com.liferay.util.servlet.filters.CacheResponseData;
58  import com.liferay.util.servlet.filters.CacheResponseUtil;
59  
60  import java.io.IOException;
61  
62  import java.util.Properties;
63  
64  import javax.servlet.FilterChain;
65  import javax.servlet.FilterConfig;
66  import javax.servlet.ServletException;
67  import javax.servlet.ServletRequest;
68  import javax.servlet.ServletResponse;
69  import javax.servlet.http.HttpServletRequest;
70  import javax.servlet.http.HttpServletResponse;
71  import javax.servlet.http.HttpSession;
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   * @author Raymond Aug�
79   *
80   */
81  public class LayoutCacheFilter extends BaseFilter implements PortalInitable {
82  
83      public static final boolean USE_FILTER = GetterUtil.getBoolean(
84          PropsUtil.get(LayoutCacheFilter.class.getName()), true);
85  
86      public static final String ENCODING = GetterUtil.getString(
87          SystemProperties.get("file.encoding"), StringPool.UTF8);
88  
89      public void portalInit() {
90          _pattern = GetterUtil.getInteger(
91              _config.getInitParameter("pattern"));
92  
93          if ((_pattern != _PATTERN_FRIENDLY) &&
94              (_pattern != _PATTERN_LAYOUT) &&
95              (_pattern != _PATTERN_RESOURCE)) {
96  
97              _log.error("Layout cache pattern is invalid");
98          }
99      }
100 
101     public void init(FilterConfig config) throws ServletException {
102         super.init(config);
103 
104         _config = config;
105 
106         PortalInitableUtil.init(this);
107     }
108 
109     public void doFilter(
110             ServletRequest req, ServletResponse res, FilterChain chain)
111         throws IOException, ServletException {
112 
113         if (_log.isDebugEnabled()) {
114             if (USE_FILTER) {
115                 _log.debug("Layout cache is enabled");
116             }
117             else {
118                 _log.debug("Layout cache is disabled");
119             }
120         }
121 
122         HttpServletRequest httpReq = (HttpServletRequest)req;
123         HttpServletResponse httpRes = (HttpServletResponse)res;
124 
125         if (USE_FILTER && !isPortletRequest(httpReq) && isLayout(httpReq) &&
126             !isSignedIn(httpReq) && !isInclude(httpReq) &&
127             !isAlreadyFiltered(httpReq)) {
128 
129             httpReq.setAttribute(_ALREADY_FILTERED, Boolean.TRUE);
130 
131             String key = getCacheKey(httpReq);
132 
133             long companyId = PortalInstances.getCompanyId(httpReq);
134 
135             CacheResponseData data = LayoutCacheUtil.getCacheResponseData(
136                 companyId, key);
137 
138             if (data == null) {
139                 if (!isCacheable(companyId, httpReq)) {
140                     if (_log.isDebugEnabled()) {
141                         _log.debug("Layout is not cacheable " + key);
142                     }
143 
144                     doFilter(LayoutCacheFilter.class, req, res, chain);
145 
146                     return;
147                 }
148 
149                 if (_log.isInfoEnabled()) {
150                     _log.info("Caching layout " + key);
151                 }
152 
153                 CacheResponse cacheResponse = new CacheResponse(
154                     httpRes, ENCODING);
155 
156                 doFilter(LayoutCacheFilter.class, req, cacheResponse, chain);
157 
158                 data = new CacheResponseData(
159                     cacheResponse.getData(), cacheResponse.getContentType(),
160                     cacheResponse.getHeaders());
161 
162                 LastPath lastPath = (LastPath)httpReq.getAttribute(
163                     WebKeys.LAST_PATH);
164 
165                 if (lastPath != null) {
166                     data.setAttribute(WebKeys.LAST_PATH, lastPath);
167                 }
168 
169                 if (data.getData().length > 0) {
170                     LayoutCacheUtil.putCacheResponseData(companyId, key, data);
171                 }
172             }
173             else {
174                 LastPath lastPath = (LastPath)data.getAttribute(
175                     WebKeys.LAST_PATH);
176 
177                 if (lastPath != null) {
178                     HttpSession ses = httpReq.getSession();
179 
180                     ses.setAttribute(WebKeys.LAST_PATH, lastPath);
181                 }
182             }
183 
184             CacheResponseUtil.write(httpRes, data);
185         }
186         else {
187             if (_log.isDebugEnabled()) {
188                 _log.debug("Did not request a layout");
189             }
190 
191             doFilter(LayoutCacheFilter.class, req, res, chain);
192         }
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(HttpUtil.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                     PropsValues.
280                         LAYOUT_FRIENDLY_URL_PRIVATE_GROUP_SERVLET_MAPPING) ||
281                 servletPath.startsWith(
282                     PropsValues.
283                         LAYOUT_FRIENDLY_URL_PRIVATE_USER_SERVLET_MAPPING)) {
284 
285                 privateLayout = true;
286             }
287             else if (servletPath.startsWith(
288                         PropsValues.
289                             LAYOUT_FRIENDLY_URL_PUBLIC_SERVLET_MAPPING)) {
290 
291                 privateLayout = false;
292             }
293         }
294         catch (NoSuchLayoutException nsle) {
295             if (_log.isWarnEnabled()) {
296                 _log.warn(nsle);
297             }
298         }
299         catch (Exception e) {
300             if (_log.isWarnEnabled()) {
301                 _log.error(e);
302             }
303 
304             return 0;
305         }
306 
307         // Layout friendly URL
308 
309         friendlyURL = null;
310 
311         if ((pos != -1) && ((pos + 1) != pathInfo.length())) {
312             friendlyURL = pathInfo.substring(pos, pathInfo.length());
313         }
314 
315         if (Validator.isNull(friendlyURL)) {
316             return 0;
317         }
318 
319         // If there is no layout path take the first from the group or user
320 
321         try {
322             Layout layout = LayoutLocalServiceUtil.getFriendlyURLLayout(
323                 groupId, privateLayout, friendlyURL);
324 
325             return layout.getPlid();
326         }
327         catch (NoSuchLayoutException nsle) {
328             _log.warn(nsle);
329 
330             return 0;
331         }
332         catch (Exception e) {
333             _log.error(e);
334 
335             return 0;
336         }
337     }
338 
339     protected boolean isAlreadyFiltered(HttpServletRequest req) {
340         if (req.getAttribute(_ALREADY_FILTERED) != null) {
341             return true;
342         }
343         else {
344             return false;
345         }
346     }
347 
348     protected boolean isCacheable(long companyId, HttpServletRequest req) {
349         if (_pattern == _PATTERN_RESOURCE) {
350             return true;
351         }
352 
353         try {
354             long plid = getPlid(
355                 companyId, req.getPathInfo(), req.getServletPath(),
356                 ParamUtil.getLong(req, "p_l_id"));
357 
358             if (plid <= 0) {
359                 return false;
360             }
361 
362             Layout layout = LayoutLocalServiceUtil.getLayout(plid);
363 
364             if (!layout.getType().equals(LayoutImpl.TYPE_PORTLET)) {
365                 return false;
366             }
367 
368             Properties props = layout.getTypeSettingsProperties();
369 
370             for (int i = 0; i < 10; i++) {
371                 String columnId = "column-" + i;
372 
373                 String settings = props.getProperty(columnId, StringPool.BLANK);
374 
375                 String[] portlets = StringUtil.split(settings);
376 
377                 for (int j = 0; j < portlets.length; j++) {
378                     String portletId = StringUtil.extractFirst(
379                         portlets[j], PortletImpl.INSTANCE_SEPARATOR);
380 
381                     Portlet portlet = PortletLocalServiceUtil.getPortletById(
382                         companyId, portletId);
383 
384                     if (!portlet.isLayoutCacheable()) {
385                         return false;
386                     }
387                 }
388             }
389         }
390         catch(Exception e) {
391             return false;
392         }
393 
394         return true;
395     }
396 
397     protected boolean isInclude(HttpServletRequest req) {
398         String uri = (String)req.getAttribute(
399             JavaConstants.JAVAX_SERVLET_INCLUDE_REQUEST_URI);
400 
401         if (uri == null) {
402             return false;
403         }
404         else {
405             return true;
406         }
407     }
408 
409     protected boolean isLayout(HttpServletRequest req) {
410         if ((_pattern == _PATTERN_FRIENDLY) ||
411             (_pattern == _PATTERN_RESOURCE)) {
412 
413             return true;
414         }
415         else {
416             String plid = ParamUtil.getString(req, "p_l_id");
417 
418             if (Validator.isNotNull(plid)) {
419                 return true;
420             }
421             else {
422                 return false;
423             }
424         }
425     }
426 
427     protected boolean isPortletRequest(HttpServletRequest req) {
428         String portletId = ParamUtil.getString(req, "p_p_id");
429 
430         if (Validator.isNull(portletId)) {
431             return false;
432         }
433         else {
434             return true;
435         }
436     }
437 
438     protected boolean isSignedIn(HttpServletRequest req) {
439         long userId = PortalUtil.getUserId(req);
440         String remoteUser = req.getRemoteUser();
441 
442         if ((userId <= 0) && (remoteUser == null)) {
443             return false;
444         }
445         else {
446             return true;
447         }
448     }
449 
450     private static final String _ALREADY_FILTERED =
451         LayoutCacheFilter.class + "_ALREADY_FILTERED";
452 
453     private static final int _PATTERN_FRIENDLY = 0;
454 
455     private static final int _PATTERN_LAYOUT = 1;
456 
457     private static final int _PATTERN_RESOURCE = 2;
458 
459     private static final String _BROWSER_TYPE_IE_7 = "ie_7";
460 
461     private static final String _BROWSER_TYPE_IE = "ie";
462 
463     private static final String _BROWSER_TYPE_OTHER = "other";
464 
465     private static Log _log = LogFactoryUtil.getLog(LayoutCacheFilter.class);
466 
467     private FilterConfig _config;
468     private int _pattern;
469 
470 }