001    /**
002     * Copyright (c) 2000-2013 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.BufferCacheServletResponse;
022    import com.liferay.portal.kernel.servlet.HttpHeaders;
023    import com.liferay.portal.kernel.servlet.ServletResponseUtil;
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.JavaConstants;
028    import com.liferay.portal.kernel.util.StringPool;
029    import com.liferay.portal.kernel.util.StringUtil;
030    import com.liferay.portal.servlet.filters.IgnoreModuleRequestFilter;
031    import com.liferay.portal.util.PropsUtil;
032    
033    import java.io.File;
034    
035    import java.net.URL;
036    import java.net.URLConnection;
037    
038    import javax.servlet.FilterChain;
039    import javax.servlet.FilterConfig;
040    import javax.servlet.ServletContext;
041    import javax.servlet.http.HttpServletRequest;
042    import javax.servlet.http.HttpServletResponse;
043    
044    /**
045     * @author Eduardo Lundgren
046     * @author Raymond Augé
047     */
048    public class DynamicCSSFilter extends IgnoreModuleRequestFilter {
049    
050            public static final boolean ENABLED = GetterUtil.getBoolean(
051                    PropsUtil.get(DynamicCSSFilter.class.getName()));
052    
053            @Override
054            public void init(FilterConfig filterConfig) {
055                    super.init(filterConfig);
056    
057                    _servletContext = filterConfig.getServletContext();
058    
059                    File tempDir = (File)_servletContext.getAttribute(
060                            JavaConstants.JAVAX_SERVLET_CONTEXT_TEMPDIR);
061    
062                    _tempDir = new File(tempDir, _TEMP_DIR);
063    
064                    _tempDir.mkdirs();
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                    return String.valueOf(cacheKeyGenerator.finish());
083            }
084    
085            protected Object getDynamicContent(
086                            HttpServletRequest request, HttpServletResponse response,
087                            FilterChain filterChain)
088                    throws Exception {
089    
090                    String requestURI = request.getRequestURI();
091    
092                    String requestPath = requestURI;
093    
094                    String contextPath = request.getContextPath();
095    
096                    if (!contextPath.equals(StringPool.SLASH)) {
097                            requestPath = requestPath.substring(contextPath.length());
098                    }
099    
100                    URL resourceURL = _servletContext.getResource(requestPath);
101    
102                    if (resourceURL == null) {
103                            return null;
104                    }
105    
106                    URLConnection urlConnection = resourceURL.openConnection();
107    
108                    String cacheCommonFileName = getCacheFileName(request);
109    
110                    File cacheContentTypeFile = new File(
111                            _tempDir, cacheCommonFileName + "_E_CONTENT_TYPE");
112                    File cacheDataFile = new File(
113                            _tempDir, cacheCommonFileName + "_E_DATA");
114    
115                    if (cacheDataFile.exists() &&
116                            (cacheDataFile.lastModified() >= urlConnection.getLastModified())) {
117    
118                            if (cacheContentTypeFile.exists()) {
119                                    String contentType = FileUtil.read(cacheContentTypeFile);
120    
121                                    response.setContentType(contentType);
122                            }
123    
124                            return cacheDataFile;
125                    }
126    
127                    String dynamicContent = null;
128    
129                    String content = null;
130    
131                    try {
132                            if (requestPath.endsWith(_CSS_EXTENSION)) {
133                                    if (_log.isInfoEnabled()) {
134                                            _log.info("Parsing SASS on CSS " + requestPath);
135                                    }
136    
137                                    content = StringUtil.read(urlConnection.getInputStream());
138    
139                                    dynamicContent = DynamicCSSUtil.parseSass(
140                                            _servletContext, request, requestPath, content);
141    
142                                    response.setContentType(ContentTypes.TEXT_CSS);
143    
144                                    FileUtil.write(cacheContentTypeFile, ContentTypes.TEXT_CSS);
145                            }
146                            else if (requestPath.endsWith(_JSP_EXTENSION)) {
147                                    if (_log.isInfoEnabled()) {
148                                            _log.info("Parsing SASS on JSP or servlet " + requestPath);
149                                    }
150    
151                                    BufferCacheServletResponse bufferCacheServletResponse =
152                                            new BufferCacheServletResponse(response);
153    
154                                    processFilter(
155                                            DynamicCSSFilter.class, request, bufferCacheServletResponse,
156                                            filterChain);
157    
158                                    bufferCacheServletResponse.finishResponse();
159    
160                                    content = bufferCacheServletResponse.getString();
161    
162                                    dynamicContent = DynamicCSSUtil.parseSass(
163                                            _servletContext, request, requestPath, content);
164    
165                                    FileUtil.write(
166                                            cacheContentTypeFile,
167                                            bufferCacheServletResponse.getContentType());
168                            }
169                            else {
170                                    return null;
171                            }
172                    }
173                    catch (Exception e) {
174                            _log.error("Unable to parse SASS on CSS " + requestPath, e);
175    
176                            if (_log.isDebugEnabled()) {
177                                    _log.debug(content);
178                            }
179    
180                            response.setHeader(
181                                    HttpHeaders.CACHE_CONTROL,
182                                    HttpHeaders.CACHE_CONTROL_NO_CACHE_VALUE);
183                    }
184    
185                    if (dynamicContent != null) {
186                            FileUtil.write(cacheDataFile, dynamicContent);
187                    }
188                    else {
189                            dynamicContent = content;
190                    }
191    
192                    return dynamicContent;
193            }
194    
195            @Override
196            protected void processFilter(
197                            HttpServletRequest request, HttpServletResponse response,
198                            FilterChain filterChain)
199                    throws Exception {
200    
201                    Object parsedContent = getDynamicContent(
202                            request, response, filterChain);
203    
204                    if (parsedContent == null) {
205                            processFilter(
206                                    DynamicCSSFilter.class, request, response, filterChain);
207                    }
208                    else {
209                            if (parsedContent instanceof File) {
210                                    ServletResponseUtil.write(response, (File)parsedContent);
211                            }
212                            else if (parsedContent instanceof String) {
213                                    ServletResponseUtil.write(response, (String)parsedContent);
214                            }
215                    }
216            }
217    
218            protected String sterilizeQueryString(String queryString) {
219                    return StringUtil.replace(
220                            queryString, new String[] {StringPool.SLASH, StringPool.BACK_SLASH},
221                            new String[] {StringPool.UNDERLINE, StringPool.UNDERLINE});
222            }
223    
224            private static final String _CSS_EXTENSION = ".css";
225    
226            private static final String _JSP_EXTENSION = ".jsp";
227    
228            private static final String _TEMP_DIR = "css";
229    
230            private static Log _log = LogFactoryUtil.getLog(DynamicCSSFilter.class);
231    
232            private ServletContext _servletContext;
233            private File _tempDir;
234    
235    }