001    /**
002     * Copyright (c) 2000-2012 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.scripting.ruby;
016    
017    import com.liferay.portal.kernel.log.Log;
018    import com.liferay.portal.kernel.log.LogFactoryUtil;
019    import com.liferay.portal.kernel.scripting.BaseScriptingExecutor;
020    import com.liferay.portal.kernel.scripting.ExecutionException;
021    import com.liferay.portal.kernel.scripting.ScriptingException;
022    import com.liferay.portal.kernel.util.AggregateClassLoader;
023    import com.liferay.portal.kernel.util.FileUtil;
024    import com.liferay.portal.kernel.util.StringPool;
025    import com.liferay.portal.kernel.util.SystemProperties;
026    import com.liferay.portal.util.ClassLoaderUtil;
027    import com.liferay.portal.util.PortalUtil;
028    import com.liferay.portal.util.PropsValues;
029    
030    import java.io.File;
031    import java.io.FileInputStream;
032    import java.io.FileNotFoundException;
033    
034    import java.util.ArrayList;
035    import java.util.HashMap;
036    import java.util.List;
037    import java.util.Map;
038    import java.util.Set;
039    
040    import jodd.io.ZipUtil;
041    
042    import org.jruby.RubyInstanceConfig.CompileMode;
043    import org.jruby.RubyInstanceConfig;
044    import org.jruby.embed.LocalContextScope;
045    import org.jruby.embed.ScriptingContainer;
046    import org.jruby.embed.internal.LocalContextProvider;
047    import org.jruby.exceptions.RaiseException;
048    
049    /**
050     * @author Alberto Montero
051     * @author Raymond Augé
052     */
053    public class RubyExecutor extends BaseScriptingExecutor {
054    
055            public static final String LANGUAGE = "ruby";
056    
057            public RubyExecutor() {
058                    try {
059                            initRubyGems();
060                    }
061                    catch (Exception e) {
062                            _log.error(e, e);
063                    }
064    
065                    _scriptingContainer = new ScriptingContainer(
066                            LocalContextScope.THREADSAFE);
067    
068                    LocalContextProvider localContextProvider =
069                            _scriptingContainer.getProvider();
070    
071                    RubyInstanceConfig rubyInstanceConfig =
072                            localContextProvider.getRubyInstanceConfig();
073    
074                    if (PropsValues.SCRIPTING_JRUBY_COMPILE_MODE.equals(
075                                    _COMPILE_MODE_FORCE)) {
076    
077                            rubyInstanceConfig.setCompileMode(CompileMode.FORCE);
078                    }
079                    else if (PropsValues.SCRIPTING_JRUBY_COMPILE_MODE.equals(
080                                            _COMPILE_MODE_JIT)) {
081    
082                            rubyInstanceConfig.setCompileMode(CompileMode.JIT);
083                    }
084    
085                    rubyInstanceConfig.setJitThreshold(
086                            PropsValues.SCRIPTING_JRUBY_COMPILE_THRESHOLD);
087                    rubyInstanceConfig.setLoader(ClassLoaderUtil.getPortalClassLoader());
088    
089                    _basePath = PortalUtil.getPortalLibDir();
090    
091                    _loadPaths = new ArrayList<String>(
092                            PropsValues.SCRIPTING_JRUBY_LOAD_PATHS.length);
093    
094                    for (String gemLibPath : PropsValues.SCRIPTING_JRUBY_LOAD_PATHS) {
095                            _loadPaths.add(gemLibPath);
096                    }
097    
098                    rubyInstanceConfig.setLoadPaths(_loadPaths);
099    
100                    _scriptingContainer.setCurrentDirectory(_basePath);
101            }
102    
103            @Override
104            public Map<String, Object> eval(
105                            Set<String> allowedClasses, Map<String, Object> inputObjects,
106                            Set<String> outputNames, File scriptFile,
107                            ClassLoader... classLoaders)
108                    throws ScriptingException {
109    
110                    return eval(
111                            allowedClasses, inputObjects, outputNames, scriptFile, null,
112                            classLoaders);
113            }
114    
115            public Map<String, Object> eval(
116                            Set<String> allowedClasses, Map<String, Object> inputObjects,
117                            Set<String> outputNames, String script, ClassLoader... classLoaders)
118                    throws ScriptingException {
119    
120                    return eval(
121                            allowedClasses, inputObjects, outputNames, null, script,
122                            classLoaders);
123            }
124    
125            public String getLanguage() {
126                    return LANGUAGE;
127            }
128    
129            protected Map<String, Object> eval(
130                            Set<String> allowedClasses, Map<String, Object> inputObjects,
131                            Set<String> outputNames, File scriptFile, String script,
132                            ClassLoader... classLoaders)
133                    throws ScriptingException {
134    
135                    if (allowedClasses != null) {
136                            throw new ExecutionException(
137                                    "Constrained execution not supported for Ruby");
138                    }
139    
140                    try {
141                            LocalContextProvider localContextProvider =
142                                    _scriptingContainer.getProvider();
143    
144                            RubyInstanceConfig rubyInstanceConfig =
145                                    localContextProvider.getRubyInstanceConfig();
146    
147                            rubyInstanceConfig.setCurrentDirectory(_basePath);
148    
149                            if ((classLoaders != null) && (classLoaders.length > 0)) {
150                                    ClassLoader aggregateClassLoader =
151                                            AggregateClassLoader.getAggregateClassLoader(
152                                                    ClassLoaderUtil.getPortalClassLoader(), classLoaders);
153    
154                                    rubyInstanceConfig.setLoader(aggregateClassLoader);
155                            }
156    
157                            rubyInstanceConfig.setLoadPaths(_loadPaths);
158    
159                            for (Map.Entry<String, Object> entry : inputObjects.entrySet()) {
160                                    String inputName = entry.getKey();
161                                    Object inputObject = entry.getValue();
162    
163                                    if (!inputName.startsWith(StringPool.DOLLAR)) {
164                                            inputName = StringPool.DOLLAR + inputName;
165                                    }
166    
167                                    _scriptingContainer.put(inputName, inputObject);
168                            }
169    
170                            if (scriptFile != null) {
171                                    _scriptingContainer.runScriptlet(
172                                            new FileInputStream(scriptFile), scriptFile.toString());
173                            }
174                            else {
175                                    _scriptingContainer.runScriptlet(script);
176                            }
177    
178                            if (outputNames == null) {
179                                    return null;
180                            }
181    
182                            Map<String, Object> outputObjects = new HashMap<String, Object>();
183    
184                            for (String outputName : outputNames) {
185                                    outputObjects.put(
186                                            outputName, _scriptingContainer.get(outputName));
187                            }
188    
189                            return outputObjects;
190                    }
191                    catch (RaiseException re) {
192                            throw new ScriptingException(
193                                    re.getException().message.asJavaString() + "\n\n", re);
194                    }
195                    catch (FileNotFoundException fnfe) {
196                            throw new ScriptingException(fnfe);
197                    }
198            }
199    
200            protected void initRubyGems() throws Exception {
201                    File rubyGemsJarFile = new File(
202                            PropsValues.LIFERAY_LIB_PORTAL_DIR, "ruby-gems.jar");
203    
204                    if (!rubyGemsJarFile.exists()) {
205                            if (_log.isWarnEnabled()) {
206                                    _log.warn(rubyGemsJarFile + " does not exist");
207                            }
208    
209                            return;
210                    }
211    
212                    String tmpDir = SystemProperties.get(SystemProperties.TMP_DIR);
213    
214                    File rubyDir = new File(tmpDir + "/liferay/ruby");
215    
216                    if (!rubyDir.exists() ||
217                            (rubyDir.lastModified() < rubyGemsJarFile.lastModified())) {
218    
219                            FileUtil.deltree(rubyDir);
220    
221                            rubyDir.mkdirs();
222    
223                            ZipUtil.unzip(rubyGemsJarFile, rubyDir);
224    
225                            rubyDir.setLastModified(rubyGemsJarFile.lastModified());
226                    }
227            }
228    
229            private static final String _COMPILE_MODE_FORCE = "force";
230    
231            private static final String _COMPILE_MODE_JIT = "jit";
232    
233            private static Log _log = LogFactoryUtil.getLog(RubyExecutor.class);
234    
235            private String _basePath;
236            private List<String> _loadPaths;
237            private ScriptingContainer _scriptingContainer;
238    
239    }