All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.liferay.portal.scripting.ruby.RubyExecutor Maven / Gradle / Ivy

There is a newer version: 7.0.0-nightly
Show newest version
/**
 * Copyright (c) 2000-2013 Liferay, Inc. All rights reserved.
 *
 * This library is free software; you can redistribute it and/or modify it under
 * the terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation; either version 2.1 of the License, or (at your option)
 * any later version.
 *
 * This library is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
 * details.
 */

package com.liferay.portal.scripting.ruby;

import com.liferay.portal.kernel.log.Log;
import com.liferay.portal.kernel.log.LogFactoryUtil;
import com.liferay.portal.kernel.scripting.BaseScriptingExecutor;
import com.liferay.portal.kernel.scripting.ExecutionException;
import com.liferay.portal.kernel.scripting.ScriptingException;
import com.liferay.portal.kernel.util.AggregateClassLoader;
import com.liferay.portal.kernel.util.ArrayUtil;
import com.liferay.portal.kernel.util.FileUtil;
import com.liferay.portal.kernel.util.NamedThreadFactory;
import com.liferay.portal.kernel.util.ReflectionUtil;
import com.liferay.portal.kernel.util.StringPool;
import com.liferay.portal.kernel.util.SystemProperties;
import com.liferay.portal.util.ClassLoaderUtil;
import com.liferay.portal.util.PropsValues;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;

import java.lang.reflect.Field;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
import java.util.concurrent.ThreadFactory;

import jodd.io.ZipUtil;

import org.jruby.Ruby;
import org.jruby.RubyInstanceConfig;
import org.jruby.RubyInstanceConfig.CompileMode;
import org.jruby.embed.LocalContextScope;
import org.jruby.embed.ScriptingContainer;
import org.jruby.embed.internal.LocalContextProvider;
import org.jruby.exceptions.RaiseException;

/**
 * @author Alberto Montero
 * @author Raymond Augé
 */
public class RubyExecutor extends BaseScriptingExecutor {

	public static final String LANGUAGE = "ruby";

	public RubyExecutor() {
		try {
			initRubyGems();
		}
		catch (Exception e) {
			_log.error(e, e);
		}

		_scriptingContainer = new ScriptingContainer(
			LocalContextScope.THREADSAFE);

		LocalContextProvider localContextProvider =
			_scriptingContainer.getProvider();

		RubyInstanceConfig rubyInstanceConfig =
			localContextProvider.getRubyInstanceConfig();

		if (PropsValues.SCRIPTING_JRUBY_COMPILE_MODE.equals(
				_COMPILE_MODE_FORCE)) {

			rubyInstanceConfig.setCompileMode(CompileMode.FORCE);
		}
		else if (PropsValues.SCRIPTING_JRUBY_COMPILE_MODE.equals(
					_COMPILE_MODE_JIT)) {

			rubyInstanceConfig.setCompileMode(CompileMode.JIT);
		}

		rubyInstanceConfig.setJitThreshold(
			PropsValues.SCRIPTING_JRUBY_COMPILE_THRESHOLD);
		rubyInstanceConfig.setLoader(ClassLoaderUtil.getPortalClassLoader());

		_basePath = PropsValues.LIFERAY_LIB_PORTAL_DIR;

		_loadPaths = new ArrayList(
			PropsValues.SCRIPTING_JRUBY_LOAD_PATHS.length);

		for (String gemLibPath : PropsValues.SCRIPTING_JRUBY_LOAD_PATHS) {
			_loadPaths.add(gemLibPath);
		}

		rubyInstanceConfig.setLoadPaths(_loadPaths);

		_scriptingContainer.setCurrentDirectory(_basePath);
	}

	@Override
	public Map eval(
			Set allowedClasses, Map inputObjects,
			Set outputNames, File scriptFile,
			ClassLoader... classLoaders)
		throws ScriptingException {

		return eval(
			allowedClasses, inputObjects, outputNames, scriptFile, null,
			classLoaders);
	}

	@Override
	public Map eval(
			Set allowedClasses, Map inputObjects,
			Set outputNames, String script, ClassLoader... classLoaders)
		throws ScriptingException {

		return eval(
			allowedClasses, inputObjects, outputNames, null, script,
			classLoaders);
	}

	@Override
	public String getLanguage() {
		return LANGUAGE;
	}

	public void setExecuteInSeparateThread(boolean executeInSeparateThread) {
		_executeInSeparateThread = executeInSeparateThread;
	}

	protected Map doEval(
			Set allowedClasses, Map inputObjects,
			Set outputNames, File scriptFile, String script,
			ClassLoader... classLoaders)
		throws ScriptingException {

		if (allowedClasses != null) {
			throw new ExecutionException(
				"Constrained execution not supported for Ruby");
		}

		try {
			LocalContextProvider localContextProvider =
				_scriptingContainer.getProvider();

			RubyInstanceConfig rubyInstanceConfig =
				localContextProvider.getRubyInstanceConfig();

			rubyInstanceConfig.setCurrentDirectory(_basePath);

			if (ArrayUtil.isNotEmpty(classLoaders)) {
				ClassLoader aggregateClassLoader =
					AggregateClassLoader.getAggregateClassLoader(
						ClassLoaderUtil.getPortalClassLoader(), classLoaders);

				rubyInstanceConfig.setLoader(aggregateClassLoader);
			}

			rubyInstanceConfig.setLoadPaths(_loadPaths);

			for (Map.Entry entry : inputObjects.entrySet()) {
				String inputName = entry.getKey();
				Object inputObject = entry.getValue();

				if (!inputName.startsWith(StringPool.DOLLAR)) {
					inputName = StringPool.DOLLAR + inputName;
				}

				_scriptingContainer.put(inputName, inputObject);
			}

			if (scriptFile != null) {
				_scriptingContainer.runScriptlet(
					new FileInputStream(scriptFile), scriptFile.toString());
			}
			else {
				_scriptingContainer.runScriptlet(script);
			}

			if (outputNames == null) {
				return null;
			}

			Map outputObjects = new HashMap();

			for (String outputName : outputNames) {
				outputObjects.put(
					outputName, _scriptingContainer.get(outputName));
			}

			return outputObjects;
		}
		catch (RaiseException re) {
			throw new ScriptingException(
				re.getException().message.asJavaString() + "\n\n", re);
		}
		catch (FileNotFoundException fnfe) {
			throw new ScriptingException(fnfe);
		}
		finally {
			try {
				_globalRuntimeField.set(null, null);
			}
			catch (Exception e) {
				_log.error(e, e);
			}
		}
	}

	protected Map eval(
			Set allowedClasses, Map inputObjects,
			Set outputNames, File scriptFile, String script,
			ClassLoader... classLoaders)
		throws ScriptingException {

		if (!_executeInSeparateThread) {
			return doEval(
				allowedClasses, inputObjects, outputNames, scriptFile, script,
				classLoaders);
		}

		EvalCallable evalCallable = new EvalCallable(
			allowedClasses, inputObjects, outputNames, scriptFile, script,
			classLoaders);

		FutureTask> futureTask =
			new FutureTask>(evalCallable);

		Thread oneTimeExecutorThread = _threadFactory.newThread(futureTask);

		oneTimeExecutorThread.start();

		try {
			oneTimeExecutorThread.join();

			return futureTask.get();
		}
		catch (Exception e) {
			futureTask.cancel(true);
			oneTimeExecutorThread.interrupt();

			throw new ScriptingException(e);
		}
	}

	protected void initRubyGems() throws Exception {
		File rubyGemsJarFile = new File(
			PropsValues.LIFERAY_LIB_PORTAL_DIR, "ruby-gems.jar");

		if (!rubyGemsJarFile.exists()) {
			if (_log.isWarnEnabled()) {
				_log.warn(rubyGemsJarFile + " does not exist");
			}

			return;
		}

		String tmpDir = SystemProperties.get(SystemProperties.TMP_DIR);

		File rubyDir = new File(tmpDir + "/liferay/ruby");

		if (!rubyDir.exists() ||
			(rubyDir.lastModified() < rubyGemsJarFile.lastModified())) {

			FileUtil.deltree(rubyDir);

			rubyDir.mkdirs();

			ZipUtil.unzip(rubyGemsJarFile, rubyDir);

			rubyDir.setLastModified(rubyGemsJarFile.lastModified());
		}
	}

	private static final String _COMPILE_MODE_FORCE = "force";

	private static final String _COMPILE_MODE_JIT = "jit";

	private static Log _log = LogFactoryUtil.getLog(RubyExecutor.class);

	private static Field _globalRuntimeField;

	private static ThreadFactory _threadFactory =
		new NamedThreadFactory(
			RubyExecutor.class.getName(), Thread.NORM_PRIORITY,
			RubyExecutor.class.getClassLoader());

	static {
		try {
			_globalRuntimeField = ReflectionUtil.getDeclaredField(
				Ruby.class, "globalRuntime");
		}
		catch (Exception e) {
			throw new ExceptionInInitializerError(e);
		}
	}

	private String _basePath;
	private boolean _executeInSeparateThread = true;
	private List _loadPaths;
	private ScriptingContainer _scriptingContainer;

	private class EvalCallable implements Callable> {

		public EvalCallable(
			Set allowedClasses, Map inputObjects,
			Set outputNames, File scriptFile, String script,
			ClassLoader[] classLoaders) {

			_allowedClasses = allowedClasses;
			_inputObjects = inputObjects;
			_outputNames = outputNames;
			_scriptFile = scriptFile;
			_script = script;
			_classLoaders = classLoaders;
		}

		@Override
		public Map call() throws Exception {
			return doEval(
				_allowedClasses, _inputObjects, _outputNames, _scriptFile,
				_script, _classLoaders);
		}

		private final Set _allowedClasses;
		private final Map _inputObjects;
		private final Set _outputNames;
		private final File _scriptFile;
		private final String _script;
		private final ClassLoader[] _classLoaders;

	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy