blob: 6ea778045fdf2297174aeefc55285d61f7ba97a6 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.avalon.framework.parameters.Parameters;
import org.apache.avalon.framework.service.ServiceException;
import org.apache.cocoon.ProcessingException;
import org.apache.cocoon.components.source.SourceUtil;
import org.apache.cocoon.environment.ObjectModelHelper;
import org.apache.cocoon.environment.Request;
import org.apache.cocoon.environment.Session;
import org.apache.cocoon.environment.SourceResolver;
import org.apache.commons.httpclient.HostConfiguration;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpMethod;
import org.apache.commons.httpclient.NameValuePair;
import org.apache.commons.httpclient.URI;
import org.apache.commons.httpclient.URIException;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.lang.StringUtils;
import org.apache.excalibur.source.Source;
import org.apache.excalibur.source.SourceException;
import org.apache.excalibur.xml.sax.SAXParser;
import org.apache.regexp.RE;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
/**
*
* The WebServiceProxyGenerator is intended to:
*
* 1) Allow easy syndication of dynamic interactive content as a natural extension of the currently popular static content syndication with RSS.
*
* 2) Allow transparent routing of web service request through GET, POST, SOAP-RPC and SOAP-DOC binding methods.
*
* 3) Allow almost full control through sitemap configuration.
*
* 4) Allow use of Cocoon components for content formatting, aggregation and styling through a tight integration with the Cocoon sitemap.
*
* 5) Require 0 (zero) lines of Java or other business logic code in most cases.
*
* 6) Be generic and flexible enough to allow custom extensions for advanced and non-typical uses.
*
* 7) Support sessions, authentication, http 1.1, https, request manipulation, redirects following, connection pooling, and others.
*
* 8) Use the Jakarta HttpClient library which provides many sophisticated features for HTTP connections.
*
* 9) (TBD) Use Axis for SOAP-RPC and SOAP-DOC bindings.
*
*
* @author <a href="mailto:ivelin@apache.org">Ivelin Ivanov</a>, June 30, 2002
* @author <a href="mailto:tony@apache.org">Tony Collen</a>, December 2, 2002
* @version CVS $Id$
*/
public class WebServiceProxyGenerator extends ServiceableGenerator {
private static final String HTTP_CLIENT = "HTTP_CLIENT";
private static final String METHOD_GET = "GET";
private static final String METHOD_POST = "POST";
private HttpClient httpClient = null;
private String configuredHttpMethod = null;
public void setup(SourceResolver resolver, Map objectModel, String src, Parameters par) throws ProcessingException, SAXException, IOException {
super.setup(resolver, objectModel, src, par);
try {
Source inputSource = resolver.resolveURI(super.source);
this.source = inputSource.getURI();
} catch (SourceException se) {
throw SourceUtil.handle("Unable to resolve " + super.source, se);
}
this.configuredHttpMethod = par.getParameter("wsproxy-method", METHOD_GET);
this.httpClient = this.getHttpClient();
}
/**
* Generate XML data.
*/
public void generate() throws IOException, SAXException, ProcessingException {
SAXParser parser = null;
try {
if (this.getLogger().isDebugEnabled()) {
this.getLogger().debug("processing Web Service request: " + this.source);
}
// forward request and bring response back
byte[] response = this.fetch();
if (this.getLogger().isDebugEnabled()) {
this.getLogger().debug("response: " + new String(response));
}
ByteArrayInputStream responseStream = new ByteArrayInputStream(response);
InputSource inputSource = new InputSource(responseStream);
parser = (SAXParser)this.manager.lookup(SAXParser.ROLE);
parser.parse(inputSource, super.xmlConsumer);
} catch (ServiceException ex) {
throw new ProcessingException("WebServiceProxyGenerator.generate() error", ex);
} finally {
this.manager.release(parser);
}
} // generate
/**
* Recycle this component.
* All instance variables are set to <code>null</code>.
*/
public void recycle() {
this.httpClient = null;
this.configuredHttpMethod = null;
super.recycle();
}
/**
* Forwards the request and returns the response.
*
* The rest is probably out of date:
* Will use a UrlGetMethod to benefit the cacheing mechanism
* and intermediate proxy servers.
* It is potentially possible that the size of the request
* may grow beyond a certain limit for GET and it will require POST instead.
*
* @return byte[] XML response
*/
public byte[] fetch() throws ProcessingException {
final HttpMethod method;
// check which method (GET or POST) to use.
if (this.configuredHttpMethod.equalsIgnoreCase(METHOD_POST)) {
method = new PostMethod(this.source);
} else {
method = new GetMethod(this.source);
}
if (this.getLogger().isDebugEnabled()) {
this.getLogger().debug("request HTTP method: " + method.getName());
}
// this should probably be exposed as a sitemap option
method.setFollowRedirects(true);
// copy request parameters and merge with URL parameters
Request request = ObjectModelHelper.getRequest(objectModel);
List<NameValuePair> paramList = new ArrayList<NameValuePair>();
Enumeration<String> enumeration = (Enumeration<String>) request.getParameterNames();
while (enumeration.hasMoreElements()) {
String pname = enumeration.nextElement();
String[] paramsForName = request.getParameterValues(pname);
for (String value : paramsForName) {
NameValuePair pair = new NameValuePair(pname, value);
paramList.add(pair);
}
}
if (paramList.size() > 0) {
NameValuePair[] allSubmitParams = new NameValuePair[paramList.size()];
paramList.toArray(allSubmitParams);
String urlQryString = method.getQueryString();
// use HttpClient encoding routines
method.setQueryString(allSubmitParams);
String submitQryString = method.getQueryString();
// set final web service query string
// sometimes the querystring is null here...
if (null == urlQryString) {
method.setQueryString(submitQryString);
} else {
method.setQueryString(urlQryString + "&" + submitQryString);
}
} // if there are submit parameters
final byte[] response;
try {
int httpStatus = httpClient.executeMethod(method);
if (httpStatus < 400) {
if (this.getLogger().isDebugEnabled()) {
this.getLogger().debug("Return code when accessing the remote Url: " + httpStatus);
}
} else {
throw new ProcessingException("The remote returned error " + httpStatus + " when attempting to access remote URL:" + method.getURI());
}
response = method.getResponseBody();
} catch (URIException e) {
throw new ProcessingException("There is a problem with the URI: " + this.source, e);
} catch (IOException e) {
try {
throw new ProcessingException("Exception when attempting to access the remote URL: " + method.getURI(), e);
} catch (URIException ue) {
throw new ProcessingException("There is a problem with the URI: " + this.source, ue);
}
} finally {
/* It is important to always read the entire response and release the
* connection regardless of whether the server returned an error or not.
* {@link http://jakarta.apache.org/commons/httpclient/tutorial.html}
*/
method.releaseConnection();
}
return response;
} // fetch
/**
* Create one per client session.
*/
protected HttpClient getHttpClient() throws ProcessingException {
Request request = ObjectModelHelper.getRequest(objectModel);
Session session = request.getSession(true);
HttpClient httpClient = null;
if (session != null) {
httpClient = (HttpClient)session.getAttribute(HTTP_CLIENT);
}
if (httpClient == null) {
final URI uri;
final String host;
httpClient = new HttpClient();
HostConfiguration config = httpClient.getHostConfiguration();
if (config == null) {
config = new HostConfiguration();
}
/* TODO: fixme!
* When the specified source sent to the wsproxy is not "http"
* (e.g. "cocoon:/"), the HttpClient throws an exception. Does the source
* here need to be resolved before being set in the HostConfiguration?
*/
try {
uri = new URI(this.source);
host = uri.getHost();
config.setHost(uri);
} catch (URIException ex) {
throw new ProcessingException("URI format error: " + ex, ex);
}
// Check the http.nonProxyHosts to see whether or not the current
// host needs to be served through the proxy server.
boolean proxiableHost = true;
String nonProxyHosts = System.getProperty("http.nonProxyHosts");
if (nonProxyHosts != null)
{
StringTokenizer tok = new StringTokenizer(nonProxyHosts, "|");
while (tok.hasMoreTokens()) {
String nonProxiableHost = tok.nextToken().trim();
// XXX is there any other characters that need to be
// escaped?
nonProxiableHost = StringUtils.replace(nonProxiableHost, ".", "\\.");
nonProxiableHost = StringUtils.replace(nonProxiableHost, "*", ".*");
// XXX do we want .example.com to match
// computer.example.com? it seems to be a very common
// idiom for the nonProxyHosts, in that case then we want
// to change "^" to "^.*"
final RE re;
try {
re = new RE("^" + nonProxiableHost + "$");
}
catch (Exception ex) {
throw new ProcessingException("Regex syntax error: " + ex, ex);
}
if (re.match(host))
{
proxiableHost = false;
break;
}
}
}
if (proxiableHost && System.getProperty("http.proxyHost") != null) {
String proxyHost = System.getProperty("http.proxyHost");
int proxyPort = Integer.parseInt(System.getProperty("http.proxyPort"));
config.setProxy(proxyHost, proxyPort);
}
httpClient.setHostConfiguration(config);
session.setAttribute(HTTP_CLIENT, httpClient);
}
return httpClient;
}
}