001
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.NamedThreadFactory;
025 import com.liferay.portal.kernel.util.ReflectionUtil;
026 import com.liferay.portal.kernel.util.StringPool;
027 import com.liferay.portal.kernel.util.SystemProperties;
028 import com.liferay.portal.util.ClassLoaderUtil;
029 import com.liferay.portal.util.PortalUtil;
030 import com.liferay.portal.util.PropsValues;
031
032 import java.io.File;
033 import java.io.FileInputStream;
034 import java.io.FileNotFoundException;
035
036 import java.lang.reflect.Field;
037
038 import java.util.ArrayList;
039 import java.util.HashMap;
040 import java.util.List;
041 import java.util.Map;
042 import java.util.Set;
043 import java.util.concurrent.Callable;
044 import java.util.concurrent.FutureTask;
045 import java.util.concurrent.ThreadFactory;
046
047 import jodd.io.ZipUtil;
048
049 import org.jruby.Ruby;
050 import org.jruby.RubyInstanceConfig.CompileMode;
051 import org.jruby.RubyInstanceConfig;
052 import org.jruby.embed.LocalContextScope;
053 import org.jruby.embed.ScriptingContainer;
054 import org.jruby.embed.internal.LocalContextProvider;
055 import org.jruby.exceptions.RaiseException;
056
057
061 public class RubyExecutor extends BaseScriptingExecutor {
062
063 public static final String LANGUAGE = "ruby";
064
065 public RubyExecutor() {
066 try {
067 initRubyGems();
068 }
069 catch (Exception e) {
070 _log.error(e, e);
071 }
072
073 _scriptingContainer = new ScriptingContainer(
074 LocalContextScope.THREADSAFE);
075
076 LocalContextProvider localContextProvider =
077 _scriptingContainer.getProvider();
078
079 RubyInstanceConfig rubyInstanceConfig =
080 localContextProvider.getRubyInstanceConfig();
081
082 if (PropsValues.SCRIPTING_JRUBY_COMPILE_MODE.equals(
083 _COMPILE_MODE_FORCE)) {
084
085 rubyInstanceConfig.setCompileMode(CompileMode.FORCE);
086 }
087 else if (PropsValues.SCRIPTING_JRUBY_COMPILE_MODE.equals(
088 _COMPILE_MODE_JIT)) {
089
090 rubyInstanceConfig.setCompileMode(CompileMode.JIT);
091 }
092
093 rubyInstanceConfig.setJitThreshold(
094 PropsValues.SCRIPTING_JRUBY_COMPILE_THRESHOLD);
095 rubyInstanceConfig.setLoader(ClassLoaderUtil.getPortalClassLoader());
096
097 _basePath = PortalUtil.getPortalLibDir();
098
099 _loadPaths = new ArrayList<String>(
100 PropsValues.SCRIPTING_JRUBY_LOAD_PATHS.length);
101
102 for (String gemLibPath : PropsValues.SCRIPTING_JRUBY_LOAD_PATHS) {
103 _loadPaths.add(gemLibPath);
104 }
105
106 rubyInstanceConfig.setLoadPaths(_loadPaths);
107
108 _scriptingContainer.setCurrentDirectory(_basePath);
109 }
110
111 @Override
112 public Map<String, Object> eval(
113 Set<String> allowedClasses, Map<String, Object> inputObjects,
114 Set<String> outputNames, File scriptFile,
115 ClassLoader... classLoaders)
116 throws ScriptingException {
117
118 return eval(
119 allowedClasses, inputObjects, outputNames, scriptFile, null,
120 classLoaders);
121 }
122
123 public Map<String, Object> eval(
124 Set<String> allowedClasses, Map<String, Object> inputObjects,
125 Set<String> outputNames, String script, ClassLoader... classLoaders)
126 throws ScriptingException {
127
128 return eval(
129 allowedClasses, inputObjects, outputNames, null, script,
130 classLoaders);
131 }
132
133 public String getLanguage() {
134 return LANGUAGE;
135 }
136
137 public void setExecuteInSeparateThread(boolean executeInSeparateThread) {
138 _executeInSeparateThread = executeInSeparateThread;
139 }
140
141 protected Map<String, Object> doEval(
142 Set<String> allowedClasses, Map<String, Object> inputObjects,
143 Set<String> outputNames, File scriptFile, String script,
144 ClassLoader... classLoaders)
145 throws ScriptingException {
146
147 if (allowedClasses != null) {
148 throw new ExecutionException(
149 "Constrained execution not supported for Ruby");
150 }
151
152 try {
153 LocalContextProvider localContextProvider =
154 _scriptingContainer.getProvider();
155
156 RubyInstanceConfig rubyInstanceConfig =
157 localContextProvider.getRubyInstanceConfig();
158
159 rubyInstanceConfig.setCurrentDirectory(_basePath);
160
161 if ((classLoaders != null) && (classLoaders.length > 0)) {
162 ClassLoader aggregateClassLoader =
163 AggregateClassLoader.getAggregateClassLoader(
164 ClassLoaderUtil.getPortalClassLoader(), classLoaders);
165
166 rubyInstanceConfig.setLoader(aggregateClassLoader);
167 }
168
169 rubyInstanceConfig.setLoadPaths(_loadPaths);
170
171 for (Map.Entry<String, Object> entry : inputObjects.entrySet()) {
172 String inputName = entry.getKey();
173 Object inputObject = entry.getValue();
174
175 if (!inputName.startsWith(StringPool.DOLLAR)) {
176 inputName = StringPool.DOLLAR + inputName;
177 }
178
179 _scriptingContainer.put(inputName, inputObject);
180 }
181
182 if (scriptFile != null) {
183 _scriptingContainer.runScriptlet(
184 new FileInputStream(scriptFile), scriptFile.toString());
185 }
186 else {
187 _scriptingContainer.runScriptlet(script);
188 }
189
190 if (outputNames == null) {
191 return null;
192 }
193
194 Map<String, Object> outputObjects = new HashMap<String, Object>();
195
196 for (String outputName : outputNames) {
197 outputObjects.put(
198 outputName, _scriptingContainer.get(outputName));
199 }
200
201 return outputObjects;
202 }
203 catch (RaiseException re) {
204 throw new ScriptingException(
205 re.getException().message.asJavaString() + "\n\n", re);
206 }
207 catch (FileNotFoundException fnfe) {
208 throw new ScriptingException(fnfe);
209 }
210 finally {
211 try {
212 _globalRuntimeField.set(null, null);
213 }
214 catch (Exception e) {
215 _log.error(e, e);
216 }
217 }
218 }
219
220 protected Map<String, Object> eval(
221 Set<String> allowedClasses, Map<String, Object> inputObjects,
222 Set<String> outputNames, File scriptFile, String script,
223 ClassLoader... classLoaders)
224 throws ScriptingException {
225
226 if (!_executeInSeparateThread) {
227 return doEval(
228 allowedClasses, inputObjects, outputNames, scriptFile, script,
229 classLoaders);
230 }
231
232 EvalCallable evalCallable = new EvalCallable(
233 allowedClasses, inputObjects, outputNames, scriptFile, script,
234 classLoaders);
235
236 FutureTask<Map<String, Object>> futureTask =
237 new FutureTask<Map<String, Object>>(evalCallable);
238
239 Thread oneTimeExecutorThread = _threadFactory.newThread(futureTask);
240
241 oneTimeExecutorThread.start();
242
243 try {
244 oneTimeExecutorThread.join();
245
246 return futureTask.get();
247 }
248 catch (Exception e) {
249 futureTask.cancel(true);
250 oneTimeExecutorThread.interrupt();
251
252 throw new ScriptingException(e);
253 }
254 }
255
256 protected void initRubyGems() throws Exception {
257 File rubyGemsJarFile = new File(
258 PropsValues.LIFERAY_LIB_PORTAL_DIR, "ruby-gems.jar");
259
260 if (!rubyGemsJarFile.exists()) {
261 if (_log.isWarnEnabled()) {
262 _log.warn(rubyGemsJarFile + " does not exist");
263 }
264
265 return;
266 }
267
268 String tmpDir = SystemProperties.get(SystemProperties.TMP_DIR);
269
270 File rubyDir = new File(tmpDir + "/liferay/ruby");
271
272 if (!rubyDir.exists() ||
273 (rubyDir.lastModified() < rubyGemsJarFile.lastModified())) {
274
275 FileUtil.deltree(rubyDir);
276
277 rubyDir.mkdirs();
278
279 ZipUtil.unzip(rubyGemsJarFile, rubyDir);
280
281 rubyDir.setLastModified(rubyGemsJarFile.lastModified());
282 }
283 }
284
285 private static final String _COMPILE_MODE_FORCE = "force";
286
287 private static final String _COMPILE_MODE_JIT = "jit";
288
289 private static Log _log = LogFactoryUtil.getLog(RubyExecutor.class);
290
291 private static Field _globalRuntimeField;
292
293 private static ThreadFactory _threadFactory =
294 new NamedThreadFactory(
295 RubyExecutor.class.getName(), Thread.NORM_PRIORITY,
296 RubyExecutor.class.getClassLoader());
297
298 static {
299 try {
300 _globalRuntimeField = ReflectionUtil.getDeclaredField(
301 Ruby.class, "globalRuntime");
302 }
303 catch (Exception e) {
304 throw new ExceptionInInitializerError(e);
305 }
306 }
307
308 private String _basePath;
309 private boolean _executeInSeparateThread = true;
310 private List<String> _loadPaths;
311 private ScriptingContainer _scriptingContainer;
312
313 private class EvalCallable implements Callable<Map<String, Object>> {
314
315 public EvalCallable(
316 Set<String> allowedClasses, Map<String, Object> inputObjects,
317 Set<String> outputNames, File scriptFile, String script,
318 ClassLoader[] classLoaders) {
319
320 _allowedClasses = allowedClasses;
321 _inputObjects = inputObjects;
322 _outputNames = outputNames;
323 _scriptFile = scriptFile;
324 _script = script;
325 _classLoaders = classLoaders;
326 }
327
328 public Map<String, Object> call() throws Exception {
329 return doEval(
330 _allowedClasses, _inputObjects, _outputNames, _scriptFile,
331 _script, _classLoaders);
332 }
333
334 private final Set<String> _allowedClasses;
335 private final Map<String, Object> _inputObjects;
336 private final Set<String> _outputNames;
337 private final File _scriptFile;
338 private final String _script;
339 private final ClassLoader[] _classLoaders;
340
341 }
342
343 }