diff options
Diffstat (limited to 'framework/bundles/org.eclipse.ecf.remoteservice.rest/src/org/eclipse/ecf/remoteservice/rest/RestService.java')
-rw-r--r-- | framework/bundles/org.eclipse.ecf.remoteservice.rest/src/org/eclipse/ecf/remoteservice/rest/RestService.java | 409 |
1 files changed, 409 insertions, 0 deletions
diff --git a/framework/bundles/org.eclipse.ecf.remoteservice.rest/src/org/eclipse/ecf/remoteservice/rest/RestService.java b/framework/bundles/org.eclipse.ecf.remoteservice.rest/src/org/eclipse/ecf/remoteservice/rest/RestService.java new file mode 100644 index 000000000..8428ae155 --- /dev/null +++ b/framework/bundles/org.eclipse.ecf.remoteservice.rest/src/org/eclipse/ecf/remoteservice/rest/RestService.java @@ -0,0 +1,409 @@ +/******************************************************************************* + * Copyright (c) 2009 EclipseSource and others. All rights reserved. This + * program and the accompanying materials are made available under the terms of + * the Eclipse Public License v1.0 which accompanies this distribution, and is + * available at http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * EclipseSource - initial API and implementation + *******************************************************************************/ +package org.eclipse.ecf.remoteservice.rest; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.URL; +import java.text.ParseException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.commons.httpclient.Credentials; +import org.apache.commons.httpclient.HttpClient; +import org.apache.commons.httpclient.HttpException; +import org.apache.commons.httpclient.HttpMethod; +import org.apache.commons.httpclient.HttpStatus; +import org.apache.commons.httpclient.NameValuePair; +import org.apache.commons.httpclient.UsernamePasswordCredentials; +import org.apache.commons.httpclient.auth.AuthScope; +import org.apache.commons.httpclient.methods.DeleteMethod; +import org.apache.commons.httpclient.methods.GetMethod; +import org.apache.commons.httpclient.methods.PostMethod; +import org.apache.commons.httpclient.methods.PutMethod; +import org.apache.commons.httpclient.methods.RequestEntity; +import org.apache.commons.httpclient.methods.StringRequestEntity; +import org.apache.commons.httpclient.params.HttpClientParams; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.ecf.core.identity.ID; +import org.eclipse.ecf.core.security.Callback; +import org.eclipse.ecf.core.security.CallbackHandler; +import org.eclipse.ecf.core.security.IConnectContext; +import org.eclipse.ecf.core.security.NameCallback; +import org.eclipse.ecf.core.security.ObjectCallback; +import org.eclipse.ecf.core.security.UnsupportedCallbackException; +import org.eclipse.ecf.core.util.ECFException; +import org.eclipse.ecf.internal.remoteservice.rest.ResourceRepresentationFactory; +import org.eclipse.ecf.remoteservice.IRemoteCall; +import org.eclipse.ecf.remoteservice.IRemoteCallListener; +import org.eclipse.ecf.remoteservice.IRemoteService; +import org.eclipse.ecf.remoteservice.IRemoteServiceReference; +import org.eclipse.ecf.remoteservice.events.IRemoteCallCompleteEvent; +import org.eclipse.ecf.remoteservice.events.IRemoteCallStartEvent; +import org.eclipse.ecf.remoteservice.rest.identity.RestID; +import org.eclipse.equinox.concurrent.future.AbstractExecutor; +import org.eclipse.equinox.concurrent.future.IFuture; +import org.eclipse.equinox.concurrent.future.IProgressRunnable; +import org.eclipse.equinox.concurrent.future.ThreadsExecutor; + +/** + * This class represents a REST service from the client side of view. So a RESTful + * web service can be accessed via the methods provided by this class. Mostly the + * methods are inherited from {@link IRemoteService}. + */ +public class RestService implements IRemoteService { + + /** + * inner class implementing the asynchronous result object. This + * implementation also provides the calling infrastructure. + */ + private class AsyncResult extends Thread { + + // the result of the call. + Object result; + + // the exception, if any happened during the call. + Throwable exception; + + // the remote call object. + IRestCall call; + + // the callback listener, if provided. + private IRemoteCallListener listener; + + // constructor + AsyncResult(final IRestCall call, final IRemoteCallListener listener) { + this.call = call; + this.listener = listener; + } + + // the call happens here. + public void run() { + Object r = null; + Throwable e = null; + + final long reqID = getNextID(); + + if (listener != null) { + listener.handleEvent(new IRemoteCallStartEvent() { + public IRemoteCall getCall() { + // TODO: should return the remoteCall + return null; + } + + public IRemoteServiceReference getReference() { + return reference; + } + + public long getRequestId() { + return reqID; + } + }); + } + + try { + r = callSync(call); + } catch (Throwable t) { + e = t; + } + + synchronized (AsyncResult.this) { + result = r; + exception = e; + AsyncResult.this.notify(); + } + + if (listener != null) { + listener.handleEvent(new IRemoteCallCompleteEvent() { + + public Throwable getException() { + return exception; + } + + public Object getResponse() { + return result; + } + + public boolean hadException() { + return exception != null; + } + + public long getRequestId() { + return reqID; + } + }); + } + } + } + + private int nextID; + private IRemoteServiceReference reference; + private Object proxy; + + public RestService(IRemoteServiceReference reference) { + this.reference = reference; + } + + public RestService(Object proxy) { + this.proxy = proxy; + reference = null; + } + + public RestService() { + reference = null; + } + + /** + * get the next call id. + * + * @return the next call id. + */ + synchronized long getNextID() { + return nextID++; + } + + public void callAsync(IRemoteCall call, IRemoteCallListener listener) { + callAsync(lookupRestCall(call), listener); + } + + public void callAsync(IRestCall restCall, IRemoteCallListener listener) { + new AsyncResult(restCall, listener).start(); + } + + public IFuture callAsync(final IRemoteCall call) { + return callAsync(lookupRestCall(call)); + } + + public IFuture callAsync(final IRestCall call) { + final AbstractExecutor executor = new ThreadsExecutor(); + return executor.execute(new IProgressRunnable() { + public Object run(IProgressMonitor monitor) throws Exception { + return callSync(call); + } + }, null); + } + + public Object callSync(IRemoteCall call) throws ECFException { + return callSync(lookupRestCall(call)); + } + + public Object callSync(IRestCall call) throws ECFException { + if(call == null) + throw new ECFException("no IRestCall found for IRemoteCall"); + return callHttpMethod(call); + } + + private IRestCall lookupRestCall(IRemoteCall call) { + if(reference instanceof RestServiceReference) { + RestServiceReference ref = (RestServiceReference) reference; + RestContainer container = ref.getContainer(); + return container.lookupRestCall(call); + } + return null; + } + + /** + * Calls the Rest service with given URL of IRestCall. The returned value is + * the response body as an InputStream. + * + * @param restCall + * The Rest Service to call represented by an IRestCall object + * @return The InputStream of the response body or <code>null</code> if an + * error occurs. + */ + public Object callHttpMethod(final IRestCall restCall) throws ECFException { + // call the method + HttpClient httpClient = new HttpClient(); + String url = handleURI(restCall.getURI().toString()); + HttpMethod httpMethod = createHttpMethod(restCall, url); + // add additional request headers + handleRequestHeaders(httpMethod, restCall.getRequestHeaders()); + // handle authentication + handleAuthentication(httpClient, httpMethod); + Object response = null; + // needed because a resource can link to another resource + httpClient.getParams().setParameter(HttpClientParams.ALLOW_CIRCULAR_REDIRECTS, new Boolean(true)); + // execute method + try { + int responseCode = httpClient.executeMethod(httpMethod); + if(responseCode == HttpStatus.SC_OK) { + response = ResourceRepresentationFactory.getDefault() + .createResourceRepresentation(httpMethod, restCall); + if(proxy != null && proxy instanceof IRestResponseProcessor) + ((IRestResponseProcessor) proxy).processResource(response); + } else + throw new ECFException("Service returned status code: " + responseCode); + } catch (HttpException e) { + throw new ECFException(e); + } catch (IOException e) { + throw new ECFException(e); + } catch (ParseException e) { + throw new ECFException(e); + } + return response; + } + + protected void handleRequestHeaders(HttpMethod httpMethod, Map requestHeaders) { + if(requestHeaders != null) { + Set keySet = requestHeaders.keySet(); + Object[] headers = keySet.toArray(); + for(int i = 0; i < headers.length; i++) { + String key = (String) headers[i]; + String value = (String) requestHeaders.get(key); + httpMethod.addRequestHeader(key, value); + } + } + } + + protected HttpMethod createHttpMethod(IRestCall restCall, String url) throws ECFException { + HttpMethod httpMethod = null; + String method = restCall.getMethod(); + if(method.equals(IRestCall.HTTP_GET)) { + httpMethod = new GetMethod(url); + addGetParams(httpMethod, restCall.getParameters()); + } else if(method.equals(IRestCall.HTTP_POST)) { + httpMethod = new PostMethod(url); + addPostParams(httpMethod, restCall.getParameters(), restCall + .getRequestEntity()); + } else if(method.equals(IRestCall.HTTP_PUT)) { + httpMethod = new PutMethod(url); + addPutRequestBody(restCall, httpMethod); + } else if(method.equals(IRestCall.HTTP_DELETE)) { + httpMethod = new DeleteMethod(url); + } else { + throw new ECFException("HTTP method not supported"); + } + return httpMethod; + } + + protected void addPutRequestBody(IRestCall restCall, HttpMethod httpMethod) throws ECFException { + PutMethod putMethod = (PutMethod) httpMethod; + if(restCall.getParameters()[0] instanceof String) { + String body = (String) restCall.getParameters()[0]; + RequestEntity entity; + try { + entity = new StringRequestEntity(body, null, null); + } catch (UnsupportedEncodingException e) { + throw new ECFException( + "An error occured while creating the request entity", e); + } + putMethod.setRequestEntity(entity); + } else { + throw new ECFException( + "For put the first Parameter must be a String"); + } + } + + protected void addPostParams(HttpMethod httpMethod, Object[] restParams, RequestEntity requestEntity) { + PostMethod postMethod = (PostMethod) httpMethod; + // query parameters exclude a request entity + if(restParams != null && restParams.length > 0) { + postMethod.addParameters(toNameValuePairs(restParams)); + } else if(requestEntity != null) { + postMethod.setRequestEntity(requestEntity); + } + } + + protected void addGetParams(HttpMethod httpMethod, Object[] restParams) { + if(restParams != null) { + httpMethod.setQueryString(toNameValuePairs(restParams)); + } + } + + private NameValuePair[] toNameValuePairs(Object[] restParams) { + List nameValueList = new ArrayList(restParams.length); + for(int i = 0; i < restParams.length; i++) { + if(restParams[i] instanceof String) { + String param = (String) restParams[i]; + int indexOfEquals = param.indexOf('='); + String key = param.substring(0, indexOfEquals); + String value = param.substring(indexOfEquals + 1, param + .length()); + nameValueList.add(new NameValuePair(key, value)); + } + } + return (NameValuePair[]) nameValueList.toArray(new NameValuePair[nameValueList.size()]); + } + + protected void handleAuthentication(HttpClient httpClient, HttpMethod method) { + if(reference instanceof RestServiceReference) { + RestServiceReference ref = (RestServiceReference) reference; + RestContainer container = ref.getContainer(); + IConnectContext connectContext = container.getConnectContext(); + if(connectContext != null) { + NameCallback nameCallback = new NameCallback(""); + ObjectCallback passwordCallback = new ObjectCallback(); + Callback[] callbacks = new Callback[] { nameCallback, + passwordCallback }; + CallbackHandler callbackHandler = connectContext + .getCallbackHandler(); + if(callbackHandler == null) + return; + try { + callbackHandler.handle(callbacks); + String username = nameCallback.getName(); + String password = (String) passwordCallback.getObject(); + AuthScope authscope = new AuthScope(null, -1); + Credentials credentials = new UsernamePasswordCredentials( + username, password); + httpClient.getState() + .setCredentials(authscope, credentials); + method.setDoAuthentication(true); + } catch (IOException e) { + e.printStackTrace(); + } catch (UnsupportedCallbackException e) { + e.printStackTrace(); + } + + } + } + } + + private String handleURI(String uri) { + if(uri.indexOf("http://") > -1) + return uri; + ID containerID = reference.getContainerID(); + + if(containerID instanceof RestID) { + RestID id = (RestID) containerID; + URL baseURL = id.getBaseURL(); + String baseUrlString = baseURL.toExternalForm(); + int length = baseUrlString.length(); + char[] lastChar = new char[1]; + baseUrlString.getChars(length - 1, length, lastChar, 0); + char[] firstMethodChar = new char[1]; + uri.getChars(0, 1, firstMethodChar, 0); + if((lastChar[0] == '/' && firstMethodChar[0] != '/') + || (lastChar[0] != '/' && firstMethodChar[0] == '/')) + return baseUrlString + uri; + else if(lastChar[0] == '/' && firstMethodChar[0] == '/') { + String tempurl = baseUrlString.substring(0, length - 1); + return tempurl + uri; + } else if(lastChar[0] != '/' && firstMethodChar[0] != '/') + return baseUrlString + "/" + uri; + } + return null; + } + + public Object getProxy() throws ECFException { + return proxy; + } + + void setReference(IRemoteServiceReference reference) { + this.reference = reference; + } + + public void fireAsync(IRemoteCall call) throws ECFException { + callAsync(call); + } + +} |