/* * 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.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 { HttpMethod method = null; // 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); ArrayList paramList = new ArrayList(); Enumeration enumeration = request.getParameterNames(); while (enumeration.hasMoreElements()) { String pname = (String)enumeration.nextElement(); String[] paramsForName = request.getParameterValues(pname); for (int i = 0; i < paramsForName.length; i++) { NameValuePair pair = new NameValuePair(pname, paramsForName[i]); 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 byte[] response = null; 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()); } } 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} */ response = method.getResponseBody(); method.releaseConnection(); } return response; } // fetch /** * Create one per client session. */ protected HttpClient getHttpClient() throws ProcessingException { URI uri = null; String host = null; 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) { 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 "^.*" RE re = null; 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; } } // class