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.security.pacl.PACLClassLoaderUtil;
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(
088                            PACLClassLoaderUtil.getPortalClassLoader());
089    
090                    _basePath = PortalUtil.getPortalLibDir();
091    
092                    _loadPaths = new ArrayList<String>(
093                            PropsValues.SCRIPTING_JRUBY_LOAD_PATHS.length);
094    
095                    for (String gemLibPath : PropsValues.SCRIPTING_JRUBY_LOAD_PATHS) {
096                            _loadPaths.add(gemLibPath);
097                    }
098    
099                    rubyInstanceConfig.setLoadPaths(_loadPaths);
100    
101                    _scriptingContainer.setCurrentDirectory(_basePath);
102            }
103    
104            @Override
105            public Map<String, Object> eval(
106                            Set<String> allowedClasses, Map<String, Object> inputObjects,
107                            Set<String> outputNames, File scriptFile,
108                            ClassLoader... classLoaders)
109                    throws ScriptingException {
110    
111                    return eval(
112                            allowedClasses, inputObjects, outputNames, scriptFile, null,
113                            classLoaders);
114            }
115    
116            public Map<String, Object> eval(
117                            Set<String> allowedClasses, Map<String, Object> inputObjects,
118                            Set<String> outputNames, String script, ClassLoader... classLoaders)
119                    throws ScriptingException {
120    
121                    return eval(
122                            allowedClasses, inputObjects, outputNames, null, script,
123                            classLoaders);
124            }
125    
126            public String getLanguage() {
127                    return LANGUAGE;
128            }
129    
130            protected Map<String, Object> eval(
131                            Set<String> allowedClasses, Map<String, Object> inputObjects,
132                            Set<String> outputNames, File scriptFile, String script,
133                            ClassLoader... classLoaders)
134                    throws ScriptingException {
135    
136                    if (allowedClasses != null) {
137                            throw new ExecutionException(
138                                    "Constrained execution not supported for Ruby");
139                    }
140    
141                    try {
142                            LocalContextProvider localContextProvider =
143                                    _scriptingContainer.getProvider();
144    
145                            RubyInstanceConfig rubyInstanceConfig =
146                                    localContextProvider.getRubyInstanceConfig();
147    
148                            rubyInstanceConfig.setCurrentDirectory(_basePath);
149    
150                            if ((classLoaders != null) && (classLoaders.length > 0)) {
151                                    ClassLoader aggregateClassLoader =
152                                            AggregateClassLoader.getAggregateClassLoader(
153                                                    PACLClassLoaderUtil.getPortalClassLoader(),
154                                                    classLoaders);
155    
156                                    rubyInstanceConfig.setLoader(aggregateClassLoader);
157                            }
158    
159                            rubyInstanceConfig.setLoadPaths(_loadPaths);
160    
161                            for (Map.Entry<String, Object> entry : inputObjects.entrySet()) {
162                                    String inputName = entry.getKey();
163                                    Object inputObject = entry.getValue();
164    
165                                    if (!inputName.startsWith(StringPool.DOLLAR)) {
166                                            inputName = StringPool.DOLLAR + inputName;
167                                    }
168    
169                                    _scriptingContainer.put(inputName, inputObject);
170                            }
171    
172                            if (scriptFile != null) {
173                                    _scriptingContainer.runScriptlet(
174                                            new FileInputStream(scriptFile), scriptFile.toString());
175                            }
176                            else {
177                                    _scriptingContainer.runScriptlet(script);
178                            }
179    
180                            if (outputNames == null) {
181                                    return null;
182                            }
183    
184                            Map<String, Object> outputObjects = new HashMap<String, Object>();
185    
186                            for (String outputName : outputNames) {
187                                    outputObjects.put(
188                                            outputName, _scriptingContainer.get(outputName));
189                            }
190    
191                            return outputObjects;
192                    }
193                    catch (RaiseException re) {
194                            throw new ScriptingException(
195                                    re.getException().message.asJavaString() + "\n\n", re);
196                    }
197                    catch (FileNotFoundException fnfe) {
198                            throw new ScriptingException(fnfe);
199                    }
200            }
201    
202            protected void initRubyGems() throws Exception {
203                    File rubyGemsJarFile = new File(
204                            PropsValues.LIFERAY_LIB_PORTAL_DIR, "ruby-gems.jar");
205    
206                    if (!rubyGemsJarFile.exists()) {
207                            if (_log.isWarnEnabled()) {
208                                    _log.warn(rubyGemsJarFile + " does not exist");
209                            }
210    
211                            return;
212                    }
213    
214                    String tmpDir = SystemProperties.get(SystemProperties.TMP_DIR);
215    
216                    File rubyDir = new File(tmpDir + "/liferay/ruby");
217    
218                    if (!rubyDir.exists() ||
219                            (rubyDir.lastModified() < rubyGemsJarFile.lastModified())) {
220    
221                            FileUtil.deltree(rubyDir);
222    
223                            rubyDir.mkdirs();
224    
225                            ZipUtil.unzip(rubyGemsJarFile, rubyDir);
226    
227                            rubyDir.setLastModified(rubyGemsJarFile.lastModified());
228                    }
229            }
230    
231            private static final String _COMPILE_MODE_FORCE = "force";
232    
233            private static final String _COMPILE_MODE_JIT = "jit";
234    
235            private static Log _log = LogFactoryUtil.getLog(RubyExecutor.class);
236    
237            private String _basePath;
238            private List<String> _loadPaths;
239            private ScriptingContainer _scriptingContainer;
240    
241    }