Commits

plightbo  committed 9d7dd22

XSLT support added thanks to Philipp Meier

git-svn-id: http://svn.opensymphony.com/svn/webwork/trunk@311573baa09-0c28-0410-bef9-dab3c582ae83

  • Participants
  • Parent commits 77227a4

Comments (0)

Files changed (21)

File src/etc/example/xslt.xml

+<!DOCTYPE xwork PUBLIC "-//OpenSymphony Group//XWork 1.0//EN" "http://www.opensymphony.com/xwork/xwork-1.0.dtd">
+
+<xwork>
+    <package name="xslt" namespace="/xslt" extends="webwork-default">
+        <action name="XSLTTest" class="com.opensymphony.webwork.example.ui.SimpleAction">
+            <result name="success" type="xslt">/xslt/simpleAction.xslt</result>
+        </action>
+
+        <action name="LoanCalc" class="com.opensymphony.webwork.example.LoanCalc">
+            <result name="success" type="xslt">/xslt/loanCalc.xslt</result>
+            <result name="input" type="xslt">/xslt/loanCalc.xslt</result>
+            <result name="error" type="xslt">/xslt/loanCalc.xslt</result>
+
+        </action>
+
+        <action name="LoanCalcTree" class="com.opensymphony.webwork.example.LoanCalc">
+            <result name="success" type="xslt">/xslt/showtree.xslt</result>
+            <result name="input" type="xslt">/xslt/showtree.xslt</result>
+            <result name="error" type="xslt">/xslt/showtree.xslt</result>
+        </action>
+
+    </package>
+</xwork>

File src/etc/example/xwork.xml

 
     </package>
 
+    <include file="xslt.xml"/>
+
     <include file="month.xml"/>
 
     <include file="i18n.xml"/>

File src/example/com/opensymphony/webwork/example/LoanCalc.java

+package com.opensymphony.webwork.example;
+
+import com.opensymphony.xwork.ActionSupport;
+
+import java.util.List;
+import java.util.ArrayList;
+
+/**
+ * @author <a href="mailto:meier@meisterbohne.de">Philipp Meier</a>
+ * Date: 15.10.2003
+ * Time: 18:27:34
+ */
+public class LoanCalc extends ActionSupport {
+    private float presentValue = 1000;
+    private float interestRate = 0.08f;
+    private float instalment = 100;
+    protected static final int MAXTERMS = 100;
+
+    private List monthlyDues;
+
+    public void setPresentValue(float presentValue) {
+        this.presentValue = presentValue;
+    }
+
+    public float getPresentValue() {
+        return presentValue;
+    }
+
+    public void setInterestRate(float interestRate) {
+        this.interestRate = interestRate;
+    }
+
+    public float getInterestRate() {
+        return interestRate;
+    }
+
+    public void setInstalment(float instalment) {
+        this.instalment = instalment;
+    }
+
+    public float getInstalment() {
+        return instalment;
+    }
+
+    public List getMonthlyDues() {
+        return monthlyDues;
+    }
+
+    protected void doValidation() {
+        if (presentValue <= 0) {
+            addFieldError("presentValue", "must be > 0");
+        }
+
+        if (interestRate <= 0) {
+            addFieldError("interestRate", "must be > 0");
+        }
+
+        if (instalment <= 0) {
+            addFieldError("instalment", "must be > 0");
+        }
+    }
+
+    protected String doExecute() {
+        monthlyDues = new ArrayList();
+        int month = 0;
+        for (float rest = presentValue; rest > 0.0001f && month <= MAXTERMS; month++) {
+            float interest = rest * interestRate / 12;
+            float redemption = instalment - interest;
+            float newRest = rest - redemption;
+
+            if (newRest < 0) {
+                monthlyDues.add(new MonthlyDue(month, rest, interest, rest, rest + interest));
+            } else {
+                monthlyDues.add(new MonthlyDue(month, rest, interest, redemption, instalment));
+            }
+            rest = newRest;
+        }
+
+        if (month > MAXTERMS) {
+            addActionError("Term is longer than " + MAXTERMS + " months!");
+            return ERROR;
+        }
+
+        return SUCCESS;
+    }
+
+    public String execute() throws Exception {
+        doValidation();
+        if (hasErrors()) {
+            return INPUT;
+        } else {
+            return doExecute();
+        }
+    }
+
+    public class MonthlyDue {
+        MonthlyDue(int month, float presentValue, float interest, float redemption, float instalment) {
+            this.month = month;
+            this.presentValue = presentValue;
+            this.interest = interest;
+            this.redemption = redemption;
+            this.instalment = instalment;
+        }
+
+        public int getMonth() {
+            return month;
+        }
+
+        public float getPresentValue() {
+            return presentValue;
+        }
+
+        public float getInterest() {
+            return interest;
+        }
+
+        public float getRedemption() {
+            return redemption;
+        }
+
+        public float getInstalment() {
+            return instalment;
+        }
+
+        private int month;
+        private float presentValue;
+        private float interest;
+        private float redemption;
+        private float instalment;
+    }
+
+}

File src/java/com/opensymphony/webwork/views/xslt/AdapterNode.java

+package com.opensymphony.webwork.views.xslt;
+
+import org.w3c.dom.Node;
+import com.sun.jdi.Location;
+
+/**
+ * @author <a href="mailto:meier@meisterbohne.de">Philipp Meier</a>
+ * Date: 10.10.2003
+ * Time: 19:41:49
+ */
+public interface AdapterNode extends Node {
+    String getPropertyName();
+
+    Node getNextSibling(AdapterNode child);
+
+    DOMAdapter getRootAdapter();
+
+    AdapterNode getParentAdapterNode();
+
+    Object getValue();
+}

File src/java/com/opensymphony/webwork/views/xslt/ArrayAdapter.java

+package com.opensymphony.webwork.views.xslt;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.util.List;
+import java.util.ArrayList;
+
+/**
+ * @author <a href="mailto:meier@meisterbohne.de">Philipp Meier</a>
+ * Date: 14.10.2003
+ * Time: 18:59:07
+ */
+public class ArrayAdapter extends DefaultElementAdapter {
+
+    private Log log = LogFactory.getLog(this.getClass());
+
+    public ArrayAdapter(DOMAdapter rootAdapter, AdapterNode parent, String propertyName, Object value) {
+        super(rootAdapter, parent, propertyName, value);
+    }
+
+
+    protected List buildChildrenAdapters() {
+        List children = new ArrayList();
+        Object[] values = (Object[]) getValue();
+        for (int i = 0; i < values.length; i++) {
+            AdapterNode childAdapter = getRootAdapter().adapt(getRootAdapter(), this, "item", values[i]);
+            children.add(childAdapter);
+            log.debug(this + " adding adapter: " + childAdapter);
+        }
+        return children;
+    }
+}

File src/java/com/opensymphony/webwork/views/xslt/BeanAdapter.java

+package com.opensymphony.webwork.views.xslt;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.beans.IntrospectionException;
+import java.beans.Introspector;
+import java.beans.PropertyDescriptor;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:meier@meisterbohne.de">Philipp Meier</a>
+ * Date: 10.10.2003
+ * Time: 20:08:17
+ */
+public class BeanAdapter extends DefaultElementAdapter {
+
+    private Log log = LogFactory.getLog(this.getClass());
+
+    private static final Object[] NULLPARAMS = new Object[0];
+
+    /**
+     * Cache can savely be static because the cached information is
+     * the same for all instances of this class.
+     */
+    private static Map propertyDescriptorCache;
+
+
+    public BeanAdapter(DOMAdapter rootAdapter, AdapterNode parent, String propertyName, Object value) {
+        super(rootAdapter, parent, propertyName, value);
+    }
+
+    public String getTagName() {
+        return getPropertyName();
+    }
+
+    protected List buildChildrenAdapters() {
+        List newAdapters = new ArrayList();
+        Class type = getValue().getClass();
+        PropertyDescriptor[] props = getPropertyDescriptors(getValue());
+        if (props.length > 0) {
+            for (int i = 0; i < props.length; i++) {
+                Method m = props[i].getReadMethod();
+                if (m == null) {
+                    //FIXME: write only property or indexed access
+                    continue;
+                }
+                String propertyName = props[i].getName();
+                Object propertyValue;
+                /** 999 white magic hack start 999 **
+                 * some property accessors will throw exceptions, e.g. getLocale() in webwork.ActionSupport *grrr*
+                 * IMHO property accessors should not have those side effects - meier@meisterbohne.de
+                 */
+                try {
+                    propertyValue = m.invoke(getValue(), NULLPARAMS);
+                } catch (Exception e) {
+                    continue;
+                }
+                /** 999 white magic hack end 999 **/
+                AdapterNode childAdapter;
+                if (propertyValue == null) {
+                    childAdapter = getRootAdapter().adaptNullValue(getRootAdapter(), this, propertyName);
+                } else {
+                    childAdapter = getRootAdapter().adapt(getRootAdapter(), this, propertyName, propertyValue);
+                }
+                newAdapters.add(childAdapter);
+                log.debug(this + " adding adapter: " + childAdapter);
+            }
+
+        } else {
+            // No properties found
+            log.info("Class " + type.getName() + " has no readable properties, " +
+                    " trying to adapt " + getPropertyName() + " with ToStringAdapter...");
+            //newAdapters.add(new ToStringAdapter(getRootAdapter(), this, getPropertyName(), getValue()));
+        }
+
+        return newAdapters;
+    }
+
+
+    /**
+     * Caching facade method to Introspector.getBeanInfo(Class, Class).getPropertyDescriptors();
+     **/
+    private synchronized PropertyDescriptor[] getPropertyDescriptors
+            (Object
+            bean) {
+        try {
+            if (propertyDescriptorCache == null) {
+                propertyDescriptorCache = new HashMap();
+            }
+
+            PropertyDescriptor[] props = (PropertyDescriptor[]) propertyDescriptorCache.get(bean.getClass());
+            if (props == null) {
+
+                log.debug("Caching property descriptor for " + bean.getClass().getName());
+                props = Introspector.getBeanInfo(bean.getClass(), Object.class).getPropertyDescriptors();
+                propertyDescriptorCache.put(bean.getClass(), props);
+            }
+            return props;
+        } catch (IntrospectionException e) {
+            throw new RuntimeException("Error getting property descriptors for " + bean, e);
+        }
+    }
+
+
+}

File src/java/com/opensymphony/webwork/views/xslt/CollectionAdapter.java

+package com.opensymphony.webwork.views.xslt;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+
+/**
+ * @author <a href="mailto:meier@meisterbohne.de">Philipp Meier</a>
+ * Date: 14.10.2003
+ * Time: 18:59:07
+ */
+public class CollectionAdapter extends DefaultElementAdapter {
+
+    private Log log = LogFactory.getLog(this.getClass());
+
+    public CollectionAdapter(DOMAdapter rootAdapter, AdapterNode parent, String propertyName, Object value) {
+        super(rootAdapter, parent, propertyName, value);
+    }
+
+
+    protected List buildChildrenAdapters() {
+        Collection values = (Collection) getValue();
+        List children = new ArrayList(values.size());
+        for (Iterator i = values.iterator(); i.hasNext();) {
+            AdapterNode childAdapter = getRootAdapter().adapt(getRootAdapter(), this, "item", i.next());
+            children.add(childAdapter);
+            log.debug(this + " adding adapter: " + childAdapter);
+
+        }
+        return children;
+    }
+}

File src/java/com/opensymphony/webwork/views/xslt/CollectionNodeList.java

+package com.opensymphony.webwork.views.xslt;
+
+import org.w3c.dom.NodeList;
+import org.w3c.dom.Node;
+
+import java.util.List;
+
+/**
+ * @author <a href="mailto:meier@meisterbohne.de">Philipp Meier</a>
+ * Date: 10.10.2003
+ * Time: 20:40:44
+ */
+public class CollectionNodeList implements NodeList {
+    private List nodes;
+
+    public CollectionNodeList(List nodes) {
+        this.nodes = nodes;
+    }
+
+    public Node item(int i) {
+        return (Node) nodes.get(i);
+    }
+
+    public int getLength() {
+        return nodes.size();
+    }
+}

File src/java/com/opensymphony/webwork/views/xslt/DOMAdapter.java

+package com.opensymphony.webwork.views.xslt;
+
+import org.w3c.dom.Node;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.util.Collection;
+
+/**
+ * @author <a href="mailto:meier@meisterbohne.de">Philipp Meier</a>
+ * Date: 10.10.2003
+ * Time: 19:39:13
+ */
+public class DOMAdapter {
+
+    public AdapterNode adapt(Object value) throws IllegalAccessException, InstantiationException {
+        return new DocumentAdapter(this, null, "result", value);
+    }
+
+    public String getPropertyName() {
+        return "[Root]";
+    }
+
+    public short getNodeType() {
+        return Node.PROCESSING_INSTRUCTION_NODE; // What to return, is DOMAdapter a Node at last?
+    }
+
+    public AdapterNode adapt(DOMAdapter rootAdapter, Node parent, String propertyName, Object value) {
+        Class klass = value.getClass();
+        Class adapterClass = findAdapterForClass(klass);
+
+        try {
+            if (adapterClass == null) {
+                if (klass.isArray()) {
+                    adapterClass = ArrayAdapter.class;
+                } else if (String.class.isAssignableFrom(klass) || klass.isPrimitive() ||
+                           Number.class.isAssignableFrom(klass)) {
+                    adapterClass = ToStringAdapter.class;
+                } else if (Collection.class.isAssignableFrom(klass)) {
+                    adapterClass = CollectionAdapter.class;
+                } else {
+                    adapterClass = BeanAdapter.class;
+                }
+            }
+
+            Constructor c = adapterClass.getConstructor(
+                    new Class[]{DOMAdapter.class, AdapterNode.class, String.class, Object.class});
+            AdapterNode adapter = ((AdapterNode) c.newInstance(new Object[]{this, parent, propertyName, value}));
+
+            return adapter;
+        } catch (IllegalAccessException e) {
+            throw new RuntimeException("Cannot adapt " + value + " (" + propertyName + ")", e);
+        } catch (InstantiationException e) {
+            throw new RuntimeException("Cannot adapt " + value + " (" + propertyName + ")", e);
+        } catch (NoSuchMethodException e) {
+            throw new RuntimeException(
+                    "Adapter Class " + adapterClass.getName() + " must define the right constructor.", e);
+        } catch (InvocationTargetException e) {
+            throw new RuntimeException("Cannot adapt " + value + " (" + propertyName + ")", e);
+        }
+    }
+
+    //TODO: implement Configuration option to provide additional adapter classes
+    private Class findAdapterForClass(Class klass) {
+
+        return null;
+    }
+
+    public AdapterNode adaptNullValue(DOMAdapter rootAdapter, BeanAdapter parent, String propertyName) {
+        return new ToStringAdapter(rootAdapter, parent, propertyName, "null");
+    }
+
+}

File src/java/com/opensymphony/webwork/views/xslt/DefaultAdapterNode.java

+package com.opensymphony.webwork.views.xslt;
+
+import org.w3c.dom.Node;
+import org.w3c.dom.DOMException;
+import org.w3c.dom.NodeList;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Document;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * @author <a href="mailto:meier@meisterbohne.de">Philipp Meier</a>
+ * Date: 10.10.2003
+ * Time: 19:46:43
+ */
+public abstract class DefaultAdapterNode implements Node, AdapterNode {
+
+    private DOMAdapter rootAdapter;
+    private AdapterNode parent;
+    private String propertyName;
+    private Object value;
+
+    private static final NodeList EMPTY_NODELIST = new NodeList() {
+        public Node item(int i) {
+            return null;
+        }
+
+        public int getLength() {
+            return 0;
+        }
+    };
+
+    public DefaultAdapterNode(DOMAdapter rootAdapter, AdapterNode parent, String propertyName, Object value) {
+        //assert rootAdapter != null : "rootAdapter == null";
+        this.rootAdapter = rootAdapter;
+        //assert parent != null : "parent == null";
+        this.parent = parent;
+        //assert propertyName != null : "propertyName == null";
+        this.propertyName = propertyName;
+        this.value = value;
+        LogFactory.getLog(getClass()).debug("Creating " + this);
+    }
+
+    public boolean equals(Object other) {
+        try {
+            AdapterNode otherNode = (AdapterNode) other;
+            boolean result = true;
+            result &= getRootAdapter().equals(otherNode.getRootAdapter());
+            result &= getPropertyName().equals(otherNode.getPropertyName());
+            result &= (getValue() != null ?
+                       getValue().equals(otherNode.getValue()) : otherNode.getValue() == null);
+            result &= (getParentAdapterNode() != null ?
+                       getParentAdapterNode().equals(otherNode.getParentAdapterNode()) : otherNode.getParentAdapterNode() == null);
+            return result;
+        } catch (ClassCastException e) {
+            return false;
+        }
+    }
+
+    public int hashCode() {
+        return getRootAdapter().hashCode() * 37 +
+               (getParentAdapterNode() != null ? getParentAdapterNode().hashCode() * 41 : 0) +
+               getPropertyName().hashCode() * 43 +
+               (getValue() != null ? getValue().hashCode() * 47 : 0);
+    }
+
+    public DOMAdapter getRootAdapter() {
+        return rootAdapter;
+    }
+
+    public String getPropertyName() {
+        return propertyName;
+    }
+
+    public Object getValue() {
+        return value;
+    }
+
+    protected void operationNotSupported() {
+        throw new RuntimeException("Operation not supported.");
+    }
+
+    public String getNodeValue() throws DOMException {
+        operationNotSupported();
+        return null;
+    }
+
+    public void setNodeValue(String string) throws DOMException {
+        operationNotSupported();
+    }
+
+    public Node getParentNode() {
+        return parent;
+    }
+
+
+    public NamedNodeMap getAttributes() {
+        return null;
+    }
+
+    public Document getOwnerDocument() {
+        return null;
+    }
+
+
+    public NodeList getChildNodes() {
+        return EMPTY_NODELIST;
+    }
+
+
+    public Node getFirstChild() {
+        return null;
+    }
+
+    public Node getLastChild() {
+        return null;
+    }
+
+    public Node getPreviousSibling() {
+        return null;
+    }
+
+    public Node getNextSibling() {
+        return getParentAdapterNode() != null ? getParentAdapterNode().getNextSibling(this) : null;
+    }
+
+    public Node getNextSibling(AdapterNode child) {
+        return null;
+    }
+
+    public AdapterNode getParentAdapterNode() {
+        return parent;
+    }
+
+    public Node insertBefore(Node node, Node node1) throws DOMException {
+        operationNotSupported();
+        return null;
+    }
+
+    public Node replaceChild(Node node, Node node1) throws DOMException {
+        operationNotSupported();
+        return null;
+    }
+
+    public Node removeChild(Node node) throws DOMException {
+        operationNotSupported();
+        return null;
+    }
+
+    public Node appendChild(Node node) throws DOMException {
+        operationNotSupported();
+        return null;
+    }
+
+    public boolean hasChildNodes() {
+        return false;
+    }
+
+    public Node cloneNode(boolean b) {
+        operationNotSupported();
+        return null;
+    }
+
+    public void normalize() {
+        operationNotSupported();
+    }
+
+    public boolean isSupported(String string, String string1) {
+        operationNotSupported();
+        return false;
+    }
+
+    public String getNamespaceURI() {
+        return null;
+    }
+
+    public String getPrefix() {
+        return null;
+    }
+
+    public void setPrefix(String string) throws DOMException {
+        operationNotSupported();
+    }
+
+    public String getLocalName() {
+        return null;
+    }
+
+    public boolean hasAttributes() {
+        return false;
+    }
+}

File src/java/com/opensymphony/webwork/views/xslt/DefaultElementAdapter.java

+package com.opensymphony.webwork.views.xslt;
+
+import org.w3c.dom.Attr;
+import org.w3c.dom.DOMException;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.ArrayList;
+
+/**
+ * @author <a href="mailto:meier@meisterbohne.de">Philipp Meier</a>
+ * Date: 14.10.2003
+ * Time: 19:01:02
+ */
+public abstract class DefaultElementAdapter extends DefaultAdapterNode implements AdapterNode, Element {
+    private List adapters;
+
+    public DefaultElementAdapter(DOMAdapter rootAdapter, AdapterNode parent, String propertyName, Object value) {
+        super(rootAdapter, parent, propertyName, value);
+    }
+
+    public short getNodeType() {
+        return Node.ELEMENT_NODE;
+    }
+
+    public String getTagName() {
+        return getPropertyName();
+    }
+
+    public String getNodeName() {
+        return getTagName();
+    }
+
+    public NodeList getElementsByTagName(String tagName) {
+        initChildrenIfNessecary();
+
+        if (tagName.equals("*")) {
+            return new CollectionNodeList(getAdapters());
+        } else {
+            LinkedList filteredChildren = new LinkedList();
+            for (Iterator i = getAdapters().iterator(); i.hasNext();) {
+                AdapterNode adapterNode = (AdapterNode) i.next();
+                if (adapterNode.getNodeName().equals(tagName)) {
+                    filteredChildren.add(adapterNode);
+                }
+            }
+            return new CollectionNodeList(filteredChildren);
+        }
+    }
+
+    protected void initChildrenIfNessecary() {
+        if (adapters == null) {
+            adapters = new ArrayList();
+            synchronized (adapters) {
+                adapters = buildChildrenAdapters();
+            }
+        }
+    }
+
+    public Node getNextSibling(AdapterNode child) {
+        int index = getAdapters().indexOf(child);
+        if (index < 0) {
+            throw new RuntimeException(child + " is no child of " + this);
+        }
+        int siblingIndex = index+1;
+        Node sibling = (0 < siblingIndex && siblingIndex < getAdapters().size()) ? ((Node) getAdapters().get(siblingIndex)) : null;
+        return sibling;
+    }
+
+
+    public NodeList getElementsByTagNameNS(String string, String string1) {
+        return null;
+    }
+
+    public NodeList getChildNodes() {
+        return getElementsByTagName("*");
+    }
+
+    public Node getFirstChild() {
+        return getChildNodes().getLength() > 0 ? getChildNodes().item(0) : null;
+    }
+
+    public Node getLastChild() {
+        return getChildNodes().getLength() > 0 ? getChildNodes().item(getChildNodes().getLength() - 1) : null;
+    }
+
+    public boolean hasChildNodes() {
+        return getElementsByTagName("*").getLength() > 0;
+    }
+
+
+    /**************************************************************************************
+     * No attributes, return empty attributes if asked.
+     **************************************************************************************/
+
+    public String getAttribute(String string) {
+        return "";
+    }
+
+    public Attr getAttributeNode(String string) {
+        return null;
+    }
+
+    public String getAttributeNS(String string, String string1) {
+        return null;
+    }
+
+    public Attr getAttributeNodeNS(String string, String string1) {
+        operationNotSupported();
+        return null;
+    }
+
+    public boolean hasAttribute
+            (String
+            string) {
+        return false;
+    }
+
+    public boolean hasAttributeNS
+            (String
+            string, String
+            string1) {
+        return false;
+    }
+
+    /**************************************************************************************
+     * Not supported below
+     **************************************************************************************/
+
+    //
+    public void setAttribute
+            (String
+            string, String
+            string1) throws DOMException {
+        operationNotSupported();
+    }
+
+    public void removeAttribute
+            (String
+            string) throws DOMException {
+        operationNotSupported();
+    }
+
+    public Attr setAttributeNode
+            (Attr
+            attr) throws DOMException {
+        operationNotSupported();
+        return null;
+    }
+
+    public Attr removeAttributeNode
+            (Attr
+            attr) throws DOMException {
+        operationNotSupported();
+        return null;
+    }
+
+    public void setAttributeNS(String string, String string1, String string2) throws DOMException {
+        operationNotSupported();
+    }
+
+    public void removeAttributeNS(String string, String string1) throws DOMException {
+        operationNotSupported();
+    }
+
+    public Attr setAttributeNodeNS(Attr attr) throws DOMException {
+        operationNotSupported();
+        return null;
+    }
+
+    protected List getAdapters() {
+        initChildrenIfNessecary();
+        return adapters;
+    }
+
+    protected abstract List buildChildrenAdapters();
+}

File src/java/com/opensymphony/webwork/views/xslt/DocumentAdapter.java

+package com.opensymphony.webwork.views.xslt;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.DocumentType;
+import org.w3c.dom.DOMImplementation;
+import org.w3c.dom.Element;
+import org.w3c.dom.DOMException;
+import org.w3c.dom.DocumentFragment;
+import org.w3c.dom.Text;
+import org.w3c.dom.Comment;
+import org.w3c.dom.CDATASection;
+import org.w3c.dom.ProcessingInstruction;
+import org.w3c.dom.Attr;
+import org.w3c.dom.EntityReference;
+import org.w3c.dom.NodeList;
+import org.w3c.dom.Node;
+
+/**
+ * @author <a href="mailto:meier@meisterbohne.de">Philipp Meier</a>
+ * Date: 14.10.2003
+ * Time: 17:24:05
+ */
+public class DocumentAdapter extends DefaultAdapterNode implements Document {
+    private BeanAdapter rootElement;
+
+    public DocumentAdapter(DOMAdapter rootAdapter, AdapterNode parent, String propertyName, Object value) {
+        super(rootAdapter, parent, propertyName, value);
+        rootElement = new BeanAdapter(getRootAdapter(), this, getPropertyName(), getValue());
+    }
+
+    public String getNodeName() {
+        return "#document";
+    }
+
+    public short getNodeType() {
+        return Node.DOCUMENT_NODE;
+    }
+
+    public DocumentType getDoctype() {
+        return null;
+    }
+
+    public DOMImplementation getImplementation() {
+        return null;
+    }
+
+    public Element getDocumentElement() {
+
+        return rootElement;
+    }
+
+    public NodeList getChildNodes() {
+        return new NodeList() {
+            public Node item(int i) {
+                return rootElement;
+            }
+
+            public int getLength() {
+                return 1;
+            }
+        };
+    }
+
+
+    public boolean hasChildNodes() {
+        return true;
+    }
+
+    public Node getFirstChild() {
+        return rootElement;
+    }
+
+    public Node getLastChild() {
+        return rootElement;
+    }
+
+    public Node getNextSibling(AdapterNode value) {
+        return null;
+    }
+
+    public Element createElement(String string) throws DOMException {
+        return null;
+    }
+
+    public DocumentFragment createDocumentFragment() {
+        return null;
+    }
+
+    public Text createTextNode(String string) {
+        return null;
+    }
+
+    public Comment createComment(String string) {
+        return null;
+    }
+
+    public CDATASection createCDATASection(String string) throws DOMException {
+        return null;
+    }
+
+    public ProcessingInstruction createProcessingInstruction(String string, String string1) throws DOMException {
+        return null;
+    }
+
+    public Attr createAttribute(String string) throws DOMException {
+        return null;
+    }
+
+    public EntityReference createEntityReference(String string) throws DOMException {
+        return null;
+    }
+
+    public NodeList getElementsByTagName(String string) {
+        return null;
+    }
+
+    public Node importNode(Node node, boolean b) throws DOMException {
+        return null;
+    }
+
+    public Element createElementNS(String string, String string1) throws DOMException {
+        return null;
+    }
+
+    public Attr createAttributeNS(String string, String string1) throws DOMException {
+        return null;
+    }
+
+    public NodeList getElementsByTagNameNS(String string, String string1) {
+        return null;
+    }
+
+    public Element getElementById(String string) {
+        return null;
+    }
+}

File src/java/com/opensymphony/webwork/views/xslt/ServletURIResolver.java

+package com.opensymphony.webwork.views.xslt;
+
+import org.apache.commons.logging.LogFactory;
+import org.apache.commons.logging.Log;
+
+import javax.xml.transform.URIResolver;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.Source;
+import javax.xml.transform.stream.StreamSource;
+import javax.servlet.ServletContext;
+import java.io.InputStream;
+
+/**
+ * @author <a href="mailto:meier@meisterbohne.de">Philipp Meier</a>
+ * Date: 14.10.2003
+ * Time: 16:50:06
+ */
+public class ServletURIResolver implements URIResolver {
+    protected static Log log = LogFactory.getLog(URIResolver.class);
+
+
+    static final String protocol = "res:";
+    private ServletContext sc;
+
+    public ServletURIResolver(ServletContext sc) {
+        this.sc = sc;
+    }
+
+    public Source resolve(String href, String base)
+            throws TransformerException {
+        if (href.startsWith(protocol)) {
+            String res = href.substring(protocol.length());
+            log.debug("Resolving resource <" + res + ">");
+            InputStream is = sc.getResourceAsStream(res);
+            if (is == null) {
+                throw new TransformerException("Resource " + res + " not found in resources.");
+            }
+            return new StreamSource(is);
+        }
+        throw new TransformerException("Cannot handle procotol of resource " + href);
+    }
+}

File src/java/com/opensymphony/webwork/views/xslt/SimpleTextNode.java

+package com.opensymphony.webwork.views.xslt;
+
+import org.w3c.dom.DOMException;
+import org.w3c.dom.Node;
+import org.w3c.dom.Text;
+
+/**
+ * @author <a href="mailto:meier@meisterbohne.de">Philipp Meier</a>
+ * Date: 10.10.2003
+ * Time: 19:45:12
+ */
+public class SimpleTextNode extends DefaultAdapterNode implements Text, AdapterNode {
+    public SimpleTextNode(DOMAdapter rootAdapter, AdapterNode parent, String propertyName, Object value) {
+        super(rootAdapter, parent, propertyName, value);
+    }
+
+    public short getNodeType() {
+        return Node.TEXT_NODE;
+    }
+
+    public String getNodeName() {
+        return "#text";
+    }
+
+    public String getNodeValue() throws DOMException {
+        return getStringValue();
+    }
+
+    public Text splitText(int i) throws DOMException {
+        throw new RuntimeException("Operation not supported");
+    }
+
+    public String getData() throws DOMException {
+        return getStringValue();
+    }
+
+    private String getStringValue() {
+        return getValue().toString();
+    }
+
+    public void setData(String string) throws DOMException {
+        throw new RuntimeException("Operation not supported");
+    }
+
+    public int getLength() {
+        return getStringValue().length();
+    }
+
+    public String substringData(int beginIndex, int endIndex) throws DOMException {
+        return getStringValue().substring(beginIndex, endIndex);
+    }
+
+    public void appendData(String string) throws DOMException {
+        throw new RuntimeException("Operation not supported");
+    }
+
+    public void insertData(int i, String string) throws DOMException {
+        throw new RuntimeException("Operation not supported");
+    }
+
+    public void deleteData(int i, int i1) throws DOMException {
+        throw new RuntimeException("Operation not supported");
+
+    }
+
+    public void replaceData(int i, int i1, String string) throws DOMException {
+        throw new RuntimeException("Operation not supported");
+    }
+}

File src/java/com/opensymphony/webwork/views/xslt/ToStringAdapter.java

+package com.opensymphony.webwork.views.xslt;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:meier@meisterbohne.de">Philipp Meier</a>
+ * Date: 10.10.2003
+ * Time: 19:45:12
+ */
+public class ToStringAdapter extends DefaultElementAdapter {
+    public ToStringAdapter(DOMAdapter rootAdapter, AdapterNode parent, String propertyName, Object value) {
+        super(rootAdapter, parent, propertyName, value);
+    }
+
+    protected List buildChildrenAdapters() {
+        List children = new ArrayList();
+        children.add(new SimpleTextNode(getRootAdapter(), this, "text", getValue()));
+        return children;
+    }
+}
+

File src/java/com/opensymphony/webwork/views/xslt/XSLTResult.java

+/*
+ * Copyright (c) 2002-2003 by OpenSymphony
+ * All rights reserved.
+ */
+package com.opensymphony.webwork.views.xslt;
+
+import com.opensymphony.webwork.ServletActionContext;
+import com.opensymphony.webwork.config.Configuration;
+import com.opensymphony.xwork.ActionContext;
+import com.opensymphony.xwork.ActionInvocation;
+import com.opensymphony.xwork.Result;
+import com.opensymphony.xwork.util.OgnlValueStack;
+import com.opensymphony.xwork.util.TextParseUtil;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import javax.servlet.http.HttpServletResponse;
+import javax.xml.transform.OutputKeys;
+import javax.xml.transform.Source;
+import javax.xml.transform.Templates;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+import javax.xml.transform.stream.StreamSource;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.Writer;
+import java.net.URL;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:meier@meisterbohne.de">Philipp Meier</a>
+ */
+public class XSLTResult implements Result {
+    //~ Static fields/initializers /////////////////////////////////////////////
+
+    private static final Log log = LogFactory.getLog(XSLTResult.class);
+    public static final String DEFAULT_PARAM = "location";
+
+    //~ Instance fields ////////////////////////////////////////////////////////
+
+    private String location;
+    private boolean parse;
+    private Map templatesCache;
+
+    //~ Protected config ///////////////////////////////////////////////////////
+    protected boolean noCache = false;
+
+
+    //~ Constructors ///////////////////////////////////////////////////////////
+    public XSLTResult() {
+        templatesCache = new HashMap();
+        noCache = Configuration.getString("webwork.xslt.nocache").trim().equalsIgnoreCase("true");
+    }
+
+    //~ Methods ////////////////////////////////////////////////////////////////
+
+    public void setLocation(String location) {
+        this.location = location;
+    }
+
+    public void setParse(boolean parse) {
+        this.parse = parse;
+    }
+
+    public void execute(ActionInvocation invocation) throws Exception {
+        long startTime = System.currentTimeMillis();
+
+        if (parse) {
+            OgnlValueStack stack = ActionContext.getContext().getValueStack();
+            location = TextParseUtil.translateVariables(location, stack);
+        }
+
+        try {
+            HttpServletResponse response = ServletActionContext.getResponse();
+
+            Writer writer = response.getWriter();
+
+            // Create a transformer for the stylesheet.
+            Templates templates = getTemplates(location);
+            Transformer transformer = templates.newTransformer();
+
+            String mimeType = templates.getOutputProperties().getProperty(OutputKeys.MEDIA_TYPE);
+            if (mimeType == null) {
+                // guess (this is a servlet, so text/html might be the best guess)
+                mimeType = "text/html";
+            }
+            response.setContentType(mimeType);
+
+            Source xmlSource = getTraxSourceForStack(invocation.getAction());
+
+            // Transform the source XML to System.out.
+            PrintWriter out = response.getWriter();
+
+            transformer.transform(xmlSource, new StreamResult(out));
+
+            out.close(); // ...and flush...
+            if (log.isDebugEnabled()) {
+                log.debug("Time:" + (System.currentTimeMillis() - startTime) + "ms");
+            }
+
+            writer.flush();
+        } catch (Exception e) {
+            log.error("Unable to render XSLT Template, '" + location + "'", e);
+            throw e;
+        }
+    }
+
+    private Source getTraxSourceForStack(Object action) throws IllegalAccessException, InstantiationException {
+        return new DOMSource(new DOMAdapter().adapt(action));
+    }
+
+    private Templates getTemplates(String path) throws TransformerException, IOException {
+        String pathFromRequest = ServletActionContext.getRequest().getParameter("xslt.location");
+        if (pathFromRequest != null) {
+            path = pathFromRequest;
+        }
+        if (path == null) {
+            throw new TransformerException("Stylesheet path is null");
+        }
+        Templates templates = (Templates) templatesCache.get(path);
+        if (noCache || templates == null) {
+            synchronized (templatesCache) {
+                URL resource = ServletActionContext.getServletContext().getResource(path);
+                if (resource == null) {
+                    throw new TransformerException("Stylesheet " + path + " not found in resources.");
+                }
+
+                // This may result in the template being put into the cache multiple times
+                // if concurrent requests are made, but that's ok.
+                log.debug("Preparing new XSLT stylesheet: " + path);
+                TransformerFactory factory = TransformerFactory.newInstance();
+                log.trace("Uri-Resolver is: " + factory.getURIResolver());
+                factory.setURIResolver(new ServletURIResolver(ServletActionContext.getServletContext()));
+                log.trace("Uri-Resolver is: " + factory.getURIResolver());
+                templates = factory.newTemplates(new StreamSource(resource.openStream()));
+                templatesCache.put(path, templates);
+            }
+        }
+        return templates;
+
+    }
+}

File src/java/webwork-default.xml

             <result-type name="redirect" class="com.opensymphony.webwork.dispatcher.ServletRedirectResult"/>
             <result-type name="velocity" class="com.opensymphony.webwork.dispatcher.VelocityResult"/>
             <result-type name="chain" class="com.opensymphony.xwork.ActionChainResult"/>
+            <result-type name="xslt" class="com.opensymphony.webwork.views.xslt.XSLTResult"/>
         </result-types>
 
         <interceptors>

File src/webapp/WEB-INF/classes/webwork.properties

 
 # added the MockTag to the path of Tags that the TagDirective will search through
 webwork.velocity.tag.path = com.opensymphony.webwork.views.velocity.ui, org.displaytag.tags
+
+webwork.xslt.nocache=false
+

File src/webapp/xslt/loanCalc.xslt

+<?xml version="1.0"?>
+<!--
+
+ WebWork, Web Application Framework
+
+ Distributable under Apache license.
+ See terms of license at opensource.org
+
+ XSLT Stylesheet for LoalCalc
+
+ Author: Philipp Meier <meier@o-matic.de>
+ Version: $Revision$
+
+-->
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
+  <xsl:output method="html" media-type="text/html" />
+  <xsl:template match="result">
+    <html>
+      <head>
+        <title>Loan Calculator</title>
+      </head>
+
+      <body>
+        <h1>Loan Calculator</h1>
+
+        <form>
+          <table>
+            <tr>
+              <td>Present Value</td>
+
+              <td>
+                <input name="presentValue">
+                  <xsl:attribute name="value">
+                    <xsl:value-of select="presentValue" />
+                  </xsl:attribute>
+                </input>
+              </td>
+            </tr>
+
+            <tr>
+              <td>Interest Rate</td>
+
+              <td>
+                <input name="interestRate">
+                  <xsl:attribute name="value">
+                    <xsl:value-of select="interestRate" />
+                  </xsl:attribute>
+                </input>
+              </td>
+            </tr>
+
+            <tr>
+              <td>Instalment</td>
+
+              <td>
+                <input name="instalment">
+                  <xsl:attribute name="value">
+                    <xsl:value-of select="instalment" />
+                  </xsl:attribute>
+                </input>
+              </td>
+            </tr>
+
+            <tr>
+              <td>&#160;</td>
+
+              <td>
+                <input type="submit"
+                value="Calculate monthly dues" />
+              </td>
+            </tr>
+          </table>
+        </form>
+
+
+        <xsl:if test="hasErrorMessages = 'true'">
+
+            <xsl:apply-templates select="errorMessages"/>
+
+        </xsl:if>
+
+
+        <xsl:apply-templates select="monthlyDues" />
+      </body>
+    </html>
+  </xsl:template>
+
+  <xsl:template match="monthlyDues">
+    <table>
+      <tr>
+        <th>Month</th>
+
+        <th>Present Value</th>
+
+        <th>Interest</th>
+
+        <th>Redemption</th>
+
+        <th>Instalment</th>
+      </tr>
+
+      <xsl:apply-templates />
+    </table>
+  </xsl:template>
+
+  <xsl:template match="monthlyDues/item">
+    <tr>
+      <xsl:if test="position() mod 2">
+        <xsl:attribute name="bgcolor">#CCCCCC</xsl:attribute>
+      </xsl:if>
+
+      <td align="right">
+        <xsl:value-of select="month" />
+      </td>
+
+      <td align="right">
+        <xsl:value-of
+        select="format-number(presentValue, '#0.00')" />
+      </td>
+
+      <td align="right">
+        <xsl:value-of select="format-number(interest, '#0.00')" />
+      </td>
+
+      <td align="right">
+        <xsl:value-of select="format-number(redemption, '#0.00')" />
+      </td>
+
+      <td align="right">
+        <xsl:value-of select="format-number(instalment, '#0.00')" />
+      </td>
+    </tr>
+  </xsl:template>
+
+  <xsl:template match="errorMessages/item">
+    <p style="color: red;"><xsl:value-of select="text()"/></p>
+
+  </xsl:template>
+</xsl:stylesheet>

File src/webapp/xslt/showTree.xslt

+<?xml version="1.0"?>
+<!--
+
+ WebWork, Web Application Framework
+
+ Distributable under Apache license.
+ See terms of license at opensource.org
+
+ This XSLT stylesheet renders any xml to a fancy html view.
+
+ Author: Philipp Meier <meier@o-matic.de>
+ Version: $Revision$
+
+-->
+
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
+<xsl:output method="html" media-type="text/html" />
+
+<xsl:template  match="/">
+<html>
+    <head>
+        <title>XML Showtree</title>
+        <style type="text/css">
+          body {
+            background-color: white;
+            color: black;
+            font-size: 10pt;
+            font-family: verdana, arial, sans-serif;
+          }
+
+          div.element {
+            color: blue;
+          }
+
+          div.text {
+            color: black;
+          }
+
+        </style>
+    </head>
+    <body>
+        <xsl:apply-templates mode="showtree"/>
+    </body>
+</html>
+</xsl:template>
+
+<!-- match non-empty elements -->
+<xsl:template match="*[*]|*[text()]|*[comment()]|*[processing-instruction()]" mode="showtree">
+    <xsl:variable name="level">
+        <xsl:value-of select="count(ancestor::*)" />
+    </xsl:variable>
+    <div class="element" style="margin-left: {$level*20}pt;">
+        <xsl:value-of select="name(.)"/> <xsl:apply-templates select="@*" mode="showtree"/>
+    </div>
+    <xsl:apply-templates mode="showtree"/>
+</xsl:template>
+
+<!-- match empty elements -->
+<xsl:template match="*" mode="showtree">
+    <xsl:variable name="level">
+        <xsl:value-of select="count(ancestor::*)" />
+    </xsl:variable>
+    <div class="element" style="margin-left: {$level*20}pt;">
+        <xsl:value-of select="name(.)"/> <xsl:apply-templates select="@*" mode="showtree"/>
+    </div>
+</xsl:template>
+
+<!-- match text -->
+<xsl:template match="text()" mode="showtree">
+    <xsl:variable name="level">
+        <xsl:value-of select="count(ancestor::*)" />
+    </xsl:variable>
+    <div class="text" style="margin-left: {$level*20}pt;"><xsl:value-of select="." /></div>
+</xsl:template>
+
+
+<xsl:template match="@*" mode="showtree">
+  <xsl:value-of select="concat(' ',name(.),'=')"/>&quot;<xsl:value-of select="." />&quot;
+</xsl:template>
+
+</xsl:stylesheet>

File src/webapp/xslt/simpleAction.xslt

+<?xml version="1.0"?>
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
+    <xsl:output method="html" media-type="text/html"/>
+
+    <xsl:template match="result">
+        <html>
+            <head>
+                <title>Webwork XSLT Demot - Simple Actions</title>
+            </head>
+            <body>
+
+                <h1>Webwork XSLT Demot - Simple Actions</h1>
+                <p>This demonstrates the capabilites of the new XSLTResult for webwork2.</p>
+
+                <xsl:apply-templates select="scalar"/>
+
+                <xsl:apply-templates select="multiList"/>
+
+            </body>
+        </html>
+    </xsl:template>
+
+    <xsl:template match="scalar">
+        <h2>Scalar</h2>
+        <p>This is the property scalar</p>
+        <xsl:value-of select="."/>
+    </xsl:template>
+
+    <xsl:template match="multiList">
+        <h2>MultiList</h2>
+        <p>This is the property multiList</p>
+        <ul>
+            <xsl:for-each select="*">
+                <li><xsl:apply-templates select="."/></li>
+            </xsl:for-each>
+        </ul>
+    </xsl:template>
+
+    <xsl:template match="list">
+        <h2>List</h2>
+        <p>This is a the property list</p>
+        <ul>
+            <xsl:for-each select="*">
+                <li><xsl:apply-templates select="."/></li>
+            </xsl:for-each>
+        </ul>
+    </xsl:template>
+
+</xsl:stylesheet>