blob: 7d9bc1615308e9c216ccd7fe365350804c6d4b6f [file] [log] [blame]
/*
* Copyright 1999-2004 The Apache Software Foundation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.cocoon.generation;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Map;
import org.apache.avalon.framework.configuration.Configurable;
import org.apache.avalon.framework.configuration.Configuration;
import org.apache.avalon.framework.configuration.ConfigurationException;
import org.apache.avalon.framework.parameters.Parameters;
import org.apache.avalon.framework.service.ServiceException;
import org.apache.cocoon.ProcessingException;
import org.apache.cocoon.ResourceNotFoundException;
import org.apache.cocoon.environment.SourceResolver;
import org.apache.commons.httpclient.HttpConnection;
import org.apache.commons.httpclient.HttpMethodBase;
import org.apache.commons.httpclient.HttpState;
import org.apache.commons.httpclient.HttpURL;
import org.apache.commons.httpclient.HostConfiguration;
import org.apache.commons.httpclient.NameValuePair;
import org.apache.commons.httpclient.URIException;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.excalibur.xml.sax.SAXParser;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.AttributesImpl;
/**
* The <code>HttpProxyGenerator</code> is a Cocoon generator using the
* <b>Jakarta Commons HTTPClient Library</b> to access an XML stream
* over HTTP.
*
* @author <a href="mailto:ivelin@apache.org">Ivelin Ivanov</a>, June 2002
* @author <a href="mailto:tony@apache.org">Tony Collen</a>, December 2002
* @author <a href="mailto:pier@apache.org">Pier Fumagalli</a>, February 2003
* @version CVS $Id: HttpProxyGenerator.java,v 1.7 2004/03/05 13:02:20 bdelacretaz Exp $
*/
public class HttpProxyGenerator extends ServiceableGenerator implements Configurable {
/** The HTTP method to use at request time. */
private HttpMethodBase method = null;
/** The base HTTP URL for requests. */
private HttpURL url = null;
/** The list of request parameters for the request */
private ArrayList reqParams = null;
/** The list of query parameters for the request */
private ArrayList qryParams = null;
/** Wether we want a debug output or not */
private boolean debug = false;
/**
* Default (empty) constructor.
*/
public HttpProxyGenerator() {
super();
}
/**
* Set up this <code>Generator</code> instance from its sitemap <code>Configuration</code>
*
* @param configuration The base <code>Configuration</code> for this <code>Generator</code>.
* @throws ConfigurationException If this instance cannot be configured properly.
* @see #recycle()
*/
public void configure(Configuration configuration)
throws ConfigurationException {
/* Setup the HTTP method to use. */
String method = configuration.getChild("method").getValue("GET");
if ("GET".equalsIgnoreCase(method)) {
this.method = new GetMethod();
} else if ("POST".equalsIgnoreCase(method)) {
this.method = new PostMethod();
/* TODO: Is this still needed? Does it refer to a bug in bugzilla?
* At least the handling in httpclient has completely changed.
* Work around a bug from the HttpClient library */
((PostMethod) this.method).setRequestBody("");
} else {
throw new ConfigurationException("Invalid method \"" + method + "\" specified"
+ " at " + configuration.getChild("method").getLocation());
}
/* Create the base URL */
String url = configuration.getChild("url").getValue(null);
try {
if (url != null) this.url = new HttpURL(url);
} catch (URIException e) {
throw new ConfigurationException("Cannot process URL \"" + url + "\" specified"
+ " at " + configuration.getChild("url").getLocation());
}
/* Prepare the base request and query parameters */
this.reqParams = this.getParams(configuration.getChildren("param"));
this.qryParams = this.getParams(configuration.getChildren("query"));
}
/**
* Setup this <code>Generator</code> with its runtime configurations and parameters
* specified in the sitemap, and prepare it for generation.
*
* @param sourceResolver The <code>SourceResolver</code> instance resolving sources by
* system identifiers.
* @param objectModel The Cocoon "object model" <code>Map</code>
* @param parameters The runtime <code>Parameters</code> instance.
* @throws ProcessingException If this instance could not be setup.
* @throws SAXException If a SAX error occurred during setup.
* @throws IOException If an I/O error occurred during setup.
* @see #recycle()
*/
public void setup(SourceResolver sourceResolver, Map objectModel,
String source, Parameters parameters)
throws ProcessingException, SAXException, IOException {
/* Do the usual stuff */
super.setup(sourceResolver, objectModel, source, parameters);
/*
* Parameter handling: In case the method is a POST method, query
* parameters and request parameters will be two different arrays
* (one for the body, one for the query string, otherwise it's going
* to be the same one, as all parameters are passed on the query string
*/
ArrayList req = new ArrayList();
ArrayList qry = req;
if (this.method instanceof PostMethod) qry = new ArrayList();
req.addAll(this.reqParams);
qry.addAll(this.qryParams);
/*
* Parameter handling: complete or override the configured parameters with
* those specified in the pipeline.
*/
String names[] = parameters.getNames();
for (int x = 0; x < names.length; x++) {
String name = names[x];
String value = parameters.getParameter(name, null);
if (value == null) continue;
if (name.startsWith("query:")) {
name = name.substring("query:".length());
qry.add(new NameValuePair(name, value));
} else if (name.startsWith("param:")) {
name = name.substring("param:".length());
req.add(new NameValuePair(name, value));
} else if (name.startsWith("query-override:")) {
name = name.substring("query-override:".length());
qry = overrideParams(qry, name, value);
} else if (name.startsWith("param-override:")) {
name = name.substring("param-override:".length());
req = overrideParams(req, name, value);
}
}
/* Process the current source URL in relation to the configured one */
HttpURL src = (super.source == null ? null : new HttpURL(super.source));
if (this.url != null) src = (src == null ? this.url : new HttpURL(this.url, src));
if (src == null) throw new ProcessingException("No URL specified");
if (src.isRelativeURI()) {
throw new ProcessingException("Invalid URL \"" + src.toString() + "\"");
}
/* Configure the method with the resolved URL */
HostConfiguration hc = new HostConfiguration();
hc.setHost(src);
this.method.setHostConfiguration(hc);
this.method.setPath(src.getPath());
this.method.setQueryString(src.getQuery());
/* And now process the query string (from the parameters above) */
if (qry.size() > 0) {
String qs = this.method.getQueryString();
NameValuePair nvpa[] = new NameValuePair[qry.size()];
this.method.setQueryString((NameValuePair []) qry.toArray(nvpa));
if (qs != null) {
this.method.setQueryString(qs + "&" + this.method.getQueryString());
}
}
/* Finally process the body parameters */
if ((this.method instanceof PostMethod) && (req.size() > 0)) {
PostMethod post = (PostMethod) this.method;
NameValuePair nvpa[] = new NameValuePair[req.size()];
post.setRequestBody((NameValuePair []) req.toArray(nvpa));
}
/* Check the debugging flag */
this.debug = parameters.getParameterAsBoolean("debug", false);
}
/**
* Recycle this instance, clearing all done during setup and generation, and reverting
* back to what was configured in the sitemap.
*
* @see #configure(Configuration)
* @see #setup(SourceResolver, Map, String, Parameters)
* @see #generate()
*/
public void recycle() {
/* Recycle the method */
this.method.recycle();
/* TODO: Is this still needed? Does it refer to a bug in bugzilla?
* At least the handling in httpclient has completely changed.
* Work around a bug from the HttpClient library */
if (this.method instanceof PostMethod) ((PostMethod) this.method).setRequestBody("");
/* Clean up our parent */
super.recycle();
}
/**
* Parse the remote <code>InputStream</code> accessed over HTTP.
*
* @throws ResourceNotFoundException If the remote HTTP resource could not be found.
* @throws ProcessingException If an error occurred processing generation.
* @throws SAXException If an error occurred parsing or processing XML in the pipeline.
* @throws IOException If an I/O error occurred accessing the HTTP server.
*/
public void generate()
throws ResourceNotFoundException, ProcessingException, SAXException, IOException {
/* Do the boring stuff in case we have to do a debug output (blablabla) */
if (this.debug) {
this.generateDebugOutput();
return;
}
/* Call up the remote HTTP server */
HttpConnection connection = new HttpConnection(this.method.getHostConfiguration());
HttpState state = new HttpState();
this.method.setFollowRedirects(true);
int status = this.method.execute(state, connection);
if (status == 404) {
throw new ResourceNotFoundException("Unable to access \"" + this.method.getURI()
+ "\" (HTTP 404 Error)");
} else if ((status < 200) || (status > 299)) {
throw new IOException("Unable to access HTTP resource at \""
+ this.method.getURI().toString() + "\" (status=" + status + ")");
}
InputStream response = this.method.getResponseBodyAsStream();
/* Let's try to set up our InputSource from the response output stream and to parse it */
SAXParser parser = null;
try {
InputSource inputSource = new InputSource(response);
parser = (SAXParser) this.manager.lookup(SAXParser.ROLE);
parser.parse(inputSource, super.xmlConsumer);
} catch (ServiceException ex) {
throw new ProcessingException("Unable to get parser", ex);
} finally {
this.manager.release(parser);
this.method.releaseConnection();
connection.close();
}
}
/**
* Generate debugging output as XML data from the current configuration.
*
* @throws SAXException If an error occurred parsing or processing XML in the pipeline.
* @throws IOException If an I/O error occurred accessing the HTTP server.
*/
private void generateDebugOutput()
throws SAXException, IOException {
super.xmlConsumer.startDocument();
AttributesImpl attributes = new AttributesImpl();
attributes.addAttribute("", "method", "method", "CDATA", this.method.getName());
attributes.addAttribute("", "url", "url", "CDATA", this.method.getURI().toString());
attributes.addAttribute("", "protocol", "protocol", "CDATA",
(this.method.isHttp11() ? "HTTP/1.1" : "HTTP/1.0"));
super.xmlConsumer.startElement("", "request", "request", attributes);
if (this.method instanceof PostMethod) {
String body = ((PostMethod) this.method).getRequestBodyAsString();
attributes = new AttributesImpl();
attributes.addAttribute("", "name", "name", "CDATA", "Content-Type");
attributes.addAttribute("", "value", "value", "CDATA", "application/x-www-form-urlencoded");
super.xmlConsumer.startElement("", "header", "header", attributes);
super.xmlConsumer.endElement("", "header", "header");
attributes = new AttributesImpl();
attributes.addAttribute("", "name", "name", "CDATA", "Content-Length");
attributes.addAttribute("", "value", "value", "CDATA", Integer.toString(body.length()));
super.xmlConsumer.startElement("", "header", "header", attributes);
super.xmlConsumer.endElement("", "header", "header");
attributes = new AttributesImpl();
super.xmlConsumer.startElement("", "body", "body", attributes);
super.xmlConsumer.characters(body.toCharArray(), 0, body.length());
super.xmlConsumer.endElement("", "body", "body");
}
super.xmlConsumer.endElement("", "request", "request");
super.xmlConsumer.endDocument();
return;
}
/**
* Prepare a map of parameters from an array of <code>Configuration</code>
* items.
*
* @param configurations An array of <code>Configuration</code> elements.
* @return A <code>List</code> of <code>NameValuePair</code> elements.
* @throws ConfigurationException If a parameter doesn't specify a name.
*/
private ArrayList getParams(Configuration configurations[])
throws ConfigurationException {
ArrayList list = new ArrayList();
if (configurations.length < 1) return (list);
for (int x = 0; x < configurations.length; x++) {
Configuration configuration = configurations[x];
String name = configuration.getAttribute("name", null);
if (name == null) {
throw new ConfigurationException("No name specified for parameter at "
+ configuration.getLocation());
}
String value = configuration.getAttribute("value", null);
if (value != null) list.add(new NameValuePair(name, value));
Configuration subconfigurations[] = configuration.getChildren("value");
for (int y = 0; y < subconfigurations.length; y++) {
value = subconfigurations[y].getValue(null);
if (value != null) list.add(new NameValuePair(name, value));
}
}
return (list);
}
/**
* Override the value for a named parameter in a specfied <code>ArrayList</code>
* or add it if the parameter was not found.
*
* @param list The <code>ArrayList</code> where the parameter is stored.
* @param name The parameter name.
* @param value The new parameter value.
* @return The same <code>List</code> of <code>NameValuePair</code> elements.
*/
private ArrayList overrideParams(ArrayList list, String name, String value) {
Iterator iterator = list.iterator();
while (iterator.hasNext()) {
NameValuePair param = (NameValuePair) iterator.next();
if (param.getName().equals(name)) {
iterator.remove();
break;
}
}
list.add(new NameValuePair(name, value));
return (list);
}
}