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.tools.sass;
016    
017    import com.liferay.portal.kernel.util.NamedThreadFactory;
018    import com.liferay.portal.kernel.util.StringUtil;
019    import com.liferay.portal.kernel.util.SystemProperties;
020    import com.liferay.portal.scripting.ruby.RubyExecutor;
021    
022    import java.io.IOException;
023    
024    import java.util.concurrent.ConcurrentHashMap;
025    import java.util.concurrent.ConcurrentMap;
026    import java.util.concurrent.ExecutorService;
027    import java.util.concurrent.Executors;
028    import java.util.concurrent.TimeUnit;
029    
030    import org.jruby.RubyArray;
031    import org.jruby.RubyException;
032    import org.jruby.embed.ScriptingContainer;
033    import org.jruby.exceptions.RaiseException;
034    import org.jruby.runtime.builtin.IRubyObject;
035    
036    /**
037     * @author Minhchau Dang
038     * @author Shuyang Zhou
039     */
040    public class SassExecutorUtil {
041    
042            public static SassFile execute(String docrootDirName, String fileName) {
043                    SassFile sassFile = _sassFileCache.get(fileName);
044    
045                    if (sassFile != null) {
046                            return sassFile;
047                    }
048    
049                    sassFile = new SassFile(docrootDirName, fileName);
050    
051                    SassFile previousSassFile = _sassFileCache.putIfAbsent(
052                            fileName, sassFile);
053    
054                    if (previousSassFile != null) {
055                            sassFile = previousSassFile;
056                    }
057                    else {
058                            _executorService.submit(sassFile);
059                    }
060    
061                    return sassFile;
062            }
063    
064            public static void init(String docrootDirName, String portalCommonDirName)
065                    throws IOException {
066    
067                    _executorService = Executors.newFixedThreadPool(
068                            2,
069                            new NamedThreadFactory(
070                                    "SassExecutor", Thread.NORM_PRIORITY,
071                                    SassExecutorUtil.class.getClassLoader()));
072    
073                    _mainThread = Thread.currentThread();
074    
075                    _docrootDirName = docrootDirName;
076                    _portalCommonDirName = portalCommonDirName;
077    
078                    RubyExecutor rubyExecutor = new RubyExecutor();
079    
080                    rubyExecutor.setExecuteInSeparateThread(false);
081    
082                    _scriptingContainer = rubyExecutor.getScriptingContainer();
083    
084                    String rubyScript = StringUtil.read(
085                            SassExecutorUtil.class.getClassLoader(),
086                            "com/liferay/portal/servlet/filters/dynamiccss" +
087                                    "/dependencies/main.rb");
088    
089                    _scriptObject = _scriptingContainer.runScriptlet(rubyScript);
090            }
091    
092            public static String parse(String fileName, String content) {
093                    String filePath = _docrootDirName.concat(fileName);
094    
095                    String cssThemePath = filePath;
096    
097                    int pos = filePath.lastIndexOf("/css/");
098    
099                    if (pos >= 0) {
100                            cssThemePath = filePath.substring(0, pos + 4);
101                    }
102    
103                    try {
104                            return _scriptingContainer.callMethod(
105                                    _scriptObject, "process",
106                                    new Object[] {
107                                            content, _portalCommonDirName, filePath, cssThemePath,
108                                            _TMP_DIR, false
109                                    },
110                                    String.class);
111                    }
112                    catch (Exception e) {
113                            if (e instanceof RaiseException) {
114                                    RaiseException raiseException = (RaiseException)e;
115    
116                                    RubyException rubyException = raiseException.getException();
117    
118                                    System.err.println(
119                                            String.valueOf(rubyException.message.toJava(String.class)));
120    
121                                    IRubyObject iRubyObject = rubyException.getBacktrace();
122    
123                                    RubyArray rubyArray = (RubyArray)iRubyObject.toJava(
124                                            RubyArray.class);
125    
126                                    for (int i = 0; i < rubyArray.size(); i++) {
127                                            Object object = rubyArray.get(i);
128    
129                                            System.err.println(String.valueOf(object));
130                                    }
131                            }
132                            else {
133                                    e.printStackTrace();
134                            }
135    
136                            _exception = new Exception("Unable to parse " + fileName, e);
137    
138                            _mainThread.interrupt();
139                    }
140    
141                    return content;
142            }
143    
144            public static void persist() throws Exception {
145                    _executorService.shutdown();
146    
147                    try {
148                            if (!_executorService.awaitTermination(
149                                            _WAIT_MINTUES, TimeUnit.MINUTES)) {
150    
151                                    System.err.println(
152                                            "Abort processing Sass files after waiting " +
153                                                    _WAIT_MINTUES + " minutes");
154                            }
155                    }
156                    catch (InterruptedException ie) {
157                            if (_exception == null) {
158                                    throw ie;
159                            }
160    
161                            throw _exception;
162                    }
163    
164                    for (SassFile sassFile : _sassFileCache.values()) {
165                            sassFile.writeCacheFiles();
166    
167                            System.out.println(sassFile);
168                    }
169            }
170    
171            private static final String _TMP_DIR = SystemProperties.get(
172                    SystemProperties.TMP_DIR);
173    
174            private static final int _WAIT_MINTUES = 30;
175    
176            private static String _docrootDirName;
177            private static Exception _exception;
178            private static ExecutorService _executorService;
179            private static Thread _mainThread;
180            private static String _portalCommonDirName;
181            private static ConcurrentMap<String, SassFile> _sassFileCache =
182                    new ConcurrentHashMap<String, SassFile>();
183            private static ScriptingContainer _scriptingContainer;
184            private static Object _scriptObject;
185    
186    }