001    /**
002     * Copyright (c) 2000-present 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.PortalWebResourceConstants;
024    import com.liferay.portal.kernel.servlet.PortalWebResourcesUtil;
025    import com.liferay.portal.kernel.servlet.ServletResponseUtil;
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.HttpUtil;
030    import com.liferay.portal.kernel.util.JavaConstants;
031    import com.liferay.portal.kernel.util.StringPool;
032    import com.liferay.portal.kernel.util.StringUtil;
033    import com.liferay.portal.kernel.util.URLUtil;
034    import com.liferay.portal.servlet.filters.IgnoreModuleRequestFilter;
035    import com.liferay.portal.util.PortalUtil;
036    import com.liferay.portal.util.PropsUtil;
037    
038    import java.io.File;
039    
040    import java.net.URL;
041    
042    import javax.servlet.FilterChain;
043    import javax.servlet.FilterConfig;
044    import javax.servlet.ServletContext;
045    import javax.servlet.http.HttpServletRequest;
046    import javax.servlet.http.HttpServletResponse;
047    
048    /**
049     * @author Eduardo Lundgren
050     * @author Raymond Aug??
051     */
052    public class DynamicCSSFilter extends IgnoreModuleRequestFilter {
053    
054            public static final boolean ENABLED = GetterUtil.getBoolean(
055                    PropsUtil.get(DynamicCSSFilter.class.getName()));
056    
057            @Override
058            public void init(FilterConfig filterConfig) {
059                    super.init(filterConfig);
060    
061                    _servletContext = filterConfig.getServletContext();
062    
063                    File tempDir = (File)_servletContext.getAttribute(
064                            JavaConstants.JAVAX_SERVLET_CONTEXT_TEMPDIR);
065    
066                    _tempDir = new File(tempDir, _TEMP_DIR);
067    
068                    _tempDir.mkdirs();
069    
070                    DynamicCSSUtil.init(_servletContext);
071            }
072    
073            protected String getCacheFileName(HttpServletRequest request) {
074                    CacheKeyGenerator cacheKeyGenerator =
075                            CacheKeyGeneratorUtil.getCacheKeyGenerator(
076                                    DynamicCSSFilter.class.getName());
077    
078                    cacheKeyGenerator.append(HttpUtil.getProtocol(request.isSecure()));
079                    cacheKeyGenerator.append(StringPool.UNDERLINE);
080                    cacheKeyGenerator.append(request.getRequestURI());
081    
082                    String queryString = request.getQueryString();
083    
084                    if (queryString != null) {
085                            cacheKeyGenerator.append(sterilizeQueryString(queryString));
086                    }
087    
088                    if (PortalUtil.isRightToLeft(request)) {
089                            cacheKeyGenerator.append("_rtl");
090                    }
091    
092                    return String.valueOf(cacheKeyGenerator.finish());
093            }
094    
095            protected Object getDynamicContent(
096                            HttpServletRequest request, HttpServletResponse response,
097                            FilterChain filterChain)
098                    throws Exception {
099    
100                    boolean bundleResource = false;
101                    ServletContext servletContext = _servletContext;
102    
103                    String requestPath = getRequestPath(request);
104    
105                    URL resourceURL = _servletContext.getResource(requestPath);
106    
107                    if (resourceURL == null) {
108                            resourceURL = PortalWebResourcesUtil.getServletContextResource(
109                                    requestPath);
110    
111                            if (resourceURL == null) {
112                                    return null;
113                            }
114    
115                            bundleResource = true;
116                            servletContext = PortalWebResourcesUtil.getServletContext(
117                                    PortalWebResourceConstants.RESOURCE_TYPE_CSS);
118                    }
119    
120                    String cacheCommonFileName = getCacheFileName(request);
121    
122                    File cacheContentTypeFile = new File(
123                            _tempDir, cacheCommonFileName + "_E_CONTENT_TYPE");
124                    File cacheDataFile = new File(
125                            _tempDir, cacheCommonFileName + "_E_DATA");
126    
127                    if (cacheDataFile.exists() &&
128                            (cacheDataFile.lastModified() >=
129                                    URLUtil.getLastModifiedTime(resourceURL))) {
130    
131                            if (cacheContentTypeFile.exists()) {
132                                    String contentType = FileUtil.read(cacheContentTypeFile);
133    
134                                    response.setContentType(contentType);
135                            }
136    
137                            return cacheDataFile;
138                    }
139    
140                    String dynamicContent = null;
141    
142                    String content = null;
143    
144                    try {
145                            if (requestPath.endsWith(_CSS_EXTENSION)) {
146                                    if (_log.isInfoEnabled()) {
147                                            _log.info("Parsing SASS on CSS " + requestPath);
148                                    }
149    
150                                    content = StringUtil.read(resourceURL.openStream());
151    
152                                    if (bundleResource) {
153                                            dynamicContent = DynamicCSSUtil.replaceToken(
154                                                    servletContext, request, content);
155                                    }
156                                    else {
157                                            dynamicContent = DynamicCSSUtil.parseSass(
158                                                    servletContext, request, requestPath, content);
159                                    }
160    
161                                    response.setContentType(ContentTypes.TEXT_CSS);
162    
163                                    FileUtil.write(cacheContentTypeFile, ContentTypes.TEXT_CSS);
164                            }
165                            else if (requestPath.endsWith(_JSP_EXTENSION)) {
166                                    if (_log.isInfoEnabled()) {
167                                            _log.info("Parsing SASS on JSP or servlet " + requestPath);
168                                    }
169    
170                                    BufferCacheServletResponse bufferCacheServletResponse =
171                                            new BufferCacheServletResponse(response);
172    
173                                    processFilter(
174                                            DynamicCSSFilter.class, request, bufferCacheServletResponse,
175                                            filterChain);
176    
177                                    bufferCacheServletResponse.finishResponse(false);
178    
179                                    content = bufferCacheServletResponse.getString();
180    
181                                    dynamicContent = DynamicCSSUtil.parseSass(
182                                            servletContext, request, requestPath, content);
183    
184                                    FileUtil.write(
185                                            cacheContentTypeFile,
186                                            bufferCacheServletResponse.getContentType());
187                            }
188                            else {
189                                    return null;
190                            }
191                    }
192                    catch (Exception e) {
193                            _log.error("Unable to parse SASS on CSS " + requestPath, e);
194    
195                            if (_log.isDebugEnabled()) {
196                                    _log.debug(content);
197                            }
198    
199                            response.setHeader(
200                                    HttpHeaders.CACHE_CONTROL,
201                                    HttpHeaders.CACHE_CONTROL_NO_CACHE_VALUE);
202                    }
203    
204                    if (dynamicContent != null) {
205                            FileUtil.write(cacheDataFile, dynamicContent);
206                    }
207                    else {
208                            dynamicContent = content;
209                    }
210    
211                    return dynamicContent;
212            }
213    
214            protected String getRequestPath(HttpServletRequest request) {
215                    String requestPath = request.getRequestURI();
216    
217                    String contextPath = request.getContextPath();
218    
219                    if (!contextPath.equals(StringPool.SLASH)) {
220                            requestPath = requestPath.substring(contextPath.length());
221                    }
222    
223                    return requestPath;
224            }
225    
226            @Override
227            protected boolean isModuleRequest(HttpServletRequest request) {
228                    String requestURI = request.getRequestURI();
229    
230                    if (PortalWebResourcesUtil.isResourceContextPath(requestURI)) {
231                            return false;
232                    }
233    
234                    return super.isModuleRequest(request);
235            }
236    
237            @Override
238            protected void processFilter(
239                            HttpServletRequest request, HttpServletResponse response,
240                            FilterChain filterChain)
241                    throws Exception {
242    
243                    Object parsedContent = getDynamicContent(
244                            request, response, filterChain);
245    
246                    if (parsedContent == null) {
247                            processFilter(
248                                    DynamicCSSFilter.class, request, response, filterChain);
249                    }
250                    else {
251                            if (parsedContent instanceof File) {
252                                    ServletResponseUtil.write(response, (File)parsedContent);
253                            }
254                            else if (parsedContent instanceof String) {
255                                    ServletResponseUtil.write(response, (String)parsedContent);
256                            }
257                    }
258            }
259    
260            protected String sterilizeQueryString(String queryString) {
261                    return StringUtil.replace(
262                            queryString, new String[] {StringPool.SLASH, StringPool.BACK_SLASH},
263                            new String[] {StringPool.UNDERLINE, StringPool.UNDERLINE});
264            }
265    
266            private static final String _CSS_EXTENSION = ".css";
267    
268            private static final String _JSP_EXTENSION = ".jsp";
269    
270            private static final String _TEMP_DIR = "css";
271    
272            private static final Log _log = LogFactoryUtil.getLog(
273                    DynamicCSSFilter.class);
274    
275            private ServletContext _servletContext;
276            private File _tempDir;
277    
278    }