001    /**
002     * Copyright (c) 2000-2011 Liferay, Inc. All rights reserved.
003     *
004     * The contents of this file are subject to the terms of the Liferay Enterprise
005     * Subscription License ("License"). You may not use this file except in
006     * compliance with the License. You can obtain a copy of the License by
007     * contacting Liferay, Inc. See the License for the specific language governing
008     * permissions and limitations under the License, including but not limited to
009     * distribution rights of the Software.
010     *
011     *
012     *
013     */
014    
015    package com.liferay.portal.servlet.filters.dynamiccss;
016    
017    import com.liferay.portal.kernel.log.Log;
018    import com.liferay.portal.kernel.log.LogFactoryUtil;
019    import com.liferay.portal.kernel.servlet.HttpHeaders;
020    import com.liferay.portal.kernel.servlet.ServletContextUtil;
021    import com.liferay.portal.kernel.servlet.ServletResponseUtil;
022    import com.liferay.portal.kernel.servlet.StringServletResponse;
023    import com.liferay.portal.kernel.util.CharPool;
024    import com.liferay.portal.kernel.util.ContentTypes;
025    import com.liferay.portal.kernel.util.FileUtil;
026    import com.liferay.portal.kernel.util.GetterUtil;
027    import com.liferay.portal.kernel.util.StringBundler;
028    import com.liferay.portal.kernel.util.StringPool;
029    import com.liferay.portal.kernel.util.StringUtil;
030    import com.liferay.portal.kernel.util.SystemProperties;
031    import com.liferay.portal.kernel.util.Validator;
032    import com.liferay.portal.kernel.util.WebKeys;
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 Object getDynamicContent(
070                            HttpServletRequest request, HttpServletResponse response,
071                            FilterChain filterChain)
072                    throws Exception {
073    
074                    String requestURI = request.getRequestURI();
075    
076                    String requestPath = requestURI;
077    
078                    String contextPath = request.getContextPath();
079    
080                    if (!contextPath.equals(StringPool.SLASH)) {
081                            requestPath = requestPath.substring(contextPath.length());
082                    }
083    
084                    String realPath = ServletContextUtil.getRealPath(
085                            _servletContext, requestPath);
086    
087                    if (realPath == null) {
088                            return null;
089                    }
090    
091                    realPath = StringUtil.replace(
092                            realPath, CharPool.BACK_SLASH, CharPool.SLASH);
093    
094                    File file = new File(realPath);
095    
096                    if (!file.exists()) {
097                            return null;
098                    }
099    
100                    request.setAttribute(WebKeys.CSS_REAL_PATH, realPath);
101    
102                    StringBundler sb = new StringBundler(4);
103    
104                    sb.append(_tempDir);
105                    sb.append(requestURI);
106    
107                    String queryString = request.getQueryString();
108    
109                    if (queryString != null) {
110                            sb.append(_QUESTION_SEPARATOR);
111                            sb.append(sterilizeQueryString(queryString));
112                    }
113    
114                    String cacheCommonFileName = sb.toString();
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                    String cssRealPath = (String)request.getAttribute(
137                            WebKeys.CSS_REAL_PATH);
138    
139                    try {
140                            if (realPath.endsWith(_CSS_EXTENSION)) {
141                                    if (_log.isInfoEnabled()) {
142                                            _log.info("Parsing SASS on CSS " + file);
143                                    }
144    
145                                    content = FileUtil.read(file);
146    
147                                    dynamicContent = DynamicCSSUtil.parseSass(
148                                            request, cssRealPath, content);
149    
150                                    response.setContentType(ContentTypes.TEXT_CSS);
151    
152                                    FileUtil.write(cacheContentTypeFile, ContentTypes.TEXT_CSS);
153                            }
154                            else if (realPath.endsWith(_JSP_EXTENSION)) {
155                                    if (_log.isInfoEnabled()) {
156                                            _log.info("Parsing SASS on JSP " + file);
157                                    }
158    
159                                    StringServletResponse stringResponse =
160                                            new StringServletResponse(response);
161    
162                                    processFilter(
163                                            DynamicCSSFilter.class, request, stringResponse,
164                                            filterChain);
165    
166                                    CacheResponseUtil.setHeaders(
167                                            response, stringResponse.getHeaders());
168    
169                                    response.setContentType(stringResponse.getContentType());
170    
171                                    content = stringResponse.getString();
172    
173                                    dynamicContent = DynamicCSSUtil.parseSass(
174                                            request, cssRealPath, content);
175    
176                                    FileUtil.write(
177                                            cacheContentTypeFile, stringResponse.getContentType());
178                            }
179                            else {
180                                    return null;
181                            }
182                    }
183                    catch (Exception e) {
184                            _log.error("Unable to parse SASS on CSS " + cssRealPath, e);
185    
186                            if (_log.isDebugEnabled()) {
187                                    _log.debug(content);
188                            }
189    
190                            response.setHeader(
191                                    HttpHeaders.CACHE_CONTROL,
192                                    HttpHeaders.CACHE_CONTROL_NO_CACHE_VALUE);
193                    }
194    
195                    if (dynamicContent != null) {
196                            FileUtil.write(cacheDataFile, dynamicContent);
197                    }
198                    else {
199                            dynamicContent = content;
200                    }
201    
202                    return dynamicContent;
203            }
204    
205            @Override
206            protected void processFilter(
207                            HttpServletRequest request, HttpServletResponse response,
208                            FilterChain filterChain)
209                    throws Exception {
210    
211                    Object parsedContent = getDynamicContent(
212                            request, response, filterChain);
213    
214                    if (parsedContent == null) {
215                            processFilter(
216                                    DynamicCSSFilter.class, request, response, filterChain);
217                    }
218                    else {
219                            if (parsedContent instanceof File) {
220                                    ServletResponseUtil.write(response, (File)parsedContent);
221                            }
222                            else if (parsedContent instanceof String) {
223                                    ServletResponseUtil.write(response, (String)parsedContent);
224                            }
225                    }
226            }
227    
228            protected String sterilizeQueryString(String queryString) {
229                    return StringUtil.replace(
230                            queryString,
231                            new String[] {StringPool.SLASH, StringPool.BACK_SLASH},
232                            new String[] {StringPool.UNDERLINE, StringPool.UNDERLINE});
233            }
234    
235            private static final String _CSS_EXTENSION = ".css";
236    
237            private static final String _JSP_EXTENSION = ".jsp";
238    
239            private static final String _QUESTION_SEPARATOR = "_Q_";
240    
241            private static final String _TEMP_DIR =
242                    SystemProperties.get(SystemProperties.TMP_DIR) + "/liferay/css";
243    
244            private static Log _log = LogFactoryUtil.getLog(DynamicCSSFilter.class);
245    
246            private ServletContext _servletContext;
247            private String _servletContextName;
248            private String _tempDir = _TEMP_DIR;
249    
250    }