summaryrefslogtreecommitdiffstats
path: root/tags/java-stable-20060304/sca/container.js/src/main/java/org/apache/tuscany/container/js/rhino/RhinoScript.java
blob: 4ddee12fe460e03cf0c0fa8c62f8ab8a118caea7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
/**
 *
 *  Copyright 2005 The Apache Software Foundation or its licensors, as applicable.
 *
 *  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 org.apache.tuscany.container.js.rhino;

import java.util.Iterator;
import java.util.Map;

import org.mozilla.javascript.Context;
import org.mozilla.javascript.ContextFactory;
import org.mozilla.javascript.Function;
import org.mozilla.javascript.Script;
import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.Wrapper;

/**
 * Represents, and is responsible for dispatching to, a JavaScript artifact in Rhino
 */
public class RhinoScript {

    private String scriptName;

    private String script;

    private Scriptable scriptScope;

    private Scriptable sharedScope;

    /*
     * Enable dynamic scopes so a script can be used concurrently with a global shared scope and individual execution
     * scopes. See http://www.mozilla.org/rhino/scopes.html TODO: need to review how ths fits in with Tuscany scopes
     */
    private static class MyFactory extends ContextFactory {
        protected boolean hasFeature(Context cx, int featureIndex) {
            if (featureIndex == Context.FEATURE_DYNAMIC_SCOPE) {
                return true;
            }
            return super.hasFeature(cx, featureIndex);
        }
    }

    static {
        ContextFactory.initGlobal(new MyFactory());
    }

    /**
     * Create a new RhinoInvoker.
     * 
     * @param scriptName the name of the script. Can be anything, only used in messages to identify the script
     * @param script the complete script
     */
    public RhinoScript(String scriptName, String script) {
        this(scriptName, script, (Map) null);
    }

    /**
     * Create a new RhinoInvoker.
     * 
     * @param scriptName the name of the script. Can be anything, only used in messages to identify the script
     * @param script the complete script
     * @param context name-value pairs that are added in to the scope where the script is compiled. May be null. The
     *        value objects are made available to the script by using a variable with the name.
     */
    public RhinoScript(String scriptName, String script, Map context) {
        this.scriptName = scriptName;
        this.script = script;
        initScriptScope(scriptName, script, context);
        initSharedScope();
    }

    /**
     * Construct a RhinoInvoker from another RhinoInvoker object. This uses the original script scope so the script
     * doesn't need to be recompiled.
     */
    protected RhinoScript(String scriptName, String script, Scriptable scriptScope) {
        this.scriptName = scriptName;
        this.script = script;
        this.scriptScope = scriptScope;
        initSharedScope();
    }

    /**
     * Invoke a script function
     * 
     * @param functionName the name of the function to invoke.
     * @param arg arguments to the function, may be a single object or an array of objects.
     * @return the function return value.
     */
    public Object invoke(String functionName, Object args) {
        return invoke(functionName, args, null, null);
    }

    /**
     * Invoke a script function
     * 
     * @param functionName the name of the function to invoke.
     * @param arg arguments to the function, may be a single object or an array of objects.
     * @param contexts a Map of name-value pairs which are added to the invocation Scope to enable the script to access
     *        the values by using the variable in name.
     * @return the function return value.
     */
    public Object invoke(String functionName, Object args, Map contexts) {
        return invoke(functionName, args, null, contexts);
    }

    /**
     * Invoke a script function
     * 
     * @param functionName the name of the function to invoke.
     * @param arg arguments to the function, may be a single object or an array of objects.
     * @param responseClass the desired class of the response object.
     * @param contexts a Map of name-value pairs which are added to the invocation Scope to enable the script to access
     *        the values by using the variable in name.
     * @return the function return value.
     */
    public Object invoke(String functionName, Object arg, Class responseClass, Map contexts) {
        Context cx = Context.enter();
        try {
            Function function = getFunction(scriptScope, functionName);
            Scriptable invocationScope = getInvocationScope(cx, contexts);
            Object[] args = processArgs(arg, invocationScope);
            Object jsResponse = function.call(cx, invocationScope, invocationScope, args);
            Object response = processResponse(jsResponse, responseClass);
            return response;
        } finally {
            Context.exit();
        }
    }

    /**
     * Turn args to JS objects and convert any OMElement to E4X XML
     */
    protected Object[] processArgs(Object arg, Scriptable scope) {
        // TODO: implement pluggable way to transform objects (eg SDO or AXIOM) to E4X XML objects
        // if (arg instanceof OMElement) {
        // try {
        // arg = E4XAXIOMUtils.toScriptableObject((OMElement) arg, scope);
        // } catch (XmlException e) {
        // throw new RuntimeException(e);
        // }
        // } else if (arg instanceof MessageContext) {
        // arg = new E4XMessageContext((MessageContext) arg, scope);
        // }
        Object[] args;
        if (arg == null) {
            args = new Object[] { null };
        } else if (arg.getClass().isArray()) {
            args = (Object[]) arg;
            for (int i = 0; i < args.length; i++) {
                args[i] = Context.toObject(args[i], scope);
            }
        } else {
            args = new Object[] { Context.toObject(arg, scope) };
        }
        return args;
    }

    /**
     * Unwrap and convert response
     */
    protected Object processResponse(Object response, Class responseClass) {
        // TODO: implement pluggable way to transform E4X XML into specific objects (eg SDO or AXIOM)
        // } else if (response instanceof XMLObject) {
        // response = E4XAXIOMUtils.toOMElement((XMLObject) response);
        if (Context.getUndefinedValue().equals(response)) {
            response = null;
        } else if (response instanceof Wrapper) {
            response = ((Wrapper) response).unwrap();
        } else {
            if (responseClass != null) {
                response = Context.toType(response, responseClass);
            } else {
                response = Context.toType(response, String.class);
            }
        }
        return response;
    }

    /**
     * Create a Rhino scope and compile the script into it
     */
    protected void initScriptScope(String fileName, String scriptCode, Map context) {
        Context cx = Context.enter();
        try {

            this.scriptScope = cx.initStandardObjects(null, true);
            Script compiledScript = cx.compileString(scriptCode, fileName, 1, null);
            compiledScript.exec(cx, scriptScope);
            addContexts(scriptScope, context);

        } finally {
            Context.exit();
        }
    }

    /**
     * Initializes the shared scope
     */
    protected void initSharedScope() {
        Context cx = Context.enter();
        try {

            this.sharedScope = cx.newObject(scriptScope);
            sharedScope.setPrototype(scriptScope);
            sharedScope.setParentScope(null);

        } finally {
            Context.exit();
        }
    }

    /**
     * Get a Rhino scope for the function invocation. If the invocation has no context objects then this will use the
     * shared scope otherwise a new scope is created to hold the context objects. Any new variables the executing script
     * might define will go in the sharedScope. This new scope is just to hold the invocation specific context objects.
     */
    protected Scriptable getInvocationScope(Context cx, Map contexts) {

        Scriptable scope;
        if (contexts == null || contexts.size() == 0) {
            scope = sharedScope;
        } else {
            scope = cx.newObject(sharedScope);
            scope.setPrototype(sharedScope);
            scope.setParentScope(null);
            addContexts(scope, contexts);
        }

        return scope;
    }

    /**
     * Add the context to the scope. This will make the objects available to a script by using the name it was added
     * with.
     */
    protected void addContexts(Scriptable scope, Map contexts) {
        if (contexts != null) {
            for (Iterator i = contexts.keySet().iterator(); i.hasNext();) {
                String name = (String) i.next();
                Object value = contexts.get(name);
                if (value != null) {
                    scope.put(name, scope, Context.toObject(value, scope));
                }
            }
        }
    }

    /**
     * Get the Rhino Function object for the named script function
     */
    protected Function getFunction(Scriptable scope, String functionName) {

        Object handleObj = scope.get(functionName, scope);

        if (!(handleObj instanceof Function)) {
            throw new RuntimeException("script function '" + functionName + "' is undefined or not a function in script "
                    + scriptName);
        }

        return (Function) handleObj;
    }

    /**
     * Make a copy of this RhinoScript object. This shares the script scope to avoid the overhead of recompiling the
     * script, and to allow any initialization done by the script to be shared.
     */
    public RhinoScript copy() {
        return new RhinoScript(scriptName, script, scriptScope);
    }

    /**
     * Update the scope where the script is complied with new context values
     * 
     * @param properties
     */
    public void updateScriptScope(Map context) {
        Context.enter();
        try {
            addContexts(scriptScope, context);
        } finally {
            Context.exit();
        }
    }

}