cancel
Showing results for 
Search instead for 
Did you mean: 

'GroovyPresentationScriptProcessor'

deas0815
Star Contributor
Star Contributor
Hallo everybody,

I was wondering whether (and how) one would/should implement a "GroovyPresentationScriptProcessor" so one would use
groovy instead of JavaScript or both (choosing the interpreter dependent on the extension of the script).

I guess I could implement that myself, but I'd like to avoid  reinventing the wheel.

I stepped across "groovyProcessor.amp" at http://forge.alfresco.com/frs/?group_id=105. The project looks
close to what I want but seems orphaned.

Is this approach (groovyProcessor.amp and the idea in general) wrong ?

Suggestions/Hints how/where to continue are appreciated.

regards
Andreas
20 REPLIES 20

pmonks
Star Contributor
Star Contributor
While the idea of implementing a Groovy aware ScriptProcessor is a good one (particularly longer term), currently that framework is fairly heavily Javascript centric, and I don't believe it will be possible to integrate Groovy (or other scripting languages) at that level without quite a bit of knock-on work required elsewhere in the Alfresco code.  And for that level of disruption / impact I'd rather wait and see what approach the Alfresco engineers are thinking of, rather than risk wasting my time heading down a path that heads off at a tangent.  Of course feel free to have a crack at it if you like - don't let my risk aversion hold you back!  :wink:

In the meantime however, what I've been exploring is implementing extensions in Groovy, using the Java versions of those extension points.  In some cases (eg. Web Scripts) this can't be done directly and requires a little bit of (reusable) Java glue code, but for many of these extension mechanisms Groovy can already be used directly - this is possible due to Spring's dynamic beans functionality (http://static.springframework.org/spring/docs/2.0.x/reference/dynamic-language.html).  In fact this approach should also work for BeanShell and JRuby, although I haven't tried that myself.

This has a number of advantages, including:
  • Using Spring dynamic beans means that developers can get all the usual benefits of a scripted language - hot redeployment, no server restarts etc.

  • The developer isn't limited to the Javascript API - they can leverage all of Alfresco's native APIs as well as any other native Java API that's in the Alfresco classpath

  • It doesn't break (or even change!) how Alfresco invokes extensions - from Alfresco's perspective it's dealing with just another native Java extension wired in via Spring

  • It doesn't change the security / access rules that Alfresco already has - Groovy extensions would play by exactly the same rules that native Java extensions already have
The only downsides I've hit to date are:
  1. This only works for Alfresco 3 and up - the 2.x releases include an older version of Spring that doesn't support dynamic beans properly

  2. In some cases (notably Web Scripts) you can't wire in a Spring dynamic bean directly, so some small Java glue code is required to delegate calls to the Groovy script

  3. The initial configuration of the Groovy script can't be done dynamically ie. you have to reconfigure Spring then restart Alfresco once before you get into the edit / test cycle
IMVHO these are pretty minor concerns though, since (1) most people should be on, or looking to move to, Alfresco 3 ASAP; (2) the glue code is trivial and reusable, so it only needs to be written once then can be forgotten about; and (3) the initial configuration of a custom extension is (for me at least) typically a vanishingly small percentage of the total time I spend implementing an extension.

Cheers,
Peter

deas0815
Star Contributor
Star Contributor
Hallo everybody, hallo Peter,

Peter: I also noticed that a "real" Groovy replacement for the Javascript Processor requires a few changes as the Javascript
based code is a bit scattered. Hence I would also appreciate a statement from the alfresco engineering team where there
are heading to regarding this.

Regarding grails controllers/views I more and more see SURF as a portal "replacement" as the framework has quite a lot
in common with a portal (w/o the personalization) - Pages, navigation, themes, chrome, regions, components and templates.
Seeing it from this perspective, I was wondering whether "application components" should be implemented just like portals
usually do it - Put the application in another servlet context and then do cross context calls.

Regarding grails, you would have to be a little careful with javascript and tags but I guess that would be the cleanest approach.
You could even implement the application without it beeing embedded in SURF and you should not have to care about
libraries beeing compatible (SURF + grails/JSF/whatever).

The bridge based approach (s. portals bridges) may head into library conflicts.

Comments ?

Suggestions ?

cheers
Andreas

pmonks
Star Contributor
Star Contributor
A Surf / Grails integration should be possible via a similar mechanism to what I've been using for repo Web Scripts - after all Surf components are just Web Scripts running outside the repo.  The questions are whether Grails requires access to the raw request and response objects (which is possible in a Java backed Web Script, but is not recommended) and whether it's capable of generating page fragments rather than fully formed pages (which would remain the responsibility of Surf).

BTW, mrogers (who posted on Jan 29th) is an Alfresco engineer, so I think we already have a statement.  :wink:

Cheers,
Peter

deas0815
Star Contributor
Star Contributor
Peter: Sometimes there are reasons to access the raw Servlet API objects. Today, I need content generated by webforms in controller code (webscript)
in virtual tomcat and a remotely running SURF app. The only way I've found so far is using the requestdispatcher and forwarding a request
to the default servlet or the endpoint proxy servlet. You also need this when you want to create (Groovy) "handy" Model-Beans from within
controller code as mentioned above.

Am I missing something ? Smiley Happy

Regarding "full blown" SURF application components, I still favour the "portal-style" cross context approach in general. I'll go ask the grails people for
their comments as I'm personally most interested in grails.

@Alfresco Engineering: Which approach in general would you suggest to implement "SURF application components" when one wants a full blown
web-application stack ?

Is there something wrong with the portal-style "cross context" approach ?

cheers
Andreas

alexander
Champ in-the-making
Champ in-the-making
Just an update:

groovyProcessor.amp (http://forge.alfresco.com/projects/minigrails/) was updated for version 3 stable. it allows using .groovy and .gsp in webscripts running in the context of repository (but not in standalone web applications like Surf / Share).

Webscripts functionality is not enabled by default, it requires changes in webscript-framework-application-context.xml:

<bean id="webscript.default" parent="webscript" class="org.alfresco.web.scripts.DeclarativeWebScript" scope="prototype"/>
has to be changed to:

<bean id="webscript.default" parent="webscript" class="org.alfresco.module.GroovyProcessor.WebScript.DeclarativeWebScript" scope="prototype">
        <property name="serviceRegistry">
            <ref bean="ServiceRegistry"/>
        </property>
</bean>

All previous functionality like custom tags, encoders etc. should still be supported (did not test too extensively).

Thanks
Alexander

deas0815
Star Contributor
Star Contributor
@Alexander: Great, I'll have a look at it.

In the meantime, I've made a quick hack implementing a SuperPresentationScriptProcessor which also supports Groovy controllers. It is
an extension to PresentationScriptProcessor which defaults to javascript but tries groovy when javascript is not found. Works fine
with SURF for me.

Just in case somebody brave is interested Smiley Wink

import groovy.lang.Binding;
import groovy.lang.GroovyCodeSource;
import groovy.lang.GroovyShell;

import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;

import org.alfresco.web.scripts.PresentationScriptProcessor;
import org.alfresco.web.scripts.ScriptContent;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class SuperPresentationScriptProcessor extends
      PresentationScriptProcessor {
   private static final Log logger = LogFactory
         .getLog(SuperPresentationScriptProcessor.class);
   private Map<String, GroovyCodeSource> groovyScriptCache = new ConcurrentHashMap<String, GroovyCodeSource>(
         256);
   private boolean groovyEnabled = true;

   public SuperPresentationScriptProcessor() {
      super();
      logger.debug("In god we trust :)");
   }

   /** AbstractWebScript has .js hard coded */
   @Override
   public Object executeScript(ScriptContent location,
         Map<String, Object> model) {
      logger.debug("Execute script " + location.getPath());
      String path = location.getPath();

      Object retval = null;
      if (isGroovyEnabled() && path.endsWith(".groovy")) {
         logger.debug("Its groovy !");
         HashMap<String, Object> variables = new HashMap<String, Object>();
         Map<String, Object> mod = model;
         if (mod != null) {
            for (String key : mod.keySet()) {
               Object obj = mod.get(key);
               variables.put(key, obj);
            }
         } else {
            mod = new HashMap<String, Object>();
         }
         variables.put("model", mod);
         GroovyCodeSource gs = this.groovyScriptCache.get(path);
         if (gs == null) {
            String codeBase = "";
            String name = "Groovy_" + UUID.randomUUID().toString().replaceAll("-", "");
            gs = new GroovyCodeSource(location.getInputStream(), name, "");
            this.groovyScriptCache.put(path, gs);
         } else {
            logger.debug("Using cached instance of groovy script " + path);
         }
         GroovyShell groovyShell = new GroovyShell(new Binding(variables));
         retval = groovyShell.evaluate(gs);
         logger.debug("Groovy script " + path + " executed");
      } else {
         logger.debug("Its not groovy");
         retval = super.executeScript(location, model);
      }
      logger.debug("model ready = " + model.get("ready"));
      return retval;// Nobody cares about it actually !
   }

   @Override
   public void reset() {
      super.reset();
      this.groovyScriptCache.clear();
   }

   /**
    *  AbstractWebScript passes .js hardcoded - try .js and .groovy
    *   protected ScriptDetails getExecuteScript(String mimetype)
    *   ScriptResourceHelper.recurseScriptImports calls
    *   ScriptResourceLoader.loadScriptResource
    */
   @Override
   public ScriptContent findScript(String path) {
      logger.debug("find script " + path);
      String basePath = path.replaceAll("\\.js", "");
      ScriptContent scriptContent = super.findScript(basePath + ".js");
      if (scriptContent == null && isGroovyEnabled()) {
         logger.debug("No script with path " + basePath
                  + ".js found, trying " + basePath + ".groovy");
         scriptContent = super.findScript(basePath + ".groovy");
      }
      if (scriptContent != null) {
         logger.debug("Got script for base path " + basePath + " : "
               + scriptContent.getPath());
      } else {
         logger.debug("Got no script for base path " + basePath);
      }
      return scriptContent;
   }

   public boolean isGroovyEnabled() {
      return groovyEnabled;
   }

   public void setGroovyEnabled(boolean groovyEnabled) {
      this.groovyEnabled = groovyEnabled;
   }

}

regarding SURF + Grails integrations, I'll check how much effort th "the portal style integration" is.

cheers
Andreas

deas0815
Star Contributor
Star Contributor
just in case somebody is interested, I've now asked the grails people for their 2c regarding integration
of grails and SURF. The posting is here: http://www.nabble.com/Alfresco-SURF-%2B-Grails-%3A-Comments-for-integration-td21898436.html

Luis: I personally would not call the rest of grails "overhead". Smiley Happy Imagine you want a real full blown modern application running in a SURF based website: Persistence with a complex model, WebFlow, Form Validation, AJAX etc. Which approach would you suggest in this case ? Of course grails is just one of the many stacks available - it just happens to be the one I personally prefer. Smiley Happy

cheers
Andreas

pmonks
Star Contributor
Star Contributor
One comment (which is based on little more than a quick perusal of the GroovyPresentationScriptProcessor, so might be completely misguided :winkSmiley Happy:  I'm not sure it's a good idea to inject the default script processor model object into Groovy scripts, since currently the default model is quite specific to Rhino (the JS interpreter Alfresco uses).  For Groovy (which has a much tighter integration with native Java than Rhino does), it would be better to inject the Java equivalents of the appropriate root scope objects, rather than the Rhino-compatible facades.

While this might seem like a lot of work, I think injecting just the ServiceRegistry (along with any script context - the document/space NodeRef for an action, for example) would be sufficient for most Groovy scripts.  Unlike the Javascript API (which doesn't have an equivalent of the ServiceRegistry, let alone the ability to instantiate native Alfresco objects directly), Groovy scripts should be able to get at just about everything from the ServiceRegistry.

Cheers,
Peter

alexander
Champ in-the-making
Champ in-the-making
Peter

Default model (and also ScriptNode properties and methods) make it quite intuitive to someone who wants to write SCRIPTS. It would require knowledge of Alfresco Java API to write even simple scripts if this was not the case.

This injection does no harm because:
1) Javascript API is written quite efficiently. Effectively they are just bunch of helper methods that allow working with properties faster. The only exception may be "content" operation that can not be optimised well using Javascript API.
2) One can always  decide to ignore it and to provide ServiceRegistry object to your Groovy scripts and go from there. This will require building simple processor extension (class that is based on BaseProcessorExtension and wired similar to other extensions in module-context.xml).

What I plan to explore  is a way to auto-inject any Alfresco beans into Groovy script by name match. For example something like this:

def documentPath = nodeService.getPath(document)

will automatically find bean "nodeService" if this variable or property does not exist and bean "nodeService" is defined in Spring. Does it sound like a good idea?

Thanks
Alexander

pmonks
Star Contributor
Star Contributor
The problem is that mixing calls to the Javascript and Java APIs is not recommended (see http://wiki.alfresco.com/wiki/JavaScript_API_For_Alfresco_2.1#Native_Java_API_Access), so allowing both of them to be used from Groovy scripts (or any other scripting environment) is likely to create subtle problems that are difficult to troubleshoot.  For that reason it would be better to limit Groovy scripts to one or the other, but never both at the same time - that's the best way to ensure developers (particularly those new to Alfresco) don't inadvertently get themselves into trouble.  FWIW I'd prefer to see only the Java APIs available to Groovy scripts, for the simple reason that it would be difficult to make them unavailable to Groovy, given Groovy's deliberately tight integration with Java.

Now whether those native Java APIs get wrapped into "Groovier" APIs is a separate question, and I think doing so makes perfect sense.  The "builder" pattern that some other Groovy libraries are following seems to me to have good applicability to the native Alfresco APIs, for example.

Cheers,
Peter
Getting started

Tags


Find what you came for

We want to make your experience in Hyland Connect as valuable as possible, so we put together some helpful links.