Expand my Community achievements bar.

SOLVED

Invoking a JS method using Rhino Script Engine

Avatar

Level 2

Hi All

 

We are trying to execute handlebar templates via a Script Engine Inside AEM. This Handlebar Execution needs to done using the Rhino Script Engine as we don't want to go to with Java based execution in order to be flexible in future to replace handlebars with any other client side templating language. I am able to load the Rhino script Engine successfully and also able to evaluate the handlebar.js file using the engine. However when I am trying to obtain a"javax.script.Invocable" object from "org.apache.sling.scripting.javascript.internal.RhinoJavaScriptEngine" to call a JS method, I am getting below error.

 

Caused by: org.apache.sling.api.SlingException: Cannot get DefaultSlingScript: org.apache.sling.scripting.javascript.internal.RhinoJavaScriptEngine cannot be cast to javax.script.Invocable

at org.apache.sling.scripting.core.impl.DefaultSlingScript.service(DefaultSlingScript.java:481)

at com.day.cq.wcm.tags.IncludeTag.includeScript(IncludeTag.java:167)

at com.day.cq.wcm.tags.IncludeTag.doEndTag(IncludeTag.java:87)

at org.apache.jsp.apps.saks.components.testing.testing_jsp._jspx_meth_cq_005finclude_005f0(testing_jsp.java:178)

at org.apache.jsp.apps.saks.components.testing.testing_jsp._jspService(testing_jsp.java:152)

at org.apache.sling.scripting.jsp.jasper.runtime.HttpJspBase.service(HttpJspBase.java:70)

at javax.servlet.http.HttpServlet.service(HttpServlet.java:722)

at org.apache.sling.scripting.jsp.jasper.servlet.JspServletWrapper.service(JspServletWrapper.java:502)

 

It seems AEM has done the implementation of Rhino Engine in a different way as JDK because the same code runs fine as a Stand alone Java Program. Below is the code snippet being used to execute the handlebars as suggested on Java Scripting Programmer's Guide.

 

    ScriptEngine engine = scriptEngineManager.getEngineByName("javascript");

    URL fileURL = FrameworkUtil.getBundle(getClass()).getEntry("META-INF/js/handlebars.js");

            try {

                String jsSource = IOUtils.toString(fileURL.openStream());

                engine.eval(jsSource);

                } catch (IOException e) {

                      e.printStackTrace();

                }

    Object mustache = engine.eval("Handlebars");

     Invocable invocable = (Invocable) engine;

    String template = "Email addresses of {{contact.name}}:\n"
                + "{{#contact.emails}}\n" + "- {{.}}\n" + "{{/contact.emails}}";

   Object compiledTemplate = invocable.invokeMethod(handlebar, "compile",template);

If anyone has done a similar thing using Rhino or any other JS Engine inside AEM, please help me identify the cause & solution to above problem.

 

Thanks

Nitin

1 Accepted Solution

Avatar

Correct answer by
Employee

If you are just trying to do this from the scripting tier, you can do several other things:

  • Use ESP scripts for your component scripts
  • Use ESP scripts invoked from JSPs (via sling:include/cq:include)
  • Use Sightly

For example, here's a Sightly JS Use object which uses Handlebars:

use(function () { load("/etc/clientlibs/granite/handlebars/source/handlebars.js"); var templateText = "Email addresses of {{contact.name}}:\n" + "{{#contact.emails}}\n" + "- {{.}}\n" + "{{/contact.emails}}"; var template = Handlebars.compile(templateText); return { output : template({contact : { name : 'foo', emails : [ 'a@a.com', 'b@b.com' ] } }) }; });

Of course in reality, you wouldn't want to hardcode the object like that :)

View solution in original post

9 Replies

Avatar

Employee

I think you're going to have to post some code.

FWIW, it is true that RhinoJavaScriptEngine cannot be cast to Invocable. I guess the question is why you need to do this.

Avatar

Employee

Since you are invoking this from inside a JSP you might want to consider using the Java derivative, moustache.java.  Rhino is much slower than compiled Java, so you'd get better performance if all you need is Moustache (with no modifications/extensions).

If your goal is specifically to get the Rhino engine working, you have to get the engine and pass your script in as a parameter.   In this case it seems you are getting the Rhino engine but are mis-casting the object to something else.  Can you provide us an example of the source code indicated in the stack trace where you are invoking Rhino?

Avatar

Level 2

We don't want to do it using a Java bundle for handlebar as we want to have the flexibility of replacing handlebars with dust or react in future & although we have a Java based Processor for Handlebars (jknack) , not sure if we will have for other templating languages or not. We wanted to use Nashorn as the Script Engine for the same, but some I am even not able to get Nashorn working inside AEM  (Please see http://help-forums.adobe.com/content/adobeforums/en/experience-manager-forum/adobe-experience-manage... ) so had to work with Rhino for the time being. 

Avatar

Level 2

Sorry I didn't pasted code in the issue itself. So, We are trying to execute handlebar templates inside AEM. We want to render these templates using a JS Engine, so if tomorrow we need to replace handlebar with other templating language as dust etc, we can do the same. I am able to load the Rhino script Engine successfully and also able to evaluate the handlebar.js file using the engine. However when I am trying to obtain a"javax.script.Invocable" object from "org.apache.sling.scripting.javascript.internal.RhinoJavaScriptEngine" to call a JS method, I getting this error.

 

    ScriptEngine engine = scriptEngineManager.getEngineByName("javascript");

    URL fileURL = FrameworkUtil.getBundle(getClass()).getEntry("META-INF/js/handlebars.js");

            try {

                String jsSource = IOUtils.toString(fileURL.openStream());

                engine.eval(jsSource);

                } catch (IOException e) {

                      e.printStackTrace();

                }

    Object mustache = engine.eval("Handlebars");

   Invocable invocable = (Invocable) engine;

   String template = "Email addresses of {{contact.name}}:\n"
                + "{{#contact.emails}}\n" + "- {{.}}\n" + "{{/contact.emails}}";

   Object compiledTemplate = invocable.invokeMethod(handlebar, "compile",template);

 

Thanks

Nitin

Avatar

Employee

Right, that cast won't work. Is there somewhere you saw that it was documented?

Avatar

Correct answer by
Employee

If you are just trying to do this from the scripting tier, you can do several other things:

  • Use ESP scripts for your component scripts
  • Use ESP scripts invoked from JSPs (via sling:include/cq:include)
  • Use Sightly

For example, here's a Sightly JS Use object which uses Handlebars:

use(function () { load("/etc/clientlibs/granite/handlebars/source/handlebars.js"); var templateText = "Email addresses of {{contact.name}}:\n" + "{{#contact.emails}}\n" + "- {{.}}\n" + "{{/contact.emails}}"; var template = Handlebars.compile(templateText); return { output : template({contact : { name : 'foo', emails : [ 'a@a.com', 'b@b.com' ] } }) }; });

Of course in reality, you wouldn't want to hardcode the object like that :)

Avatar

Level 2

Hi Justin,

This cast is from JDK docs it seems AEM hasn't implemented it in the same way as JDK has done.

http://docs.oracle.com/javase/7/docs/technotes/guides/scripting/programmer_guide/

Thanks

Nitin

Avatar

Employee

Oh, and BTW, there is already a community-based Handlebars ScriptEngine here: http://svn.apache.org/repos/asf/sling/whiteboard/ieb/handlebars

To me, the idea of replacing all your HBS templates with Dust seems like it is going to be a pain regardless of how you implement the template compilation.

Avatar

Level 2

Hi Justin,

The engine @ http://svn.apache.org/repos/asf/sling/whiteboard/ieb/handlebars/ use jknack which is a java based implementation of handlebars. We have already used it for our Server Side Java based Execution. What we are trying to do here is Server Side JS Based Execution.

Thanks

Nitin