/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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.cxf.javascript; import java.io.IOException; import java.io.Reader; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Collection; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.logging.Logger; import org.apache.cxf.common.classloader.ClassLoaderUtils; import org.apache.cxf.common.logging.LogUtils; import org.apache.cxf.javascript.service.ServiceJavascriptBuilder; import org.apache.cxf.javascript.types.SchemaJavascriptBuilder; import org.apache.cxf.service.model.SchemaInfo; import org.apache.cxf.service.model.ServiceInfo; import org.apache.cxf.test.TestUtilities; import org.junit.Assert; import org.mozilla.javascript.Context; import org.mozilla.javascript.ContextFactory; import org.mozilla.javascript.Function; import org.mozilla.javascript.JavaScriptException; import org.mozilla.javascript.RhinoException; import org.mozilla.javascript.Scriptable; import org.mozilla.javascript.ScriptableObject; /** * Test utilities class with some Javascript capability included. */ public class JavascriptTestUtilities extends TestUtilities { private static final Logger LOG = LogUtils.getL7dLogger(JavascriptTestUtilities.class); private static boolean rhinoDebuggerUp; private ContextFactory rhinoContextFactory; private ScriptableObject rhinoScope; private Context rhinoContext; public static class JavaScriptAssertionFailed extends RuntimeException { private static final long serialVersionUID = -4776862940267084055L; public JavaScriptAssertionFailed(String what) { super(what); } } public static class JsAssert extends ScriptableObject { private static final long serialVersionUID = 8541723718764399405L; public JsAssert() { } public void jsConstructor(String exp) { LOG.severe("Assertion failed: " + exp); throw new JavaScriptAssertionFailed(exp); } @Override public String getClassName() { return "Assert"; } } public static class Trace extends ScriptableObject { private static final long serialVersionUID = -8478529983202430575L; public Trace() { } //when acting as a function public Trace(String msg) { jsStaticFunction_trace(msg); } @Override public String getClassName() { return "org_apache_cxf_trace"; } // CHECKSTYLE:OFF public static void jsStaticFunction_trace(String message) { LOG.fine(message); } // CHECKSTYLE:ON } public static class Notifier extends ScriptableObject { private static final long serialVersionUID = 4581431707286545806L; private boolean notified; public Notifier() { } @Override public String getClassName() { return "org_apache_cxf_notifier"; } public synchronized boolean waitForJavascript(long timeout) { while (!notified) { try { wait(timeout); return notified; } catch (InterruptedException e) { // do nothing. } } return true; // only here if true on entry. } // CHECKSTYLE:OFF public synchronized void jsFunction_notify() { notified = true; notifyAll(); } // CHECKSTYLE:ON } public static class CountDownNotifier extends ScriptableObject { private static final long serialVersionUID = 2057930200102896264L; private CountDownLatch latch; public CountDownNotifier() { } @Override public String getClassName() { return "org_apache_cxf_count_down_notifier"; } public synchronized boolean waitForJavascript(long timeout) { while (true) { try { return latch.await(timeout, TimeUnit.MILLISECONDS); // if it returns at all, we're done. } catch (InterruptedException ie) { // empty on purpose. } } } // CHECKSTYLE:OFF public void jsConstructor(int count) { latch = new CountDownLatch(count); } public void jsFunction_count() { latch.countDown(); } // CHECKSTYLE:ON } public JavascriptTestUtilities(Class<?> classpathReference) { super(classpathReference); } public void initializeRhino() { rhinoContextFactory = new ContextFactory(); if (System.getProperty("cxf.jsdebug") != null && !rhinoDebuggerUp) { try { Class<?> debuggerMain = ClassLoaderUtils.loadClass("org.mozilla.javascript.tools.debugger.Main", getClass()); if (debuggerMain != null) { Method mainMethod = debuggerMain.getMethod("mainEmbedded", ContextFactory.class, Scriptable.class, String.class); mainMethod.invoke(null, rhinoContextFactory, rhinoScope, "Debug embedded JavaScript."); rhinoDebuggerUp = true; } } catch (Exception e) { LOG.log(Level.WARNING, "Failed to launch Rhino debugger", e); } } rhinoContext = rhinoContextFactory.enterContext(); rhinoScope = rhinoContext.initStandardObjects(); try { ScriptableObject.defineClass(rhinoScope, JsAssert.class); ScriptableObject.defineClass(rhinoScope, Trace.class); ScriptableObject.defineClass(rhinoScope, Notifier.class); ScriptableObject.defineClass(rhinoScope, CountDownNotifier.class); // so that the stock test for IE can gracefully fail. rhinoContext.evaluateString(rhinoScope, "var window = new Object();", "<internal>", 0, null); } catch (IllegalAccessException e) { throw new RuntimeException(e); } catch (InstantiationException e) { throw new RuntimeException(e); } catch (InvocationTargetException e) { throw new RuntimeException(e); } finally { Context.exit(); } JsSimpleDomNode.register(rhinoScope); JsSimpleDomParser.register(rhinoScope); JsNamedNodeMap.register(rhinoScope); JsXMLHttpRequest.register(rhinoScope); } public void readResourceIntoRhino(String resourceClasspath) throws IOException { Reader js = getResourceAsReader(resourceClasspath); rhinoContextFactory.enterContext(rhinoContext); try { rhinoContext.evaluateReader(rhinoScope, js, resourceClasspath, 1, null); } finally { Context.exit(); } } public void readStringIntoRhino(String js, String sourceName) { LOG.fine(sourceName + ":\n" + js); rhinoContextFactory.enterContext(rhinoContext); try { rhinoContext.evaluateString(rhinoScope, js, sourceName, 1, null); } finally { Context.exit(); } } public ScriptableObject getRhinoScope() { return rhinoScope; } public ContextFactory getRhinoContextFactory() { return rhinoContextFactory; } public interface JSRunnable<T> { T run(Context context); } public <T> T runInsideContext(Class<T> clazz, JSRunnable<?> runnable) { rhinoContextFactory.enterContext(rhinoContext); try { return clazz.cast(runnable.run(rhinoContext)); } finally { Context.exit(); } } public Object javaToJS(Object value) { return Context.javaToJS(value, rhinoScope); } public Object rhinoNewObject(final String constructorName) { return runInsideContext(Object.class, new JSRunnable<Object>() { public Object run(Context context) { return context.newObject(rhinoScope, constructorName); } }); } /** * Evaluate a javascript expression, returning the raw Rhino object. * * @param jsExpression the javascript expression. * @return return value. */ public Object rhinoEvaluate(final String jsExpression) { return runInsideContext(Object.class, new JSRunnable<Object>() { public Object run(Context context) { return rhinoContext.evaluateString(rhinoScope, jsExpression, "<testcase>", 1, null); } }); } /** * Call a method on a Javascript object. * * @param that the object. * @param methodName method name. * @param args arguments. * @return */ public Object rhinoCallMethod(Scriptable that, String methodName, Object... args) { return ScriptableObject.callMethod(rhinoContext, that, methodName, args); } /** * Call a method on a Javascript object and convert result to specified class. Convert to the requested * class. * * @param <T> type * @param clazz class object. * @param that Javascript object. * @param methodName method * @param args arguments * @return return value. */ public <T> T rhinoCallMethodConvert(Class<T> clazz, Scriptable that, String methodName, Object... args) { return clazz.cast(Context.jsToJava(rhinoCallMethod(that, methodName, args), clazz)); } /** * Call a method on a Javascript object inside context brackets. * * @param <T> return type. * @param clazz class for the return type. * @param that object * @param methodName method * @param args arguments. Caller must run javaToJS as appropriate * @return return value. */ public <T> T rhinoCallMethodInContext(final Class<T> clazz, final Scriptable that, final String methodName, final Object... args) { // we end up performing the cast twice to make the compiler happy. return runInsideContext(clazz, new JSRunnable<T>() { public T run(Context context) { return rhinoCallMethodConvert(clazz, that, methodName, args); } }); } /** * Evaluate a Javascript expression, converting the return value to a convenient Java type. * * @param <T> The desired type * @param jsExpression the javascript expression. * @param clazz the Class object for the desired type. * @return the result. */ public <T> T rhinoEvaluateConvert(String jsExpression, Class<T> clazz) { return clazz.cast(Context.jsToJava(rhinoEvaluate(jsExpression), clazz)); } /** * Call a JavaScript function within the Context. Optionally, require it to throw an exception equal to a * supplied object. If the exception is called for, this function will either return null or Assert. * * @param expectingException Exception desired, or null. * @param functionName Function to call. * @param args args for the function. Be sure to Javascript-ify them as appropriate. * @return */ public Object rhinoCallExpectingExceptionInContext(final Object expectingException, final String functionName, final Object... args) { return runInsideContext(Object.class, new JSRunnable<Object>() { public Object run(Context context) { return rhinoCallExpectingException(expectingException, functionName, args); } }); } /** * Call a Javascript function, identified by name, on a set of arguments. Optionally, expect it to throw * an exception. * * @param expectingException * @param functionName * @param args * @return */ public Object rhinoCallExpectingException(final Object expectingException, final String functionName, final Object... args) { Object fObj = rhinoScope.get(functionName, rhinoScope); if (!(fObj instanceof Function)) { throw new RuntimeException("Missing test function " + functionName); } Function function = (Function)fObj; try { return function.call(rhinoContext, rhinoScope, rhinoScope, args); } catch (RhinoException angryRhino) { if (expectingException != null && angryRhino instanceof JavaScriptException) { JavaScriptException jse = (JavaScriptException)angryRhino; Assert.assertEquals(jse.getValue(), expectingException); return null; } String trace = angryRhino.getScriptStackTrace(); Assert.fail("JavaScript error: " + angryRhino.toString() + " " + trace); } catch (JavaScriptAssertionFailed assertion) { Assert.fail(assertion.getMessage()); } return null; } public Object rhinoCallInContext(String functionName, Object... args) { return rhinoCallExpectingExceptionInContext(null, functionName, args); } public Object rhinoCall(String functionName, Object... args) { return rhinoCallExpectingException(null, functionName, args); } public <T> T rhinoCallConvert(String functionName, Class<T> clazz, Object... args) { return clazz.cast(Context.jsToJava(rhinoCallInContext(functionName, args), clazz)); } public void loadJavascriptForService(ServiceInfo serviceInfo) { Collection<SchemaInfo> schemata = serviceInfo.getSchemas(); BasicNameManager nameManager = BasicNameManager.newNameManager(serviceInfo); NamespacePrefixAccumulator prefixManager = new NamespacePrefixAccumulator(serviceInfo .getXmlSchemaCollection()); for (SchemaInfo schema : schemata) { SchemaJavascriptBuilder builder = new SchemaJavascriptBuilder(serviceInfo .getXmlSchemaCollection(), prefixManager, nameManager); String allThatJavascript = builder.generateCodeForSchema(schema.getSchema()); readStringIntoRhino(allThatJavascript, schema.toString() + ".js"); } ServiceJavascriptBuilder serviceBuilder = new ServiceJavascriptBuilder(serviceInfo, null, prefixManager, nameManager); serviceBuilder.walk(); String serviceJavascript = serviceBuilder.getCode(); readStringIntoRhino(serviceJavascript, serviceInfo.getName() + ".js"); } public static String scriptableToString(Scriptable scriptable) { StringBuilder builder = new StringBuilder(); for (Object propid : scriptable.getIds()) { String propIdString = Context.toString(propid); int propIntKey = -1; try { propIntKey = Integer.parseInt(propIdString); } catch (NumberFormatException nfe) { // dummy. } String propValue; if (propIntKey >= 0) { propValue = Context.toString(scriptable.get(propIntKey, scriptable)); } else { propValue = Context.toString(scriptable.get(propIdString, scriptable)); } builder.append(propIdString); builder.append(": "); builder.append(propValue); builder.append("; "); } return builder.toString(); } }