com.ibm.sbt.services.client.ClientService.java Source code

Java tutorial

Introduction

Here is the source code for com.ibm.sbt.services.client.ClientService.java

Source

/*
 *  Copyright IBM Corp. 2012
 * 
 * Licensed 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 com.ibm.sbt.services.client;

import static com.ibm.sbt.services.client.base.CommonConstants.APPLICATION_JSON;
import static com.ibm.sbt.services.client.base.CommonConstants.APPLICATION_OCTET_STREAM;
import static com.ibm.sbt.services.client.base.CommonConstants.APPLICATION_XML;
import static com.ibm.sbt.services.client.base.CommonConstants.BINARY;
import static com.ibm.sbt.services.client.base.CommonConstants.BINARY_OCTET_STREAM;
import static com.ibm.sbt.services.client.base.CommonConstants.CH_SLASH;
import static com.ibm.sbt.services.client.base.CommonConstants.CONTENT_ENCODING;
import static com.ibm.sbt.services.client.base.CommonConstants.CONTENT_TYPE;
import static com.ibm.sbt.services.client.base.CommonConstants.GZIP;
import static com.ibm.sbt.services.client.base.CommonConstants.HTML;
import static com.ibm.sbt.services.client.base.CommonConstants.INIT_URL_PARAM;
import static com.ibm.sbt.services.client.base.CommonConstants.JSON;
import static com.ibm.sbt.services.client.base.CommonConstants.LOCATION_HEADER;
import static com.ibm.sbt.services.client.base.CommonConstants.MULTIPART_RELATED;
import static com.ibm.sbt.services.client.base.CommonConstants.SLUG;
import static com.ibm.sbt.services.client.base.CommonConstants.TEXT_PLAIN;
import static com.ibm.sbt.services.client.base.CommonConstants.TRANSFER_ENCODING;
import static com.ibm.sbt.services.client.base.CommonConstants.URL_PARAM;
import static com.ibm.sbt.services.client.base.CommonConstants.UTF8;
import static com.ibm.sbt.services.client.base.CommonConstants.XML;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.GZIPInputStream;

import javax.servlet.http.HttpServletResponse;

import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.auth.AuthenticationException;
import org.apache.http.client.CookieStore;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.FileEntity;
import org.apache.http.entity.InputStreamEntity;
import org.apache.http.entity.StringEntity;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.params.CoreProtocolPNames;
import org.apache.http.protocol.HTTP;
import org.apache.http.util.EntityUtils;
import org.w3c.dom.Node;

import com.ibm.commons.runtime.Context;
import com.ibm.commons.runtime.NoAccessSignal;
import com.ibm.commons.runtime.util.UrlUtil;
import com.ibm.commons.util.FastStringBuffer;
import com.ibm.commons.util.PathUtil;
import com.ibm.commons.util.StringUtil;
import com.ibm.commons.util.io.StreamUtil;
import com.ibm.commons.util.io.json.JsonArray;
import com.ibm.commons.util.io.json.JsonException;
import com.ibm.commons.util.io.json.JsonFactory;
import com.ibm.commons.util.io.json.JsonGenerator;
import com.ibm.commons.util.io.json.JsonJavaFactory;
import com.ibm.commons.util.io.json.JsonObject;
import com.ibm.commons.util.io.json.JsonParser;
import com.ibm.commons.util.profiler.Profiler;
import com.ibm.commons.util.profiler.ProfilerAggregator;
import com.ibm.commons.util.profiler.ProfilerType;
import com.ibm.commons.xml.DOMUtil;
import com.ibm.commons.xml.XMLException;
import com.ibm.commons.xml.util.XMIConverter;
import com.ibm.sbt.plugin.SbtCoreLogger;
import com.ibm.sbt.service.debug.ProxyDebugUtil;
import com.ibm.sbt.service.proxy.Proxy;
import com.ibm.sbt.service.proxy.ProxyConfigException;
import com.ibm.sbt.service.proxy.ProxyFactory;
import com.ibm.sbt.services.endpoints.Endpoint;
import com.ibm.sbt.services.endpoints.EndpointFactory;
import com.ibm.sbt.services.util.HttpDeleteWithBody;
import com.ibm.sbt.services.util.SSLUtil;

/**
 * Base class for a REST service client.
 * 
 * @author Philippe Riand
 * @author Mark Wallace
 */
public abstract class ClientService {

    protected Endpoint endpoint;

    private ClientServiceListener listener;

    // Constants for the methods
    public static final String METHOD_GET = "get"; //$NON-NLS-1$
    public static final String METHOD_PUT = "put"; //$NON-NLS-1$
    public static final String METHOD_POST = "post"; //$NON-NLS-1$
    public static final String METHOD_DELETE = "delete"; //$NON-NLS-1$
    public static final String METHOD_DELETE_BODY = "deleteBody"; //$NON-NLS-1$

    // These represents how the result should be formatted to the caller
    public static final Handler FORMAT_UNKNOWN = null;
    public static final Handler FORMAT_NULL = new HandlerNull();
    public static final Handler FORMAT_TEXT = new HandlerString();
    public static final Handler FORMAT_INPUTSTREAM = new HandlerInputStream();
    public static final Handler FORMAT_XML = new HandlerXml();
    public static final Handler FORMAT_JSON = new HandlerJson();

    // TODO: Should be moved elsewhere? What is it for?
    public static final Handler FORMAT_CONNECTIONS_OUTPUT = new HandlerConnectionHeader();

    private static final ProfilerType profilerRequest = new ProfilerType("Executing REST request, "); //$NON-NLS-1$

    private static final String sourceClass = ClientService.class.getName();
    protected static final Logger logger = Logger.getLogger(sourceClass);

    /**
     * Default constructor
     */
    public ClientService() {
    }

    /**
     * Construct a ClientService with the specified Endpoint
     * 
     * @param endpoint
     */
    public ClientService(Endpoint endpoint) {
        this.endpoint = endpoint;
    }

    /**
     * Construct a ClientService with the Endpoint with the specified name
     * 
     * @param endpoint
     */
    public ClientService(String endpointName) {
        this.endpoint = EndpointFactory.getEndpoint(endpointName);
    }

    /**
     * Return the associated Endpoint
     * 
     * @return
     */
    public Endpoint getEndpoint() {
        return endpoint;
    }

    /**
     * Set the associated Endpoint
     * 
     * @param endpoint
     */
    public void setEndpoint(Endpoint endpoint) {
        this.endpoint = endpoint;
    }

    /**
     * If there is an associated endpoint then check is authentication required
     * and if so trigger the authentication process.
     * 
     * @param args
     * @throws ClientServicesException
     */
    protected void checkAuthentication(Args args) throws ClientServicesException {
        if (endpoint != null) {
            if (endpoint.isRequiresAuthentication() && !endpoint.isAuthenticated()) {
                endpoint.authenticate(false);
            }
        }
    }

    /**
     * Get the URL path for the specified arguments and check it is valid
     * i.e. not null. If it is a null throw a ClientServicesException.
     * 
     * @param args
     * @throws ClientServicesException
     */
    protected void checkUrl(Args args) throws ClientServicesException {
        if (StringUtil.isEmpty(getUrlPath(args))) {
            throw new ClientServicesException(null, "The service URL is empty");
        }
    }

    /**
     * Check the read parameters and throw a ClientServicesException if
     * they are invalid.
     * 
     * @param parameters
     * @throws ClientServicesException
     */
    protected void checkReadParameters(Map<String, String> parameters) throws ClientServicesException {
        // nothing for now...
    }

    /**
     * Return the URL from the associated endpoint.
     * 
     * @return
     */
    public String getBaseUrl() {
        if (endpoint != null) {
            return endpoint.getUrl();
        }
        return null;
    }

    /**
     * Initialize the associated Endpoint with the specified HttpClient.
     * 
     * @param httpClient
     * @throws ClientServicesException
     */
    protected void initialize(DefaultHttpClient httpClient) throws ClientServicesException {
        if (endpoint != null) {
            CookieStore cookies = endpoint.getCookies();
            httpClient.setCookieStore(cookies);
            endpoint.initialize(httpClient);
        }
    }

    /**
     * Return true if force trust SSL certificate is set for the associated Endpoint.
     * 
     * @return
     * @throws ClientServicesException
     */
    protected boolean isForceTrustSSLCertificate() throws ClientServicesException {
        if (endpoint != null) {
            return endpoint.isForceTrustSSLCertificate();
        }
        return false;
    }

    /**
     * Return true if force trust SSL certificate is set for the associated Endpoint.
     * 
     * @return
     * @throws ClientServicesException
     */
    protected boolean isForceDisableExpectedContinue() throws ClientServicesException {
        if (endpoint != null) {
            return endpoint.isForceDisableExpectedContinue();
        }
        return false;
    }

    /**
     * Return the proxy info from the associated Endpoint or an empty string
     * 
     * @return
     * @throws ClientServicesException
     */
    protected String getHttpProxy() throws ClientServicesException {
        if (endpoint != null) {
            String proxyinfo = endpoint.getHttpProxy();
            if (StringUtil.isEmpty(proxyinfo)) {
                Context context = Context.getUnchecked();
                if (context != null) {
                    proxyinfo = Context.get().getProperty("sbt.httpProxy");
                }
            }
            return proxyinfo;
        }
        return ""; // TODO should this be null?
    }

    /**
     * Force authentication for the associated Endpoint.
     * 
     * @param args
     * @throws ClientServicesException
     */
    protected void forceAuthentication(Args args) throws ClientServicesException {
        if (endpoint != null) {
            endpoint.authenticate(true);
        } else {
            String msg = StringUtil.format("Authorization needed for service {0}", getUrlPath(args));
            throw new NoAccessSignal(msg);
        }
    }

    // =================================================================
    // Generic access
    // =================================================================

    public static class Args implements Serializable {

        private String serviceUrl; // Service URL to call, relative to the endpoint
        private Map<String, String> parameters; // Query String parameters
        private Map<String, String> headers; // HTTP Headers
        private Handler handler; // Format of the result

        public Args() {
        }

        public String getServiceUrl() {
            return serviceUrl;
        }

        public Args setServiceUrl(String url) {
            this.serviceUrl = url;
            return this;
        }

        public Map<String, String> getParameters() {
            return parameters;
        }

        public Args setParameters(Map<String, String> parameters) {
            this.parameters = parameters;
            return this;
        }

        public Args addParameter(String name, String value) {
            if (parameters == null) {
                this.parameters = new HashMap<String, String>();
            }
            parameters.put(name, value);
            return this;
        }

        public Map<String, String> getHeaders() {
            return headers;
        }

        public Args setHeaders(Map<String, String> headers) {
            this.headers = headers;
            return this;
        }

        public Args addHeader(String name, String value) {
            if (headers == null) {
                this.headers = new HashMap<String, String>();
            }
            headers.put(name, value);
            return this;
        }

        public boolean hasHeader(String name) {
            return (headers == null) ? false : headers.containsKey(name);
        }

        public Handler getHandler() {
            return handler;
        }

        public Args setHandler(Handler handler) {
            this.handler = handler;
            return this;
        }

        @Override
        public String toString() {
            StringBuilder sb = new StringBuilder();
            sb.append("{ serviceUrl:").append(serviceUrl);
            sb.append(", parameters:").append(parameters);
            sb.append(", headers:").append(headers).append("}");
            return sb.toString();
        }
    }

    protected Args createArgs(String serviceUrl, Map<String, String> parameters) throws ClientServicesException {
        Args args = new Args();
        args.setServiceUrl(serviceUrl);
        args.setParameters(parameters);
        return args;
    }

    // =================================================================
    // Request content
    // =================================================================

    public static abstract class Content {

        private final String contentType;

        protected Content(String contentType) {
            this.contentType = contentType;
        }

        protected String getContentType() {
            return contentType;
        }

        public void initRequestContent(HttpClient httpClient, HttpRequestBase httpRequestBase, Args args)
                throws ClientServicesException {
            HttpEntity entity = createEntity();

            // set the http entity to the request, along with its content type
            if (entity != null && (httpRequestBase instanceof HttpEntityEnclosingRequestBase)) {
                setEntity(httpClient, httpRequestBase, args, entity);
            }
        }

        protected void setEntity(HttpClient httpClient, HttpRequestBase httpRequestBase, Args args,
                HttpEntity entity) throws ClientServicesException {
            String contentType = getContentType();
            if (StringUtil.isNotEmpty(contentType)) {
                httpRequestBase.setHeader(CONTENT_TYPE, contentType);
            }
            ((HttpEntityEnclosingRequestBase) httpRequestBase).setEntity(entity);
        }

        protected HttpEntity createEntity() throws ClientServicesException {
            return null;
        }
    }

    public static class ContentHttpEntity extends Content {

        private final HttpEntity content;

        public ContentHttpEntity(HttpEntity content) {
            super(content.getContentType().getValue());
            this.content = content;
        }

        public ContentHttpEntity(HttpEntity content, String contentType) {
            super(contentType);
            this.content = content;
        }

        @Override
        protected HttpEntity createEntity() throws ClientServicesException {
            try {
                return content;
            } catch (Exception ex) {
                throw new ClientServicesException(ex);
            }
        }
    }

    public static class ContentString extends Content {

        private final String content;

        public ContentString(String content, String contentType) {
            super(contentType);
            this.content = content;
        }

        public ContentString(String content) {
            this(content, TEXT_PLAIN);
        }

        @Override
        protected HttpEntity createEntity() throws ClientServicesException {
            try {
                return new StringEntity(content, HTTP.UTF_8);
            } catch (Exception ex) {
                throw new ClientServicesException(ex);
            }
        }
    }

    public static class ContentJson extends Content {

        private final JsonFactory factory;
        private final Object content;

        public ContentJson(Object content, String contentType, JsonFactory factory) {
            super(contentType);
            this.content = content;
            this.factory = factory;
        }

        public ContentJson(Object content, String contentType) {
            this(content, contentType, JsonJavaFactory.instanceEx);
        }

        public ContentJson(Object content) {
            this(content, APPLICATION_JSON, JsonJavaFactory.instanceEx);
        }

        public ContentJson(Object content, JsonFactory factory) {
            this(content, APPLICATION_JSON, factory);
        }

        @Override
        protected HttpEntity createEntity() throws ClientServicesException {
            try {
                return new StringEntity(JsonGenerator.toJson(factory, content, true));
            } catch (Exception ex) {
                throw new ClientServicesException(ex);
            }
        }
    }

    public static class ContentXml extends Content {

        private Node content;

        public ContentXml(Node content, String contentType) {
            super(contentType);
            this.content = content;
        }

        public ContentXml(Node content) {
            this(content, APPLICATION_XML);
            this.content = content;
        }

        @Override
        protected HttpEntity createEntity() throws ClientServicesException {
            try {
                return new StringEntity(DOMUtil.getXMLString(content, true));
            } catch (Exception ex) {
                throw new ClientServicesException(ex);
            }
        }
    }

    public static class ContentFile extends Content {

        private final File content;
        private final String name;

        public ContentFile(String name, File content, String contentType) {
            super(contentType);
            this.name = name;
            this.content = content;
        }

        public ContentFile(File content, String contentType) {
            this(content.getName(), content, contentType);
        }

        public ContentFile(File content) {
            this(content, APPLICATION_OCTET_STREAM);
        }

        public ContentFile(String name, File content) {
            this(name, content, APPLICATION_OCTET_STREAM);
        }

        @Override
        public HttpEntity createEntity() throws ClientServicesException {
            FileEntity fileEnt = new FileEntity(content, getContentType());
            fileEnt.setContentEncoding(BINARY); // Is that OK?
            return fileEnt;
        }

        @Override
        protected void setEntity(HttpClient httpClient, HttpRequestBase httpRequestBase, Args args,
                HttpEntity entity) throws ClientServicesException {
            httpRequestBase.setHeader(SLUG, name);
            httpRequestBase.setHeader(CONTENT_TYPE, getContentType());
            super.setEntity(httpClient, httpRequestBase, args, entity);
        }
    }

    public static class ContentStream extends Content {

        private final long length;
        private final java.io.InputStream stream;
        private final String name;
        private final boolean markSupportedFromWrappedStream;

        public ContentStream(String name, InputStream stream, long length, String contentType) {
            super(contentType);
            this.length = length;
            this.markSupportedFromWrappedStream = stream.markSupported();
            if (stream instanceof BufferedInputStream) {
                this.stream = stream;
            } else {
                this.stream = new BufferedInputStream(stream);
            }
            if (!StringUtil.isEmpty(name)) {
                this.name = name.trim();
            } else {
                this.name = name;
            }
        }

        public ContentStream(InputStream stream) {
            this(null, stream, -1, BINARY_OCTET_STREAM);
        }

        public ContentStream(java.io.InputStream stream, String name) {
            this(name, stream, -1, BINARY_OCTET_STREAM);
        }

        public ContentStream(java.io.InputStream stream, long length, String name) {
            this(name, stream, length, BINARY_OCTET_STREAM);
        }

        @Override
        protected HttpEntity createEntity() throws ClientServicesException {
            InputStreamEntity inputStreamEntity = new InputStreamEntity(stream, length);
            inputStreamEntity.setContentEncoding(BINARY);
            if (length == -1) {
                inputStreamEntity.setChunked(true);
            }

            return inputStreamEntity;
        }

        @Override
        public void initRequestContent(HttpClient httpClient, HttpRequestBase httpRequestBase, Args args)
                throws ClientServicesException {
            // TODO Auto-generated method stub
            super.initRequestContent(httpClient, httpRequestBase, args);
        }

        @Override
        protected void setEntity(HttpClient httpClient, HttpRequestBase httpRequestBase, Args args,
                HttpEntity entity) throws ClientServicesException {
            if (name != null) {
                httpRequestBase.setHeader(SLUG, name);
            }
            httpRequestBase.setHeader(CONTENT_TYPE, getContentType());
            super.setEntity(httpClient, httpRequestBase, args, entity);
        }

    }

    public static class ContentList extends Content {

        private List<ContentPart> contentParts;

        protected ContentList(List<ContentPart> content) {
            this(content, MULTIPART_RELATED);
        }

        protected ContentList(List<ContentPart> content, String contentType) {
            super(contentType);

            this.contentParts = content;
        }

        @Override
        protected HttpEntity createEntity() throws ClientServicesException {
            MultipartEntityBuilder entityBuilder = MultipartEntityBuilder.create();
            for (ContentPart contentPart : contentParts) {
                if (contentPart.getData() instanceof InputStream) {
                    entityBuilder.addBinaryBody(contentPart.getName(), (InputStream) contentPart.getData(),
                            contentPart.getContentType(), contentPart.getFileName());
                } else if (contentPart.getData() instanceof byte[]) {
                    entityBuilder.addBinaryBody(contentPart.getName(), (byte[]) contentPart.getData(),
                            contentPart.getContentType(), contentPart.getFileName());
                } else if (contentPart.getData() instanceof String) {
                    entityBuilder.addTextBody(contentPart.getName(), (String) contentPart.getData(),
                            contentPart.getContentType());
                }
            }
            return entityBuilder.build();
        }

    }

    public static class ContentPart {

        private String name;
        private Object data;
        private ContentType contentType;
        private String fileName;

        public ContentPart(String name, byte[] data, String fileName, String mimeType) {
            this.name = name;
            this.data = data;
            this.fileName = fileName;
            this.contentType = ContentType.create(mimeType);
        }

        public ContentPart(String name, InputStream data, String fileName, String mimeType) {
            this.name = name;
            this.data = data;
            this.fileName = fileName;
            this.contentType = ContentType.create(mimeType);
        }

        public ContentPart(String name, String data, String mimeType) {
            this.name = name;
            this.data = data;
            this.contentType = ContentType.create(mimeType);
        }

        public String getName() {
            return name;
        }

        public Object getData() {
            return data;
        }

        public ContentType getContentType() {
            return contentType;
        }

        public String getFileName() {
            return fileName;
        }
    }

    protected Content createRequestContent(Args args, Object content) throws ClientServicesException {
        if (args.getHeaders() != null && args.getHeaders().get(CONTENT_TYPE) != null) {
            String contentType = args.getHeaders().get(CONTENT_TYPE);
            if (content instanceof String) {
                return new ContentString((String) content, contentType);
            }
            if (content instanceof Node) {
                return new ContentXml((Node) content, contentType);
            }
            if ((content instanceof JsonObject) || (content instanceof JsonArray)) {
                return new ContentJson(content, contentType);
            }
            if (content instanceof File) {
                return new ContentFile((File) content, contentType);
            }
            if (content instanceof InputStream) {
                int length = getLength(args, (InputStream) content);
                return new ContentStream(args.getHeaders().get(SLUG), (InputStream) content, length, contentType);
            }
            if (content instanceof List) {
                return new ContentList((List) content, contentType);
            }
            if (content instanceof HttpEntity) {
                return new ContentHttpEntity((HttpEntity) content, contentType);
            }
        } else {
            if (content instanceof String) {
                return new ContentString((String) content);
            }
            if (content instanceof Node) {
                return new ContentXml((Node) content);
            }
            if ((content instanceof JsonObject) || (content instanceof JsonArray)) {
                return new ContentJson(content);
            }
            if (content instanceof File) {
                return new ContentFile((File) content);
            }
            if (content instanceof InputStream) {
                int length = getLength(args, (InputStream) content);
                return new ContentStream((InputStream) content, length, args.getHeaders().get(SLUG));
            }
            if (content instanceof List) {
                return new ContentList((List) content);
            }
            if (content instanceof HttpEntity) {
                return new ContentHttpEntity((HttpEntity) content);
            }
        }

        throw new ClientServicesException(null, "Cannot create HTTP content for object of type {0}",
                content.getClass());
    }

    protected int getLength(Args args, InputStream istream) throws ClientServicesException {
        try {
            return args.hasHeader(TRANSFER_ENCODING) ? -1 : istream.available();
        } catch (IOException e) {
            throw new ClientServicesException(e);
        }
    }

    // =================================================================
    // Response Handler
    // =================================================================

    public static abstract class Handler implements Serializable {
        public abstract Object parseContent(HttpRequestBase request, HttpResponse response, HttpEntity entity)
                throws ClientServicesException, IOException;
    }

    public static class HandlerNull extends Handler {
        @Override
        public Object parseContent(HttpRequestBase request, HttpResponse response, HttpEntity entity)
                throws ClientServicesException, IOException {
            if (entity != null) {
                InputStream is = getEntityContent(request, response, entity);
                try {
                    // Just eat the entire content - requested for persistent http 1.1 sessions
                    byte[] buffer = new byte[8192];
                    while ((is.read(buffer)) > 0) {
                    }
                } finally {
                }
            }
            return null;
        }
    }

    public static class HandlerString extends Handler {
        @Override
        public Object parseContent(HttpRequestBase request, HttpResponse response, HttpEntity entity)
                throws ClientServicesException, IOException {
            if (entity != null) {
                String encoding = EntityUtils.getContentCharSet(entity);
                if (encoding == null) {
                    encoding = UTF8;
                }
                Reader reader = new InputStreamReader(getEntityContent(request, response, entity), encoding);
                try {
                    FastStringBuffer b = new FastStringBuffer();
                    b.append(reader);
                    return b.toString();
                } finally {
                    reader.close();
                }
            }
            return null;
        }
    }

    public static class HandlerJson extends Handler {
        private final JsonFactory factory;

        public HandlerJson() {
            this.factory = JsonJavaFactory.instanceEx;
        }

        public HandlerJson(JsonFactory factory) {
            this.factory = factory;
        }

        @Override
        public Object parseContent(HttpRequestBase request, HttpResponse response, HttpEntity entity)
                throws ClientServicesException, IOException {
            if (entity != null) {
                String encoding = EntityUtils.getContentCharSet(entity);
                if (encoding == null) {
                    encoding = UTF8;
                }
                Reader reader = createReader(request, response, entity, encoding);
                try {
                    if (false) {
                        String s = StreamUtil.readString(reader);
                        try {
                            return JsonParser.fromJson(factory, s);
                        } catch (JsonException ex) {
                            throw ex;
                        }
                    } else {
                        return JsonParser.fromJson(factory, reader);
                    }
                } catch (JsonException ex) {
                    IOException e = new IOException();
                    e.initCause(ex);
                    throw e;
                } finally {
                    reader.close();
                }
            }
            return null;
        }

        protected Reader createReader(HttpRequestBase request, HttpResponse response, HttpEntity entity,
                String encoding) throws IOException {
            return new InputStreamReader(getEntityContent(request, response, entity), encoding);
        }
    }

    public static class HandlerXml extends Handler {
        @Override
        public Object parseContent(HttpRequestBase request, HttpResponse response, HttpEntity entity)
                throws ClientServicesException, IOException {
            if (entity != null) {
                InputStream is = getEntityContent(request, response, entity);
                try {
                    return DOMUtil.createDocument(is);
                } catch (XMLException ex) {
                    IOException e = new IOException();
                    e.initCause(ex);
                    throw e;
                } finally {
                    is.close();
                }
            }
            return null;
        }
    }

    public static class HandlerConnectionHeader extends Handler {
        @Override
        public Object parseContent(HttpRequestBase request, HttpResponse response, HttpEntity entity)
                throws ClientServicesException, IOException {
            Header[] headers = response.getHeaders(LOCATION_HEADER);
            if (headers != null) {
                if (headers.length > 0) {
                    return headers[0].getValue();
                }
            }
            return null;
        }
    }

    public static class HandlerInputStream extends Handler {
        @Override
        public Object parseContent(HttpRequestBase request, HttpResponse response, HttpEntity entity)
                throws ClientServicesException, IOException {
            if (entity != null) {
                InputStream is = getEntityContent(request, response, entity);
                return is;
            }
            return null;
        }
    }

    public static class HandlerRaw extends Handler {
        @Override
        public Object parseContent(HttpRequestBase request, HttpResponse response, HttpEntity entity)
                throws ClientServicesException, IOException {
            return response;
        }
    }

    protected Handler findErrorHandler(HttpRequestBase request, HttpResponse response)
            throws ClientServicesException, UnsupportedEncodingException, IOException {
        throwClientServicesException(request, response);
        return null;
    }

    protected Handler findSuccessHandler(HttpRequestBase request, HttpResponse response)
            throws UnsupportedEncodingException, IOException {
        HttpEntity entity = response.getEntity();
        if (entity != null) {
            Header hd = entity.getContentType();
            if (hd != null) {
                String ct = hd.getValue();
                if (ct.indexOf(JSON) >= 0) {
                    return FORMAT_JSON;
                }
                if (ct.indexOf(XML) >= 0) {
                    return FORMAT_XML;
                }
                if (ct.indexOf(HTML) >= 0) {
                    return FORMAT_TEXT;
                }
            }
        }
        return getDefaultFormat(response, entity);
    }

    // =================================================================
    // GET
    // =================================================================

    public final Response get(String serviceUrl) throws ClientServicesException {
        return get(serviceUrl, null, null);
    }

    public final Response get(String serviceUrl, Map<String, String> parameters) throws ClientServicesException {
        return get(serviceUrl, parameters, null);
    }

    public final Response get(String serviceUrl, Handler format) throws ClientServicesException {
        return get(serviceUrl, null, format);
    }

    public final Response get(String serviceUrl, Map<String, String> parameters, Handler format)
            throws ClientServicesException {
        return get(serviceUrl, parameters, null, format);
    }

    public final Response get(String serviceUrl, Map<String, String> parameters, Map<String, String> headers,
            Handler format) throws ClientServicesException {
        Args args = createArgs(serviceUrl, parameters);
        if (headers != null) {
            args.setHeaders(headers);
        }
        args.setHandler(format);
        return get(args);
    }

    public final Response get(Args args) throws ClientServicesException {
        return xhr(METHOD_GET, args, null);
    }

    // =================================================================
    // POST
    // =================================================================

    public final Response post(String serviceUrl, Object content) throws ClientServicesException {
        return post(serviceUrl, null, content, null);
    }

    public final Response post(String serviceUrl, Map<String, String> parameters, Object content)
            throws ClientServicesException {
        return post(serviceUrl, parameters, content, null);
    }

    public final Response post(String serviceUrl, Map<String, String> parameters, Object content, Handler format)
            throws ClientServicesException {
        Args args = createArgs(serviceUrl, parameters);
        args.setHandler(format);
        return post(args, content);
    }

    public final Response post(String serviceUrl, Map<String, String> parameters, Map<String, String> headers,
            Object content, Handler format) throws ClientServicesException {
        Args args = createArgs(serviceUrl, parameters);
        args.setHandler(format);
        args.setHeaders(headers);
        return post(args, content);
    }

    public final Response post(Args args, Object content) throws ClientServicesException {
        return xhr(METHOD_POST, args, content);
    }

    // =================================================================
    // PUT
    // =================================================================

    public final Response put(String serviceUrl, Object content) throws ClientServicesException {
        return put(serviceUrl, null, content, null);
    }

    public final Response put(String serviceUrl, Map<String, String> parameters, Object content)
            throws ClientServicesException {
        return put(serviceUrl, parameters, content, null);
    }

    public final Response put(String serviceUrl, Map<String, String> parameters, Object content, Handler format)
            throws ClientServicesException {
        Args args = createArgs(serviceUrl, parameters);
        args.setHandler(format);
        return put(args, content);
    }

    public final Response put(String serviceUrl, Map<String, String> parameters, Map<String, String> headers,
            Object content, Handler format) throws ClientServicesException {
        Args args = createArgs(serviceUrl, parameters);
        args.setHandler(format);
        args.setHeaders(headers);
        return put(args, content);
    }

    public final Response put(Args args, Object content) throws ClientServicesException {
        return xhr(METHOD_PUT, args, content);
    }

    // =================================================================
    // DELETE
    // =================================================================

    public final Response delete(String serviceUrl) throws ClientServicesException {
        return delete(serviceUrl, null, null);
    }

    public final Response delete(String serviceUrl, Map<String, String> parameters) throws ClientServicesException {
        return delete(serviceUrl, parameters, null);
    }

    public final Response delete(String serviceUrl, Map<String, String> parameters, Handler format)
            throws ClientServicesException {
        Args args = createArgs(serviceUrl, parameters);
        args.setHandler(format);
        return delete(args);
    }

    public final Response delete(String serviceUrl, Map<String, String> parameters, Map<String, String> headers,
            Handler format) throws ClientServicesException {
        Args args = createArgs(serviceUrl, parameters);
        args.setHandler(format);
        args.setHeaders(headers);
        return delete(args);
    }

    public final Response delete(String serviceUrl, Map<String, String> parameters, Map<String, String> headers,
            Handler format, String content) throws ClientServicesException {
        Args args = createArgs(serviceUrl, parameters);
        args.setHandler(format);
        args.setHeaders(headers);
        return xhr(METHOD_DELETE_BODY, args, content);
    }

    public final Response delete(String serviceUrl, Map<String, String> parameters, Map<String, String> headers,
            Object content, Handler format) throws ClientServicesException {
        Args args = createArgs(serviceUrl, parameters);
        args.setHandler(format);
        args.setHeaders(headers);
        return xhr(METHOD_DELETE_BODY, args, content);
    }

    public final Response delete(Args args) throws ClientServicesException {
        return xhr(METHOD_DELETE, args, null);
    }

    // =================================================================
    // Actual request execution
    // =================================================================

    /**
     * Execute an XML Http request with the specified arguments
     * 
     * @param method
     * @param args
     * @param content
     * @return
     * @throws ClientServicesException
     */
    public Response xhr(String method, Args args, Object content) throws ClientServicesException {
        if (logger.isLoggable(Level.FINEST)) {
            logger.entering(sourceClass, "xhr", new Object[] { method, args });
        }

        // notify listener
        if (!notifyListener(method, args, content)) {
            return null;
        }

        checkAuthentication(args);
        checkUrl(args);
        checkReadParameters(args.parameters);
        String url = composeRequestUrl(args);
        Response response = null;
        if (StringUtil.equalsIgnoreCase(method, METHOD_GET)) {
            HttpGet httpGet = new HttpGet(url);
            response = execRequest(httpGet, args, content);
        } else if (StringUtil.equalsIgnoreCase(method, METHOD_POST)) {
            HttpPost httpPost = new HttpPost(url);
            response = execRequest(httpPost, args, content);
        } else if (StringUtil.equalsIgnoreCase(method, METHOD_PUT)) {
            HttpPut httpPut = new HttpPut(url);
            response = execRequest(httpPut, args, content);
        } else if (StringUtil.equalsIgnoreCase(method, METHOD_DELETE)) {
            HttpDelete httpDelete = new HttpDelete(url);
            response = execRequest(httpDelete, args, content);
        } else if (StringUtil.equalsIgnoreCase(method, METHOD_DELETE_BODY)) {
            HttpDeleteWithBody httpDelete = new HttpDeleteWithBody(url);
            response = execRequest(httpDelete, args, content);
        } else {
            throw new ClientServicesException(null, "Unsupported HTTP method {0}", method);
        }

        // notify listener
        response = notifyListener(method, args, content, response);

        if (logger.isLoggable(Level.FINEST)) {
            logger.exiting(sourceClass, "xhr", response);
        }
        return response;
    }

    /**
     * Execute the specified HttpRequest
     * 
     * @param httpRequestBase
     * @param args
     * @param content
     * @return
     * @throws ClientServicesException
     */
    protected Response execRequest(HttpRequestBase httpRequestBase, Args args, Object content)
            throws ClientServicesException {
        if (Profiler.isEnabled()) {
            String msg = httpRequestBase.getMethod().toUpperCase() + " " + getUrlPath(args);
            ProfilerAggregator agg = Profiler.startProfileBlock(profilerRequest, msg);
            long ts = Profiler.getCurrentTime();
            try {
                return _xhr(httpRequestBase, args, content);
            } finally {
                Profiler.endProfileBlock(agg, ts);
            }
        } else {
            return _xhr(httpRequestBase, args, content);
        }
    }

    /**
     * Allows clients to override the process content section of {@link #_xhr(HttpRequestBase, Args)}. <br/>
     * 
     * @param httpRequestBase
     *            the base HTTP request created by the service
     * @param content
     *            the content that is to be sent via the request
     * @param args
     *            the args (such as url params) that have been pushed through by the calling service
     * @return true if the client wants the super class to take care of processing the content
     */
    protected Response _xhr(HttpRequestBase httpRequestBase, Args args, Object content)
            throws ClientServicesException {
        DefaultHttpClient httpClient = createHttpClient(httpRequestBase, args);
        initialize(httpClient);

        // HttpClient 4.1
        // httpClient.addRequestInterceptor(new RequestAcceptEncoding());
        // httpClient.addResponseInterceptor(new ResponseContentEncoding());

        Content reqContent = null;
        if (content != null) {
            if (content instanceof Content) {
                reqContent = (Content) content;
            } else {
                reqContent = createRequestContent(args, content);
            }
        }

        prepareRequest(httpClient, httpRequestBase, args, reqContent);

        HttpResponse response = executeRequest(httpClient, httpRequestBase, args);
        return processResponse(httpClient, httpRequestBase, response, args);
    }

    protected void prepareRequest(HttpClient httpClient, HttpRequestBase httpRequestBase, Args args,
            Content content) throws ClientServicesException {
        // TODO: add support for gzip content
        // httpClient.addRequestHeader("Accept-Encoding", "gzip");

        if (args.getHeaders() != null) {
            addHeaders(httpClient, httpRequestBase, args);
        }
        if (content != null) {
            content.initRequestContent(httpClient, httpRequestBase, args);
        }
    }

    protected void addHeaders(HttpClient httpClient, HttpRequestBase httpRequestBase, Args args) {
        for (Map.Entry<String, String> e : args.getHeaders().entrySet()) {
            String headerName = e.getKey();
            String headerValue = e.getValue();
            httpRequestBase.addHeader(headerName, headerValue);
        }
    }

    /**
     * Execute the specified the request.
     * 
     * @param httpClient
     * @param httpRequestBase
     * @param args
     * @return
     * @throws ClientServicesException
     */
    protected HttpResponse executeRequest(HttpClient httpClient, HttpRequestBase httpRequestBase, Args args)
            throws ClientServicesException {
        try {
            return httpClient.execute(httpRequestBase);
        } catch (Exception ex) {
            if (logger.isLoggable(Level.FINE)) {
                String msg = "Exception ocurred while executing request {0} {1}";
                msg = StringUtil.format(msg, httpRequestBase.getMethod(), args);
                logger.log(Level.FINE, msg, ex);
            }

            if (ex instanceof ClientServicesException) {
                throw (ClientServicesException) ex;
            }
            String msg = "Error while executing the REST service {0}";
            String param = httpRequestBase.getURI().toString();
            throw new ClientServicesException(ex, msg, param);
        }
    }

    /**
     * Process the specified response
     * 
     * @param httpClient
     * @param httpRequestBase
     * @param httpResponse
     * @param args
     * @return
     * @throws ClientServicesException
     */
    protected Response processResponse(HttpClient httpClient, HttpRequestBase httpRequestBase,
            HttpResponse httpResponse, Args args) throws ClientServicesException {
        if (logger.isLoggable(Level.FINEST)) {
            logger.entering(sourceClass, "processResponse",
                    new Object[] { httpRequestBase.getURI(), httpResponse.getStatusLine() });
        }

        int statusCode = httpResponse.getStatusLine().getStatusCode();
        String reasonPhrase = httpResponse.getStatusLine().getReasonPhrase();
        if (!checkStatus(statusCode)) {
            if (SbtCoreLogger.SBT.isErrorEnabled()) {
                // Do not throw an exception here as some of the non OK responses are not error cases.
                String msg = "Client service request to: {0} did not return OK status. Status returned: {1}, reason: {2}, expected: {3}";
                msg = StringUtil.format(msg, httpRequestBase.getURI(), statusCode, reasonPhrase, HttpStatus.SC_OK);
                SbtCoreLogger.SBT.traceDebugp(this, "processResponse", msg);
            }
        }

        if (isResponseRequireAuthentication(httpResponse)) {
            forceAuthentication(args);
            throw new ClientServicesException(new AuthenticationException());
        }

        Handler format = findHandler(httpRequestBase, httpResponse, args.handler);

        Response response = new Response(httpClient, httpResponse, httpRequestBase, args, format);

        if (logger.isLoggable(Level.FINEST)) {
            logger.exiting(sourceClass, "processResponse", response);
        }
        return response;
    }

    private boolean checkStatus(int statusCode) {
        if (statusCode >= 200 && statusCode < 300) {
            return true;
        }
        return false;
    }

    // Each endpoint provides its implementation whether an authentication is required based on response.
    protected boolean isResponseRequireAuthentication(HttpResponse httpResponse) {
        int statusCode = httpResponse.getStatusLine().getStatusCode();
        if ((httpResponse.getStatusLine().getStatusCode() == HttpServletResponse.SC_UNAUTHORIZED)
                || (endpoint != null && endpoint.getAuthenticationErrorCode() == statusCode)) {
            return true;
        }
        return false;
    }

    // =================================================================
    // URL composition
    // =================================================================

    protected String composeRequestUrl(Args args) throws ClientServicesException {
        // Compose the URL
        StringBuilder b = new StringBuilder(256);

        if (!(UrlUtil.isAbsoluteUrl(args.getServiceUrl()))) { // check if url supplied is absolute
            String url = getUrlPath(args);
            if (url.charAt(url.length() - 1) == CH_SLASH) {
                url = url.substring(0, url.length() - 1);
            }
            b.append(url);
            addUrlParts(b, args);
        } else {
            // Calling app has provided the complete url, do not do url manipulation in clientservice
            b.append(args.getServiceUrl());
        }

        if (endpoint != null) { // The endpoint can be null
            Proxy proxy = null;
            try {
                proxy = ProxyFactory.getProxyConfig(endpoint.getProxyConfig());
            } catch (ProxyConfigException e) {
                if (logger.isLoggable(Level.FINE)) {
                    String msg = "Exception ocurred while fetching proxy information : composeRequestUrl";
                    logger.log(Level.FINE, msg, e);
                }
            }
            StringBuilder proxyUrl = new StringBuilder(proxy.rewriteUrl(b.toString()));
            addUrlParameters(proxyUrl, args);
            return proxyUrl.toString();
        }
        return b.toString();
    }

    protected String getUrlPath(Args args) {
        String baseUrl = getBaseUrl();
        String serviceUrl = args.getServiceUrl();
        serviceUrl = substituteServiceMapping(serviceUrl);
        return PathUtil.concat(baseUrl, serviceUrl, CH_SLASH);
    }

    protected String substituteServiceMapping(String url) {
        String regex = "\\{(.*?)\\}";

        Pattern paramsPattern = Pattern.compile(regex);
        Matcher paramsMatcher = paramsPattern.matcher(url);

        while (paramsMatcher.find()) {
            String subOut = paramsMatcher.group(1);
            String subIn = this.endpoint.getServiceMappings().get(subOut);
            if (subIn != null) {
                return url.replaceFirst("\\{" + subOut + "\\}", subIn);
            }
        }

        return url.replace("{", "").replace("}", "");
    }

    protected void addUrlParts(StringBuilder b, Args args) throws ClientServicesException {
    }

    protected void addUrlParameters(StringBuilder b, Args args) throws ClientServicesException {
        Map<String, String> parameters = args.getParameters();
        if (parameters != null) {
            boolean first = !b.toString().contains("?");
            for (Map.Entry<String, String> e : parameters.entrySet()) {
                String name = e.getKey();
                if (StringUtil.isNotEmpty(name) && isValidUrlParameter(args, name)) {
                    String value = e.getValue();
                    first = addParameter(b, first, name, value);
                }
            }
        }
    }

    protected boolean isValidUrlParameter(Args args, String name) throws ClientServicesException {
        return true;
    }

    //
    // Url Utilities
    //
    protected boolean addParameter(StringBuilder b, boolean first, String name, Date value)
            throws ClientServicesException {
        if (value != null) {
            String date = XMIConverter.composeDate(value.getTime());
            return addParameter(b, first, name, date);
        }
        return first;
    }

    protected boolean addParameter(StringBuilder b, boolean first, String name, int value)
            throws ClientServicesException {
        if (value != 0) {
            return addParameter(b, first, name, Integer.toString(value));
        }
        return first;
    }

    protected boolean addParameter(StringBuilder b, boolean first, String name, String value)
            throws ClientServicesException {
        try {
            if (value != null) {
                b.append(first ? INIT_URL_PARAM : URL_PARAM);
                b.append(name);
                b.append('=');
                b.append(URLEncoder.encode(value, UTF8));
                return false;
            }
            return first;
        } catch (UnsupportedEncodingException ex) {
            throw new ClientServicesException(ex);
        }
    }

    // =================================================================
    // Content formatting
    // =================================================================

    protected Handler getDefaultFormat(HttpResponse response, HttpEntity entity) {
        return FORMAT_INPUTSTREAM;
    }

    /**
     * Find the handler for the specified response
     * 
     * @param request
     * @param response
     * @param DateFormat
     * @return
     * @throws ClientServicesException
     */
    protected Handler findHandler(HttpRequestBase request, HttpResponse response, Handler handler)
            throws ClientServicesException {
        if (logger.isLoggable(Level.FINEST)) {
            logger.entering(sourceClass, "findHandler",
                    new Object[] { request.getURI(), response.getStatusLine(), handler });
        }

        try {
            int statusCode = response.getStatusLine().getStatusCode();
            if (response.getStatusLine().getStatusCode() == HttpServletResponse.SC_INTERNAL_SERVER_ERROR) {
                handler = findErrorHandler(request, response);
            }

            // Connections Delete API returns SC_NO_CONTENT for successful deletion.
            if (isErrorStatusCode(statusCode)) {
                handler = findErrorHandler(request, response);
            }

            if (handler == null) {
                handler = findSuccessHandler(request, response);
            }

            // SBT doesn't have a JS interpreter...
            if (handler == null) {
                handler = new HandlerRaw();
            }
        } catch (Exception ex) {
            if (ex instanceof ClientServicesException) {
                throw (ClientServicesException) ex;
            }
            throw new ClientServicesException(ex, "Error while parsing the REST service results");
        }

        if (logger.isLoggable(Level.FINEST)) {
            logger.exiting(sourceClass, "findHandler", handler);
        }
        return handler;
    }

    /**
     * @param statusCode
     * @return
     */
    protected boolean isErrorStatusCode(int statusCode) {
        return (statusCode != HttpStatus.SC_OK) && (statusCode != HttpStatus.SC_CREATED)
                && (statusCode != HttpStatus.SC_ACCEPTED) && (statusCode != HttpStatus.SC_NO_CONTENT);
    }

    /**
     * 
     * @param request
     * @param response
     * @throws ClientServicesException
     */
    protected void throwClientServicesException(HttpRequestBase request, HttpResponse response)
            throws ClientServicesException {
        throw new ClientServicesException(response, request);
    }

    // Until we move to HttpClient 4.1
    public static InputStream getEntityContent(HttpRequestBase request, HttpResponse response, HttpEntity entity)
            throws IOException {
        InputStream is = entity.getContent();
        if (is != null) {
            Header contentEncodingHeader = response.getFirstHeader(CONTENT_ENCODING);
            if (contentEncodingHeader != null && contentEncodingHeader.getValue().equalsIgnoreCase(GZIP)) {
                is = new GZIPInputStream(is);
            }
        }
        return is;
    }

    public DefaultHttpClient createHttpClient(HttpRequestBase httpRequestBase, Args args)
            throws ClientServicesException {
        // Check if we should trust the HTTPS certificates
        DefaultHttpClient httpClient = new DefaultHttpClient();
        if (isForceTrustSSLCertificate()) {
            // PHIL: we don't check the scheme here as the Apachae library will still verify the
            // certificate for some http requests...
            // String scheme = httpRequestBase.getURI().getScheme();
            // if(scheme!=null && scheme.equalsIgnoreCase("https")) {
            httpClient = SSLUtil.wrapHttpClient(httpClient);
            // }
        }
        if (isForceDisableExpectedContinue()) {
            logger.fine("Disabling Expected Continue Header");
            httpClient.getParams().setParameter(CoreProtocolPNames.USE_EXPECT_CONTINUE, false);
        }

        // Capture network traffic through a network proxy like Fiddler or WireShark for debug purposes
        if (StringUtil.isNotEmpty(getHttpProxy())) {
            return ProxyDebugUtil.wrapHttpClient(httpClient, getHttpProxy());
        }

        return httpClient;
    }

    public void setListener(ClientServiceListener listener) {
        this.listener = listener;
    }

    private boolean notifyListener(String method, Args args, Object content) throws ClientServicesException {
        if (listener != null) {
            return listener.preXhr(method, args, content);
        }
        return true;
    }

    private Response notifyListener(String method, Args args, Object content, Response response)
            throws ClientServicesException {
        if (listener != null) {
            return listener.postXhr(method, args, content, response);
        }
        return response;
    }

}