/*
* Copyright (C) 2012 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package interactivespaces.service.script.internal;
import interactivespaces.InteractiveSpacesException;
import interactivespaces.SimpleInteractiveSpacesException;
import interactivespaces.activity.Activity;
import interactivespaces.activity.ActivityFilesystem;
import interactivespaces.configuration.Configuration;
import interactivespaces.service.BaseSupportedService;
import interactivespaces.service.script.ActivityScriptWrapper;
import interactivespaces.service.script.Script;
import interactivespaces.service.script.ScriptService;
import interactivespaces.service.script.ScriptSource;
import interactivespaces.service.script.StringScriptSource;
import interactivespaces.service.script.internal.javascript.RhinoJavascriptActivityScriptFactory;
import interactivespaces.service.script.internal.python.PythonActivityScriptFactory;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import org.codehaus.groovy.jsr223.GroovyScriptEngineFactory;
import org.python.jsr223.PyScriptEngineFactory;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.script.Compilable;
import javax.script.ScriptContext;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineFactory;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import javax.script.SimpleBindings;
/**
* An {@link ScriptService} using {@code javax.script}.
*
* @author Keith M. Hughes
*/
public class JavaxScriptScriptService extends BaseSupportedService implements ScriptService {
/**
* All engines stored in the script engine.
*/
private final List<ScriptLanguage> languages = Lists.newArrayList();
/**
* A mapping from names of languages to the factory.
*/
private final Map<String, ScriptLanguage> nameToLanguage = Maps.newHashMap();
/**
* A mapping from names of extensions to the factory.
*/
private final Map<String, ScriptLanguage> extensionToLanguage = Maps.newHashMap();
@Override
public String getName() {
return ScriptService.SERVICE_NAME;
}
@Override
public void startup() {
final ClassLoader oldClassLoader = Thread.currentThread().getContextClassLoader();
Thread.currentThread().setContextClassLoader(null);
try {
// TODO(keith): Create a bundle listener which will scan bundles for
// registered factories.
GroovyScriptEngineFactory groovyScriptEngineFactory = new GroovyScriptEngineFactory();
registerLanguage("groovy", groovyScriptEngineFactory, null);
registerLanguage("python", new PyScriptEngineFactory(), new PythonActivityScriptFactory(getSpaceEnvironment()));
// registerLanguage("javascript",
// new RhinoJavascriptScriptEngineFactory(),
// new RhinoJavascriptActivityScriptFactory());
// Get any languages, other than Javascript, that
// might be available.
ScriptEngineManager mgr = new ScriptEngineManager();
for (ScriptEngineFactory factory : mgr.getEngineFactories()) {
if ("ECMAScript".equals(factory.getLanguageName())) {
registerLanguage("javascript", factory, new RhinoJavascriptActivityScriptFactory(getSpaceEnvironment()));
} else {
registerLanguage(factory.getLanguageName(), factory, null);
}
}
} finally {
Thread.currentThread().setContextClassLoader(oldClassLoader);
}
}
/**
* Register a new scripting engine factory with the factory.
*
* @param languageName
* the name of the language
* @param scriptEngineFactory
* the JSR 223 scripting factory
* @param activityScriptFactory
* the factory for making scripted activities
*/
private void registerLanguage(String languageName, ScriptEngineFactory scriptEngineFactory,
ActivityScriptFactory activityScriptFactory) {
ScriptLanguage language = new ScriptLanguage(scriptEngineFactory, activityScriptFactory);
languages.add(language);
nameToLanguage.put(scriptEngineFactory.getLanguageName(), language);
for (String name : scriptEngineFactory.getNames()) {
nameToLanguage.put(name, language);
}
for (String extension : scriptEngineFactory.getExtensions()) {
extensionToLanguage.put(extension, language);
}
if (activityScriptFactory != null) {
activityScriptFactory.initialize();
}
}
@Override
public Set<String> getLanguageNames() {
Set<String> languages = Sets.newHashSet(nameToLanguage.keySet());
return languages;
}
@Override
public void executeSimpleScript(String languageName, String script) {
executeScript(languageName, script, EMPTY_BINDINGS);
}
@Override
public void executeScript(String languageName, String script, Map<String, Object> bindings) {
executeScriptByName(languageName, new StringScriptSource(script), bindings);
}
@Override
public void executeScriptByName(String languageName, ScriptSource scriptSource, Map<String, Object> bindings) {
ScriptLanguage scriptLanguage = nameToLanguage.get(languageName);
if (scriptLanguage == null) {
throw new SimpleInteractiveSpacesException(String.format("Unable to find a script engine for language %s",
languageName));
}
executeScript(scriptLanguage, scriptSource, bindings, "language", languageName);
}
@Override
public void executeScriptByExtension(String extension, ScriptSource scriptSource, Map<String, Object> bindings) {
ScriptLanguage scriptLanguage = extensionToLanguage.get(extension);
if (scriptLanguage == null) {
throw new SimpleInteractiveSpacesException(String.format(
"Unable to find a script engine for language extension %s", extension));
}
executeScript(scriptLanguage, scriptSource, bindings, "extension", extension);
}
/**
* Execute a script.
*
* @param scriptLanguage
* the scripting language that will run the script
* @param scriptSource
* the source of the script
* @param bindings
* the extra bindings for the script
* @param idType
* the type of language ID which was used
* @param languageId
* the ID used to get the language
*
*/
private void executeScript(ScriptLanguage scriptLanguage, ScriptSource scriptSource, Map<String, Object> bindings,
String idType, String languageId) {
ScriptEngineFactory factory = scriptLanguage.getScriptEngineFactory();
if (factory == null) {
throw new InteractiveSpacesException(
String.format("Unable to find a script engine for %s %s", idType, languageId));
}
final ClassLoader oldClassLoader = Thread.currentThread().getContextClassLoader();
Thread.currentThread().setContextClassLoader(JavaxScriptScriptService.class.getClassLoader());
try {
ScriptEngine engine = factory.getScriptEngine();
if (engine != null) {
engine.setBindings(new SimpleBindings(bindings), ScriptContext.GLOBAL_SCOPE);
engine.eval(scriptSource.getScriptContents());
}
} catch (ScriptException ex) {
ex.printStackTrace();
} finally {
Thread.currentThread().setContextClassLoader(oldClassLoader);
}
}
@Override
public Script newSimpleScript(String languageName, String script) {
return newScriptByName(languageName, new StringScriptSource(script));
}
@Override
public Script newScriptByName(String languageName, ScriptSource scriptSource) {
ScriptLanguage scriptLanguage = nameToLanguage.get(languageName);
if (scriptLanguage == null) {
throw new SimpleInteractiveSpacesException(String.format("Unable to find a script engine for language %s",
languageName));
}
return newScript(scriptLanguage, scriptSource);
}
@Override
public Script newScriptByExtension(String extension, ScriptSource scriptSource) {
ScriptLanguage scriptLanguage = extensionToLanguage.get(extension);
if (scriptLanguage == null) {
throw new SimpleInteractiveSpacesException(String.format(
"Unable to find a script engine for language extension %s", extension));
}
return newScript(scriptLanguage, scriptSource);
}
/**
* Create the new script object.
*
* @param scriptLanguage
* the script language
* @param scriptSource
* source of the script
*
* @return the script
*/
private Script newScript(ScriptLanguage scriptLanguage, ScriptSource scriptSource) {
ScriptEngine engine = scriptLanguage.getScriptEngineFactory().getScriptEngine();
if (engine instanceof Compilable) {
return new CompiledScriptScript((Compilable) engine, scriptSource);
} else {
return new ScriptEngineScript(engine, scriptSource);
}
}
@Override
public ActivityScriptWrapper getActivityByName(String languageName, String objectName, ScriptSource script,
ActivityFilesystem activityFilesystem, Configuration configuration) {
ScriptLanguage language = nameToLanguage.get(languageName);
if (language == null) {
throw new SimpleInteractiveSpacesException(String.format("Unable to find a script engine for language %s",
languageName));
}
return getActivity(languageName, objectName, script, activityFilesystem, configuration, language, "name");
}
@Override
public ActivityScriptWrapper getActivityByExtension(String extension, String objectName, ScriptSource script,
ActivityFilesystem activityFilesystem, Configuration configuration) {
ScriptLanguage language = extensionToLanguage.get(extension);
if (language == null) {
throw new InteractiveSpacesException(String.format("Unable to find a script engine for language %s", extension));
}
return getActivity(extension, objectName, script, activityFilesystem, configuration, language, "extension");
}
/**
* Get the activity, if possible, from the {@link ActivityScriptFactory}.
*
* @param languageId
* ID of the language
* @param objectName
* name of the object to be created
* @param script
* the script
* @param activityFilesystem
* the file system for the activity
* @param configuration
* the configuration for the activity
* @param language
* the language of the script
* @param nameType
* how the language type is coming in
*
* @return the activity
*/
private ActivityScriptWrapper getActivity(String languageId, String objectName, ScriptSource script,
ActivityFilesystem activityFilesystem, Configuration configuration, ScriptLanguage language, String nameType) {
ActivityScriptFactory factory = language.getActivityScriptFactory();
if (factory == null) {
throw new SimpleInteractiveSpacesException(String.format("Unable to find an activity script factory for %s %s",
nameType, languageId));
}
return factory.getActivity(objectName, script, activityFilesystem, configuration);
}
/**
* Everything that can be done with a scripting language.
*
* @author Keith M. Hughes
*/
private static class ScriptLanguage {
/**
* A script language factory, if any, for the language.
*
* <p>
* Can be {@code null}.
*/
private final ScriptEngineFactory scriptEngineFactory;
/**
* The factory for creating {@link Activity} instances.
*
* <p>
* Can be {@code null}.
*/
private final ActivityScriptFactory activityScriptFactory;
/**
* Construct a script language.
*
* @param scriptEngineFactory
* the script engine factory for the language
* @param activityScriptFactory
* the activity script factory for the language
*/
public ScriptLanguage(ScriptEngineFactory scriptEngineFactory, ActivityScriptFactory activityScriptFactory) {
this.scriptEngineFactory = scriptEngineFactory;
this.activityScriptFactory = activityScriptFactory;
}
/**
* Get the script engine factory.
*
* @return the scriptEngineFactory, {@code null} if none
*/
public ScriptEngineFactory getScriptEngineFactory() {
return scriptEngineFactory;
}
/**
* Get the activity script factory.
*
* @return the activityScriptFactory, {@code null} if none
*/
public ActivityScriptFactory getActivityScriptFactory() {
return activityScriptFactory;
}
}
}