001    /**
002     * Copyright (c) 2000-2012 Liferay, Inc. All rights reserved.
003     *
004     * This library is free software; you can redistribute it and/or modify it under
005     * the terms of the GNU Lesser General Public License as published by the Free
006     * Software Foundation; either version 2.1 of the License, or (at your option)
007     * any later version.
008     *
009     * This library is distributed in the hope that it will be useful, but WITHOUT
010     * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
011     * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
012     * details.
013     */
014    
015    package com.liferay.portal.servlet.filters.dynamiccss;
016    
017    import com.liferay.portal.kernel.cache.key.CacheKeyGenerator;
018    import com.liferay.portal.kernel.cache.key.CacheKeyGeneratorUtil;
019    import com.liferay.portal.kernel.log.Log;
020    import com.liferay.portal.kernel.log.LogFactoryUtil;
021    import com.liferay.portal.kernel.servlet.HttpHeaders;
022    import com.liferay.portal.kernel.servlet.ServletContextUtil;
023    import com.liferay.portal.kernel.servlet.ServletResponseUtil;
024    import com.liferay.portal.kernel.servlet.StringServletResponse;
025    import com.liferay.portal.kernel.util.CharPool;
026    import com.liferay.portal.kernel.util.ContentTypes;
027    import com.liferay.portal.kernel.util.FileUtil;
028    import com.liferay.portal.kernel.util.GetterUtil;
029    import com.liferay.portal.kernel.util.StringPool;
030    import com.liferay.portal.kernel.util.StringUtil;
031    import com.liferay.portal.kernel.util.SystemProperties;
032    import com.liferay.portal.kernel.util.Validator;
033    import com.liferay.portal.servlet.filters.BasePortalFilter;
034    import com.liferay.portal.util.PropsUtil;
035    import com.liferay.util.servlet.filters.CacheResponseUtil;
036    
037    import java.io.File;
038    
039    import javax.servlet.FilterChain;
040    import javax.servlet.FilterConfig;
041    import javax.servlet.ServletContext;
042    import javax.servlet.http.HttpServletRequest;
043    import javax.servlet.http.HttpServletResponse;
044    
045    /**
046     * @author Eduardo Lundgren
047     * @author Raymond Augé
048     */
049    public class DynamicCSSFilter extends BasePortalFilter {
050    
051            public static final boolean ENABLED = GetterUtil.getBoolean(
052                    PropsUtil.get(DynamicCSSFilter.class.getName()));
053    
054            @Override
055            public void init(FilterConfig filterConfig) {
056                    super.init(filterConfig);
057    
058                    _servletContext = filterConfig.getServletContext();
059                    _servletContextName = GetterUtil.getString(
060                            _servletContext.getServletContextName());
061    
062                    if (Validator.isNull(_servletContextName)) {
063                            _tempDir += "/portal";
064                    }
065    
066                    DynamicCSSUtil.init();
067            }
068    
069            protected String getCacheFileName(HttpServletRequest request) {
070                    CacheKeyGenerator cacheKeyGenerator =
071                            CacheKeyGeneratorUtil.getCacheKeyGenerator(
072                                    DynamicCSSFilter.class.getName());
073    
074                    cacheKeyGenerator.append(request.getRequestURI());
075    
076                    String queryString = request.getQueryString();
077    
078                    if (queryString != null) {
079                            cacheKeyGenerator.append(sterilizeQueryString(queryString));
080                    }
081    
082                    String cacheKey = String.valueOf(cacheKeyGenerator.finish());
083    
084                    return _tempDir.concat(StringPool.SLASH).concat(cacheKey);
085            }
086    
087            protected Object getDynamicContent(
088                            HttpServletRequest request, HttpServletResponse response,
089                            FilterChain filterChain)
090                    throws Exception {
091    
092                    String requestURI = request.getRequestURI();
093    
094                    String requestPath = requestURI;
095    
096                    String contextPath = request.getContextPath();
097    
098                    if (!contextPath.equals(StringPool.SLASH)) {
099                            requestPath = requestPath.substring(contextPath.length());
100                    }
101    
102                    String realPath = ServletContextUtil.getRealPath(
103                            _servletContext, requestPath);
104    
105                    if (realPath == null) {
106                            return null;
107                    }
108    
109                    realPath = StringUtil.replace(
110                            realPath, CharPool.BACK_SLASH, CharPool.SLASH);
111    
112                    File file = new File(realPath);
113    
114                    String cacheCommonFileName = getCacheFileName(request);
115    
116                    File cacheContentTypeFile = new File(
117                            cacheCommonFileName + "_E_CONTENT_TYPE");
118                    File cacheDataFile = new File(cacheCommonFileName + "_E_DATA");
119    
120                    if ((cacheDataFile.exists()) &&
121                            (cacheDataFile.lastModified() >= file.lastModified())) {
122    
123                            if (cacheContentTypeFile.exists()) {
124                                    String contentType = FileUtil.read(cacheContentTypeFile);
125    
126                                    response.setContentType(contentType);
127                            }
128    
129                            return cacheDataFile;
130                    }
131    
132                    String dynamicContent = null;
133    
134                    String content = null;
135    
136                    try {
137                            if (realPath.endsWith(_CSS_EXTENSION) && file.exists()) {
138                                    if (_log.isInfoEnabled()) {
139                                            _log.info("Parsing SASS on CSS " + file);
140                                    }
141    
142                                    content = FileUtil.read(file);
143    
144                                    dynamicContent = DynamicCSSUtil.parseSass(
145                                            request, realPath, content);
146    
147                                    response.setContentType(ContentTypes.TEXT_CSS);
148    
149                                    FileUtil.write(cacheContentTypeFile, ContentTypes.TEXT_CSS);
150                            }
151                            else if (realPath.endsWith(_JSP_EXTENSION) || !file.exists()) {
152                                    if (_log.isInfoEnabled()) {
153                                            _log.info("Parsing SASS on JSP or servlet " + realPath);
154                                    }
155    
156                                    StringServletResponse stringResponse =
157                                            new StringServletResponse(response);
158    
159                                    processFilter(
160                                            DynamicCSSFilter.class, request, stringResponse,
161                                            filterChain);
162    
163                                    CacheResponseUtil.setHeaders(
164                                            response, stringResponse.getHeaders());
165    
166                                    response.setContentType(stringResponse.getContentType());
167    
168                                    content = stringResponse.getString();
169    
170                                    dynamicContent = DynamicCSSUtil.parseSass(
171                                            request, realPath, content);
172    
173                                    FileUtil.write(
174                                            cacheContentTypeFile, stringResponse.getContentType());
175                            }
176                            else {
177                                    return null;
178                            }
179                    }
180                    catch (Exception e) {
181                            _log.error("Unable to parse SASS on CSS " + realPath, e);
182    
183                            if (_log.isDebugEnabled()) {
184                                    _log.debug(content);
185                            }
186    
187                            response.setHeader(
188                                    HttpHeaders.CACHE_CONTROL,
189                                    HttpHeaders.CACHE_CONTROL_NO_CACHE_VALUE);
190                    }
191    
192                    if (dynamicContent != null) {
193                            FileUtil.write(cacheDataFile, dynamicContent);
194                    }
195                    else {
196                            dynamicContent = content;
197                    }
198    
199                    return dynamicContent;
200            }
201    
202            @Override
203            protected void processFilter(
204                            HttpServletRequest request, HttpServletResponse response,
205                            FilterChain filterChain)
206                    throws Exception {
207    
208                    Object parsedContent = getDynamicContent(
209                            request, response, filterChain);
210    
211                    if (parsedContent == null) {
212                            processFilter(
213                                    DynamicCSSFilter.class, request, response, filterChain);
214                    }
215                    else {
216                            if (parsedContent instanceof File) {
217                                    ServletResponseUtil.write(response, (File)parsedContent);
218                            }
219                            else if (parsedContent instanceof String) {
220                                    ServletResponseUtil.write(response, (String)parsedContent);
221                            }
222                    }
223            }
224    
225            protected String sterilizeQueryString(String queryString) {
226                    return StringUtil.replace(
227                            queryString,
228                            new String[] {StringPool.SLASH, StringPool.BACK_SLASH},
229                            new String[] {StringPool.UNDERLINE, StringPool.UNDERLINE});
230            }
231    
232            private static final String _CSS_EXTENSION = ".css";
233    
234            private static final String _JSP_EXTENSION = ".jsp";
235    
236            private static final String _TEMP_DIR =
237                    SystemProperties.get(SystemProperties.TMP_DIR) + "/liferay/css";
238    
239            private static Log _log = LogFactoryUtil.getLog(DynamicCSSFilter.class);
240    
241            private ServletContext _servletContext;
242            private String _servletContextName;
243            private String _tempDir = _TEMP_DIR;
244    
245    }