All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.milyn.smooks.scripting.groovy.GroovyContentHandlerFactory Maven / Gradle / Ivy

The newest version!
/*
	Milyn - Copyright (C) 2006 - 2010

	This library is free software; you can redistribute it and/or
	modify it under the terms of the GNU Lesser General Public
	License (version 2.1) as published by the Free Software
	Foundation.

	This library is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

	See the GNU Lesser General Public License for more details:
	http://www.gnu.org/licenses/lgpl.txt
*/

package org.milyn.smooks.scripting.groovy;

import groovy.lang.GroovyClassLoader;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.codehaus.groovy.control.CompilationFailedException;
import org.milyn.cdr.SmooksConfigurationException;
import org.milyn.cdr.SmooksResourceConfiguration;
import org.milyn.cdr.annotation.Configurator;
import org.milyn.delivery.ContentHandler;
import org.milyn.delivery.ContentHandlerFactory;
import org.milyn.delivery.DomModelCreator;
import org.milyn.delivery.Visitor;
import org.milyn.delivery.annotation.Initialize;
import org.milyn.delivery.annotation.Resource;
import org.milyn.delivery.sax.SAXElement;
import org.milyn.io.StreamUtils;
import org.milyn.javabean.context.BeanContext;
import org.milyn.util.FreeMarkerTemplate;
import org.milyn.xml.DomUtils;
import org.w3c.dom.Element;

import java.io.IOException;
import java.io.StringReader;
import java.util.HashMap;
import java.util.Map;

/**
 * {@link Visitor} Factory class for the Groovy scripting language.
 * 

* Implement DOM or SAX visitors using the Groovy scripting language. * *

Usage Tips

*
    *
  • Imports: Imports can be added via the "imports" element. A number of classes are automatically imported: *
      *
    • {@link DomUtils org.milyn.xml.DomUtils}
    • *
    • {@link BeanContext}
    • *
    • {@link org.w3c.dom org.w3c.dom.*}
    • *
    • groovy.xml.dom.DOMCategory, groovy.xml.dom.DOMUtil, groovy.xml.DOMBuilder
    • *
    *
  • *
  • Visited Element: The visited element is available to the script through the variable "element". It is also available * under a variable name equal to the element name, but only if the element name contains alpha-numeric * characters only.
  • *
  • Execute Before/After: By default, the script is executed on the visitAfter event. You can direct it to be * executed on the visitBefore by setting the "executeBefore" attribute to "true".
  • *
  • Comment/CDATA Script Wrapping: If the script contains special XML characters, it can be wrapped in an XML * Comment or CDATA section. See example below.
  • *
* *

Mixing SAX and DOM Models

* When using the SAX filter, Groovy scripts can take advantage of the {@link DomModelCreator}. This is only * the case when the script is applied on the visitAfter event of the targeted element (i.e. executeBefore="false", * which is the default). If executeBefore is set to "true", the {@link DomModelCreator} will not be utilized. *

* What this means is that you can use DOM utilities to process the targeted message fragment. The "element" * received by the Groovy script will be a DOM {@link Element}. This makes Groovy scripting via the SAX filter * a lot easier, while at the same time maintaining the ability to process huge messages in a streamed fashion. *

* Notes: *

    *
  1. Only available in default mode i.e. when executeBefore equals "false". If executeBefore is configured * "true", this facility is not available and the Groovy script will only have access to the element * as a {@link SAXElement}.
  2. *
  3. The DOM fragment must be explicitly writen to the result using "writeFragment". See example below.
  4. *
  5. There is an obvious performance overhead incurred using this facility (DOM construction). That said, it can still * be used to process huge messages because of how the {@link DomModelCreator} works for SAX.
  6. *
* *

Example Configuration

* Take an XML message such as: *
 * <shopping>
 *     <category type="groceries">
 *         <item>Chocolate</item>
 *         <item>Coffee</item>
 *     </category>
 *     <category type="supplies">
 *         <item>Paper</item>
 *         <item quantity="4">Pens</item>
 *     </category>
 *     <category type="present">
 *         <item when="Aug 10">Kathryn's Birthday</item>
 *     </category>
 * </shopping>
 * 
* * Using Groovy, we want to modify the "supplies" category in the shopping list, adding 2 to the * quantity, where the item is "Pens". To do this, we write a simple little Groovy script and target * it at the <category> elements in the message. The script simple iterates over the <item> elements * in the category and increments the quantity by 2, where the category type is "supplies" and the item is "Pens": * *
 * <?xml version="1.0"?>
 * <smooks-resource-list xmlns="http://www.milyn.org/xsd/smooks-1.1.xsd" xmlns:g="http://www.milyn.org/xsd/smooks/groovy-1.1.xsd">
 *
 *     <!--
 *     Use the SAX filter.  Note how we can still process the fragment as a DOM, and write it out
 *     to the result stream after processing.
 *     -->
 *     <params>
 *         <param name="stream.filter.type">SAX</param>
 *     </params>
 *
 *     <g:groovy executeOnElement="category">
 *         <g:script>
 *             <!--
 *             use(DOMCategory) {
 *
 *                 // modify supplies: we need an extra 2 pens
 *                 if (category.'@type' == 'supplies') {
 *                     category.item.each { item ->
 *                         if (item.text() == 'Pens') {
 *                             item['@quantity'] = item.'@quantity'.toInteger() + 2
 *                         }
 *                     }
 *                 }
 *             }
 *
 *             // Must explicitly write the fragment to the result stream when
 *             // using the SAX filter.
 *             writeFragment(category);
 *             -->
 *         </g:script>
 *     </g:groovy>
 *
 * </smooks-resource-list>
 * 
* * @author [email protected] */ @Resource(type="groovy") public class GroovyContentHandlerFactory implements ContentHandlerFactory { private static Log logger = LogFactory.getLog(GroovyContentHandlerFactory.class); private FreeMarkerTemplate classTemplate; private volatile int classGenCount = 1; @Initialize public void initialize() throws IOException { String templateText = StreamUtils.readStreamAsString(getClass().getResourceAsStream("ScriptedGroovy.ftl")); classTemplate = new FreeMarkerTemplate(templateText); } /* (non-Javadoc) * @see org.milyn.delivery.ContentHandlerFactory#create(org.milyn.cdr.SmooksResourceConfiguration) */ public ContentHandler create(SmooksResourceConfiguration configuration) throws SmooksConfigurationException { try { byte[] groovyScriptBytes = configuration.getBytes(); String groovyScript = new String(groovyScriptBytes, "UTF-8"); Object groovyObject; GroovyClassLoader groovyClassLoader = new GroovyClassLoader(getClass().getClassLoader()); try { Class groovyClass = groovyClassLoader.parseClass(groovyScript); groovyObject = groovyClass.newInstance(); } catch(CompilationFailedException e) { logger.debug("Failed to create Visitor class instance directly from script:\n==========================\n" + groovyScript + "\n==========================\n Will try applying Visitor template to script.", e); groovyObject = null; } if(!(groovyObject instanceof Visitor)) { groovyObject = createFromTemplate(groovyScript, configuration); } ContentHandler groovyResource = (ContentHandler) groovyObject; Configurator.configure(groovyResource, configuration); return groovyResource; } catch (Exception e) { throw new SmooksConfigurationException("Error constructing class from Groovy script " + configuration.getResource(), e); } } private Object createFromTemplate(String groovyScript, SmooksResourceConfiguration configuration) throws InstantiationException, IllegalAccessException { GroovyClassLoader groovyClassLoader = new GroovyClassLoader(getClass().getClassLoader()); Map templateVars = new HashMap(); String imports = configuration.getStringParameter("imports", ""); templateVars.put("imports", cleanImportsConfig(imports)); templateVars.put("visitorName", createClassName()); templateVars.put("elementName", getElementName(configuration)); templateVars.put("visitBefore", configuration.getBoolParameter("executeBefore", false)); templateVars.put("visitorScript", groovyScript); String templatedClass = classTemplate.apply(templateVars); if(groovyScript.contains("writeFragment")) { configuration.setParameter("writeFragment", "true"); } try { Class groovyClass = groovyClassLoader.parseClass(templatedClass); return groovyClass.newInstance(); } catch(CompilationFailedException e) { throw new SmooksConfigurationException("Failed to compile Groovy scripted Visitor class:\n==========================\n" + templatedClass + "\n==========================\n", e); } } private Object cleanImportsConfig(String imports) { try { StringBuffer importsBuffer = StreamUtils.trimLines(new StringReader(imports)); imports = importsBuffer.toString(); } catch (IOException e) { throw new IllegalStateException("Unexpected IOException reading String.", e); } return imports.replace("import ", "\nimport "); } private synchronized String createClassName() { StringBuilder className = new StringBuilder(); className.append("SmooksVisitor_"); className.append(System.identityHashCode(this)); className.append("_"); className.append(classGenCount++); return className.toString(); } private String getElementName(SmooksResourceConfiguration configuration) { String elementName = configuration.getTargetElement(); for (int i = 0; i < elementName.length(); i++) { if(!Character.isLetterOrDigit(elementName.charAt(i))) { return elementName + "_Mangled"; } } return elementName; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy