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.PortalClassLoaderUtil;
025 import com.liferay.portal.kernel.util.SessionParamUtil;
026 import com.liferay.portal.kernel.util.StringBundler;
027 import com.liferay.portal.kernel.util.StringPool;
028 import com.liferay.portal.kernel.util.StringUtil;
029 import com.liferay.portal.kernel.util.SystemProperties;
030 import com.liferay.portal.kernel.util.UnsyncPrintWriterPool;
031 import com.liferay.portal.kernel.util.Validator;
032 import com.liferay.portal.kernel.util.WebKeys;
033 import com.liferay.portal.model.PortletConstants;
034 import com.liferay.portal.model.Theme;
035 import com.liferay.portal.scripting.ruby.RubyExecutor;
036 import com.liferay.portal.service.ThemeLocalServiceUtil;
037 import com.liferay.portal.theme.ThemeDisplay;
038 import com.liferay.portal.tools.SassToCssBuilder;
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 PortalClassLoaderUtil.getClassLoader(),
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@",
156 "@theme_image_path@"
157 },
158 new String[] {
159 PortalUtil.getPathContext(),
160 _getThemeImagesPath(request, themeDisplay, theme)
161 });
162
163 return parsedContent;
164 }
165
166 private static String _getCssThemePath(
167 HttpServletRequest request, ThemeDisplay themeDisplay, Theme theme)
168 throws Exception {
169
170 String cssThemePath = null;
171
172 if (themeDisplay != null) {
173 cssThemePath = themeDisplay.getPathThemeCss();
174 }
175 else {
176 String cdnHost = PortalUtil.getCDNHost(request);
177 String themeStaticResourcePath = theme.getStaticResourcePath();
178
179 cssThemePath =
180 cdnHost + themeStaticResourcePath + theme.getCssPath();
181 }
182
183 return cssThemePath;
184 }
185
186 private static Theme _getTheme(
187 HttpServletRequest request, String cssRealPath) {
188
189 long companyId = PortalUtil.getCompanyId(request);
190
191 String themeId = ParamUtil.getString(request, "themeId");
192
193 Matcher portalThemeMatcher = _portalThemePattern.matcher(cssRealPath);
194
195 if (portalThemeMatcher.find()) {
196 String themePathId = portalThemeMatcher.group(1);
197
198 themePathId = StringUtil.replace(
199 themePathId, StringPool.UNDERLINE, StringPool.BLANK);
200
201 themeId = PortalUtil.getJsSafePortletId(themePathId);
202 }
203 else {
204 Matcher pluginThemeMatcher = _pluginThemePattern.matcher(
205 cssRealPath);
206
207 if (pluginThemeMatcher.find()) {
208 String themePathId = pluginThemeMatcher.group(1);
209
210 themePathId = StringUtil.replace(
211 themePathId, StringPool.UNDERLINE, StringPool.BLANK);
212
213 StringBundler sb = new StringBundler(4);
214
215 sb.append(themePathId);
216 sb.append(PortletConstants.WAR_SEPARATOR);
217 sb.append(themePathId);
218 sb.append("theme");
219
220 themePathId = sb.toString();
221
222 themeId = PortalUtil.getJsSafePortletId(themePathId);
223 }
224 }
225
226 if (Validator.isNull(themeId)) {
227 return null;
228 }
229
230 try {
231 Theme theme = ThemeLocalServiceUtil.getTheme(
232 companyId, themeId, false);
233
234 return theme;
235 }
236 catch (Exception e) {
237 _log.error(e, e);
238 }
239
240 return null;
241 }
242
243 private static String _getThemeImagesPath(
244 HttpServletRequest request, ThemeDisplay themeDisplay, Theme theme)
245 throws Exception {
246
247 String themeImagesPath = null;
248
249 if (themeDisplay != null) {
250 themeImagesPath = themeDisplay.getPathThemeImages();
251 }
252 else {
253 String cdnHost = PortalUtil.getCDNHost(request);
254 String themeStaticResourcePath = theme.getStaticResourcePath();
255
256 themeImagesPath =
257 cdnHost + themeStaticResourcePath + theme.getImagesPath();
258 }
259
260 return themeImagesPath;
261 }
262
263 private static boolean _isThemeCssFastLoad(
264 HttpServletRequest request, ThemeDisplay themeDisplay) {
265
266 if (themeDisplay != null) {
267 return themeDisplay.isThemeCssFastLoad();
268 }
269
270 return SessionParamUtil.getBoolean(
271 request, "css_fast_load", PropsValues.THEME_CSS_FAST_LOAD);
272 }
273
274 private static String _parseSass(
275 HttpServletRequest request, ThemeDisplay themeDisplay, Theme theme,
276 String cssRealPath, String content)
277 throws Exception {
278
279 Map<String, Object> inputObjects = new HashMap<String, Object>();
280
281 inputObjects.put("content", content);
282 inputObjects.put("cssRealPath", cssRealPath);
283 inputObjects.put(
284 "cssThemePath", _getCssThemePath(request, themeDisplay, theme));
285 inputObjects.put("sassCachePath", _SASS_DIR);
286
287 UnsyncByteArrayOutputStream unsyncByteArrayOutputStream =
288 new UnsyncByteArrayOutputStream();
289
290 UnsyncPrintWriter unsyncPrintWriter = UnsyncPrintWriterPool.borrow(
291 unsyncByteArrayOutputStream);
292
293 inputObjects.put("out", unsyncPrintWriter);
294
295 _rubyExecutor.eval(null, inputObjects, null, _rubyScript);
296
297 unsyncPrintWriter.flush();
298
299 return unsyncByteArrayOutputStream.toString();
300 }
301
302
305 private static String _propagateQueryString(
306 String content, String queryString) {
307
308 StringBuilder sb = new StringBuilder(content.length());
309
310 int pos = 0;
311
312 while (true) {
313 int importX = content.indexOf(_CSS_IMPORT_BEGIN, pos);
314 int importY = content.indexOf(
315 _CSS_IMPORT_END, importX + _CSS_IMPORT_BEGIN.length());
316
317 if ((importX == -1) || (importY == -1)) {
318 sb.append(content.substring(pos, content.length()));
319
320 break;
321 }
322 else {
323 sb.append(content.substring(pos, importY));
324 sb.append(CharPool.QUESTION);
325 sb.append(queryString);
326 sb.append(_CSS_IMPORT_END);
327
328 pos = importY + _CSS_IMPORT_END.length();
329 }
330 }
331
332 return sb.toString();
333 }
334
335 private static final String _CSS_IMPORT_BEGIN = "@import url(";
336
337 private static final String _CSS_IMPORT_END = ");";
338
339 private static final String _SASS_DIR =
340 SystemProperties.get(SystemProperties.TMP_DIR) + "/liferay/sass";
341
342 private static Log _log = LogFactoryUtil.getLog(DynamicCSSUtil.class);
343
344 private static Pattern _pluginThemePattern =
345 Pattern.compile("\\/([^\\/]+)-theme\\/", Pattern.CASE_INSENSITIVE);
346 private static Pattern _portalThemePattern =
347 Pattern.compile("themes\\/([^\\/]+)\\/css", Pattern.CASE_INSENSITIVE);
348 private static RubyExecutor _rubyExecutor = new RubyExecutor();
349 private static String _rubyScript;
350
351 }