/* * © 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.APPKEY; 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 endpointName */ public ClientService(String endpointName) { this.endpoint = EndpointFactory.getEndpoint(endpointName); } /** * Return the associated Endpoint * * @return {Endpoint} */ 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 {String} */ 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 {boolean} * @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 {boolean} * @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 {String} * @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, ClassNotFoundException { 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 {Response} * @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 {Response} * @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 _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); } /** * prepares the request and adds custom/required headers */ protected void prepareRequest(HttpClient httpClient, HttpRequestBase httpRequestBase, Args args, Content content) throws ClientServicesException { // TODO: add support for gzip content // httpClient.addRequestHeader("Accept-Encoding", "gzip"); //Sets the AppKey Header for the Given Request APPKEY if(endpoint != null){ String appKey = ((com.ibm.sbt.services.endpoints.AbstractEndpoint) endpoint).getAppKey(); if(appKey != null){ httpRequestBase.addHeader(APPKEY,appKey); } //Adds the Headers to the httpRequestBase java.util.HashMap<String,String> headers = ((com.ibm.sbt.services.endpoints.AbstractEndpoint) endpoint).getHeaders(); java.util.Set<String> headerNames = headers.keySet(); for(String headerName : headerNames){ String headerVal = headers.get(headerName); httpRequestBase.addHeader(headerName,headerVal); } } 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 {HttpResponse} * @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 {Response} * @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)){ try { forceAuthentication(args); } catch (ClassNotFoundException e) { throw new ClientServicesException(new AuthenticationException(), "Force Authentication Failed: Check Username and UserId "+ e.getMessage()); } throw new ClientServicesException(new AuthenticationException(), "Wrong username or password"); } 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) { //Issue 1579: Added Status Code 404 as valid status code if ((statusCode >= 200 && statusCode < 300) || statusCode == 404) { 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 handler * @return {Handler} * @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 {boolean} */ protected boolean isErrorStatusCode(int statusCode) { return (statusCode != HttpStatus.SC_OK) && (statusCode != HttpStatus.SC_CREATED) && (statusCode != HttpStatus.SC_ACCEPTED) && (statusCode != HttpStatus.SC_NO_CONTENT) && (statusCode != HttpStatus.SC_NOT_FOUND) && (statusCode != 404); } /** * * @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; } }