diff --git a/README.html b/README.html
index 0feaabad..f7ac2336 100644
--- a/README.html
+++ b/README.html
@@ -462,7 +462,29 @@ multiple threads see examples/js
As an alternative, the JSR-223 scripting interface can be used, and should always provide a separate Globals instance
-per script engine instance by using a ThreadLocal internally.
+per script engine instance by using a ThreadLocal internally.
+
+
+Considerations include
+Sandboxing
+Lua and luaj are allow for easy sandboxing of scripts in a server environment.
+
+
+
+Luaj provides sample code covering various approaches:
+
+
4 - Libraries
@@ -535,7 +557,8 @@ Luaj uses WeakReferences and the OrphanedThread error to ensure that coroutines
are properly garbage collected. For thread safety, OrphanedThread should not be caught by Java code.
See LuaThread
and OrphanedThread
-javadoc for details.
+javadoc for details. The sample code in examples/jse/CollectingOrphanedCoroutines.java
+provides working examples.
+ * This technique is useful in a server environment to expose the full set of + * lua features to each script, while preventing scripts from interfering with + * each other. + *
+ * Because each Launch gets its own {@link LuajClassLoader}, it should be possible + * to include the debug library, or let scripts manipulate shared metatables, + * or luajava, which otherwise present challenges in a server environment. + *
+ */
+public class SampleUsingClassLoader {
+
+ /** Script that manipulates the shared string metatable.
+ * When loaded by the {@link LuajClassLoader} via a {@link Launcher}
+ * created by that class, each instance of {@link LuajClassLoader} will
+ * have a completely separate version of all static variables.
+ */
+ static String script =
+ "print('args:', ...)\n" +
+ "print('abc.foo', ('abc').foo)\n" +
+ "getmetatable('abc').__index.foo = function() return 'bar' end\n" +
+ "print('abc.foo', ('abc').foo)\n" +
+ "print('abc:foo()', ('abc'):foo())\n" +
+ "return math.pi\n";
+
+ public static void main(String[] s) throws Exception {
+ // The default launcher used standard globals.
+ RunUsingDefaultLauncher();
+ RunUsingDefaultLauncher();
+ // Example using custom launcher class that instantiates debug globals.
+ RunUsingCustomLauncherClass();
+ RunUsingCustomLauncherClass();
+ }
+
+ static void RunUsingDefaultLauncher() throws Exception {
+ Launcher launcher = LuajClassLoader.NewLauncher();
+ // starts with pristine Globals including all luaj static variables.
+ print(launcher.launch(script, new Object[] { "--------" }));
+ // reuses Globals and static variables from previous step.
+ print(launcher.launch(script, new Object[] {}));
+ }
+
+ static void RunUsingCustomLauncherClass() throws Exception {
+ Launcher launcher = LuajClassLoader.NewLauncher(MyLauncher.class);
+ // starts with pristine Globals including all luaj static variables.
+ print(launcher.launch(script, new Object[] { "=========" }));
+ // reuses Globals and static variables from previous step.
+ print(launcher.launch(script, new Object[] { "" }));
+ }
+
+ /** Example of Launcher implementation performing specialized launching.
+ * When loaded by the {@link LuajClassLoader} all luaj classes will be loaded
+ * for each instance of the {@link Launcher} and not interfere with other
+ * classes loaded by other instances.
+ */
+ public static class MyLauncher implements Launcher {
+ Globals g;
+ public MyLauncher() {
+ g = JsePlatform.debugGlobals();
+ // ... plus any other customization of the user environment
+ }
+
+ public Object[] launch(String script, Object[] arg) {
+ LuaValue chunk = g.load(script, "main");
+ return new Object[] { chunk.call(LuaValue.valueOf(arg[0].toString())) };
+ }
+
+ public Object[] launch(InputStream script, Object[] arg) {
+ LuaValue chunk = g.load(script, "main", "bt", g);
+ return new Object[] { chunk.call(LuaValue.valueOf(arg[0].toString())) };
+ }
+
+ public Object[] launch(Reader script, Object[] arg) {
+ LuaValue chunk = g.load(script, "main");
+ return new Object[] { chunk.call(LuaValue.valueOf(arg[0].toString())) };
+ }
+ }
+
+ /** Print the return values as strings. */
+ private static void print(Object[] return_values) {
+ for (int i =0; i
+ * Return values with simple types are coerced into Java simple types.
+ * Tables, threads, and functions are returned as lua objects.
+ *
+ * @see Launcher
+ * @see LuajClassLoader
+ * @see LuajClassLoader#NewLauncher()
+ * @see LuajClassLoader#NewLauncher(Class)
+ * @since luaj 3.0.1
+ */
+public class DefaultLauncher implements Launcher {
+ protected Globals g;
+
+ public DefaultLauncher() {
+ g = JsePlatform.standardGlobals();
+ }
+
+ /** Launches the script with chunk name 'main' */
+ public Object[] launch(String script, Object[] arg) {
+ return launchChunk(g.load(script, "main"), arg);
+ }
+
+ /** Launches the script with chunk name 'main' and loading using modes 'bt' */
+ public Object[] launch(InputStream script, Object[] arg) {
+ return launchChunk(g.load(script, "main", "bt", g), arg);
+ }
+
+ /** Launches the script with chunk name 'main' */
+ public Object[] launch(Reader script, Object[] arg) {
+ return launchChunk(g.load(script, "main"), arg);
+ }
+
+ private Object[] launchChunk(LuaValue chunk, Object[] arg) {
+ LuaValue args[] = new LuaValue[arg.length];
+ for (int i = 0; i < args.length; ++i)
+ args[i] = CoerceJavaToLua.coerce(arg[i]);
+ Varargs results = chunk.invoke(LuaValue.varargsOf(args));
+
+ final int n = results.narg();
+ Object return_values[] = new Object[n];
+ for (int i = 0; i < n; ++i) {
+ LuaValue r = results.arg(i+1);
+ switch (r.type()) {
+ case LuaValue.TBOOLEAN:
+ return_values[i] = r.toboolean();
+ break;
+ case LuaValue.TNUMBER:
+ return_values[i] = r.todouble();
+ break;
+ case LuaValue.TINT:
+ return_values[i] = r.toint();
+ break;
+ case LuaValue.TNIL:
+ return_values[i] = null;
+ break;
+ case LuaValue.TSTRING:
+ return_values[i] = r.tojstring();
+ break;
+ case LuaValue.TUSERDATA:
+ return_values[i] = r.touserdata();
+ break;
+ default:
+ return_values[i] = r;
+ }
+ }
+ return return_values;
+ }
+}
\ No newline at end of file
diff --git a/src/jse/org/luaj/vm2/server/Launcher.java b/src/jse/org/luaj/vm2/server/Launcher.java
new file mode 100644
index 00000000..378f7c81
--- /dev/null
+++ b/src/jse/org/luaj/vm2/server/Launcher.java
@@ -0,0 +1,70 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Luaj.org. All rights reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ ******************************************************************************/
+package org.luaj.vm2.server;
+
+import java.io.InputStream;
+import java.io.Reader;
+
+/** Interface to launch lua scripts using the {@link LuajClassLoader}.
+ *
+ * Note: This class is experimental and subject to change in future versions.
+ *
+ * This interface is purposely genericized to defer class loading so that
+ * luaj classes can come from the class loader.
+ *
+ * The implementation should be acquired using {@link LuajClassLoader#NewLauncher()}
+ * or {@link LuajClassLoader#NewLauncher(Class)} which ensure that the classes are
+ * loaded to give each Launcher instance a pristine set of Globals, including
+ * the shared metatables.
+ *
+ * @see LuajClassLoader
+ * @see LuajClassLoader#NewLauncher()
+ * @see LuajClassLoader#NewLauncher(Class)
+ * @see DefaultLauncher
+ * @since luaj 3.0.1
+ */
+public interface Launcher {
+
+ /** Launch a script contained in a String.
+ *
+ * @param script The script contents.
+ * @param arg Optional arguments supplied to the script.
+ * @return return values from the script.
+ */
+ public Object[] launch(String script, Object[] arg);
+
+ /** Launch a script from an InputStream.
+ *
+ * @param script The script as an InputStream.
+ * @param arg Optional arguments supplied to the script.
+ * @return return values from the script.
+ */
+ public Object[] launch(InputStream script, Object[] arg);
+
+ /** Launch a script from a Reader.
+ *
+ * @param script The script as a Reader.
+ * @param arg Optional arguments supplied to the script.
+ * @return return values from the script.
+ */
+ public Object[] launch(Reader script, Object[] arg);
+}
\ No newline at end of file
diff --git a/src/jse/org/luaj/vm2/server/LuajClassLoader.java b/src/jse/org/luaj/vm2/server/LuajClassLoader.java
new file mode 100644
index 00000000..3886c1f8
--- /dev/null
+++ b/src/jse/org/luaj/vm2/server/LuajClassLoader.java
@@ -0,0 +1,157 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Luaj.org. All rights reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ ******************************************************************************/
+package org.luaj.vm2.server;
+
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Class loader that can be used to launch a lua script in a Java VM that has a
+ * unique set of classes for org.luaj classes.
+ *
+* Note: This class is experimental and subject to change in future versions.
+ *
+ * By using a custom class loader per script, it allows the script to have
+ * its own set of globals, including static values such as shared metatables
+ * that cannot access lua values from other scripts because their classes are
+ * loaded from different class loaders. Thus normally unsafe libraries such
+ * as luajava can be exposed to scripts in a server environment using these
+ * techniques.
+ *
+ * All classes in the package "org.luaj.vm2." are considered user classes, and
+ * loaded into this class loader from their bytes in the class path. Other
+ * classes are considered systemc classes and loaded via the system loader. This
+ * class set can be extended by overriding {@link #isUserClass(String)}.
+ *
+ * The {@link Launcher} interface is loaded as a system class by exception so
+ * that the caller may use it to launch lua scripts.
+ *
+ * By default {@link #NewLauncher()} creates a subclass of {@link Launcher} of
+ * type {@link DefaultLauncher} which creates debug globals, runs the script,
+ * and prints the return values. This behavior can be changed by supplying a
+ * different implementation class to {@link #NewLauncher(Class)} which must
+ * extend {@link Launcher}.
+ *
+ * @see Launcher
+ * @see #NewLauncher()
+ * @see #NewLauncher(Class)
+ * @see DefaultLauncher
+ * @since luaj 3.0.1
+ */
+public class LuajClassLoader extends ClassLoader {
+
+ /** String describing the luaj packages to consider part of the user classes */
+ static final String luajPackageRoot = "org.luaj.vm2.";
+
+ /** String describing the Launcher interface to be considered a system class */
+ static final String launcherInterfaceRoot = Launcher.class.getName();
+
+ /** Local cache of classes loaded by this loader. */
+ Map
+ * The {@link Launcher} that is returned will be a pristine luaj vm
+ * whose classes are loaded into this loader including static variables
+ * such as shared metatables, and should not be able to directly access
+ * variables from other Launcher instances.
+ *
+ * @return {@link Launcher} instance that can be used to launch scripts.
+ * @throws InstantiationException
+ * @throws IllegalAccessException
+ * @throws ClassNotFoundException
+ */
+ public static Launcher NewLauncher() throws InstantiationException,
+ IllegalAccessException, ClassNotFoundException {
+ return NewLauncher(DefaultLauncher.class);
+ }
+
+ /**
+ * Construct a {@link Launcher} instance that will load classes in
+ * its own {@link LuajClassLoader} using a user-supplied implementation class
+ * that implements {@link Launcher}.
+ *
+ * The {@link Launcher} that is returned will be a pristine luaj vm
+ * whose classes are loaded into this loader including static variables
+ * such as shared metatables, and should not be able to directly access
+ * variables from other Launcher instances.
+ *
+ * @return instance of type 'launcher_class' that can be used to launch scripts.
+ * @throws InstantiationException
+ * @throws IllegalAccessException
+ * @throws ClassNotFoundException
+ */
+ public static Launcher NewLauncher(Class extends Launcher> launcher_class)
+ throws InstantiationException, IllegalAccessException,
+ ClassNotFoundException {
+ final LuajClassLoader loader = new LuajClassLoader();
+ final Object instance = loader.loadAsUserClass(launcher_class.getName())
+ .newInstance();
+ return (Launcher) instance;
+ }
+
+ /**
+ * Test if a class name should be considered a user class and loaded
+ * by this loader, or a system class and loaded by the system loader.
+ * @param classname Class name to test.
+ * @return true if this should be loaded into this class loader.
+ */
+ public static boolean isUserClass(String classname) {
+ return classname.startsWith(luajPackageRoot)
+ && !classname.startsWith(launcherInterfaceRoot);
+ }
+
+ public Class> loadClass(String classname) throws ClassNotFoundException {
+ if (classes.containsKey(classname))
+ return classes.get(classname);
+ if (!isUserClass(classname))
+ return super.findSystemClass(classname);
+ return loadAsUserClass(classname);
+ }
+
+ private Class> loadAsUserClass(String classname) throws ClassNotFoundException {
+ final String path = classname.replace('.', '/').concat(".class");
+ InputStream is = getResourceAsStream(path);
+ if (is != null) {
+ try {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ byte[] b = new byte[1024];
+ for (int n = 0; (n = is.read(b)) >= 0;)
+ baos.write(b, 0, n);
+ byte[] bytes = baos.toByteArray();
+ Class> result = super.defineClass(classname, bytes, 0,
+ bytes.length);
+ classes.put(classname, result);
+ return result;
+ } catch (java.io.IOException e) {
+ throw new ClassNotFoundException("Read failed: " + classname
+ + ": " + e);
+ }
+ }
+ throw new ClassNotFoundException("Not found: " + classname);
+ }
+}