mirror of
https://github.com/honeymoose/OpenSearch.git
synced 2025-02-23 13:26:02 +00:00
Support script context stateful factory in Painless. (#25233)
This commit is contained in:
parent
68deda6d03
commit
a4471f51e4
@ -43,6 +43,7 @@ import java.security.Permissions;
|
|||||||
import java.security.PrivilegedAction;
|
import java.security.PrivilegedAction;
|
||||||
import java.security.ProtectionDomain;
|
import java.security.ProtectionDomain;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
@ -158,20 +159,126 @@ public final class PainlessScriptEngine extends AbstractComponent implements Scr
|
|||||||
|
|
||||||
compile(contextsToCompilers.get(context), loader, scriptName, scriptSource, params);
|
compile(contextsToCompilers.get(context), loader, scriptName, scriptSource, params);
|
||||||
|
|
||||||
return generateFactory(loader, context);
|
if (context.statefulFactoryClazz != null) {
|
||||||
|
return generateFactory(loader, context, generateStatefulFactory(loader, context));
|
||||||
|
} else {
|
||||||
|
return generateFactory(loader, context, WriterConstants.CLASS_TYPE);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates a factory class that will return script instances.
|
* Generates a stateful factory class that will return script instances. Acts as a middle man between
|
||||||
* Uses the newInstance method from a {@link ScriptContext#factoryClazz} to define the factory method
|
* the {@link ScriptContext#factoryClazz} and the {@link ScriptContext#instanceClazz} when used so that
|
||||||
* to create new instances of the {@link ScriptContext#instanceClazz}.
|
* the stateless factory can be used for caching and the stateful factory can act as a cache for new
|
||||||
|
* script instances. Uses the newInstance method from a {@link ScriptContext#statefulFactoryClazz} to
|
||||||
|
* define the factory method to create new instances of the {@link ScriptContext#instanceClazz}.
|
||||||
* @param loader The {@link ClassLoader} that is used to define the factory class and script class.
|
* @param loader The {@link ClassLoader} that is used to define the factory class and script class.
|
||||||
* @param context The {@link ScriptContext}'s semantics are used to define the factory class.
|
* @param context The {@link ScriptContext}'s semantics are used to define the factory class.
|
||||||
* @param <T> The factory class.
|
* @param <T> The factory class.
|
||||||
* @return A factory class that will return script instances.
|
* @return A factory class that will return script instances.
|
||||||
*/
|
*/
|
||||||
private <T> T generateFactory(Loader loader, ScriptContext<T> context) {
|
private <T> Type generateStatefulFactory(Loader loader, ScriptContext<T> context) {
|
||||||
|
int classFrames = ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS;
|
||||||
|
int classAccess = Opcodes.ACC_PUBLIC | Opcodes.ACC_SUPER | Opcodes.ACC_FINAL;
|
||||||
|
String interfaceBase = Type.getType(context.statefulFactoryClazz).getInternalName();
|
||||||
|
String className = interfaceBase + "$StatefulFactory";
|
||||||
|
String classInterfaces[] = new String[] { interfaceBase };
|
||||||
|
|
||||||
|
ClassWriter writer = new ClassWriter(classFrames);
|
||||||
|
writer.visit(WriterConstants.CLASS_VERSION, classAccess, className, null, OBJECT_TYPE.getInternalName(), classInterfaces);
|
||||||
|
|
||||||
|
Method newFactory = null;
|
||||||
|
|
||||||
|
for (Method method : context.factoryClazz.getMethods()) {
|
||||||
|
if ("newFactory".equals(method.getName())) {
|
||||||
|
newFactory = method;
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int count = 0; count < newFactory.getParameterTypes().length; ++count) {
|
||||||
|
writer.visitField(Opcodes.ACC_PRIVATE | Opcodes.ACC_FINAL, "$arg" + count,
|
||||||
|
Type.getType(newFactory.getParameterTypes()[count]).getDescriptor(), null, null).visitEnd();
|
||||||
|
}
|
||||||
|
|
||||||
|
org.objectweb.asm.commons.Method base =
|
||||||
|
new org.objectweb.asm.commons.Method("<init>", MethodType.methodType(void.class).toMethodDescriptorString());
|
||||||
|
org.objectweb.asm.commons.Method init = new org.objectweb.asm.commons.Method("<init>",
|
||||||
|
MethodType.methodType(void.class, newFactory.getParameterTypes()).toMethodDescriptorString());
|
||||||
|
|
||||||
|
GeneratorAdapter constructor = new GeneratorAdapter(Opcodes.ASM5, init,
|
||||||
|
writer.visitMethod(Opcodes.ACC_PUBLIC, init.getName(), init.getDescriptor(), null, null));
|
||||||
|
constructor.visitCode();
|
||||||
|
constructor.loadThis();
|
||||||
|
constructor.invokeConstructor(OBJECT_TYPE, base);
|
||||||
|
|
||||||
|
for (int count = 0; count < newFactory.getParameterTypes().length; ++count) {
|
||||||
|
constructor.loadThis();
|
||||||
|
constructor.loadArg(count);
|
||||||
|
constructor.putField(Type.getType(className), "$arg" + count, Type.getType(newFactory.getParameterTypes()[count]));
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor.returnValue();
|
||||||
|
constructor.endMethod();
|
||||||
|
|
||||||
|
Method newInstance = null;
|
||||||
|
|
||||||
|
for (Method method : context.statefulFactoryClazz.getMethods()) {
|
||||||
|
if ("newInstance".equals(method.getName())) {
|
||||||
|
newInstance = method;
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
org.objectweb.asm.commons.Method instance = new org.objectweb.asm.commons.Method(newInstance.getName(),
|
||||||
|
MethodType.methodType(newInstance.getReturnType(), newInstance.getParameterTypes()).toMethodDescriptorString());
|
||||||
|
|
||||||
|
List<Class<?>> parameters = new ArrayList<>(Arrays.asList(newFactory.getParameterTypes()));
|
||||||
|
parameters.addAll(Arrays.asList(newInstance.getParameterTypes()));
|
||||||
|
|
||||||
|
org.objectweb.asm.commons.Method constru = new org.objectweb.asm.commons.Method("<init>",
|
||||||
|
MethodType.methodType(void.class, parameters.toArray(new Class<?>[] {})).toMethodDescriptorString());
|
||||||
|
|
||||||
|
GeneratorAdapter adapter = new GeneratorAdapter(Opcodes.ASM5, instance,
|
||||||
|
writer.visitMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL,
|
||||||
|
instance.getName(), instance.getDescriptor(), null, null));
|
||||||
|
adapter.visitCode();
|
||||||
|
adapter.newInstance(WriterConstants.CLASS_TYPE);
|
||||||
|
adapter.dup();
|
||||||
|
|
||||||
|
for (int count = 0; count < newFactory.getParameterTypes().length; ++count) {
|
||||||
|
adapter.loadThis();
|
||||||
|
adapter.getField(Type.getType(className), "$arg" + count, Type.getType(newFactory.getParameterTypes()[count]));
|
||||||
|
}
|
||||||
|
|
||||||
|
adapter.loadArgs();
|
||||||
|
adapter.invokeConstructor(WriterConstants.CLASS_TYPE, constru);
|
||||||
|
adapter.returnValue();
|
||||||
|
adapter.endMethod();
|
||||||
|
|
||||||
|
writer.visitEnd();
|
||||||
|
|
||||||
|
loader.defineFactory(className.replace('/', '.'), writer.toByteArray());
|
||||||
|
|
||||||
|
return Type.getType(className);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a factory class that will return script instances or stateful factories.
|
||||||
|
* Uses the newInstance method from a {@link ScriptContext#factoryClazz} to define the factory method
|
||||||
|
* to create new instances of the {@link ScriptContext#instanceClazz} or uses the newFactory method
|
||||||
|
* to create new factories of the {@link ScriptContext#statefulFactoryClazz}.
|
||||||
|
* @param loader The {@link ClassLoader} that is used to define the factory class and script class.
|
||||||
|
* @param context The {@link ScriptContext}'s semantics are used to define the factory class.
|
||||||
|
* @param classType The type to be instaniated in the newFactory or newInstance method. Depends
|
||||||
|
* on whether a {@link ScriptContext#statefulFactoryClazz} is specified.
|
||||||
|
* @param <T> The factory class.
|
||||||
|
* @return A factory class that will return script instances.
|
||||||
|
*/
|
||||||
|
private <T> T generateFactory(Loader loader, ScriptContext<T> context, Type classType) {
|
||||||
int classFrames = ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS;
|
int classFrames = ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS;
|
||||||
int classAccess = Opcodes.ACC_PUBLIC | Opcodes.ACC_SUPER| Opcodes.ACC_FINAL;
|
int classAccess = Opcodes.ACC_PUBLIC | Opcodes.ACC_SUPER| Opcodes.ACC_FINAL;
|
||||||
String interfaceBase = Type.getType(context.factoryClazz).getInternalName();
|
String interfaceBase = Type.getType(context.factoryClazz).getInternalName();
|
||||||
@ -188,25 +295,37 @@ public final class PainlessScriptEngine extends AbstractComponent implements Scr
|
|||||||
writer.visitMethod(Opcodes.ACC_PUBLIC, init.getName(), init.getDescriptor(), null, null));
|
writer.visitMethod(Opcodes.ACC_PUBLIC, init.getName(), init.getDescriptor(), null, null));
|
||||||
constructor.visitCode();
|
constructor.visitCode();
|
||||||
constructor.loadThis();
|
constructor.loadThis();
|
||||||
constructor.loadArgs();
|
|
||||||
constructor.invokeConstructor(OBJECT_TYPE, init);
|
constructor.invokeConstructor(OBJECT_TYPE, init);
|
||||||
constructor.returnValue();
|
constructor.returnValue();
|
||||||
constructor.endMethod();
|
constructor.endMethod();
|
||||||
|
|
||||||
Method reflect = context.factoryClazz.getMethods()[0];
|
Method reflect = null;
|
||||||
|
|
||||||
|
for (Method method : context.factoryClazz.getMethods()) {
|
||||||
|
if ("newInstance".equals(method.getName())) {
|
||||||
|
reflect = method;
|
||||||
|
|
||||||
|
break;
|
||||||
|
} else if ("newFactory".equals(method.getName())) {
|
||||||
|
reflect = method;
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
org.objectweb.asm.commons.Method instance = new org.objectweb.asm.commons.Method(reflect.getName(),
|
org.objectweb.asm.commons.Method instance = new org.objectweb.asm.commons.Method(reflect.getName(),
|
||||||
MethodType.methodType(reflect.getReturnType(), reflect.getParameterTypes()).toMethodDescriptorString());
|
MethodType.methodType(reflect.getReturnType(), reflect.getParameterTypes()).toMethodDescriptorString());
|
||||||
org.objectweb.asm.commons.Method constru = new org.objectweb.asm.commons.Method("<init>",
|
org.objectweb.asm.commons.Method constru = new org.objectweb.asm.commons.Method("<init>",
|
||||||
MethodType.methodType(void.class, reflect.getParameterTypes()).toMethodDescriptorString());
|
MethodType.methodType(void.class, reflect.getParameterTypes()).toMethodDescriptorString());
|
||||||
|
|
||||||
GeneratorAdapter adapter = new GeneratorAdapter(Opcodes.ASM5, instance,
|
GeneratorAdapter adapter = new GeneratorAdapter(Opcodes.ASM5, instance,
|
||||||
writer.visitMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_SUPER | Opcodes.ACC_FINAL,
|
writer.visitMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL,
|
||||||
instance.getName(), instance.getDescriptor(), null, null));
|
instance.getName(), instance.getDescriptor(), null, null));
|
||||||
adapter.visitCode();
|
adapter.visitCode();
|
||||||
adapter.newInstance(WriterConstants.CLASS_TYPE);
|
adapter.newInstance(classType);
|
||||||
adapter.dup();
|
adapter.dup();
|
||||||
adapter.loadArgs();
|
adapter.loadArgs();
|
||||||
adapter.invokeConstructor(WriterConstants.CLASS_TYPE, constru);
|
adapter.invokeConstructor(classType, constru);
|
||||||
adapter.returnValue();
|
adapter.returnValue();
|
||||||
adapter.endMethod();
|
adapter.endMethod();
|
||||||
|
|
||||||
|
@ -30,6 +30,7 @@ public class FactoryTests extends ScriptTestCase {
|
|||||||
|
|
||||||
protected Collection<ScriptContext<?>> scriptContexts() {
|
protected Collection<ScriptContext<?>> scriptContexts() {
|
||||||
Collection<ScriptContext<?>> contexts = super.scriptContexts();
|
Collection<ScriptContext<?>> contexts = super.scriptContexts();
|
||||||
|
contexts.add(StatefulFactoryTestScript.CONTEXT);
|
||||||
contexts.add(FactoryTestScript.CONTEXT);
|
contexts.add(FactoryTestScript.CONTEXT);
|
||||||
contexts.add(EmptyTestScript.CONTEXT);
|
contexts.add(EmptyTestScript.CONTEXT);
|
||||||
contexts.add(TemplateScript.CONTEXT);
|
contexts.add(TemplateScript.CONTEXT);
|
||||||
@ -37,6 +38,48 @@ public class FactoryTests extends ScriptTestCase {
|
|||||||
return contexts;
|
return contexts;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public abstract static class StatefulFactoryTestScript {
|
||||||
|
private final int x;
|
||||||
|
private final int y;
|
||||||
|
|
||||||
|
public StatefulFactoryTestScript(int x, int y, int a, int b) {
|
||||||
|
this.x = x*a;
|
||||||
|
this.y = y*b;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getX() {
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getY() {
|
||||||
|
return y*2;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final String[] PARAMETERS = new String[] {"test"};
|
||||||
|
public abstract Object execute(int test);
|
||||||
|
|
||||||
|
public interface StatefulFactory {
|
||||||
|
StatefulFactoryTestScript newInstance(int a, int b);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface Factory {
|
||||||
|
StatefulFactory newFactory(int x, int y);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final ScriptContext<StatefulFactoryTestScript.Factory> CONTEXT =
|
||||||
|
new ScriptContext<>("test", StatefulFactoryTestScript.Factory.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testStatefulFactory() {
|
||||||
|
StatefulFactoryTestScript.Factory factory = scriptEngine.compile(
|
||||||
|
"stateful_factory_test", "test + x + y", StatefulFactoryTestScript.CONTEXT, Collections.emptyMap());
|
||||||
|
StatefulFactoryTestScript.StatefulFactory statefulFactory = factory.newFactory(1, 2);
|
||||||
|
StatefulFactoryTestScript script = statefulFactory.newInstance(3, 4);
|
||||||
|
assertEquals(22, script.execute(3));
|
||||||
|
statefulFactory.newInstance(5, 6);
|
||||||
|
assertEquals(26, script.execute(7));
|
||||||
|
}
|
||||||
|
|
||||||
public abstract static class FactoryTestScript {
|
public abstract static class FactoryTestScript {
|
||||||
private final Map<String, Object> params;
|
private final Map<String, Object> params;
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user