001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: package org.apache.cocoon.generation;
018:
019: import java.io.IOException;
020: import java.io.InputStream;
021: import java.util.ArrayList;
022: import java.util.Iterator;
023: import java.util.Map;
024:
025: import org.apache.avalon.framework.configuration.Configurable;
026: import org.apache.avalon.framework.configuration.Configuration;
027: import org.apache.avalon.framework.configuration.ConfigurationException;
028: import org.apache.avalon.framework.parameters.Parameters;
029: import org.apache.avalon.framework.service.ServiceException;
030:
031: import org.apache.cocoon.ProcessingException;
032: import org.apache.cocoon.ResourceNotFoundException;
033: import org.apache.cocoon.environment.SourceResolver;
034:
035: import org.apache.commons.httpclient.HttpConnection;
036: import org.apache.commons.httpclient.HttpMethodBase;
037: import org.apache.commons.httpclient.HttpState;
038: import org.apache.commons.httpclient.HttpURL;
039: import org.apache.commons.httpclient.HostConfiguration;
040: import org.apache.commons.httpclient.NameValuePair;
041: import org.apache.commons.httpclient.URIException;
042: import org.apache.commons.httpclient.methods.GetMethod;
043: import org.apache.commons.httpclient.methods.PostMethod;
044:
045: import org.apache.excalibur.xml.sax.SAXParser;
046:
047: import org.xml.sax.InputSource;
048: import org.xml.sax.SAXException;
049: import org.xml.sax.helpers.AttributesImpl;
050:
051: /**
052: * The <code>HttpProxyGenerator</code> is a Cocoon generator using the
053: * <b>Jakarta Commons HTTPClient Library</b> to access an XML stream
054: * over HTTP.
055: *
056: * @author <a href="mailto:ivelin@apache.org">Ivelin Ivanov</a>, June 2002
057: * @author <a href="mailto:tony@apache.org">Tony Collen</a>, December 2002
058: * @author <a href="mailto:pier@apache.org">Pier Fumagalli</a>, February 2003
059: * @version CVS $Id: HttpProxyGenerator.java 433543 2006-08-22 06:22:54Z crossley $
060: */
061: public class HttpProxyGenerator extends ServiceableGenerator implements
062: Configurable {
063:
064: /** The HTTP method to use at request time. */
065: private HttpMethodBase method = null;
066: /** The base HTTP URL for requests. */
067: private HttpURL url = null;
068: /** The list of request parameters for the request */
069: private ArrayList reqParams = null;
070: /** The list of query parameters for the request */
071: private ArrayList qryParams = null;
072: /** Wether we want a debug output or not */
073: private boolean debug = false;
074:
075: /**
076: * Default (empty) constructor.
077: */
078: public HttpProxyGenerator() {
079: super ();
080: }
081:
082: /**
083: * Set up this <code>Generator</code> instance from its sitemap <code>Configuration</code>
084: *
085: * @param configuration The base <code>Configuration</code> for this <code>Generator</code>.
086: * @throws ConfigurationException If this instance cannot be configured properly.
087: * @see #recycle()
088: */
089: public void configure(Configuration configuration)
090: throws ConfigurationException {
091:
092: /* Setup the HTTP method to use. */
093: String method = configuration.getChild("method")
094: .getValue("GET");
095: if ("GET".equalsIgnoreCase(method)) {
096: this .method = new GetMethod();
097: } else if ("POST".equalsIgnoreCase(method)) {
098: this .method = new PostMethod();
099: /* TODO: Is this still needed? Does it refer to a bug in bugzilla?
100: * At least the handling in httpclient has completely changed.
101: * Work around a bug from the HttpClient library */
102: ((PostMethod) this .method).setRequestBody("");
103: } else {
104: throw new ConfigurationException("Invalid method \""
105: + method + "\" specified" + " at "
106: + configuration.getChild("method").getLocation());
107: }
108:
109: /* Create the base URL */
110: String url = configuration.getChild("url").getValue(null);
111: try {
112: if (url != null)
113: this .url = new HttpURL(url);
114: } catch (URIException e) {
115: throw new ConfigurationException("Cannot process URL \""
116: + url + "\" specified" + " at "
117: + configuration.getChild("url").getLocation());
118: }
119:
120: /* Prepare the base request and query parameters */
121: this .reqParams = this .getParams(configuration
122: .getChildren("param"));
123: this .qryParams = this .getParams(configuration
124: .getChildren("query"));
125: }
126:
127: /**
128: * Setup this <code>Generator</code> with its runtime configurations and parameters
129: * specified in the sitemap, and prepare it for generation.
130: *
131: * @param sourceResolver The <code>SourceResolver</code> instance resolving sources by
132: * system identifiers.
133: * @param objectModel The Cocoon "object model" <code>Map</code>
134: * @param parameters The runtime <code>Parameters</code> instance.
135: * @throws ProcessingException If this instance could not be setup.
136: * @throws SAXException If a SAX error occurred during setup.
137: * @throws IOException If an I/O error occurred during setup.
138: * @see #recycle()
139: */
140: public void setup(SourceResolver sourceResolver, Map objectModel,
141: String source, Parameters parameters)
142: throws ProcessingException, SAXException, IOException {
143: /* Do the usual stuff */
144: super .setup(sourceResolver, objectModel, source, parameters);
145:
146: /*
147: * Parameter handling: In case the method is a POST method, query
148: * parameters and request parameters will be two different arrays
149: * (one for the body, one for the query string, otherwise it's going
150: * to be the same one, as all parameters are passed on the query string
151: */
152: ArrayList req = new ArrayList();
153: ArrayList qry = req;
154: if (this .method instanceof PostMethod)
155: qry = new ArrayList();
156: req.addAll(this .reqParams);
157: qry.addAll(this .qryParams);
158:
159: /*
160: * Parameter handling: complete or override the configured parameters with
161: * those specified in the pipeline.
162: */
163: String names[] = parameters.getNames();
164: for (int x = 0; x < names.length; x++) {
165: String name = names[x];
166: String value = parameters.getParameter(name, null);
167: if (value == null)
168: continue;
169:
170: if (name.startsWith("query:")) {
171: name = name.substring("query:".length());
172: qry.add(new NameValuePair(name, value));
173: } else if (name.startsWith("param:")) {
174: name = name.substring("param:".length());
175: req.add(new NameValuePair(name, value));
176: } else if (name.startsWith("query-override:")) {
177: name = name.substring("query-override:".length());
178: qry = overrideParams(qry, name, value);
179: } else if (name.startsWith("param-override:")) {
180: name = name.substring("param-override:".length());
181: req = overrideParams(req, name, value);
182: }
183: }
184:
185: /* Process the current source URL in relation to the configured one */
186: HttpURL src = (super .source == null ? null : new HttpURL(
187: super .source));
188: if (this .url != null)
189: src = (src == null ? this .url : new HttpURL(this .url, src));
190: if (src == null)
191: throw new ProcessingException("No URL specified");
192: if (src.isRelativeURI()) {
193: throw new ProcessingException("Invalid URL \""
194: + src.toString() + "\"");
195: }
196:
197: /* Configure the method with the resolved URL */
198: HostConfiguration hc = new HostConfiguration();
199: hc.setHost(src);
200: this .method.setHostConfiguration(hc);
201: this .method.setPath(src.getPath());
202: this .method.setQueryString(src.getQuery());
203:
204: /* And now process the query string (from the parameters above) */
205: if (qry.size() > 0) {
206: String qs = this .method.getQueryString();
207: NameValuePair nvpa[] = new NameValuePair[qry.size()];
208: this .method.setQueryString((NameValuePair[]) qry
209: .toArray(nvpa));
210: if (qs != null) {
211: this .method.setQueryString(qs + "&"
212: + this .method.getQueryString());
213: }
214: }
215:
216: /* Finally process the body parameters */
217: if ((this .method instanceof PostMethod) && (req.size() > 0)) {
218: PostMethod post = (PostMethod) this .method;
219: NameValuePair nvpa[] = new NameValuePair[req.size()];
220: post.setRequestBody((NameValuePair[]) req.toArray(nvpa));
221: }
222:
223: /* Check the debugging flag */
224: this .debug = parameters.getParameterAsBoolean("debug", false);
225: }
226:
227: /**
228: * Recycle this instance, clearing all done during setup and generation, and reverting
229: * back to what was configured in the sitemap.
230: *
231: * @see #configure(Configuration)
232: * @see #setup(SourceResolver, Map, String, Parameters)
233: * @see #generate()
234: */
235: public void recycle() {
236: /* Recycle the method */
237: this .method.recycle();
238: /* TODO: Is this still needed? Does it refer to a bug in bugzilla?
239: * At least the handling in httpclient has completely changed.
240: * Work around a bug from the HttpClient library */
241: if (this .method instanceof PostMethod)
242: ((PostMethod) this .method).setRequestBody("");
243:
244: /* Clean up our parent */
245: super .recycle();
246: }
247:
248: /**
249: * Parse the remote <code>InputStream</code> accessed over HTTP.
250: *
251: * @throws ResourceNotFoundException If the remote HTTP resource could not be found.
252: * @throws ProcessingException If an error occurred processing generation.
253: * @throws SAXException If an error occurred parsing or processing XML in the pipeline.
254: * @throws IOException If an I/O error occurred accessing the HTTP server.
255: */
256: public void generate() throws ResourceNotFoundException,
257: ProcessingException, SAXException, IOException {
258: /* Do the boring stuff in case we have to do a debug output (blablabla) */
259: if (this .debug) {
260: this .generateDebugOutput();
261: return;
262: }
263:
264: /* Call up the remote HTTP server */
265: HttpConnection connection = new HttpConnection(this .method
266: .getHostConfiguration());
267: HttpState state = new HttpState();
268: this .method.setFollowRedirects(true);
269: int status = this .method.execute(state, connection);
270: if (status == 404) {
271: throw new ResourceNotFoundException("Unable to access \""
272: + this .method.getURI() + "\" (HTTP 404 Error)");
273: } else if ((status < 200) || (status > 299)) {
274: throw new IOException(
275: "Unable to access HTTP resource at \""
276: + this .method.getURI().toString()
277: + "\" (status=" + status + ")");
278: }
279: InputStream response = this .method.getResponseBodyAsStream();
280:
281: /* Let's try to set up our InputSource from the response output stream and to parse it */
282: SAXParser parser = null;
283: try {
284: InputSource inputSource = new InputSource(response);
285: parser = (SAXParser) this .manager.lookup(SAXParser.ROLE);
286: parser.parse(inputSource, super .xmlConsumer);
287: } catch (ServiceException ex) {
288: throw new ProcessingException("Unable to get parser", ex);
289: } finally {
290: this .manager.release(parser);
291: this .method.releaseConnection();
292: connection.close();
293: }
294: }
295:
296: /**
297: * Generate debugging output as XML data from the current configuration.
298: *
299: * @throws SAXException If an error occurred parsing or processing XML in the pipeline.
300: * @throws IOException If an I/O error occurred accessing the HTTP server.
301: */
302: private void generateDebugOutput() throws SAXException, IOException {
303: super .xmlConsumer.startDocument();
304:
305: AttributesImpl attributes = new AttributesImpl();
306: attributes.addAttribute("", "method", "method", "CDATA",
307: this .method.getName());
308: attributes.addAttribute("", "url", "url", "CDATA", this .method
309: .getURI().toString());
310: attributes.addAttribute("", "protocol", "protocol", "CDATA",
311: (this .method.isHttp11() ? "HTTP/1.1" : "HTTP/1.0"));
312: super .xmlConsumer.startElement("", "request", "request",
313: attributes);
314:
315: if (this .method instanceof PostMethod) {
316: String body = ((PostMethod) this .method)
317: .getRequestBodyAsString();
318:
319: attributes.clear();
320: attributes.addAttribute("", "name", "name", "CDATA",
321: "Content-Type");
322: attributes.addAttribute("", "value", "value", "CDATA",
323: "application/x-www-form-urlencoded");
324: super .xmlConsumer.startElement("", "header", "header",
325: attributes);
326: super .xmlConsumer.endElement("", "header", "header");
327:
328: attributes.clear();
329: attributes.addAttribute("", "name", "name", "CDATA",
330: "Content-Length");
331: attributes.addAttribute("", "value", "value", "CDATA",
332: Integer.toString(body.length()));
333: super .xmlConsumer.startElement("", "header", "header",
334: attributes);
335: super .xmlConsumer.endElement("", "header", "header");
336:
337: attributes.clear();
338: super .xmlConsumer.startElement("", "body", "body",
339: attributes);
340: super .xmlConsumer.characters(body.toCharArray(), 0, body
341: .length());
342: super .xmlConsumer.endElement("", "body", "body");
343: }
344:
345: super .xmlConsumer.endElement("", "request", "request");
346:
347: super .xmlConsumer.endDocument();
348: return;
349: }
350:
351: /**
352: * Prepare a map of parameters from an array of <code>Configuration</code>
353: * items.
354: *
355: * @param configurations An array of <code>Configuration</code> elements.
356: * @return A <code>List</code> of <code>NameValuePair</code> elements.
357: * @throws ConfigurationException If a parameter doesn't specify a name.
358: */
359: private ArrayList getParams(Configuration configurations[])
360: throws ConfigurationException {
361: ArrayList list = new ArrayList();
362:
363: if (configurations.length < 1)
364: return (list);
365:
366: for (int x = 0; x < configurations.length; x++) {
367: Configuration configuration = configurations[x];
368: String name = configuration.getAttribute("name", null);
369: if (name == null) {
370: throw new ConfigurationException(
371: "No name specified for parameter at "
372: + configuration.getLocation());
373: }
374:
375: String value = configuration.getAttribute("value", null);
376: if (value != null)
377: list.add(new NameValuePair(name, value));
378:
379: Configuration subconfigurations[] = configuration
380: .getChildren("value");
381: for (int y = 0; y < subconfigurations.length; y++) {
382: value = subconfigurations[y].getValue(null);
383: if (value != null)
384: list.add(new NameValuePair(name, value));
385: }
386: }
387:
388: return (list);
389: }
390:
391: /**
392: * Override the value for a named parameter in a specfied <code>ArrayList</code>
393: * or add it if the parameter was not found.
394: *
395: * @param list The <code>ArrayList</code> where the parameter is stored.
396: * @param name The parameter name.
397: * @param value The new parameter value.
398: * @return The same <code>List</code> of <code>NameValuePair</code> elements.
399: */
400: private ArrayList overrideParams(ArrayList list, String name,
401: String value) {
402: Iterator iterator = list.iterator();
403: while (iterator.hasNext()) {
404: NameValuePair param = (NameValuePair) iterator.next();
405: if (param.getName().equals(name)) {
406: iterator.remove();
407: break;
408: }
409: }
410: list.add(new NameValuePair(name, value));
411: return (list);
412: }
413: }
|