You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ode.apache.org by mr...@apache.org on 2009/01/29 23:52:23 UTC

svn commit: r739064 - in /ode/sandbox/simpel/src: main/java/org/apache/ode/embed/messaging/ main/java/org/apache/ode/rest/ main/java/org/apache/ode/simpel/expr/ test/java/org/apache/ode/rest/

Author: mriou
Date: Thu Jan 29 22:52:23 2009
New Revision: 739064

URL: http://svn.apache.org/viewvc?rev=739064&view=rev
Log:
Support for HTTP headers in sent messages. Basic auth. Allows task creation in Singleshot.

Added:
    ode/sandbox/simpel/src/main/java/org/apache/ode/embed/messaging/Base64.java
    ode/sandbox/simpel/src/test/java/org/apache/ode/rest/SingleshotTest.java
Modified:
    ode/sandbox/simpel/src/main/java/org/apache/ode/embed/messaging/MessageExchangeContextImpl.java
    ode/sandbox/simpel/src/main/java/org/apache/ode/rest/ProcessWebResource.java
    ode/sandbox/simpel/src/main/java/org/apache/ode/simpel/expr/E4XExprRuntime.java
    ode/sandbox/simpel/src/test/java/org/apache/ode/rest/PostWith201Resource.java
    ode/sandbox/simpel/src/test/java/org/apache/ode/rest/RestfulSimPELTest.java

Added: ode/sandbox/simpel/src/main/java/org/apache/ode/embed/messaging/Base64.java
URL: http://svn.apache.org/viewvc/ode/sandbox/simpel/src/main/java/org/apache/ode/embed/messaging/Base64.java?rev=739064&view=auto
==============================================================================
--- ode/sandbox/simpel/src/main/java/org/apache/ode/embed/messaging/Base64.java (added)
+++ ode/sandbox/simpel/src/main/java/org/apache/ode/embed/messaging/Base64.java Thu Jan 29 22:52:23 2009
@@ -0,0 +1,332 @@
+package org.apache.ode.embed.messaging;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.Writer;
+
+/**
+ * Temporarily needed for HTTP basic auth. TODO remove once we start using basic auth in Jersey.
+ */
+public class Base64 {
+    private static final char[] S_BASE64CHAR = { 'A', 'B', 'C', 'D', 'E', 'F',
+            'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S',
+            'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
+            'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's',
+            't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5',
+            '6', '7', '8', '9', '+', '/' };
+
+    private static final char S_BASE64PAD = '=';
+
+    private static final byte[] S_DECODETABLE = new byte[128];
+
+    static {
+        for (int i = 0; i < S_DECODETABLE.length; i++)
+            S_DECODETABLE[i] = Byte.MAX_VALUE; // 127
+        for (int i = 0; i < S_BASE64CHAR.length; i++)
+            // 0 to 63
+            S_DECODETABLE[S_BASE64CHAR[i]] = (byte) i;
+    }
+
+    private static int decode0(char[] ibuf, byte[] obuf, int wp) {
+        int outlen = 3;
+        if (ibuf[3] == S_BASE64PAD)
+            outlen = 2;
+        if (ibuf[2] == S_BASE64PAD)
+            outlen = 1;
+        int b0 = S_DECODETABLE[ibuf[0]];
+        int b1 = S_DECODETABLE[ibuf[1]];
+        int b2 = S_DECODETABLE[ibuf[2]];
+        int b3 = S_DECODETABLE[ibuf[3]];
+        switch (outlen) {
+            case 1:
+                obuf[wp] = (byte) (b0 << 2 & 0xfc | b1 >> 4 & 0x3);
+                return 1;
+            case 2:
+                obuf[wp++] = (byte) (b0 << 2 & 0xfc | b1 >> 4 & 0x3);
+                obuf[wp] = (byte) (b1 << 4 & 0xf0 | b2 >> 2 & 0xf);
+                return 2;
+            case 3:
+                obuf[wp++] = (byte) (b0 << 2 & 0xfc | b1 >> 4 & 0x3);
+                obuf[wp++] = (byte) (b1 << 4 & 0xf0 | b2 >> 2 & 0xf);
+                obuf[wp] = (byte) (b2 << 6 & 0xc0 | b3 & 0x3f);
+                return 3;
+            default:
+                throw new RuntimeException("internalError00");
+        }
+    }
+
+    /**
+     *
+     */
+    public static byte[] decode(char[] data, int off, int len) {
+        char[] ibuf = new char[4];
+        int ibufcount = 0;
+        byte[] obuf = new byte[len / 4 * 3 + 3];
+        int obufcount = 0;
+        for (int i = off; i < off + len; i++) {
+            char ch = data[i];
+            if (ch == S_BASE64PAD || ch < S_DECODETABLE.length
+                    && S_DECODETABLE[ch] != Byte.MAX_VALUE) {
+                ibuf[ibufcount++] = ch;
+                if (ibufcount == ibuf.length) {
+                    ibufcount = 0;
+                    obufcount += decode0(ibuf, obuf, obufcount);
+                }
+            }
+        }
+        if (obufcount == obuf.length)
+            return obuf;
+        byte[] ret = new byte[obufcount];
+        System.arraycopy(obuf, 0, ret, 0, obufcount);
+        return ret;
+    }
+
+    /**
+     *
+     */
+    public static byte[] decode(String data) {
+        char[] ibuf = new char[4];
+        int ibufcount = 0;
+        byte[] obuf = new byte[data.length() / 4 * 3 + 3];
+        int obufcount = 0;
+        for (int i = 0; i < data.length(); i++) {
+            char ch = data.charAt(i);
+            if (ch == S_BASE64PAD || ch < S_DECODETABLE.length
+                    && S_DECODETABLE[ch] != Byte.MAX_VALUE) {
+                ibuf[ibufcount++] = ch;
+                if (ibufcount == ibuf.length) {
+                    ibufcount = 0;
+                    obufcount += decode0(ibuf, obuf, obufcount);
+                }
+            }
+        }
+        if (obufcount == obuf.length)
+            return obuf;
+        byte[] ret = new byte[obufcount];
+        System.arraycopy(obuf, 0, ret, 0, obufcount);
+        return ret;
+    }
+
+    /**
+     * checks input string for invalid Base64 characters
+     *
+     * @param data
+     * @return true, if String contains only valid Base64 characters. false, otherwise
+     */
+    public static boolean isValidBase64Encoding(String data) {
+        for (int i = 0; i < data.length(); i++) {
+            char ch = data.charAt(i);
+
+            if (ch == S_BASE64PAD || ch < S_DECODETABLE.length
+                    && S_DECODETABLE[ch] != Byte.MAX_VALUE) {
+                //valid character.Do nothing
+            } else if (ch == '\r' || ch == '\n') {
+                //do nothing
+            } else {
+                return false;
+            }
+        }//iterate over all characters in the string
+        return true;
+    }
+
+
+    /**
+     *
+     */
+    public static void decode(char[] data, int off, int len,
+                              OutputStream ostream) throws IOException {
+        char[] ibuf = new char[4];
+        int ibufcount = 0;
+        byte[] obuf = new byte[3];
+        for (int i = off; i < off + len; i++) {
+            char ch = data[i];
+            if (ch == S_BASE64PAD || ch < S_DECODETABLE.length
+                    && S_DECODETABLE[ch] != Byte.MAX_VALUE) {
+                ibuf[ibufcount++] = ch;
+                if (ibufcount == ibuf.length) {
+                    ibufcount = 0;
+                    int obufcount = decode0(ibuf, obuf, 0);
+                    ostream.write(obuf, 0, obufcount);
+                }
+            }
+        }
+    }
+
+    /**
+     *
+     */
+    public static void decode(String data, OutputStream ostream)
+            throws IOException {
+        char[] ibuf = new char[4];
+        int ibufcount = 0;
+        byte[] obuf = new byte[3];
+        for (int i = 0; i < data.length(); i++) {
+            char ch = data.charAt(i);
+            if (ch == S_BASE64PAD || ch < S_DECODETABLE.length
+                    && S_DECODETABLE[ch] != Byte.MAX_VALUE) {
+                ibuf[ibufcount++] = ch;
+                if (ibufcount == ibuf.length) {
+                    ibufcount = 0;
+                    int obufcount = decode0(ibuf, obuf, 0);
+                    ostream.write(obuf, 0, obufcount);
+                }
+            }
+        }
+    }
+
+    /** Returns base64 representation of specified byte array. */
+    public static String encode(byte[] data) {
+        return encode(data, 0, data.length);
+    }
+
+    /** Returns base64 representation of specified byte array. */
+    public static String encode(byte[] data, int off, int len) {
+        if (len <= 0)
+            return "";
+        char[] out = new char[len / 3 * 4 + 4];
+        int rindex = off;
+        int windex = 0;
+        int rest = len - off;
+        while (rest >= 3) {
+            int i = ((data[rindex] & 0xff) << 16)
+                    + ((data[rindex + 1] & 0xff) << 8)
+                    + (data[rindex + 2] & 0xff);
+            out[windex++] = S_BASE64CHAR[i >> 18];
+            out[windex++] = S_BASE64CHAR[(i >> 12) & 0x3f];
+            out[windex++] = S_BASE64CHAR[(i >> 6) & 0x3f];
+            out[windex++] = S_BASE64CHAR[i & 0x3f];
+            rindex += 3;
+            rest -= 3;
+        }
+        if (rest == 1) {
+            int i = data[rindex] & 0xff;
+            out[windex++] = S_BASE64CHAR[i >> 2];
+            out[windex++] = S_BASE64CHAR[(i << 4) & 0x3f];
+            out[windex++] = S_BASE64PAD;
+            out[windex++] = S_BASE64PAD;
+        } else if (rest == 2) {
+            int i = ((data[rindex] & 0xff) << 8) + (data[rindex + 1] & 0xff);
+            out[windex++] = S_BASE64CHAR[i >> 10];
+            out[windex++] = S_BASE64CHAR[(i >> 4) & 0x3f];
+            out[windex++] = S_BASE64CHAR[(i << 2) & 0x3f];
+            out[windex++] = S_BASE64PAD;
+        }
+        return new String(out, 0, windex);
+    }
+
+    /** Outputs base64 representation of the specified byte array to the specified String Buffer */
+    public static void encode(byte[] data, int off, int len, StringBuffer buffer) {
+        if (len <= 0) {
+            return;
+        }
+
+        char[] out = new char[4];
+        int rindex = off;
+        int rest = len - off;
+        while (rest >= 3) {
+            int i = ((data[rindex] & 0xff) << 16)
+                    + ((data[rindex + 1] & 0xff) << 8)
+                    + (data[rindex + 2] & 0xff);
+            out[0] = S_BASE64CHAR[i >> 18];
+            out[1] = S_BASE64CHAR[(i >> 12) & 0x3f];
+            out[2] = S_BASE64CHAR[(i >> 6) & 0x3f];
+            out[3] = S_BASE64CHAR[i & 0x3f];
+            buffer.append(out);
+            rindex += 3;
+            rest -= 3;
+        }
+        if (rest == 1) {
+            int i = data[rindex] & 0xff;
+            out[0] = S_BASE64CHAR[i >> 2];
+            out[1] = S_BASE64CHAR[(i << 4) & 0x3f];
+            out[2] = S_BASE64PAD;
+            out[3] = S_BASE64PAD;
+            buffer.append(out);
+        } else if (rest == 2) {
+            int i = ((data[rindex] & 0xff) << 8) + (data[rindex + 1] & 0xff);
+            out[0] = S_BASE64CHAR[i >> 10];
+            out[1] = S_BASE64CHAR[(i >> 4) & 0x3f];
+            out[2] = S_BASE64CHAR[(i << 2) & 0x3f];
+            out[3] = S_BASE64PAD;
+            buffer.append(out);
+        }
+    }
+
+    /** Outputs base64 representation of the specified byte array to a byte stream. */
+    public static void encode(byte[] data, int off, int len,
+                              OutputStream ostream) throws IOException {
+        if (len <= 0)
+            return;
+        byte[] out = new byte[4];
+        int rindex = off;
+        int rest = len - off;
+        while (rest >= 3) {
+            int i = ((data[rindex] & 0xff) << 16)
+                    + ((data[rindex + 1] & 0xff) << 8)
+                    + (data[rindex + 2] & 0xff);
+            out[0] = (byte) S_BASE64CHAR[i >> 18];
+            out[1] = (byte) S_BASE64CHAR[(i >> 12) & 0x3f];
+            out[2] = (byte) S_BASE64CHAR[(i >> 6) & 0x3f];
+            out[3] = (byte) S_BASE64CHAR[i & 0x3f];
+            ostream.write(out, 0, 4);
+            rindex += 3;
+            rest -= 3;
+        }
+        if (rest == 1) {
+            int i = data[rindex] & 0xff;
+            out[0] = (byte) S_BASE64CHAR[i >> 2];
+            out[1] = (byte) S_BASE64CHAR[(i << 4) & 0x3f];
+            out[2] = (byte) S_BASE64PAD;
+            out[3] = (byte) S_BASE64PAD;
+            ostream.write(out, 0, 4);
+        } else if (rest == 2) {
+            int i = ((data[rindex] & 0xff) << 8) + (data[rindex + 1] & 0xff);
+            out[0] = (byte) S_BASE64CHAR[i >> 10];
+            out[1] = (byte) S_BASE64CHAR[(i >> 4) & 0x3f];
+            out[2] = (byte) S_BASE64CHAR[(i << 2) & 0x3f];
+            out[3] = (byte) S_BASE64PAD;
+            ostream.write(out, 0, 4);
+        }
+    }
+
+    /** Outputs base64 representation of the specified byte array to a character stream. */
+    public static void encode(byte[] data, int off, int len, Writer writer)
+            throws IOException {
+        if (len <= 0)
+            return;
+        char[] out = new char[4];
+        int rindex = off;
+        int rest = len - off;
+        int output = 0;
+        while (rest >= 3) {
+            int i = ((data[rindex] & 0xff) << 16)
+                    + ((data[rindex + 1] & 0xff) << 8)
+                    + (data[rindex + 2] & 0xff);
+            out[0] = S_BASE64CHAR[i >> 18];
+            out[1] = S_BASE64CHAR[(i >> 12) & 0x3f];
+            out[2] = S_BASE64CHAR[(i >> 6) & 0x3f];
+            out[3] = S_BASE64CHAR[i & 0x3f];
+            writer.write(out, 0, 4);
+            rindex += 3;
+            rest -= 3;
+            output += 4;
+            if (output % 76 == 0)
+                writer.write("\n");
+        }
+        if (rest == 1) {
+            int i = data[rindex] & 0xff;
+            out[0] = S_BASE64CHAR[i >> 2];
+            out[1] = S_BASE64CHAR[(i << 4) & 0x3f];
+            out[2] = S_BASE64PAD;
+            out[3] = S_BASE64PAD;
+            writer.write(out, 0, 4);
+        } else if (rest == 2) {
+            int i = ((data[rindex] & 0xff) << 8) + (data[rindex + 1] & 0xff);
+            out[0] = S_BASE64CHAR[i >> 10];
+            out[1] = S_BASE64CHAR[(i >> 4) & 0x3f];
+            out[2] = S_BASE64CHAR[(i << 2) & 0x3f];
+            out[3] = S_BASE64PAD;
+            writer.write(out, 0, 4);
+        }
+    }
+}

Modified: ode/sandbox/simpel/src/main/java/org/apache/ode/embed/messaging/MessageExchangeContextImpl.java
URL: http://svn.apache.org/viewvc/ode/sandbox/simpel/src/main/java/org/apache/ode/embed/messaging/MessageExchangeContextImpl.java?rev=739064&r1=739063&r2=739064&view=diff
==============================================================================
--- ode/sandbox/simpel/src/main/java/org/apache/ode/embed/messaging/MessageExchangeContextImpl.java (original)
+++ ode/sandbox/simpel/src/main/java/org/apache/ode/embed/messaging/MessageExchangeContextImpl.java Thu Jan 29 22:52:23 2009
@@ -7,7 +7,7 @@
 import org.w3c.dom.Element;
 import org.w3c.dom.Document;
 import org.w3c.dom.Node;
-import org.xml.sax.SAXException;
+import org.w3c.dom.NodeList;
 
 import javax.wsdl.Operation;
 import javax.wsdl.Fault;
@@ -15,7 +15,7 @@
 import javax.xml.namespace.QName;
 import java.util.Set;
 import java.util.HashSet;
-import java.io.IOException;
+import java.io.UnsupportedEncodingException;
 
 import com.sun.jersey.api.client.config.ClientConfig;
 import com.sun.jersey.api.client.config.DefaultClientConfig;
@@ -106,8 +106,10 @@
         ClientResponse resp;
         WebResource.Builder wr = c.resource(res.getUrl()).path("/").accept(res.getContentType()).type(res.getContentType());
         if (restOutMessageExchange.getRequest() != null) {
+            Element payload = restOutMessageExchange.getRequest().getMessage();
+            handleOutHeaders(payload, wr);
             resp = wr.method(res.getMethod().toUpperCase(), ClientResponse.class,
-                    DOMUtils.domToString(unwrapToPayload(restOutMessageExchange.getRequest().getMessage())));
+                    DOMUtils.domToString(unwrapToPayload(payload)));
         } else resp = wr.method(res.getMethod().toUpperCase(), ClientResponse.class);
 
         if (resp.getStatus() == 204) {
@@ -115,10 +117,29 @@
             return;
         }
 
-        // TODO check more status
         String response = resp.getEntity(String.class);
+
+        if (resp.getStatus() == 401) {
+            QName faultName = new QName("http://ode.apache.org/fault/http/", "http401");
+            Document odeMsg = DOMUtils.newDocument();
+            Element odeMsgEl = odeMsg.createElementNS(null, "message");
+            odeMsg.appendChild(odeMsgEl);
+            Element partElmt = odeMsg.createElement("payload");
+            odeMsgEl.appendChild(partElmt);
+            Element methodElmt = odeMsg.createElementNS(faultName.getNamespaceURI(), faultName.getLocalPart());
+            partElmt.appendChild(methodElmt);
+            methodElmt.setTextContent(response);
+
+            Message responseMsg = restOutMessageExchange.createMessage(null);
+            responseMsg.setMessage(odeMsgEl);
+            restOutMessageExchange.replyWithFault(faultName, responseMsg);
+            return;
+        }
+
+        // TODO handle failure status
+        // TODO allow POST over simple form url-encoded
         Element responseXML = null;
-        if (response != null && response.length() > 0) {
+        if (response != null && response.trim().length() > 0) {
             try {
                 responseXML = DOMUtils.stringToDOM(response);
             } catch (Exception e) {
@@ -187,6 +208,34 @@
         return payload;
     }
 
+    private void handleOutHeaders(Element msg, WebResource.Builder wr) {
+        Element root = DOMUtils.getFirstChildElement(DOMUtils.getFirstChildElement(msg));
+        Node headers = DOMUtils.findChildByName(root, new QName(null, "headers"));
+        if (headers != null) {
+            NodeList headerElmts = headers.getChildNodes();
+            for (int m = 0; m < headerElmts.getLength(); m++) {
+                Node n = headerElmts.item(m);
+                if (n.getNodeType() == Node.ELEMENT_NODE) {
+                    if (n.getNodeName().equals("basicAuth")) {
+                        Element login = DOMUtils.findChildByName((Element) n, new QName(null, "login"));
+                        Element password = DOMUtils.findChildByName((Element) n, new QName(null, "password"));
+                        if (login != null && password != null) {
+                            // TODO rely on Jersey basic auth once 1.0.2 is released
+                            try {
+                                byte[] unencoded = (login.getTextContent() + ":" + password.getTextContent()).getBytes("UTF-8");
+                                String credString = Base64.encode(unencoded);
+                                String authHeader = "Basic " + credString;
+                                wr.header("authorization", authHeader);
+                            } catch (UnsupportedEncodingException e) {
+                                throw new RuntimeException(e);
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+
     private Element withHeaders(Element element) {
         Element res = DOMUtils.findChildByName(element, new QName(null, "headers"));
         if (res == null) {

Modified: ode/sandbox/simpel/src/main/java/org/apache/ode/rest/ProcessWebResource.java
URL: http://svn.apache.org/viewvc/ode/sandbox/simpel/src/main/java/org/apache/ode/rest/ProcessWebResource.java?rev=739064&r1=739063&r2=739064&view=diff
==============================================================================
--- ode/sandbox/simpel/src/main/java/org/apache/ode/rest/ProcessWebResource.java (original)
+++ ode/sandbox/simpel/src/main/java/org/apache/ode/rest/ProcessWebResource.java Thu Jan 29 22:52:23 2009
@@ -13,6 +13,7 @@
 import javax.ws.rs.core.MultivaluedMap;
 import javax.ws.rs.core.UriInfo;
 import javax.ws.rs.core.Context;
+import javax.xml.namespace.QName;
 import java.util.List;
 import java.util.Map;
 
@@ -62,7 +63,8 @@
                 Document doc = DOMUtils.newDocument();
                 Element docElmt = doc.createElement("document");
                 Element partElmt = doc.createElement("payload");
-                Element rootElmt = doc.createElement("root");
+                // For some reason, this sometimes ends up in assignment as a node with no local name if not ns'd
+                Element rootElmt = doc.createElementNS(null, "root");
                 doc.appendChild(docElmt);
                 docElmt.appendChild(partElmt);
                 partElmt.appendChild(rootElmt);

Modified: ode/sandbox/simpel/src/main/java/org/apache/ode/simpel/expr/E4XExprRuntime.java
URL: http://svn.apache.org/viewvc/ode/sandbox/simpel/src/main/java/org/apache/ode/simpel/expr/E4XExprRuntime.java?rev=739064&r1=739063&r2=739064&view=diff
==============================================================================
--- ode/sandbox/simpel/src/main/java/org/apache/ode/simpel/expr/E4XExprRuntime.java (original)
+++ ode/sandbox/simpel/src/main/java/org/apache/ode/simpel/expr/E4XExprRuntime.java Thu Jan 29 22:52:23 2009
@@ -18,6 +18,7 @@
 import org.w3c.dom.Document;
 import org.w3c.dom.Node;
 import org.w3c.dom.Element;
+import org.w3c.dom.NodeList;
 
 import javax.xml.namespace.QName;
 import java.util.*;
@@ -108,7 +109,9 @@
                 // Only content is copied, need to wrap
                 Document doc = DOMUtils.newDocument();
                 Element wrapper = doc.createElement("assignWrapper");
-                wrapper.appendChild(doc.importNode(XMLLibImpl.toDomNode(res), true));
+                Node resNode = doc.importNode(XMLLibImpl.toDomNode(res), true);
+                wrapper.appendChild(resNode);
+                if (resNode.getNodeType() == Node.ELEMENT_NODE) mergeHeaders((Element) resNode);
                 resList.add(wrapper);
             } catch (IllegalArgumentException e) {
                 // Rhino makes it pretty hard to use it sXML impl, XML and XMLList are package level
@@ -197,11 +200,14 @@
                 // When we're querying on headers, the sub-element is supposed to be right under the
                 // current. To avoid pollution of the main user variable we store it one level up so
                 // we're readjusting here.
-                if (_expr.getExpr().indexOf(".headers") > 0 && node.getParentNode() != null &&
-                        node.getParentNode().getNodeType() == Node.ELEMENT_NODE) {
+                if (!forceDelegate && (_expr.getExpr().indexOf(".headers") > 0
+                        || (_expr.getLValue() != null && _expr.getLValue().indexOf(".headers") > 0)) 
+                        && node.getParentNode() != null
+                        && node.getParentNode().getNodeType() == Node.ELEMENT_NODE) {
                     Element parent = (Element) node.getParentNode();
                     Element headers = DOMUtils.findChildByName(parent, new QName(null, "headers"));
-                    node.appendChild(node.getOwnerDocument().importNode(headers, true));
+                    if (headers != null)
+                        node.appendChild(node.getOwnerDocument().importNode(headers, true));
                 }
 
                 // Have to remove the xml header otherwise it confuses Rhino
@@ -279,4 +285,29 @@
         for (String s : ss) buffer.append(s);
         return buffer.toString();
     }
+
+    private void mergeHeaders(Element elmt) {
+        // As a convenience during E4X assignment, headers is a subnode of the main node. To avoid pollution
+        // it's actually stored as a subnode of the parent so we move/merge here.
+        NodeList elmtHeadersNL = elmt.getElementsByTagName("headers");
+        if (elmtHeadersNL.getLength() > 0) {
+            Element elmtHeaders = (Element) elmtHeadersNL.item(0);
+            Element parent = (Element) elmt.getParentNode();
+            elmt.removeChild(elmtHeaders);
+            Element parentHeaders = DOMUtils.getElementByID(parent, "headers");
+            if (parentHeaders == null) {
+                parent.appendChild(elmtHeaders);
+            } else {
+                NodeList headerChildren = elmtHeaders.getChildNodes();
+                for (int m = 0; m < headerChildren.getLength(); m++) {
+                    Node n = headerChildren.item(m);
+                    if (n.getNodeType() == Node.ELEMENT_NODE) {
+                        n.getParentNode().removeChild(n);
+                        parentHeaders.appendChild(n);
+                    }
+                }
+            }
+        }
+    }
+    
 }

Modified: ode/sandbox/simpel/src/test/java/org/apache/ode/rest/PostWith201Resource.java
URL: http://svn.apache.org/viewvc/ode/sandbox/simpel/src/test/java/org/apache/ode/rest/PostWith201Resource.java?rev=739064&r1=739063&r2=739064&view=diff
==============================================================================
--- ode/sandbox/simpel/src/test/java/org/apache/ode/rest/PostWith201Resource.java (original)
+++ ode/sandbox/simpel/src/test/java/org/apache/ode/rest/PostWith201Resource.java Thu Jan 29 22:52:23 2009
@@ -13,6 +13,6 @@
     @Consumes("application/xml")
     @Produces("application/xml")
     public Response post() {
-        return Response.status(201).header("Location", "http://foo/bar").build();
+        return Response.status(201).header("Location", "http://foo/bar").entity("<response><appStatus>ok</appStatus></response>").build();
     }
 }

Modified: ode/sandbox/simpel/src/test/java/org/apache/ode/rest/RestfulSimPELTest.java
URL: http://svn.apache.org/viewvc/ode/sandbox/simpel/src/test/java/org/apache/ode/rest/RestfulSimPELTest.java?rev=739064&r1=739063&r2=739064&view=diff
==============================================================================
--- ode/sandbox/simpel/src/test/java/org/apache/ode/rest/RestfulSimPELTest.java (original)
+++ ode/sandbox/simpel/src/test/java/org/apache/ode/rest/RestfulSimPELTest.java Thu Jan 29 22:52:23 2009
@@ -250,7 +250,7 @@
         assertEquals(response, "http://foo/bar");
     }
 
-    private static final String HELLO_FORM_WORLD =
+    private static final String HELLO_FORM_WORLD = // TODO reply with HTML
             "process HelloFormWorld { \n" +
             "   receive(self) { |form| \n" +
             "       helloXml = <hello>{\"Hello \" + form.firstname + \" \" + form.lastname}</hello>; \n" +

Added: ode/sandbox/simpel/src/test/java/org/apache/ode/rest/SingleshotTest.java
URL: http://svn.apache.org/viewvc/ode/sandbox/simpel/src/test/java/org/apache/ode/rest/SingleshotTest.java?rev=739064&view=auto
==============================================================================
--- ode/sandbox/simpel/src/test/java/org/apache/ode/rest/SingleshotTest.java (added)
+++ ode/sandbox/simpel/src/test/java/org/apache/ode/rest/SingleshotTest.java Thu Jan 29 22:52:23 2009
@@ -0,0 +1,62 @@
+package org.apache.ode.rest;
+
+import org.apache.ode.EmbeddedServer;
+import org.apache.ode.Descriptor;
+import org.junit.Ignore;
+import junit.framework.TestCase;
+import com.sun.jersey.api.client.config.ClientConfig;
+import com.sun.jersey.api.client.config.DefaultClientConfig;
+import com.sun.jersey.api.client.Client;
+import com.sun.jersey.api.client.WebResource;
+import com.sun.jersey.api.client.ClientResponse;
+
+/**
+ * For now this test requires a running instance of Singleshot, otherwise it will fail.
+ */
+@Ignore
+public class SingleshotTest extends TestCase {
+
+    EmbeddedServer server;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        server = new EmbeddedServer();
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+        server.stop();
+    }
+
+    private static final String TASK_CREATOR =
+            "shotBase = \"http://localhost:3000\" \n" +
+            "process TaskCreator { \n" +
+            "   receive(self) { |task| \n" +
+            "       task.headers.basicAuth.login = \"mriou\"; \n" +
+            "       task.headers.basicAuth.password = \"secret\"; \n" +
+            "       resp = request(shotBase + \"/tasks\", \"POST\", task); \n" +
+            "       reply(resp); \n" +
+            "   }\n" +
+            "}";
+
+    public void testCreateTask() throws Exception {
+        server.start();
+        Descriptor desc = new Descriptor();
+        desc.setAddress("/taskcreator");
+        server.deploy(TASK_CREATOR, desc);
+
+        ClientConfig cc = new DefaultClientConfig();
+        Client c = Client.create(cc);
+
+        WebResource wr = c.resource("http://localhost:3434/taskcreator");
+        ClientResponse resp = wr.path("/").accept("application/xml").type("application/xml")
+                .post(ClientResponse.class, "<task><title>SimPEL Task</title>" +
+                        "<description>Test task created from a SimPEL process.</description>" +
+                        "<owner>mriou</owner>" +
+                        "<data>&#x7B;&#x7D;</data></task>"); // Opening and closing curly literals escaped
+        String response = resp.getEntity(String.class);
+        System.out.println(response);
+    }
+}