001
014
015 package com.liferay.portal.servlet.filters.dynamiccss;
016
017 import com.liferay.portal.kernel.io.unsync.UnsyncByteArrayOutputStream;
018 import com.liferay.portal.kernel.io.unsync.UnsyncPrintWriter;
019 import com.liferay.portal.kernel.log.Log;
020 import com.liferay.portal.kernel.log.LogFactoryUtil;
021 import com.liferay.portal.kernel.util.CharPool;
022 import com.liferay.portal.kernel.util.FileUtil;
023 import com.liferay.portal.kernel.util.ParamUtil;
024 import com.liferay.portal.kernel.util.SessionParamUtil;
025 import com.liferay.portal.kernel.util.StringBundler;
026 import com.liferay.portal.kernel.util.StringPool;
027 import com.liferay.portal.kernel.util.StringUtil;
028 import com.liferay.portal.kernel.util.SystemProperties;
029 import com.liferay.portal.kernel.util.UnsyncPrintWriterPool;
030 import com.liferay.portal.kernel.util.Validator;
031 import com.liferay.portal.kernel.util.WebKeys;
032 import com.liferay.portal.model.PortletConstants;
033 import com.liferay.portal.model.Theme;
034 import com.liferay.portal.scripting.ruby.RubyExecutor;
035 import com.liferay.portal.service.ThemeLocalServiceUtil;
036 import com.liferay.portal.theme.ThemeDisplay;
037 import com.liferay.portal.tools.SassToCssBuilder;
038 import com.liferay.portal.util.ClassLoaderUtil;
039 import com.liferay.portal.util.PortalUtil;
040 import com.liferay.portal.util.PropsValues;
041
042 import java.io.File;
043
044 import java.util.HashMap;
045 import java.util.Map;
046 import java.util.regex.Matcher;
047 import java.util.regex.Pattern;
048
049 import javax.servlet.http.HttpServletRequest;
050
051 import org.apache.commons.lang.time.StopWatch;
052
053
057 public class DynamicCSSUtil {
058
059 public static void init() {
060 try {
061 _rubyScript = StringUtil.read(
062 ClassLoaderUtil.getPortalClassLoader(),
063 "com/liferay/portal/servlet/filters/dynamiccss/main.rb");
064 }
065 catch (Exception e) {
066 _log.error(e, e);
067 }
068 }
069
070 public static String parseSass(
071 HttpServletRequest request, String cssRealPath, String content)
072 throws Exception {
073
074 if (!DynamicCSSFilter.ENABLED) {
075 return content;
076 }
077
078 StopWatch stopWatch = null;
079
080 if (_log.isDebugEnabled()) {
081 stopWatch = new StopWatch();
082
083 stopWatch.start();
084 }
085
086
087
088 if (request == null) {
089 return content;
090 }
091
092 ThemeDisplay themeDisplay = (ThemeDisplay)request.getAttribute(
093 WebKeys.THEME_DISPLAY);
094
095 Theme theme = null;
096
097 if (themeDisplay == null) {
098 theme = _getTheme(request, cssRealPath);
099
100 if (theme == null) {
101 String currentURL = PortalUtil.getCurrentURL(request);
102
103 if (_log.isWarnEnabled()) {
104 _log.warn("No theme found for " + currentURL);
105 }
106
107 return content;
108 }
109 }
110
111 String parsedContent = null;
112
113 boolean themeCssFastLoad = _isThemeCssFastLoad(request, themeDisplay);
114
115 File cssRealFile = new File(cssRealPath);
116 File cacheCssRealFile = SassToCssBuilder.getCacheFile(cssRealPath);
117
118 if (themeCssFastLoad && cacheCssRealFile.exists() &&
119 (cacheCssRealFile.lastModified() == cssRealFile.lastModified())) {
120
121 parsedContent = FileUtil.read(cacheCssRealFile);
122
123 if (_log.isDebugEnabled()) {
124 _log.debug(
125 "Loading SASS cache from " + cacheCssRealFile + " takes " +
126 stopWatch.getTime() + " ms");
127 }
128 }
129 else {
130 content = SassToCssBuilder.parseStaticTokens(content);
131
132 String queryString = request.getQueryString();
133
134 if (!themeCssFastLoad && Validator.isNotNull(queryString)) {
135 content = _propagateQueryString(content, queryString);
136 }
137
138 parsedContent = _parseSass(
139 request, themeDisplay, theme, cssRealPath, content);
140
141 if (_log.isDebugEnabled()) {
142 _log.debug(
143 "Parsing SASS for " + cssRealPath + " takes " +
144 stopWatch.getTime() + " ms");
145 }
146 }
147
148 if (Validator.isNull(parsedContent)) {
149 return content;
150 }
151
152 parsedContent = StringUtil.replace(
153 parsedContent,
154 new String[] {
155 "@portal_ctx@", "@theme_image_path@"
156 },
157 new String[] {
158 PortalUtil.getPathContext(),
159 _getThemeImagesPath(request, themeDisplay, theme)
160 });
161
162 return parsedContent;
163 }
164
165 private static String _getCssThemePath(
166 HttpServletRequest request, ThemeDisplay themeDisplay, Theme theme)
167 throws Exception {
168
169 String cssThemePath = null;
170
171 if (themeDisplay != null) {
172 cssThemePath = themeDisplay.getPathThemeCss();
173 }
174 else {
175 String cdnHost = StringPool.BLANK;
176
177 if (PortalUtil.isCDNDynamicResourcesEnabled(request)) {
178 cdnHost = PortalUtil.getCDNHost(request);
179 }
180
181 String themeStaticResourcePath = theme.getStaticResourcePath();
182
183 cssThemePath =
184 cdnHost + themeStaticResourcePath + theme.getCssPath();
185 }
186
187 return cssThemePath;
188 }
189
190 private static Theme _getTheme(
191 HttpServletRequest request, String cssRealPath) {
192
193 long companyId = PortalUtil.getCompanyId(request);
194
195 String themeId = ParamUtil.getString(request, "themeId");
196
197 Matcher portalThemeMatcher = _portalThemePattern.matcher(cssRealPath);
198
199 if (portalThemeMatcher.find()) {
200 String themePathId = portalThemeMatcher.group(1);
201
202 themePathId = StringUtil.replace(
203 themePathId, StringPool.UNDERLINE, StringPool.BLANK);
204
205 themeId = PortalUtil.getJsSafePortletId(themePathId);
206 }
207 else {
208 Matcher pluginThemeMatcher = _pluginThemePattern.matcher(
209 cssRealPath);
210
211 if (pluginThemeMatcher.find()) {
212 String themePathId = pluginThemeMatcher.group(1);
213
214 themePathId = StringUtil.replace(
215 themePathId, StringPool.UNDERLINE, StringPool.BLANK);
216
217 StringBundler sb = new StringBundler(4);
218
219 sb.append(themePathId);
220 sb.append(PortletConstants.WAR_SEPARATOR);
221 sb.append(themePathId);
222 sb.append("theme");
223
224 themePathId = sb.toString();
225
226 themeId = PortalUtil.getJsSafePortletId(themePathId);
227 }
228 }
229
230 if (Validator.isNull(themeId)) {
231 return null;
232 }
233
234 try {
235 Theme theme = ThemeLocalServiceUtil.getTheme(
236 companyId, themeId, false);
237
238 return theme;
239 }
240 catch (Exception e) {
241 _log.error(e, e);
242 }
243
244 return null;
245 }
246
247 private static String _getThemeImagesPath(
248 HttpServletRequest request, ThemeDisplay themeDisplay, Theme theme)
249 throws Exception {
250
251 String themeImagesPath = null;
252
253 if (themeDisplay != null) {
254 themeImagesPath = themeDisplay.getPathThemeImages();
255 }
256 else {
257 String cdnHost = PortalUtil.getCDNHost(request);
258 String themeStaticResourcePath = theme.getStaticResourcePath();
259
260 themeImagesPath =
261 cdnHost + themeStaticResourcePath + theme.getImagesPath();
262 }
263
264 return themeImagesPath;
265 }
266
267 private static boolean _isThemeCssFastLoad(
268 HttpServletRequest request, ThemeDisplay themeDisplay) {
269
270 if (themeDisplay != null) {
271 return themeDisplay.isThemeCssFastLoad();
272 }
273
274 return SessionParamUtil.getBoolean(
275 request, "css_fast_load", PropsValues.THEME_CSS_FAST_LOAD);
276 }
277
278 private static String _parseSass(
279 HttpServletRequest request, ThemeDisplay themeDisplay, Theme theme,
280 String cssRealPath, String content)
281 throws Exception {
282
283 Map<String, Object> inputObjects = new HashMap<String, Object>();
284
285 inputObjects.put("content", content);
286 inputObjects.put("cssRealPath", cssRealPath);
287 inputObjects.put(
288 "cssThemePath", _getCssThemePath(request, themeDisplay, theme));
289 inputObjects.put("sassCachePath", _SASS_DIR);
290
291 UnsyncByteArrayOutputStream unsyncByteArrayOutputStream =
292 new UnsyncByteArrayOutputStream();
293
294 UnsyncPrintWriter unsyncPrintWriter = UnsyncPrintWriterPool.borrow(
295 unsyncByteArrayOutputStream);
296
297 inputObjects.put("out", unsyncPrintWriter);
298
299 _rubyExecutor.eval(null, inputObjects, null, _rubyScript);
300
301 unsyncPrintWriter.flush();
302
303 return unsyncByteArrayOutputStream.toString();
304 }
305
306
309 private static String _propagateQueryString(
310 String content, String queryString) {
311
312 StringBuilder sb = new StringBuilder(content.length());
313
314 int pos = 0;
315
316 while (true) {
317 int importX = content.indexOf(_CSS_IMPORT_BEGIN, pos);
318 int importY = content.indexOf(
319 _CSS_IMPORT_END, importX + _CSS_IMPORT_BEGIN.length());
320
321 if ((importX == -1) || (importY == -1)) {
322 sb.append(content.substring(pos));
323
324 break;
325 }
326 else {
327 sb.append(content.substring(pos, importY));
328 sb.append(CharPool.QUESTION);
329 sb.append(queryString);
330 sb.append(_CSS_IMPORT_END);
331
332 pos = importY + _CSS_IMPORT_END.length();
333 }
334 }
335
336 return sb.toString();
337 }
338
339 private static final String _CSS_IMPORT_BEGIN = "@import url(";
340
341 private static final String _CSS_IMPORT_END = ");";
342
343 private static final String _SASS_DIR =
344 SystemProperties.get(SystemProperties.TMP_DIR) + "/liferay/sass";
345
346 private static Log _log = LogFactoryUtil.getLog(DynamicCSSUtil.class);
347
348 private static Pattern _pluginThemePattern = Pattern.compile(
349 "\\/([^\\/]+)-theme\\/", Pattern.CASE_INSENSITIVE);
350 private static Pattern _portalThemePattern = Pattern.compile(
351 "themes\\/([^\\/]+)\\/css", Pattern.CASE_INSENSITIVE);
352 private static RubyExecutor _rubyExecutor = new RubyExecutor();
353 private static String _rubyScript;
354
355 }