FileServiceCaller.java |
/* * $Id: FileServiceCaller.java,v 1.26 2013/01/23 11:36:37 agoubard Exp $ * * See the COPYRIGHT file for redistribution and use restrictions. */ package org.xins.client; import java.io.File; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.URI; import java.net.URISyntaxException; import java.nio.charset.Charset; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.servlet.ServletException; import org.xins.common.FormattedParameters; import org.xins.common.MandatoryArgumentChecker; import org.xins.common.Utils; import org.xins.common.http.HTTPCallConfig; import org.xins.common.http.HTTPCallException; import org.xins.common.http.HTTPCallRequest; import org.xins.common.http.HTTPCallResult; import org.xins.common.http.HTTPCallResultData; import org.xins.common.http.HTTPStatusCodeVerifier; import org.xins.common.http.StatusCodeHTTPCallException; import org.xins.common.service.CallConfig; import org.xins.common.service.CallException; import org.xins.common.service.CallRequest; import org.xins.common.service.CallResult; import org.xins.common.service.Descriptor; import org.xins.common.service.GenericCallException; import org.xins.common.service.IOCallException; import org.xins.common.service.ServiceCaller; import org.xins.common.service.TargetDescriptor; import org.xins.common.service.UnsupportedProtocolException; import org.xins.common.servlet.container.LocalServletHandler; import org.xins.common.servlet.container.XINSServletResponse; import org.xins.common.text.URLEncoding; /** * Call a XINS API using the internal Servlet container. This service caller * doesn't send data over the network but directly invoke the Servlet method. * * @version $Revision: 1.26 $ $Date: 2013/01/23 11:36:37 $ * @author <a href="mailto:anthony.goubard@japplis.com">Anthony Goubard</a> * * @since XINS 1.5.0 */ class FileServiceCaller extends ServiceCaller { /** * Charset for the error messages. */ private static final Charset UTF_CHARSET = Charset.forName("UTF-8"); /** * The pool of the loaded XINS APIs. The key is the location of the WAR * file, as a {@link TargetDescriptor}, the value is the {@link LocalServletHandler}. */ private static HashMap<TargetDescriptor, LocalServletHandler> SERVLETS = new HashMap<TargetDescriptor, LocalServletHandler>(); /** * Constructs a new <code>HTTPServiceCaller</code> object with the * specified descriptor and call configuration. * * @param descriptor * the descriptor of the service, cannot be <code>null</code>. * * @param callConfig * the call configuration, or <code>null</code> if a default one should * be used. * * @throws IllegalArgumentException * if <code>descriptor == null</code>. * * @throws UnsupportedProtocolException * if <code>descriptor</code> is or contains a {@link TargetDescriptor} * with an unsupported protocol. */ public FileServiceCaller(Descriptor descriptor, HTTPCallConfig callConfig) throws IllegalArgumentException, UnsupportedProtocolException { // Call superclass constructor super(descriptor, callConfig); } /** * Constructs a new <code>FileServiceCaller</code> object with the * specified descriptor and call configuration. * * @param descriptor * the descriptor of the service, cannot be <code>null</code>. * * @throws IllegalArgumentException * if <code>descriptor == null</code>. * * @throws UnsupportedProtocolException * if <code>descriptor</code> is or contains a {@link TargetDescriptor} * with an unsupported protocol. */ public FileServiceCaller(Descriptor descriptor) throws IllegalArgumentException, UnsupportedProtocolException { this(descriptor, (HTTPCallConfig) null); } /** * Returns a default <code>CallConfig</code> object. This method is called * by the <code>ServiceCaller</code> constructor if no * <code>CallConfig</code> object was given. * * <p>The implementation of this method in class {@link FileServiceCaller} * returns a standard {@link HTTPCallConfig}. * * @return * a new {@link HTTPCallConfig} instance, never <code>null</code>. */ protected CallConfig getDefaultCallConfig() { return new HTTPCallConfig(); } protected CallResult createCallResult(CallRequest request, TargetDescriptor succeededTarget, long duration, List<CallException> exceptions, Object result) throws ClassCastException { return new HTTPCallResult((HTTPCallRequest) request, succeededTarget, duration, exceptions, (HTTPCallResultData) result); } protected boolean isProtocolSupportedImpl(String protocol) { return "file".equalsIgnoreCase(protocol); } /** * Executes a request towards the specified target. If the call succeeds, * then a {@link HTTPCallResult} object is returned, otherwise a * {@link CallException} is thrown. * * <p>The implementation of this method in class * <code>HTTPServiceCaller</code> delegates to * {@link #call(HTTPCallRequest,HTTPCallConfig)}. * * @param request * the call request to be executed, must be an instance of class * {@link HTTPCallRequest}, cannot be <code>null</code>. * * @param callConfig * the call configuration, never <code>null</code> and should always be * an instance of class {@link HTTPCallConfig}. * * @param target * the target to call, cannot be <code>null</code>. * * @return * the result, if and only if the call succeeded, always an instance of * class {@link HTTPCallResult}, never <code>null</code>. * * @throws ClassCastException * if the specified <code>request</code> object is not <code>null</code> * and not an instance of class {@link HTTPCallRequest}. * * @throws IllegalArgumentException * if <code>target == null || request == null</code>. * * @throws CallException * if the call to the specified target failed. */ public Object doCallImpl(CallRequest request, CallConfig callConfig, TargetDescriptor target) throws ClassCastException, IllegalArgumentException, CallException { long start = System.currentTimeMillis(); long duration; LocalServletHandler servletHandler = SERVLETS.get(target); if (servletHandler == null) { String fileLocation = target.getURL(); try { File warFile = new File(new URI(fileLocation)); servletHandler = new LocalServletHandler(warFile); SERVLETS.put(target, servletHandler); } catch (URISyntaxException usex) { Log.log_2117(usex); } catch (ServletException sex) { Log.log_2117(sex); } } Map<String, String> parameters = ((HTTPCallRequest) request).getParameters(); // Get the parameters for logging FormattedParameters params = new FormattedParameters(parameters, null, "", "?", 160); // Get URL value String url = target.getURL(); // Loop through the parameters StringBuffer query = new StringBuffer(255); query.append("/?"); for (Map.Entry<String, String> parameter : parameters.entrySet()) { String key = parameter.getKey(); String value = parameter.getValue(); if (value == null) { value = ""; } // Add this parameter key/value combination. if (key != null) { if (query.length() > 2) { query.append("&"); } query.append(URLEncoding.encode(key)); query.append("="); query.append(URLEncoding.encode(value)); } } XINSServletResponse response; try { response = servletHandler.query(query.toString()); } catch (IOException exception) { duration = System.currentTimeMillis() - start; org.xins.common.Log.log_1109(exception, url, params, duration); throw new IOCallException(request, target, duration, exception); } // Retrieve the data returned from the call HTTPCallResultData data; try { String result = response.getResult(); byte[] resultData = null; if (result != null) { resultData = result.getBytes(response.getCharacterEncoding()); } data = new HTTPCallResultDataHandler(response.getStatus(), resultData); } catch (UnsupportedEncodingException ueex) { throw Utils.logProgrammingError(ueex); } // Determine the HTTP status code int code = data.getStatusCode(); duration = System.currentTimeMillis() - start; HTTPStatusCodeVerifier verifier = ((HTTPCallRequest)request).getStatusCodeVerifier(); // Status code is considered acceptable if (verifier == null || verifier.isAcceptable(code)) { org.xins.common.Log.log_1107(url, params, duration, code); // Status code is considered unacceptable } else { org.xins.common.Log.log_1108(url, params, duration, code); String details = data.getData() == null ? null : new String(data.getData(), UTF_CHARSET); throw new StatusCodeHTTPCallException((HTTPCallRequest) request, target, duration, code, details); } return new HTTPCallResult((HTTPCallRequest) request, target, duration, null, data); } /** * Performs the specified request towards the HTTP service. If the call * succeeds with one of the targets, then a {@link HTTPCallResult} object * is returned, that combines the HTTP status code and the data returned. * Otherwise, if none of the targets could successfully be called, a * {@link CallException} is thrown. * * @param request * the call request, not <code>null</code>. * * @param callConfig * the call configuration to use, or <code>null</code>. * * @return * the result of the call, cannot be <code>null</code>. * * @throws IllegalArgumentException * if <code>request == null</code>. * * @throws GenericCallException * if the first call attempt failed due to a generic reason and all the * other call attempts failed as well. * * @throws HTTPCallException * if the first call attempt failed due to an HTTP-related reason and * all the other call attempts failed as well. */ public HTTPCallResult call(HTTPCallRequest request, HTTPCallConfig callConfig) throws IllegalArgumentException, GenericCallException, HTTPCallException { // Check preconditions MandatoryArgumentChecker.check("request", request); // Perform the call CallResult callResult; try { callResult = doCall(request, callConfig); // Allow GenericCallException, HTTPCallException and Error to proceed, // but block other kinds of exceptions and throw an Error instead. } catch (GenericCallException exception) { throw exception; } catch (HTTPCallException exception) { throw exception; } catch (Exception exception) { throw Utils.logProgrammingError(exception); } return (HTTPCallResult) callResult; } /** * Performs the specified request towards the HTTP service. If the call * succeeds with one of the targets, then a {@link HTTPCallResult} object * is returned, that combines the HTTP status code and the data returned. * Otherwise, if none of the targets could successfully be called, a * {@link CallException} is thrown. * * @param request * the call request, not <code>null</code>. * * @return * the result of the call, cannot be <code>null</code>. * * @throws IllegalArgumentException * if <code>request == null</code>. * * @throws GenericCallException * if the first call attempt failed due to a generic reason and all the * other call attempts failed as well. * * @throws HTTPCallException * if the first call attempt failed due to an HTTP-related reason and * all the other call attempts failed as well. */ public HTTPCallResult call(HTTPCallRequest request) throws IllegalArgumentException, GenericCallException, HTTPCallException { return call(request, (HTTPCallConfig) null); } /** * Container of the data part of an HTTP call result. * * @version $Revision: 1.26 $ $Date: 2013/01/23 11:36:37 $ * @author <a href="mailto:anthony.goubard@japplis.com">Anthony Goubard</a> * * @since XINS 1.5.0 */ private static final class HTTPCallResultDataHandler implements HTTPCallResultData { /** * The HTTP status code. */ private final int _code; /** * The data returned. */ private final byte[] _data; /** * Constructs a new <code>HTTPCallResultDataHandler</code> object. * * @param code * the HTTP status code. * * @param data * the data returned from the call, as a set of bytes. */ HTTPCallResultDataHandler(int code, byte[] data) { _code = code; _data = data; } /** * Returns the HTTP status code. * * @return * the HTTP status code. */ public int getStatusCode() { return _code; } /** * Returns the result data as a byte array. Note that this is not a copy or * clone of the internal data structure, but it is a link to the actual * data structure itself. * * @return * a byte array of the result data, never <code>null</code>. */ public byte[] getData() { return _data; } } }