001: /*
002: * $Id: FileServiceCaller.java,v 1.19 2007/09/18 08:45:07 agoubard Exp $
003: *
004: * Copyright 2003-2007 Orange Nederland Breedband B.V.
005: * See the COPYRIGHT file for redistribution and use restrictions.
006: */
007: package org.xins.client;
008:
009: import java.io.File;
010: import java.io.IOException;
011: import java.io.UnsupportedEncodingException;
012: import java.util.HashMap;
013: import java.util.Iterator;
014:
015: import javax.servlet.ServletException;
016: import org.xins.common.FormattedParameters;
017:
018: import org.xins.common.MandatoryArgumentChecker;
019: import org.xins.common.Utils;
020: import org.xins.common.collections.PropertyReader;
021: import org.xins.common.http.HTTPCallConfig;
022: import org.xins.common.http.HTTPCallException;
023: import org.xins.common.http.HTTPCallRequest;
024: import org.xins.common.http.HTTPCallResult;
025: import org.xins.common.http.HTTPCallResultData;
026: import org.xins.common.http.HTTPStatusCodeVerifier;
027: import org.xins.common.http.StatusCodeHTTPCallException;
028: import org.xins.common.service.CallConfig;
029: import org.xins.common.service.CallException;
030: import org.xins.common.service.CallExceptionList;
031: import org.xins.common.service.CallRequest;
032: import org.xins.common.service.CallResult;
033: import org.xins.common.service.Descriptor;
034: import org.xins.common.service.GenericCallException;
035: import org.xins.common.service.IOCallException;
036: import org.xins.common.service.ServiceCaller;
037: import org.xins.common.service.TargetDescriptor;
038: import org.xins.common.service.UnsupportedProtocolException;
039: import org.xins.common.servlet.container.LocalServletHandler;
040: import org.xins.common.servlet.container.XINSServletResponse;
041: import org.xins.common.text.URLEncoding;
042:
043: /**
044: * Call a XINS API using the internal Servlet container. This service caller
045: * doesn't send data over the network but directly invoke the Servlet method.
046: *
047: * @version $Revision: 1.19 $ $Date: 2007/09/18 08:45:07 $
048: * @author <a href="mailto:anthony.goubard@japplis.com">Anthony Goubard</a>
049: *
050: * @since XINS 1.5.0
051: */
052: class FileServiceCaller extends ServiceCaller {
053:
054: /**
055: * The pool of the loaded XINS APIs. The key is the location of the WAR
056: * file, as a {@link TargetDescriptor}, the value is the {@link LocalServletHandler}.
057: */
058: private static HashMap SERVLETS = new HashMap();
059:
060: /**
061: * Constructs a new <code>HTTPServiceCaller</code> object with the
062: * specified descriptor and call configuration.
063: *
064: * @param descriptor
065: * the descriptor of the service, cannot be <code>null</code>.
066: *
067: * @param callConfig
068: * the call configuration, or <code>null</code> if a default one should
069: * be used.
070: *
071: * @throws IllegalArgumentException
072: * if <code>descriptor == null</code>.
073: *
074: * @throws UnsupportedProtocolException
075: * if <code>descriptor</code> is or contains a {@link TargetDescriptor}
076: * with an unsupported protocol.
077: */
078: public FileServiceCaller(Descriptor descriptor,
079: HTTPCallConfig callConfig) throws IllegalArgumentException,
080: UnsupportedProtocolException {
081:
082: // Call superclass constructor
083: super (descriptor, callConfig);
084: }
085:
086: /**
087: * Constructs a new <code>FileServiceCaller</code> object with the
088: * specified descriptor and call configuration.
089: *
090: * @param descriptor
091: * the descriptor of the service, cannot be <code>null</code>.
092: *
093: * @throws IllegalArgumentException
094: * if <code>descriptor == null</code>.
095: *
096: * @throws UnsupportedProtocolException
097: * if <code>descriptor</code> is or contains a {@link TargetDescriptor}
098: * with an unsupported protocol.
099: */
100: public FileServiceCaller(Descriptor descriptor)
101: throws IllegalArgumentException,
102: UnsupportedProtocolException {
103:
104: this (descriptor, (HTTPCallConfig) null);
105: }
106:
107: /**
108: * Returns a default <code>CallConfig</code> object. This method is called
109: * by the <code>ServiceCaller</code> constructor if no
110: * <code>CallConfig</code> object was given.
111: *
112: * <p>The implementation of this method in class {@link FileServiceCaller}
113: * returns a standard {@link HTTPCallConfig}.
114: *
115: * @return
116: * a new {@link HTTPCallConfig} instance, never <code>null</code>.
117: */
118: protected CallConfig getDefaultCallConfig() {
119: return new HTTPCallConfig();
120: }
121:
122: protected CallResult createCallResult(CallRequest request,
123: TargetDescriptor succeededTarget, long duration,
124: CallExceptionList exceptions, Object result)
125: throws ClassCastException {
126:
127: return new HTTPCallResult((HTTPCallRequest) request,
128: succeededTarget, duration, exceptions,
129: (HTTPCallResultData) result);
130: }
131:
132: protected boolean isProtocolSupportedImpl(String protocol) {
133: return "file".equalsIgnoreCase(protocol);
134: }
135:
136: /**
137: * Executes a request towards the specified target. If the call succeeds,
138: * then a {@link HTTPCallResult} object is returned, otherwise a
139: * {@link CallException} is thrown.
140: *
141: * <p>The implementation of this method in class
142: * <code>HTTPServiceCaller</code> delegates to
143: * {@link #call(HTTPCallRequest,HTTPCallConfig)}.
144: *
145: * @param request
146: * the call request to be executed, must be an instance of class
147: * {@link HTTPCallRequest}, cannot be <code>null</code>.
148: *
149: * @param callConfig
150: * the call configuration, never <code>null</code> and should always be
151: * an instance of class {@link HTTPCallConfig}.
152: *
153: * @param target
154: * the target to call, cannot be <code>null</code>.
155: *
156: * @return
157: * the result, if and only if the call succeeded, always an instance of
158: * class {@link HTTPCallResult}, never <code>null</code>.
159: *
160: * @throws ClassCastException
161: * if the specified <code>request</code> object is not <code>null</code>
162: * and not an instance of class {@link HTTPCallRequest}.
163: *
164: * @throws IllegalArgumentException
165: * if <code>target == null || request == null</code>.
166: *
167: * @throws CallException
168: * if the call to the specified target failed.
169: */
170: public Object doCallImpl(CallRequest request,
171: CallConfig callConfig, TargetDescriptor target)
172: throws ClassCastException, IllegalArgumentException,
173: CallException {
174:
175: long start = System.currentTimeMillis();
176: long duration;
177: LocalServletHandler servletHandler = (LocalServletHandler) SERVLETS
178: .get(target);
179: if (servletHandler == null) {
180: String fileLocation = target.getURL();
181: File warFile = new File(fileLocation.substring(7).replace(
182: '/', File.separatorChar));
183: try {
184: servletHandler = new LocalServletHandler(warFile);
185: SERVLETS.put(target, servletHandler);
186: } catch (ServletException sex) {
187:
188: Log.log_2117(sex);
189: }
190: }
191:
192: PropertyReader parameters = ((HTTPCallRequest) request)
193: .getParameters();
194:
195: // Get the parameters for logging
196: FormattedParameters params = new FormattedParameters(
197: parameters, null, "", "?", 160);
198:
199: // Get URL value
200: String url = target.getURL();
201:
202: // Loop through the parameters
203: StringBuffer query = new StringBuffer(255);
204: Iterator keys = parameters.getNames();
205: while (keys.hasNext()) {
206:
207: // Get the parameter key
208: String key = (String) keys.next();
209:
210: // Get the value
211: String value = parameters.get(key);
212: if (value == null) {
213: value = "";
214: }
215:
216: // Add this parameter key/value combination.
217: if (key != null) {
218:
219: if (query.length() > 0) {
220: query.append("&");
221: }
222: query.append(URLEncoding.encode(key));
223: query.append("=");
224: query.append(URLEncoding.encode(value));
225: }
226: }
227:
228: XINSServletResponse response;
229: try {
230: response = servletHandler.query(query.toString());
231: } catch (IOException exception) {
232: duration = System.currentTimeMillis() - start;
233: org.xins.common.Log.log_1109(exception, url, params,
234: duration);
235: throw new IOCallException(request, target, duration,
236: exception);
237:
238: }
239:
240: // Retrieve the data returned from the call
241: HTTPCallResultData data;
242: try {
243: String result = response.getResult();
244: byte[] resultData = null;
245: if (result != null) {
246: resultData = result.getBytes(response
247: .getCharacterEncoding());
248: }
249: data = new HTTPCallResultDataHandler(response.getStatus(),
250: resultData);
251: } catch (UnsupportedEncodingException ueex) {
252: throw Utils.logProgrammingError(ueex);
253: }
254:
255: // Determine the HTTP status code
256: int code = data.getStatusCode();
257:
258: duration = System.currentTimeMillis() - start;
259:
260: HTTPStatusCodeVerifier verifier = ((HTTPCallRequest) request)
261: .getStatusCodeVerifier();
262:
263: // Status code is considered acceptable
264: if (verifier == null || verifier.isAcceptable(code)) {
265: org.xins.common.Log.log_1107(url, params, duration, code);
266:
267: // Status code is considered unacceptable
268: } else {
269: org.xins.common.Log.log_1108(url, params, duration, code);
270:
271: throw new StatusCodeHTTPCallException(
272: (HTTPCallRequest) request, target, duration, code);
273: }
274:
275: return new HTTPCallResult((HTTPCallRequest) request, target,
276: duration, null, data);
277: }
278:
279: /**
280: * Performs the specified request towards the HTTP service. If the call
281: * succeeds with one of the targets, then a {@link HTTPCallResult} object
282: * is returned, that combines the HTTP status code and the data returned.
283: * Otherwise, if none of the targets could successfully be called, a
284: * {@link CallException} is thrown.
285: *
286: * @param request
287: * the call request, not <code>null</code>.
288: *
289: * @param callConfig
290: * the call configuration to use, or <code>null</code>.
291: *
292: * @return
293: * the result of the call, cannot be <code>null</code>.
294: *
295: * @throws IllegalArgumentException
296: * if <code>request == null</code>.
297: *
298: * @throws GenericCallException
299: * if the first call attempt failed due to a generic reason and all the
300: * other call attempts failed as well.
301: *
302: * @throws HTTPCallException
303: * if the first call attempt failed due to an HTTP-related reason and
304: * all the other call attempts failed as well.
305: */
306: public HTTPCallResult call(HTTPCallRequest request,
307: HTTPCallConfig callConfig) throws IllegalArgumentException,
308: GenericCallException, HTTPCallException {
309:
310: // Check preconditions
311: MandatoryArgumentChecker.check("request", request);
312:
313: // Perform the call
314: CallResult callResult;
315: try {
316: callResult = doCall(request, callConfig);
317:
318: // Allow GenericCallException, HTTPCallException and Error to proceed,
319: // but block other kinds of exceptions and throw an Error instead.
320: } catch (GenericCallException exception) {
321: throw exception;
322: } catch (HTTPCallException exception) {
323: throw exception;
324: } catch (Exception exception) {
325: throw Utils.logProgrammingError(exception);
326: }
327:
328: return (HTTPCallResult) callResult;
329: }
330:
331: /**
332: * Performs the specified request towards the HTTP service. If the call
333: * succeeds with one of the targets, then a {@link HTTPCallResult} object
334: * is returned, that combines the HTTP status code and the data returned.
335: * Otherwise, if none of the targets could successfully be called, a
336: * {@link CallException} is thrown.
337: *
338: * @param request
339: * the call request, not <code>null</code>.
340: *
341: * @return
342: * the result of the call, cannot be <code>null</code>.
343: *
344: * @throws IllegalArgumentException
345: * if <code>request == null</code>.
346: *
347: * @throws GenericCallException
348: * if the first call attempt failed due to a generic reason and all the
349: * other call attempts failed as well.
350: *
351: * @throws HTTPCallException
352: * if the first call attempt failed due to an HTTP-related reason and
353: * all the other call attempts failed as well.
354: */
355: public HTTPCallResult call(HTTPCallRequest request)
356: throws IllegalArgumentException, GenericCallException,
357: HTTPCallException {
358: return call(request, (HTTPCallConfig) null);
359: }
360:
361: /**
362: * Container of the data part of an HTTP call result.
363: *
364: * @version $Revision: 1.19 $ $Date: 2007/09/18 08:45:07 $
365: * @author <a href="mailto:anthony.goubard@japplis.com">Anthony Goubard</a>
366: *
367: * @since XINS 1.5.0
368: */
369: private static final class HTTPCallResultDataHandler implements
370: HTTPCallResultData {
371:
372: /**
373: * The HTTP status code.
374: */
375: private final int _code;
376:
377: /**
378: * The data returned.
379: */
380: private final byte[] _data;
381:
382: /**
383: * Constructs a new <code>HTTPCallResultDataHandler</code> object.
384: *
385: * @param code
386: * the HTTP status code.
387: *
388: * @param data
389: * the data returned from the call, as a set of bytes.
390: */
391: HTTPCallResultDataHandler(int code, byte[] data) {
392: _code = code;
393: _data = data;
394: }
395:
396: /**
397: * Returns the HTTP status code.
398: *
399: * @return
400: * the HTTP status code.
401: */
402: public int getStatusCode() {
403: return _code;
404: }
405:
406: /**
407: * Returns the result data as a byte array. Note that this is not a copy or
408: * clone of the internal data structure, but it is a link to the actual
409: * data structure itself.
410: *
411: * @return
412: * a byte array of the result data, never <code>null</code>.
413: */
414: public byte[] getData() {
415: return _data;
416: }
417: }
418: }
|