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 527282 2007-04-10 20:33:10Z joerg $
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: ByteArrayInputStream responseStream = new ByteArrayInputStream(
125: response);
126: InputSource inputSource = new InputSource(responseStream);
127: parser = (SAXParser) this .manager.lookup(SAXParser.ROLE);
128: parser.parse(inputSource, super .xmlConsumer);
129:
130: } catch (ServiceException ex) {
131: throw new ProcessingException(
132: "WebServiceProxyGenerator.generate() error", ex);
133: } finally {
134: this .manager.release(parser);
135: }
136:
137: } // generate
138:
139: /**
140: * Recycle this component.
141: * All instance variables are set to <code>null</code>.
142: */
143: public void recycle() {
144: this .httpClient = null;
145: this .configuredHttpMethod = null;
146: super .recycle();
147: }
148:
149: /**
150: * Forwards the request and returns the response.
151: *
152: * The rest is probably out of date:
153: * Will use a UrlGetMethod to benefit the cacheing mechanism
154: * and intermediate proxy servers.
155: * It is potentially possible that the size of the request
156: * may grow beyond a certain limit for GET and it will require POST instead.
157: *
158: * @return byte[] XML response
159: */
160: public byte[] fetch() throws ProcessingException {
161: HttpMethod method = null;
162:
163: // check which method (GET or POST) to use.
164: if (this .configuredHttpMethod.equalsIgnoreCase(METHOD_POST)) {
165: method = new PostMethod(this .source);
166: } else {
167: method = new GetMethod(this .source);
168: }
169:
170: if (this .getLogger().isDebugEnabled()) {
171: this .getLogger().debug(
172: "request HTTP method: " + method.getName());
173: }
174:
175: // this should probably be exposed as a sitemap option
176: method.setFollowRedirects(true);
177:
178: // copy request parameters and merge with URL parameters
179: Request request = ObjectModelHelper.getRequest(objectModel);
180:
181: ArrayList paramList = new ArrayList();
182: Enumeration enumeration = request.getParameterNames();
183: while (enumeration.hasMoreElements()) {
184: String pname = (String) enumeration.nextElement();
185: String[] paramsForName = request.getParameterValues(pname);
186: for (int i = 0; i < paramsForName.length; i++) {
187: NameValuePair pair = new NameValuePair(pname,
188: paramsForName[i]);
189: paramList.add(pair);
190: }
191: }
192:
193: if (paramList.size() > 0) {
194: NameValuePair[] allSubmitParams = new NameValuePair[paramList
195: .size()];
196: paramList.toArray(allSubmitParams);
197:
198: String urlQryString = method.getQueryString();
199:
200: // use HttpClient encoding routines
201: method.setQueryString(allSubmitParams);
202: String submitQryString = method.getQueryString();
203:
204: // set final web service query string
205:
206: // sometimes the querystring is null here...
207: if (null == urlQryString) {
208: method.setQueryString(submitQryString);
209: } else {
210: method.setQueryString(urlQryString + "&"
211: + submitQryString);
212: }
213:
214: } // if there are submit parameters
215:
216: byte[] response = null;
217: try {
218: int httpStatus = httpClient.executeMethod(method);
219: if (httpStatus < 400) {
220: if (this .getLogger().isDebugEnabled()) {
221: this .getLogger().debug(
222: "Return code when accessing the remote Url: "
223: + httpStatus);
224: }
225: } else {
226: throw new ProcessingException(
227: "The remote returned error "
228: + httpStatus
229: + " when attempting to access remote URL:"
230: + method.getURI());
231: }
232: } catch (URIException e) {
233: throw new ProcessingException(
234: "There is a problem with the URI: " + this .source,
235: e);
236: } catch (IOException e) {
237: try {
238: throw new ProcessingException(
239: "Exception when attempting to access the remote URL: "
240: + method.getURI(), e);
241: } catch (URIException ue) {
242: throw new ProcessingException(
243: "There is a problem with the URI: "
244: + this .source, ue);
245: }
246: } finally {
247: /* It is important to always read the entire response and release the
248: * connection regardless of whether the server returned an error or not.
249: * {@link http://jakarta.apache.org/commons/httpclient/tutorial.html}
250: */
251: response = method.getResponseBody();
252: method.releaseConnection();
253: }
254:
255: return response;
256: } // fetch
257:
258: /**
259: * Create one per client session.
260: */
261: protected HttpClient getHttpClient() throws ProcessingException {
262: URI uri = null;
263: String host = null;
264: Request request = ObjectModelHelper.getRequest(objectModel);
265: Session session = request.getSession(true);
266: HttpClient httpClient = null;
267: if (session != null) {
268: httpClient = (HttpClient) session.getAttribute(HTTP_CLIENT);
269: }
270: if (httpClient == null) {
271: httpClient = new HttpClient();
272: HostConfiguration config = httpClient
273: .getHostConfiguration();
274: if (config == null) {
275: config = new HostConfiguration();
276: }
277:
278: /* TODO: fixme!
279: * When the specified source sent to the wsproxy is not "http"
280: * (e.g. "cocoon:/"), the HttpClient throws an exception. Does the source
281: * here need to be resolved before being set in the HostConfiguration?
282: */
283: try {
284: uri = new URI(this .source);
285: host = uri.getHost();
286: config.setHost(uri);
287: } catch (URIException ex) {
288: throw new ProcessingException(
289: "URI format error: " + ex, ex);
290: }
291:
292: // Check the http.nonProxyHosts to see whether or not the current
293: // host needs to be served through the proxy server.
294: boolean proxiableHost = true;
295: String nonProxyHosts = System
296: .getProperty("http.nonProxyHosts");
297: if (nonProxyHosts != null) {
298: StringTokenizer tok = new StringTokenizer(
299: nonProxyHosts, "|");
300:
301: while (tok.hasMoreTokens()) {
302: String nonProxiableHost = tok.nextToken().trim();
303:
304: // XXX is there any other characters that need to be
305: // escaped?
306: nonProxiableHost = StringUtils.replace(
307: nonProxiableHost, ".", "\\.");
308: nonProxiableHost = StringUtils.replace(
309: nonProxiableHost, "*", ".*");
310:
311: // XXX do we want .example.com to match
312: // computer.example.com? it seems to be a very common
313: // idiom for the nonProxyHosts, in that case then we want
314: // to change "^" to "^.*"
315: RE re = null;
316: try {
317: re = new RE("^" + nonProxiableHost + "$");
318: } catch (Exception ex) {
319: throw new ProcessingException(
320: "Regex syntax error: " + ex, ex);
321: }
322:
323: if (re.match(host)) {
324: proxiableHost = false;
325: break;
326: }
327: }
328: }
329:
330: if (proxiableHost
331: && System.getProperty("http.proxyHost") != null) {
332: String proxyHost = System.getProperty("http.proxyHost");
333: int proxyPort = Integer.parseInt(System
334: .getProperty("http.proxyPort"));
335: config.setProxy(proxyHost, proxyPort);
336: }
337:
338: httpClient.setHostConfiguration(config);
339:
340: session.setAttribute(HTTP_CLIENT, httpClient);
341: }
342: return httpClient;
343: }
344:
345: } // class
|