From b6dff22c2177eedb6e9d6463fdc55bea7bf75e0e Mon Sep 17 00:00:00 2001
From: Ralf Jung <ralfjung-e@gmx.de>
Date: Sat, 23 Jul 2011 17:13:40 +0200
Subject: [PATCH] implemenet POST and PUT for URLGenerator

---
 .../oxf/processor/generator/URLGenerator.java      |   67 ++++++++++++++------
 .../oxf/xml/schemas/url-generator-config.rng       |   14 ++++
 2 files changed, 62 insertions(+), 19 deletions(-)

diff --git a/src/java/org/orbeon/oxf/processor/generator/URLGenerator.java b/src/java/org/orbeon/oxf/processor/generator/URLGenerator.java
index 5181b08..f36c590 100644
--- a/src/java/org/orbeon/oxf/processor/generator/URLGenerator.java
+++ b/src/java/org/orbeon/oxf/processor/generator/URLGenerator.java
@@ -23,6 +23,7 @@ import org.orbeon.oxf.pipeline.api.ExternalContext;
 import org.orbeon.oxf.pipeline.api.PipelineContext;
 import org.orbeon.oxf.pipeline.api.XMLReceiver;
 import org.orbeon.oxf.processor.*;
+import org.orbeon.oxf.processor.serializer.BinaryTextXMLReceiver;
 import org.orbeon.oxf.resources.ResourceManagerWrapper;
 import org.orbeon.oxf.resources.URLFactory;
 import org.orbeon.oxf.resources.handler.OXFHandler;
@@ -41,6 +42,7 @@ import org.xml.sax.*;
 
 import javax.xml.transform.dom.DOMSource;
 import java.io.IOException;
+import java.io.ByteArrayOutputStream;
 import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.net.MalformedURLException;
@@ -121,7 +123,7 @@ public class URLGenerator extends ProcessorImpl {
     public URLGenerator(URL url, String contentType, boolean forceContentType, String encoding, boolean forceEncoding,
                       boolean ignoreConnectionEncoding, XMLUtils.ParserConfiguration parserConfiguration, boolean handleLexical,
                       String mode, Map<String, String[]> headerNameValues, String forwardHeaders, boolean cacheUseLocalCache) {
-        this.localConfigURIReferences = new ConfigURIReferences(new Config(url, contentType, forceContentType, encoding,
+        this.localConfigURIReferences = new ConfigURIReferences(new Config(url, Connection.Method.GET.name(), contentType, forceContentType, encoding,
                 forceEncoding, ignoreConnectionEncoding, parserConfiguration, handleLexical, mode,
                 headerNameValues, forwardHeaders,
                 cacheUseLocalCache, new TidyConfig(null)));
@@ -130,6 +132,7 @@ public class URLGenerator extends ProcessorImpl {
 
     private static class Config {
         private URL url;
+        private String method = Connection.Method.GET.name();
         private String contentType = ProcessorUtils.DEFAULT_CONTENT_TYPE;
         private boolean forceContentType = DEFAULT_FORCE_CONTENT_TYPE;
         private String encoding;
@@ -166,12 +169,13 @@ public class URLGenerator extends ProcessorImpl {
             this.tidyConfig = new TidyConfig(null);
         }
 
-        public Config(URL url, String contentType, boolean forceContentType, String encoding, boolean forceEncoding,
+        public Config(URL url, String method, String contentType, boolean forceContentType, String encoding, boolean forceEncoding,
                       boolean ignoreConnectionEncoding, XMLUtils.ParserConfiguration parserConfiguration,
                       boolean handleLexical, String mode, Map<String, String[]> headerNameValues, String forwardHeaders,
                       boolean cacheUseLocalCache, TidyConfig tidyConfig) {
 
             this.url = url;
+            this.method = method;
             this.contentType = contentType;
             this.forceContentType = forceContentType;
             this.encoding = encoding;
@@ -192,6 +196,10 @@ public class URLGenerator extends ProcessorImpl {
         public URL getURL() {
             return url;
         }
+        
+        public String getMethod() {
+            return method;
+        }
 
         public String getContentType() {
             return contentType;
@@ -243,7 +251,7 @@ public class URLGenerator extends ProcessorImpl {
 
         @Override
         public String toString() {
-            return "[" + getURL().toExternalForm() + "|" + getContentType() + "|" + getEncoding() + "|" + parserConfiguration.getKey() + "|" + isHandleLexical() + "|" + isForceContentType()
+            return "[" + getURL().toExternalForm() + "|" + getMethod() + "|" + getContentType() + "|" + getEncoding() + "|" + parserConfiguration.getKey() + "|" + isHandleLexical() + "|" + isForceContentType()
                     + "|" + isForceEncoding() + "|" + isIgnoreConnectionEncoding() + "|" + tidyConfig + "]";
         }
     }
@@ -291,6 +299,9 @@ public class URLGenerator extends ProcessorImpl {
                                 if (url == null) {
                                     throw new ValidationException("URL generator found null URL for config:\n" + Dom4jUtils.domToString(configElement), locationData);
                                 }
+                                String method = XPathUtils.selectStringValueNormalize(configElement, "/config/method");
+                                if (method == null) method = Connection.Method.GET.name();
+                                else method = method.toUpperCase();
 
                                 // Get content-type
                                 final String contentType = XPathUtils.selectStringValueNormalize(configElement, "/config/content-type");
@@ -360,7 +371,7 @@ public class URLGenerator extends ProcessorImpl {
                                             : URLFactory.createURL(url);
 
                                     // Create configuration
-                                    final Config config = new Config(fullURL, contentType, forceContentType, encoding, forceEncoding,
+                                    final Config config = new Config(fullURL, method, contentType, forceContentType, encoding, forceEncoding,
                                             ignoreConnectionEncoding, new XMLUtils.ParserConfiguration(validating, handleXInclude, externalEntities), handleLexical, mode,
                                             headerNameValues, forwardHeaders,
                                             cacheUseLocalCache, tidyConfig);
@@ -398,9 +409,16 @@ public class URLGenerator extends ProcessorImpl {
                             ((SAXStore) cachedResource).replay(xmlReceiver);
                         } else {
                             // We need to read the resource
+                            byte[] requestBody = null;
+                            if (!configURIReferences.config.getMethod().equals(Connection.Method.GET.name())) {
+                                // put the "data" input into the requestBody
+                                final ByteArrayOutputStream os = new ByteArrayOutputStream();
+                                readInputAsSAX(pipelineContext, INPUT_DATA, new BinaryTextXMLReceiver(os));
+                                requestBody = os.toByteArray();
+                            }
 
                             final URLGeneratorState state = (URLGenerator.URLGeneratorState) URLGenerator.this.getState(pipelineContext);
-                            handler = state.ensureMainResourceHandler(pipelineContext, configURIReferences.config);
+                            handler = state.ensureMainResourceHandler(pipelineContext, configURIReferences.config, requestBody);
 
                             // Find content-type to use. If the config says to force the
                             // content-type, we use the content-type provided by the user.
@@ -422,7 +440,7 @@ public class URLGenerator extends ProcessorImpl {
 
                             // Get and cache validity as the handler is open, as validity is likely to be used later
                             // again for caching reasons
-                            final Long validity = (Long) getHandlerValidity(pipelineContext, configURIReferences.config.getURL(), handler);
+                            final Long validity = (Long) getHandlerValidity(pipelineContext, configURIReferences.config, handler);
 
                             // Create store for caching if necessary
                             final XMLReceiver output = isUseLocalCache ? new SAXStore(xmlReceiver) : xmlReceiver;
@@ -532,6 +550,8 @@ public class URLGenerator extends ProcessorImpl {
                     ConfigURIReferences configURIReferences = getConfigURIReferences(pipelineContext);
                     if (configURIReferences == null)
                         return null;
+                    if (!configURIReferences.config.getMethod().equals(Connection.Method.GET.name()))
+                        return null; // can't say anything without the body
 
                     List<Object> validities = new ArrayList<Object>();
 
@@ -544,12 +564,13 @@ public class URLGenerator extends ProcessorImpl {
                     }
                     // Handle main document and config
                     final URLGeneratorState state = (URLGenerator.URLGeneratorState) URLGenerator.this.getState(pipelineContext);
-                    final ResourceHandler resourceHandler = state.ensureMainResourceHandler(pipelineContext, configURIReferences.config);
-                    validities.add(getHandlerValidity(pipelineContext, configURIReferences.config.getURL(), resourceHandler));
+                    final ResourceHandler resourceHandler = state.ensureMainResourceHandler(pipelineContext, configURIReferences.config, null); // we know this is a GET request, a null body is okay
+                    validities.add(getHandlerValidity(pipelineContext, configURIReferences.config, resourceHandler));
                     // Handle dependencies if any
                     if (configURIReferences.uriReferences != null) {
                         for (URIProcessorOutputImpl.URIReference uriReference: configURIReferences.uriReferences) {
-                            validities.add(getHandlerValidity(pipelineContext, URLFactory.createURL(uriReference.context, uriReference.spec), null));
+                            Config config = new Config(URLFactory.createURL(uriReference.context, uriReference.spec)); // not sure if this is correct... will use default settings for headers, method, ...
+                            validities.add(getHandlerValidity(pipelineContext, config, null));
                         }
                     }
                     return validities;
@@ -558,9 +579,9 @@ public class URLGenerator extends ProcessorImpl {
                 }
             }
 
-            private Object getHandlerValidity(PipelineContext pipelineContext, URL url, ResourceHandler handler) {
+            private Object getHandlerValidity(PipelineContext pipelineContext, Config config, ResourceHandler handler) {
                 final URLGeneratorState state = (URLGenerator.URLGeneratorState) URLGenerator.this.getState(pipelineContext);
-                final String urlString = url.toExternalForm();
+                final String urlString = config.getURL().toExternalForm();
                 if (state.isLastModifiedSet(urlString)) {
                     // Found value in state cache
                     return state.getLastModified(urlString);
@@ -570,9 +591,9 @@ public class URLGenerator extends ProcessorImpl {
                         final boolean mustDestroyHandler;
                         if (handler == null) {
                             // This should happen only for dependencies
-                            handler = OXFHandler.PROTOCOL.equals(url.getProtocol())
-                                ? new OXFResourceHandler(new Config(url)) // Should use full config so that headers are forwarded?
-                                : new URLResourceHandler(pipelineContext, new Config(url));// Should use full config so that headers are forwarded?
+                            handler = OXFHandler.PROTOCOL.equals(config.getURL().getProtocol())
+                                ? new OXFResourceHandler(config)
+                                : new URLResourceHandler(pipelineContext, config, null);
 
                             mustDestroyHandler = true;
                         } else {
@@ -641,6 +662,8 @@ public class URLGenerator extends ProcessorImpl {
         private InputStream inputStream;
 
         public OXFResourceHandler(Config config) {
+            if (!config.getMethod().equals(Connection.Method.GET.name()))
+                throw new OXFException("Can't "+config.getMethod()+" in OXFResourceHandler");
             this.config = config;
         }
 
@@ -732,11 +755,15 @@ public class URLGenerator extends ProcessorImpl {
         private PipelineContext pipelineContext;
         private Config config;
         private ConnectionResult connectionResult;
+        private byte[] requestBody;
         private InputStream inputStream;
 
-        public URLResourceHandler(PipelineContext pipelineContext, Config config) {
+        public URLResourceHandler(PipelineContext pipelineContext, Config config, byte []requestBody) {
+            if (!config.getMethod().equals(Connection.Method.GET.name()) && requestBody == null)
+                throw new OXFException("Can't "+config.getMethod()+" without requestBody");
             this.pipelineContext = pipelineContext;
             this.config = config;
+            this.requestBody = requestBody;
         }
 
         public String getResourceMediaType() throws IOException {
@@ -767,6 +794,8 @@ public class URLGenerator extends ProcessorImpl {
         }
 
         public Object getValidity() throws IOException {
+            if (!config.getMethod().equals(Connection.Method.GET.name()))
+                return null;
             openConnection();
             return connectionResult.getLastModified();
         }
@@ -791,8 +820,8 @@ public class URLGenerator extends ProcessorImpl {
             if (connectionResult == null) {
                 final ExternalContext externalContext = (ExternalContext) pipelineContext.getAttribute(PipelineContext.EXTERNAL_CONTEXT);
                 // TODO: pass logging callback
-                connectionResult = new Connection().open(externalContext, indentedLogger, false, Connection.Method.GET.name(),
-                        config.getURL(), null, null, null, null, null, config.getHeaderNameValues(), config.getForwardHeaders());
+                connectionResult = new Connection().open(externalContext, indentedLogger, false, config.getMethod(),
+                        config.getURL(), null, null, null, null, requestBody, config.getHeaderNameValues(), config.getForwardHeaders());
                 inputStream = connectionResult.getResponseInputStream();
             }
         }
@@ -900,12 +929,12 @@ public class URLGenerator extends ProcessorImpl {
             return (result instanceof String) ? null : (Long) result;
         }
 
-        public ResourceHandler ensureMainResourceHandler(PipelineContext pipelineContext, Config config) {
+        public ResourceHandler ensureMainResourceHandler(PipelineContext pipelineContext, Config config, byte[] requestBody) {
             if (mainResourceHandler == null) {
                 // Create and remember handler
                 mainResourceHandler = OXFHandler.PROTOCOL.equals(config.getURL().getProtocol())
                         ? new OXFResourceHandler(config)
-                        : new URLResourceHandler(pipelineContext, config);
+                        : new URLResourceHandler(pipelineContext, config, requestBody);
                 // Make sure it is destroyed when the pipeline ends at the latest
                 pipelineContext.addContextListener(new PipelineContext.ContextListener() {
                     public void contextDestroyed(boolean success) {
diff --git a/src/java/org/orbeon/oxf/xml/schemas/url-generator-config.rng b/src/java/org/orbeon/oxf/xml/schemas/url-generator-config.rng
index a0cb399..82ef2de 100644
--- a/src/java/org/orbeon/oxf/xml/schemas/url-generator-config.rng
+++ b/src/java/org/orbeon/oxf/xml/schemas/url-generator-config.rng
@@ -24,6 +24,20 @@
                         <data type="string"/>
                     </element>
                     <optional>
+                        <element name="method">
+                            <choice>
+                                <value>get</value>
+                                <value>GET</value>
+                                <value>post</value>
+                                <value>POST</value>
+                                <value>put</value>
+                                <value>PUT</value>
+                                <value>delete</value>
+                                <value>DELETE</value>
+                            </choice>
+                        </element>
+                    </optional>
+                    <optional>
                         <element name="content-type">
                             <data type="string">
                                 <!-- See: http://www.faqs.org/rfcs/rfc2045.html -->
-- 
1.7.5.4

