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