Pergunta

If I pass an object to Rhino in Java, is there an annotation I can use in that object's class to hide methods\fields from Rhino JavaScripts to make them inaccessible to the JavaScripts? Or any other method of doing this?

I did some research, and it looks like I could wrap the Objects with Scriptable, but that seems like a messier solution than what might be potentially really easy to do as it seems like it would be a fairly standard feature.

Thanks.

Foi útil?

Solução

I wasn't able to find any support for this in the Rhino engine (if anyone knows of such a thing, please say so.)

That said, it is easy to implement. You need to implement your own WrapFactory, and your own NativeJavaObject. You also need to invoke create your own ContextFactory to assure your WrapFactory is being used on all Context objects used throughout the script engine. This sounds like a lot of work... but really it is actually just a lot of wrapper code. Here is a snippet of my implementation. WHAT IS MISSING: is the actual call to ContextFactory.initGlobal to set the global ContextFactory to your implementation of ContextFactory.

Obviously this code is not thread safe

class ProtectedContextFactory extends ContextFactory
{
    private static final ProtectedWrapFactory wrapper = new ProtectedWrapFactory();

    @Override
    protected Context makeContext()
    {
        Context c = super.makeContext();
        c.setWrapFactory(wrapper);

        return c;
    }
}

class ProtectedWrapFactory extends WrapFactory
{
    @Override
    public Scriptable wrapAsJavaObject(Context cx, Scriptable scope, Object javaObject, Class<?> staticType)
    {
        return new ProtectedNativeJavaObject(scope, javaObject, staticType);
    }
}

class ProtectedNativeJavaObject extends NativeJavaObject
{
    private static final HashMap<Class<?>, ArrayList<String>> CLASS_PROTECTION_CACHE = new HashMap<Class<?>, ArrayList<String>>();

    private ArrayList<String> m_protectedMembers;

    public ProtectedNativeJavaObject(Scriptable scope, Object javaObject, Class<?> staticType)
    {
        super(scope, javaObject, staticType);

        Class<?> clazz = javaObject != null ? javaObject.getClass() : staticType;

        m_protectedMembers = CLASS_PROTECTION_CACHE.get(clazz);

        if(m_protectedMembers == null)
            m_protectedMembers = processClass(clazz);
    }

    private static ArrayList<String> processClass(Class<?> clazz)
    {
        ArrayList<String> protectedMethods = new ArrayList<String>();

        CLASS_PROTECTION_CACHE.put(clazz, protectedMethods);

        for(Method m : clazz.getMethods())
        {
            if(m.getAnnotation(ScriptHiddenMember.class) != null)
                protectedMethods.add(m.getName());
        }

        for(Field f : clazz.getFields())
        {
            if(f.getAnnotation(ScriptHiddenMember.class) != null)
                protectedMethods.add(f.getName());
        }
        return protectedMethods;
    }

    @Override
    public boolean has(String name, Scriptable start)
    {
        if(m_protectedMembers.contains(name))
            return false;
        else
            return super.has(name, start);
    }

    @Override
    public Object get(String name, Scriptable start)
    {
        if(m_protectedMembers.contains(name))
            return NOT_FOUND;
        else
            return super.get(name, start);
    }
}

@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ScriptHiddenMember {}

Outras dicas

Using an interface and hiding methods by not including them in the interface will not work, the script will still be able to access methods without signatures in the interface, even when you add the object to the engine while casting it to it's interface. The way I've found to hide methods is to declare them private, protected, or (no modifier); this keeps the script from having access. If you use protected or (no modifier), then you can still use them in your code in other classes (assuming you are calling them from a valid place) while having them hidden from JS.

For variables, as long as you declare them private, protected, or (no modifier), and do not include any getter methods, the variable will be hidden. Public variables are accessible even without getter methods.

Using this interface:

public interface NodeInterface {

    public int getPosition();

}

And this implementing class:

public class Node implements NodeInterface{

    private int x = 5;

    public int y = 8;

    private int position = 0;

    public int getPosition(){return position;}

    private String getString(){return "hello";}

    public String getBing(){return "bing";}

}

Results in this code:

ScriptEngineManager factory = new ScriptEngineManager();
ScriptEngine engine = factory.getEngineByName("JavaScript");
NodeInterface node = (NodeInterface)new Node();
engine.put("node", node);
try {
    engine.eval("println(node.x)");//undefined
    engine.eval("println(node.y)");//8
    engine.eval("println(node.position)");//0
    engine.eval("println(node.getPosition())");//0
    engine.eval("println(node.getBing());");//hello
    engine.eval("println(node.getString())");//TypeError: Cannot find function getString.
} catch (ScriptException e1) {
    e1.printStackTrace();
}

Why you don't try to Java interfaces. For each class that will be exposed to Rhino, define an interface which contains getters and setters for the fields you want to expose, and methods that you want to expose.

More about how to access Java interfaces you can find on Rhino: Access Java interface variables in Javascript implementation

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top