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 org.apache.avalon.framework.parameters.Parameters;
020: import org.apache.avalon.framework.service.ServiceException;
021: import org.apache.cocoon.ProcessingException;
022: import org.apache.cocoon.components.source.SourceUtil;
023: import org.apache.cocoon.environment.ObjectModelHelper;
024: import org.apache.cocoon.environment.Request;
025: import org.apache.cocoon.environment.Session;
026: import org.apache.cocoon.environment.SourceResolver;
027: import org.apache.commons.httpclient.HostConfiguration;
028: import org.apache.commons.httpclient.HttpClient;
029: import org.apache.commons.httpclient.HttpMethod;
030: import org.apache.commons.httpclient.NameValuePair;
031: import org.apache.commons.httpclient.URI;
032: import org.apache.commons.httpclient.URIException;
033: import org.apache.commons.httpclient.methods.GetMethod;
034: import org.apache.commons.httpclient.methods.PostMethod;
035: import org.apache.commons.lang.StringUtils;
036: import org.apache.excalibur.source.Source;
037: import org.apache.excalibur.source.SourceException;
038: import org.apache.excalibur.xml.sax.SAXParser;
039: import org.apache.regexp.RE;
040: import org.xml.sax.InputSource;
041: import org.xml.sax.SAXException;
042:
043: import java.io.ByteArrayInputStream;
044: import java.io.IOException;
045: import java.util.ArrayList;
046: import java.util.Enumeration;
047: import java.util.Map;
048: import java.util.StringTokenizer;
049:
050: /**
051: *
052: * The WebServiceProxyGenerator is intended to:
053: *
054: * 1) Allow easy syndication of dynamic interactive content as a natural extension of the currently popular static content syndication with RSS.
055: *
056: * 2) Allow transparent routing of web service request through GET, POST, SOAP-RPC and SOAP-DOC binding methods.
057: *
058: * 3) Allow almost full control through sitemap configuration.
059: *
060: * 4) Allow use of Cocoon components for content formatting, aggregation and styling through a tight integration with the Cocoon sitemap.
061: *
062: * 5) Require 0 (zero) lines of Java or other business logic code in most cases.
063: *
064: * 6) Be generic and flexible enough to allow custom extensions for advanced and non-typical uses.
065: *
066: * 7) Support sessions, authentication, http 1.1, https, request manipulation, redirects following, connection pooling, and others.
067: *
068: * 8) Use the Jakarta HttpClient library which provides many sophisticated features for HTTP connections.
069: *
070: * 9) (TBD) Use Axis for SOAP-RPC and SOAP-DOC bindings.
071: *
072: *
073: * @author <a href="mailto:ivelin@apache.org">Ivelin Ivanov</a>, June 30, 2002
074: * @author <a href="mailto:tony@apache.org">Tony Collen</a>, December 2, 2002
075: * @version CVS $Id: WebServiceProxyGenerator.java 433543 2006-08-22 06:22:54Z crossley $
076: */
077: public class WebServiceProxyGenerator extends ServiceableGenerator {
078:
079: private static final String HTTP_CLIENT = "HTTP_CLIENT";
080: private static final String METHOD_GET = "GET";
081: private static final String METHOD_POST = "POST";
082:
083: private HttpClient httpClient = null;
084: private String configuredHttpMethod = null;
085:
086: public void setup(SourceResolver resolver, Map objectModel,
087: String src, Parameters par) throws ProcessingException,
088: SAXException, IOException {
089: super .setup(resolver, objectModel, src, par);
090:
091: try {
092: Source inputSource = resolver.resolveURI(super .source);
093: this .source = inputSource.getURI();
094: } catch (SourceException se) {
095: throw SourceUtil.handle(
096: "Unable to resolve " + super .source, se);
097: }
098:
099: this .configuredHttpMethod = par.getParameter("wsproxy-method",
100: METHOD_GET);
101: this .httpClient = this .getHttpClient();
102: }
103:
104: /**
105: * Generate XML data.
106: */
107: public void generate() throws IOException, SAXException,
108: ProcessingException {
109: SAXParser parser = null;
110: try {
111: if (this .getLogger().isDebugEnabled()) {
112: this .getLogger().debug(
113: "processing Web Service request: "
114: + this .source);
115: }
116:
117: // forward request and bring response back
118: byte[] response = this .fetch();
119: if (this .getLogger().isDebugEnabled()) {
120: this .getLogger().debug(
121: "response: " + new String(response));
122: }
123:
124: /* TODO: Though I avoided the getResponseBodyAsString(), the content
125: * seems not to be parsed correctly. Who cares about the encoding
126: * in the XML declaration?
127: * {@link http://jakarta.apache.org/commons/httpclient/apidocs/org/apache/commons/httpclient/HttpMethodBase.html#getResponseBodyAsString()}
128: */
129: ByteArrayInputStream responseStream = new ByteArrayInputStream(
130: response);
131: InputSource inputSource = new InputSource(responseStream);
132: parser = (SAXParser) this .manager.lookup(SAXParser.ROLE);
133: parser.parse(inputSource, super .xmlConsumer);
134:
135: } catch (ServiceException ex) {
136: throw new ProcessingException(
137: "WebServiceProxyGenerator.generate() error", ex);
138: } finally {
139: this .manager.release(parser);
140: }
141:
142: } // generate
143:
144: /**
145: * Recycle this component.
146: * All instance variables are set to <code>null</code>.
147: */
148: public void recycle() {
149: this .httpClient = null;
150: this .configuredHttpMethod = null;
151: super .recycle();
152: }
153:
154: /**
155: * Forwards the request and returns the response.
156: *
157: * The rest is probably out of date:
158: * Will use a UrlGetMethod to benefit the cacheing mechanism
159: * and intermediate proxy servers.
160: * It is potentially possible that the size of the request
161: * may grow beyond a certain limit for GET and it will require POST instead.
162: *
163: * @return byte[] XML response
164: */
165: public byte[] fetch() throws ProcessingException {
166: HttpMethod method = null;
167:
168: // check which method (GET or POST) to use.
169: if (this .configuredHttpMethod.equalsIgnoreCase(METHOD_POST)) {
170: method = new PostMethod(this .source);
171: } else {
172: method = new GetMethod(this .source);
173: }
174:
175: if (this .getLogger().isDebugEnabled()) {
176: this .getLogger().debug(
177: "request HTTP method: " + method.getName());
178: }
179:
180: // this should probably be exposed as a sitemap option
181: method.setFollowRedirects(true);
182:
183: // copy request parameters and merge with URL parameters
184: Request request = ObjectModelHelper.getRequest(objectModel);
185:
186: ArrayList paramList = new ArrayList();
187: Enumeration enumeration = request.getParameterNames();
188: while (enumeration.hasMoreElements()) {
189: String pname = (String) enumeration.nextElement();
190: String[] paramsForName = request.getParameterValues(pname);
191: for (int i = 0; i < paramsForName.length; i++) {
192: NameValuePair pair = new NameValuePair(pname,
193: paramsForName[i]);
194: paramList.add(pair);
195: }
196: }
197:
198: if (paramList.size() > 0) {
199: NameValuePair[] allSubmitParams = new NameValuePair[paramList
200: .size()];
201: paramList.toArray(allSubmitParams);
202:
203: String urlQryString = method.getQueryString();
204:
205: // use HttpClient encoding routines
206: method.setQueryString(allSubmitParams);
207: String submitQryString = method.getQueryString();
208:
209: // set final web service query string
210:
211: // sometimes the querystring is null here...
212: if (null == urlQryString) {
213: method.setQueryString(submitQryString);
214: } else {
215: method.setQueryString(urlQryString + "&"
216: + submitQryString);
217: }
218:
219: } // if there are submit parameters
220:
221: byte[] response = null;
222: try {
223: int httpStatus = httpClient.executeMethod(method);
224: if (httpStatus < 400) {
225: if (this .getLogger().isDebugEnabled()) {
226: this .getLogger().debug(
227: "Return code when accessing the remote Url: "
228: + httpStatus);
229: }
230: } else {
231: throw new ProcessingException(
232: "The remote returned error "
233: + httpStatus
234: + " when attempting to access remote URL:"
235: + method.getURI());
236: }
237: } catch (URIException e) {
238: throw new ProcessingException(
239: "There is a problem with the URI: " + this .source,
240: e);
241: } catch (IOException e) {
242: try {
243: throw new ProcessingException(
244: "Exception when attempting to access the remote URL: "
245: + method.getURI(), e);
246: } catch (URIException ue) {
247: throw new ProcessingException(
248: "There is a problem with the URI: "
249: + this .source, ue);
250: }
251: } finally {
252: /* It is important to always read the entire response and release the
253: * connection regardless of whether the server returned an error or not.
254: * {@link http://jakarta.apache.org/commons/httpclient/tutorial.html}
255: */
256: response = method.getResponseBody();
257: method.releaseConnection();
258: }
259:
260: return response;
261: } // fetch
262:
263: /**
264: * Create one per client session.
265: */
266: protected HttpClient getHttpClient() throws ProcessingException {
267: URI uri = null;
268: String host = null;
269: Request request = ObjectModelHelper.getRequest(objectModel);
270: Session session = request.getSession(true);
271: HttpClient httpClient = null;
272: if (session != null) {
273: httpClient = (HttpClient) session.getAttribute(HTTP_CLIENT);
274: }
275: if (httpClient == null) {
276: httpClient = new HttpClient();
277: HostConfiguration config = httpClient
278: .getHostConfiguration();
279: if (config == null) {
280: config = new HostConfiguration();
281: }
282:
283: /* TODO: fixme!
284: * When the specified source sent to the wsproxy is not "http"
285: * (e.g. "cocoon:/"), the HttpClient throws an exception. Does the source
286: * here need to be resolved before being set in the HostConfiguration?
287: */
288: try {
289: uri = new URI(this .source);
290: host = uri.getHost();
291: config.setHost(uri);
292: } catch (URIException ex) {
293: throw new ProcessingException(
294: "URI format error: " + ex, ex);
295: }
296:
297: // Check the http.nonProxyHosts to see whether or not the current
298: // host needs to be served through the proxy server.
299: boolean proxiableHost = true;
300: String nonProxyHosts = System
301: .getProperty("http.nonProxyHosts");
302: if (nonProxyHosts != null) {
303: StringTokenizer tok = new StringTokenizer(
304: nonProxyHosts, "|");
305:
306: while (tok.hasMoreTokens()) {
307: String nonProxiableHost = tok.nextToken().trim();
308:
309: // XXX is there any other characters that need to be
310: // escaped?
311: nonProxiableHost = StringUtils.replace(
312: nonProxiableHost, ".", "\\.");
313: nonProxiableHost = StringUtils.replace(
314: nonProxiableHost, "*", ".*");
315:
316: // XXX do we want .example.com to match
317: // computer.example.com? it seems to be a very common
318: // idiom for the nonProxyHosts, in that case then we want
319: // to change "^" to "^.*"
320: RE re = null;
321: try {
322: re = new RE("^" + nonProxiableHost + "$");
323: } catch (Exception ex) {
324: throw new ProcessingException(
325: "Regex syntax error: " + ex, ex);
326: }
327:
328: if (re.match(host)) {
329: proxiableHost = false;
330: break;
331: }
332: }
333: }
334:
335: if (proxiableHost
336: && System.getProperty("http.proxyHost") != null) {
337: String proxyHost = System.getProperty("http.proxyHost");
338: int proxyPort = Integer.parseInt(System
339: .getProperty("http.proxyPort"));
340: config.setProxy(proxyHost, proxyPort);
341: }
342:
343: httpClient.setHostConfiguration(config);
344:
345: session.setAttribute(HTTP_CLIENT, httpClient);
346: }
347: return httpClient;
348: }
349:
350: } // class
|