1

I am writing a Scala application (that is supposed to run on Hadoop using Spark) and my users are to execute JavaScript snippets that they upload and I want to provide access to certain helper functions written in Scala (like "make an HTTP call" etc.) to these JavaScript users. So what I do is writing a big JavaScriptHelpers object and then give access to that object using

engine = scriptEngineManager.getEngineByName("JavaScript")
engine.put("jql", JavaScriptHelpers)

so users can say jql.httpPost(...) from within JavaScript. The Scala code that makes this possible looks as follows:

def httpPost(where: String, params: Object): Try[String] = {
  params match {
    // JavaScript string becomes Java's String:
    case body: String =>
      // ...

    // JavaScript object becomes Java's ScriptableObject
    case obj: ScriptableObject =>
      val params = convertToMap(obj)
      // ...
  }
}

protected def convertToMap(obj: ScriptableObject): Map[String, String] = {
  (for (key <- obj.getIds().toList) yield {
    (key.toString, obj.get(key) match {
      case s: String =>
        s
      case d: java.lang.Double if d.toString.endsWith(".0") =>
        d.toInt.toString
      case other => other.toString
    })
  }).toMap
}

The only way I found to access information stored in JavaScript objects is to look at them as an instance of sun.org.mozilla.javascript.ScriptableObject. Now this works like a charm on my local OpenJDK installation

java version "1.7.0_75"
OpenJDK Runtime Environment (fedora-2.5.4.2.fc20-x86_64 u75-b13)
OpenJDK 64-Bit Server VM (build 24.75-b04, mixed mode)

but when I run the same code on my Hadoop cluster, which is running

java version "1.7.0_67"
Java(TM) SE Runtime Environment (build 1.7.0_67-b01)
Java HotSpot(TM) 64-Bit Server VM (build 24.65-b04, mixed mode)

then I get:

java.lang.NoClassDefFoundError: sun/org/mozilla/javascript/ScriptableObject
    sun.org.mozilla.javascript.internal.JavaMembers.discoverAccessibleMethods(JavaMembers.java:383)
    sun.org.mozilla.javascript.internal.JavaMembers.discoverAccessibleMethods(JavaMembers.java:335)
    sun.org.mozilla.javascript.internal.JavaMembers.reflect(JavaMembers.java:455)
    sun.org.mozilla.javascript.internal.JavaMembers.<init>(JavaMembers.java:76)
    sun.org.mozilla.javascript.internal.JavaMembers.lookupClass(JavaMembers.java:847)
    sun.org.mozilla.javascript.internal.NativeJavaObject.initMembers(NativeJavaObject.java:88)
    sun.org.mozilla.javascript.internal.NativeJavaObject.<init>(NativeJavaObject.java:78)
    sun.org.mozilla.javascript.internal.NativeJavaObject.<init>(NativeJavaObject.java:68)
    ...

and looking at the version of Rhino that Oracle bundles with the JDK 7 as downloadable from http://www.oracle.com/technetwork/opensource/jdk7-source-1634015.html it seems like all sun.org.mozilla.javascript.* classes have been moved to sun.org.mozilla.javascript.internal.*.

Now how do I deal with that situation? Is there any Rhino-independent way of accessing the fields of a JavaScript object in Java? Or, how can I force the Oracle JVM to use the ...javascript.internal.ScriptableObject while using ...javascript.ScriptableObject in my local environment?

Any help much appreciated.

tgpfeiffer
  • 1,698
  • 2
  • 18
  • 22
  • Hm. Just out of my mind: you are providing a DomainSpecificLanguage with Jacascript Syntax to your users.Did you take a look a look at JSR 233? Also converting a Javascript Object there are many Libraries that already cover the topic, my first hit was http://stackoverflow.com/questions/1395551/convert-a-json-string-to-object-in-java – chris polzer Mar 13 '15 at 11:09

1 Answers1

1

You can use function overloading instead.

// `params` matches a JavaScript string
def httpPost(where: String, params: String): Try[String] = {
  // ...
}

// `params` matches a JavaScript object
def httpPost(where: String, params: java.util.Map[_, _]): Try[String] = {
  // ...
}

This solution worked on my environment (Oracle JDK 8 and OpenJDK 1.7).

gwatanabe
  • 26
  • 3
  • Thank you very much, that worked! Note that it must indeed be `java.util.Map`, not Scala's `Map`, even if the implicit conversions from `scala.collection.JavaConversions` are present. – tgpfeiffer Mar 16 '15 at 01:56