diff --git a/.gitignore b/.gitignore index d402d68..c745262 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,5 @@ out/ .DS_Store -*.iws -*.ipr -*.iml /target .idea +*.iws diff --git a/RallyRestToolkitForJava.iml b/RallyRestToolkitForJava.iml new file mode 100644 index 0000000..bda1b42 --- /dev/null +++ b/RallyRestToolkitForJava.iml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/RallyRestToolkitForJava.ipr b/RallyRestToolkitForJava.ipr new file mode 100644 index 0000000..9e495bb --- /dev/null +++ b/RallyRestToolkitForJava.ipr @@ -0,0 +1,356 @@ + + + + + $PROJECT_DIR$/out/artifacts/rally_rest_api_2_1_0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pom.xml b/pom.xml index 24e20f8..5c50e03 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ Rally Rest Toolkit For Java com.rallydev.rest rally-rest-api - 2.0.5-SNAPSHOT + 2.1.0-SNAPSHOT A java toolkit for interacting with the Rally Rest API diff --git a/src/main/java/META-INF/MANIFEST.MF b/src/main/java/META-INF/MANIFEST.MF index b5db7bb..4d12854 100644 --- a/src/main/java/META-INF/MANIFEST.MF +++ b/src/main/java/META-INF/MANIFEST.MF @@ -1,9 +1,9 @@ Manifest-Version: "1.2" Implementation-Vendor: "Rally Software, Inc." -Implementation-Title: "com.rallydev.rest" -Implementation-Version: "2.0.3" +Implementation-Title: "com.rallydev.rest" +Implementation-Version: "2.1.0" Class-Path: "httpcore-4.2.4.jar httpclient-4.2.5.jar commons-logging-1.1.1.jar commons-codec-1.6.jar gson-2.2.4.jar" Specification-Vendor: "Rally Software, Inc." Name: "com/rallydev/rest/" -Specification-Title: "Rally Rest API for Java" -Specification-Version: "2.0.3" +Specification-Title: "Rally Rest API for Java" +Specification-Version: "2.1.0" diff --git a/src/main/java/com/rallydev/rest/RallyRestApi.java b/src/main/java/com/rallydev/rest/RallyRestApi.java index 13b02ba..fd311a9 100644 --- a/src/main/java/com/rallydev/rest/RallyRestApi.java +++ b/src/main/java/com/rallydev/rest/RallyRestApi.java @@ -1,8 +1,9 @@ package com.rallydev.rest; import com.google.gson.JsonArray; -import com.google.gson.JsonObject; -import com.google.gson.JsonPrimitive; +import com.rallydev.rest.client.ApiKeyClient; +import com.rallydev.rest.client.BasicAuthClient; +import com.rallydev.rest.client.HttpClient; import com.rallydev.rest.request.CreateRequest; import com.rallydev.rest.request.DeleteRequest; import com.rallydev.rest.request.GetRequest; @@ -13,31 +14,10 @@ import com.rallydev.rest.response.GetResponse; import com.rallydev.rest.response.QueryResponse; import com.rallydev.rest.response.UpdateResponse; -import com.rallydev.rest.util.InvalidURLException; -import org.apache.http.HttpEntity; -import org.apache.http.HttpHost; -import org.apache.http.HttpResponse; -import org.apache.http.auth.AuthScope; -import org.apache.http.auth.UsernamePasswordCredentials; -import org.apache.http.client.methods.HttpDelete; -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.client.utils.URIBuilder; -import org.apache.http.conn.BasicManagedEntity; -import org.apache.http.conn.params.ConnRoutePNames; -import org.apache.http.entity.StringEntity; -import org.apache.http.impl.auth.BasicScheme; -import org.apache.http.impl.client.DefaultHttpClient; -import org.apache.http.util.EntityUtils; import java.io.Closeable; import java.io.IOException; import java.net.URI; -import java.net.URISyntaxException; -import java.util.HashMap; -import java.util.Map; /** *

The main interface to the Rest API.

@@ -45,42 +25,7 @@ */ public class RallyRestApi implements Closeable { - private enum Header { - Library, - Name, - Vendor, - Version - } - - /** - * The default version of the WSAPI to target. - */ - public static final String DEFAULT_WSAPI_VERSION = "v2.0"; - public static final String CREATE_RESULT_KEY = "CreateResult"; - public static final String OPERATION_RESULT_KEY = "OperationResult"; - public static final String QUERY_RESULT_KEY = "QueryResult"; - - protected static final String SECURITY_ENDPOINT_DOES_NOT_EXIST = "SECURITY_ENDPOINT_DOES_NOT_EXIST"; - protected static final String SECURITY_TOKEN_PARAM_KEY = "key"; - private static final String SECURITY_TOKEN_URL = "/security/authorize"; - protected static final String SECURITY_TOKEN_KEY = "SecurityToken"; - - protected URI server; - protected URI proxy; - - protected String wsapiVersion = DEFAULT_WSAPI_VERSION; - protected DefaultHttpClient httpClient; - private UsernamePasswordCredentials usernamePasswordCredentials; - private String securityToken; - - protected Map headers = new HashMap() { - { - put(Header.Library, "Rally Rest API for Java v2.0.3"); - put(Header.Name, "Rally Rest API for Java"); - put(Header.Vendor, "Rally Software, Inc."); - put(Header.Version, "2.0.3"); - } - }; + protected HttpClient client; /** * Creates a new instance for the specified server using the specified credentials. @@ -88,11 +33,24 @@ private enum Header { * @param server The server to connect to, e.g. {@code new URI("https://rally1.rallydev.com")} * @param userName The username to be used for authentication. * @param password The password to be used for authentication. + * @deprecated Use the api key constructor instead. */ public RallyRestApi(URI server, String userName, String password) { - this.server = server; - httpClient = new DefaultHttpClient(); - setClientCredentials(server, userName, password); + this(new BasicAuthClient(server, userName, password)); + } + + /** + * Creates a new instance for the specified server using the specified API Key. + * + * @param server The server to connect to, e.g. {@code new URI("https://rally1.rallydev.com")} + * @param apiKey The API Key to be used for authentication. + */ + public RallyRestApi(URI server, String apiKey) { + this(new ApiKeyClient(server, apiKey)); + } + + protected RallyRestApi(HttpClient httpClient) { + this.client = httpClient; } /** @@ -101,8 +59,7 @@ public RallyRestApi(URI server, String userName, String password) { * @param proxy The proxy server, e.g. {@code new URI("http://my.proxy.com:8000")} */ public void setProxy(URI proxy) { - this.proxy = proxy; - httpClient.getParams().setParameter(ConnRoutePNames.DEFAULT_PROXY, new HttpHost(proxy.getHost(), proxy.getPort(), proxy.getScheme())); + client.setProxy(proxy); } /** @@ -113,15 +70,7 @@ public void setProxy(URI proxy) { * @param password The password to be used for authentication. */ public void setProxy(URI proxy, String userName, String password) { - setProxy(proxy); - setClientCredentials(proxy, userName, password); - } - - private void setClientCredentials(URI server, String userName, String password) { - usernamePasswordCredentials = new UsernamePasswordCredentials(userName, password); - httpClient.getCredentialsProvider().setCredentials( - new AuthScope(server.getHost(), server.getPort()), - usernamePasswordCredentials); + client.setProxy(proxy, userName, password); } /** @@ -131,7 +80,7 @@ private void setClientCredentials(URI server, String userName, String password) * @param value The vendor header to be included on all requests. */ public void setApplicationVendor(String value) { - headers.put(Header.Vendor, value); + client.setApplicationVendor(value); } /** @@ -141,7 +90,7 @@ public void setApplicationVendor(String value) { * @param value The vendor header to be included on all requests. */ public void setApplicationVersion(String value) { - headers.put(Header.Version, value); + client.setApplicationVersion(value); } /** @@ -151,7 +100,7 @@ public void setApplicationVersion(String value) { * @param value The vendor header to be included on all requests. */ public void setApplicationName(String value) { - headers.put(Header.Name, value); + client.setApplicationName(value); } /** @@ -162,16 +111,7 @@ public void setApplicationName(String value) { * @throws IOException if an error occurs during the creation. */ public CreateResponse create(CreateRequest request) throws IOException { - return create(request, true); - } - - private CreateResponse create(CreateRequest request, boolean retryOnFail) throws IOException { - CreateResponse createResponse = new CreateResponse(doPost(buildWsapiUrl() + request.toUrl(), request.getBody())); - if (retryOnFail && createResponse.getErrors().length != 0) { - setSecurityToken(null); - return create(request, false); - } - return createResponse; + return new CreateResponse(client.doPost(request.toUrl(), request.getBody())); } /** @@ -182,16 +122,7 @@ private CreateResponse create(CreateRequest request, boolean retryOnFail) throws * @throws IOException if an error occurs during the update. */ public UpdateResponse update(UpdateRequest request) throws IOException { - return update(request, true); - } - - private UpdateResponse update(UpdateRequest request, boolean retryOnFail) throws IOException { - UpdateResponse updateResponse = new UpdateResponse(doPost(buildWsapiUrl() + request.toUrl(), request.getBody())); - if (retryOnFail && updateResponse.getErrors().length != 0) { - setSecurityToken(null); - return update(request, false); - } - return updateResponse; + return new UpdateResponse(client.doPost(request.toUrl(), request.getBody())); } /** @@ -202,16 +133,7 @@ private UpdateResponse update(UpdateRequest request, boolean retryOnFail) throws * @throws IOException if an error occurs during the deletion. */ public DeleteResponse delete(DeleteRequest request) throws IOException { - return delete(request, true); - } - - private DeleteResponse delete(DeleteRequest request, boolean retryOnFail) throws IOException { - DeleteResponse deleteResponse = new DeleteResponse(doDelete(buildWsapiUrl() + request.toUrl())); - if (retryOnFail && deleteResponse.getErrors().length != 0) { - setSecurityToken(null); - return delete(request, false); - } - return deleteResponse; + return new DeleteResponse(client.doDelete(request.toUrl())); } /** @@ -224,14 +146,14 @@ private DeleteResponse delete(DeleteRequest request, boolean retryOnFail) throws * @throws IOException if an error occurs during the query. */ public QueryResponse query(QueryRequest request) throws IOException { - QueryResponse queryResponse = new QueryResponse(doGet(buildWsapiUrl() + request.toUrl(), false)); + QueryResponse queryResponse = new QueryResponse(client.doGet(request.toUrl())); if (queryResponse.wasSuccessful()) { int receivedRecords = request.getPageSize(); while (receivedRecords < request.getLimit() && (receivedRecords + request.getStart() - 1) < queryResponse.getTotalResultCount()) { QueryRequest pageRequest = request.clone(); pageRequest.setStart(receivedRecords + request.getStart()); - QueryResponse pageResponse = new QueryResponse(doGet(buildWsapiUrl() + pageRequest.toUrl(), false)); + QueryResponse pageResponse = new QueryResponse(client.doGet(pageRequest.toUrl())); if (pageResponse.wasSuccessful()) { JsonArray results = queryResponse.getResults(); results.addAll(pageResponse.getResults()); @@ -251,19 +173,7 @@ public QueryResponse query(QueryRequest request) throws IOException { * @throws IOException if an error occurs during the retrieval. */ public GetResponse get(GetRequest request) throws IOException { - return get(request, false); - } - - /** - * Get the specified object. Forces basic auth as part of the request if specified - * - * @param request the {@link GetRequest} specifying the object to be retrieved. - * @param forceReauth boolean indicating if basic auth should be conducted as part of the request - * @return the resulting {@link GetResponse} - * @throws IOException if an error occurs during the retrieval. - */ - protected GetResponse get(GetRequest request, boolean forceReauth) throws IOException { - return new GetResponse(doGet(buildWsapiUrl() + request.toUrl(), forceReauth)); + return new GetResponse(client.doGet(request.toUrl())); } /** @@ -272,200 +182,17 @@ protected GetResponse get(GetRequest request, boolean forceReauth) throws IOExce * @throws IOException if an error occurs releasing resources */ public void close() throws IOException { - httpClient.getConnectionManager().shutdown(); - } - - /** - * Get the current version of the WSAPI being targeted. - * Defaults to {@link RallyRestApi#DEFAULT_WSAPI_VERSION} - * - * @return the current WSAPI version. - */ - public String getWsapiVersion() { - return wsapiVersion; - } - - /** - * Set the current version of the WSAPI being targeted. - * - * @param wsapiVersion the new version, e.g. {@code "1.30"} - */ - public void setWsapiVersion(String wsapiVersion) { - this.wsapiVersion = wsapiVersion; - } - - /** - * Execute a request against the WSAPI - * - * @param request the request to be executed - * @return the JSON encoded string response - * @throws IOException if a non-200 response code is returned or if some other - * problem occurs while executing the request - */ - protected String doRequest(HttpRequestBase request) throws IOException { - for (Map.Entry header : headers.entrySet()) { - request.setHeader("X-RallyIntegration" + header.getKey().name(), header.getValue()); - } - - HttpResponse response = httpClient.execute(request); - HttpEntity entity = response.getEntity(); - if (response.getStatusLine().getStatusCode() == 200) { - return EntityUtils.toString(entity, "utf-8"); - } else if (response.getStatusLine().getStatusCode() == 500) { - ((BasicManagedEntity) entity).releaseConnection(); - throw new InvalidURLException(request.getURI().toString() + " is an Invalid URL"); - } else { - throw new IOException(response.getStatusLine().toString()); - } - } - - /** - * Execute a request against the WSAPI with a basic auth header to force reauthorization - * - * @param request the request to be executed - * @return the JSON encoded string response - * @throws IOException if a non-200 response code is returned or if some other - * problem occurs while executing the request - */ - protected String doForceReauthRequest(HttpRequestBase request) throws IOException { - request.addHeader(BasicScheme.authenticate(usernamePasswordCredentials, "UTF-8", false)); - return doRequest(request); - } - - /** - * Execute a request against WSAPI that requires a security token due to data change/creation: - * 1. Post - * 2. Put - * 3. Delete - * - * @param request the request to be executed - * @return the JSON encoded string response - * @throws IOException if a non-200 response code is returned or if some other - * problem occurs while executing the request - */ - private String doSecuredRequest(HttpRequestBase request) throws IOException { - if (!request.getMethod().equals(HttpGet.METHOD_NAME) && - !this.getWsapiVersion().matches("^1[.]\\d+")) { - try { - attachSecurityInfo(request); - } catch (URISyntaxException e) { - throw new IOException("Unable to build URI with security token", e); - } - } - - return doRequest(request); - } - - /** - * Attach the security token parameter to the request. - *

- * Response Structure: - * {"OperationResult": {"SecurityToken": "UUID"}} - * - * @param request the request to be modified - * @throws IOException if a non-200 response code is returned or if some other - * problem occurs while executing the request - */ - protected void attachSecurityInfo(HttpRequestBase request) throws IOException, URISyntaxException { - if (!SECURITY_ENDPOINT_DOES_NOT_EXIST.equals(securityToken)) { - try { - if (securityToken == null) { - GetResponse getResponse = get(new GetRequest(SECURITY_TOKEN_URL), true); - JsonObject operationResult = getResponse.getObject(); - JsonPrimitive securityTokenPrimitive = operationResult.getAsJsonPrimitive(SECURITY_TOKEN_KEY); - securityToken = securityTokenPrimitive.getAsString(); - } - request.setURI(new URIBuilder(request.getURI()).addParameter(SECURITY_TOKEN_PARAM_KEY, securityToken).build()); - } catch (InvalidURLException e) { - //swallow the exception in this case as url does not exist indicates running and old version of - //ALM without the security endpoint - securityToken = SECURITY_ENDPOINT_DOES_NOT_EXIST; - } - } - } - - /** - * Sets the value on the security token for security enabled requests - * - * @param securityToken value fo the security token - */ - protected void setSecurityToken(String securityToken) { - this.securityToken = securityToken; - } - - /** - * Returns the current value of the security token. - * - * @return string value of security token - */ - protected String getSecurityToken() { - return securityToken; - } - - /** - * Perform a post against the WSAPI - * - * @param url the request url - * @param body the body of the post - * @return the JSON encoded string response - * @throws IOException if a non-200 response code is returned or if some other - * problem occurs while executing the request - */ - protected String doPost(String url, String body) throws IOException { - HttpPost httpPost = new HttpPost(url); - httpPost.setEntity(new StringEntity(body, "utf-8")); - return doSecuredRequest(httpPost); - } - - - /** - * Perform a put against the WSAPI - * - * @param url the request url - * @param body the body of the put - * @return the JSON encoded string response - * @throws IOException if a non-200 response code is returned or if some other - * problem occurs while executing the request - */ - protected String doPut(String url, String body) throws IOException { - HttpPut httpPut = new HttpPut(url); - httpPut.setEntity(new StringEntity(body, "utf-8")); - return doSecuredRequest(httpPut); - } - - /** - * Perform a delete against the WSAPI - * - * @param url the request url - * @return the JSON encoded string response - * @throws IOException if a non-200 response code is returned or if some other - * problem occurs while executing the request - */ - protected String doDelete(String url) throws IOException { - HttpDelete httpDelete = new HttpDelete(url); - return doSecuredRequest(httpDelete); - } - - /** - * Perform a get against the WSAPI - * - * @param url the request url - * @param forceReauth - * @return the JSON encoded string response - * @throws IOException if a non-200 response code is returned or if some other - * problem occurs while executing the request - */ - protected String doGet(String url, boolean forceReauth) throws IOException { - HttpGet httpGet = new HttpGet(url); - return forceReauth ? doForceReauthRequest(httpGet) : doRequest(httpGet); + client.close(); } /** - * Get the WSAPI base url based on the current server and WSAPI version + * Get the underlying http client implementation. + * This is exposed with the intent of providing the ability to supply additional configuration to the client. + * It should not be used to directly make i/o calls. * - * @return the fully qualified WSAPI base url, e.g. https://rally1.rallydev.com/slm/webservice/1.33 + * @return the raw http client */ - protected String buildWsapiUrl() { - return server.toString() + "/slm/webservice/" + wsapiVersion; + public HttpClient getClient() { + return client; } } \ No newline at end of file diff --git a/src/main/java/com/rallydev/rest/client/ApiKeyClient.java b/src/main/java/com/rallydev/rest/client/ApiKeyClient.java new file mode 100644 index 0000000..615047f --- /dev/null +++ b/src/main/java/com/rallydev/rest/client/ApiKeyClient.java @@ -0,0 +1,31 @@ +package com.rallydev.rest.client; + +import org.apache.http.client.methods.HttpRequestBase; + +import java.io.IOException; +import java.net.URI; + +public class ApiKeyClient extends HttpClient { + + protected String apiKey; + protected static final String API_KEY_HEADER = "zsessionid"; + + public ApiKeyClient(URI server, String apiKey) { + super(server); + this.apiKey = apiKey; + } + + /** + * Execute a request against the WSAPI + * + * @param request the request to be executed + * @return the JSON encoded string response + * @throws java.io.IOException if a non-200 response code is returned or if some other + * problem occurs while executing the request + */ + @Override + protected String doRequest(HttpRequestBase request) throws IOException { + request.setHeader(API_KEY_HEADER, this.apiKey); + return super.doRequest(request); + } +} \ No newline at end of file diff --git a/src/main/java/com/rallydev/rest/client/BasicAuthClient.java b/src/main/java/com/rallydev/rest/client/BasicAuthClient.java new file mode 100644 index 0000000..4c20252 --- /dev/null +++ b/src/main/java/com/rallydev/rest/client/BasicAuthClient.java @@ -0,0 +1,80 @@ +package com.rallydev.rest.client; + +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; +import com.rallydev.rest.response.GetResponse; +import com.rallydev.rest.util.InvalidURLException; +import org.apache.http.auth.Credentials; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpRequestBase; +import org.apache.http.client.utils.URIBuilder; +import org.apache.http.impl.auth.BasicScheme; +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; + +public class BasicAuthClient extends HttpClient { + + protected static final String SECURITY_ENDPOINT_DOES_NOT_EXIST = "SECURITY_ENDPOINT_DOES_NOT_EXIST"; + protected static final String SECURITY_TOKEN_PARAM_KEY = "key"; + private static final String SECURITY_TOKEN_URL = "/security/authorize"; + protected static final String SECURITY_TOKEN_KEY = "SecurityToken"; + protected String securityToken; + private Credentials credentials; + + public BasicAuthClient(URI server, String userName, String password) { + super(server); + credentials = setClientCredentials(server, userName, password); + } + + /** + * Execute a request against the WSAPI + * + * @param request the request to be executed + * @return the JSON encoded string response + * @throws java.io.IOException if a non-200 response code is returned or if some other + * problem occurs while executing the request + */ + @Override + protected String doRequest(HttpRequestBase request) throws IOException { + if(!request.getMethod().equals(HttpGet.METHOD_NAME) && + !this.getWsapiVersion().matches("^1[.]\\d+")) { + try { + attachSecurityInfo(request); + } catch (URISyntaxException e) { + throw new IOException("Unable to build URI with security token", e); + } + } + return super.doRequest(request); + } + + /** + * Attach the security token parameter to the request. + *

+ * Response Structure: + * {"OperationResult": {"SecurityToken": "UUID"}} + * + * @param request the request to be modified + * @throws IOException if a non-200 response code is returned or if some other + * problem occurs while executing the request + */ + protected void attachSecurityInfo(HttpRequestBase request) throws IOException, URISyntaxException { + if (!SECURITY_ENDPOINT_DOES_NOT_EXIST.equals(securityToken)) { + try { + if (securityToken == null) { + HttpGet httpGet = new HttpGet(getWsapiUrl() + SECURITY_TOKEN_URL); + httpGet.addHeader(BasicScheme.authenticate(credentials, "utf-8", false)); + GetResponse getResponse = new GetResponse(doRequest(httpGet)); + JsonObject operationResult = getResponse.getObject(); + JsonPrimitive securityTokenPrimitive = operationResult.getAsJsonPrimitive(SECURITY_TOKEN_KEY); + securityToken = securityTokenPrimitive.getAsString(); + } + request.setURI(new URIBuilder(request.getURI()).addParameter(SECURITY_TOKEN_PARAM_KEY, securityToken).build()); + } catch (InvalidURLException e) { + //swallow the exception in this case as url does not exist indicates running and old version of + //ALM without the security endpoint + securityToken = SECURITY_ENDPOINT_DOES_NOT_EXIST; + } + } + } +} \ No newline at end of file diff --git a/src/main/java/com/rallydev/rest/client/HttpClient.java b/src/main/java/com/rallydev/rest/client/HttpClient.java new file mode 100644 index 0000000..31f3241 --- /dev/null +++ b/src/main/java/com/rallydev/rest/client/HttpClient.java @@ -0,0 +1,244 @@ +package com.rallydev.rest.client; + +import com.rallydev.rest.util.InvalidURLException; +import org.apache.http.HttpEntity; +import org.apache.http.HttpHost; +import org.apache.http.HttpResponse; +import org.apache.http.auth.AuthScope; +import org.apache.http.auth.Credentials; +import org.apache.http.auth.UsernamePasswordCredentials; +import org.apache.http.client.methods.*; +import org.apache.http.conn.BasicManagedEntity; +import org.apache.http.conn.params.ConnRoutePNames; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.DefaultHttpClient; +import org.apache.http.util.EntityUtils; + +import java.io.Closeable; +import java.io.IOException; +import java.net.URI; +import java.util.HashMap; +import java.util.Map; + +public abstract class HttpClient extends DefaultHttpClient + implements Closeable { + + protected URI server; + protected String wsapiVersion = "v2.0"; + + private enum Header { + Library, + Name, + Vendor, + Version + } + + private Map headers = new HashMap() { + { + put(Header.Library, "Rally Rest API for Java v2.1.0"); + put(Header.Name, "Rally Rest API for Java"); + put(Header.Vendor, "Rally Software, Inc."); + put(Header.Version, "2.1.0"); + } + }; + + protected HttpClient(URI server) { + this.server = server; + } + + /** + * Set the unauthenticated proxy server to use. By default no proxy is configured. + * + * @param proxy The proxy server, e.g. {@code new URI("http://my.proxy.com:8000")} + */ + public void setProxy(URI proxy) { + this.getParams().setParameter(ConnRoutePNames.DEFAULT_PROXY, new HttpHost(proxy.getHost(), proxy.getPort(), proxy.getScheme())); + } + + /** + * Set the authenticated proxy server to use. By default no proxy is configured. + * + * @param proxy The proxy server, e.g. {@code new URI("http://my.proxy.com:8000")} + * @param userName The username to be used for authentication. + * @param password The password to be used for authentication. + */ + public void setProxy(URI proxy, String userName, String password) { + setProxy(proxy); + setClientCredentials(proxy, userName, password); + } + + /** + * Set the value of the X-RallyIntegrationVendor header included on all requests. + * This should be set to your company name. + * + * @param value The vendor header to be included on all requests. + */ + public void setApplicationVendor(String value) { + headers.put(Header.Vendor, value); + } + + /** + * Set the value of the X-RallyIntegrationVersion header included on all requests. + * This should be set to the version of your application. + * + * @param value The vendor header to be included on all requests. + */ + public void setApplicationVersion(String value) { + headers.put(Header.Version, value); + } + + /** + * Set the value of the X-RallyIntegrationName header included on all requests. + * This should be set to the name of your application. + * + * @param value The vendor header to be included on all requests. + */ + public void setApplicationName(String value) { + headers.put(Header.Name, value); + } + + /** + * Get the current server being targeted. + * + * @return the current server. + */ + public String getServer() { + return server.toString(); + } + + /** + * Get the current version of the WSAPI being targeted. + * + * @return the current WSAPI version. + */ + public String getWsapiVersion() { + return wsapiVersion; + } + + /** + * Set the current version of the WSAPI being targeted. + * + * @param wsapiVersion the new version, e.g. {@code "1.30"} + */ + public void setWsapiVersion(String wsapiVersion) { + this.wsapiVersion = wsapiVersion; + } + + /** + * Execute a request against the WSAPI + * + * @param request the request to be executed + * @return the JSON encoded string response + * @throws IOException if a non-200 response code is returned or if some other + * problem occurs while executing the request + */ + protected String doRequest(HttpRequestBase request) throws IOException { + for (Map.Entry header : headers.entrySet()) { + request.setHeader("X-RallyIntegration" + header.getKey().name(), header.getValue()); + } + + return this.executeRequest(request); + } + + /** + * Execute a request against the WSAPI + * + * @param request the request to be executed + * @return the JSON encoded string response + * @throws IOException if a non-200 response code is returned or if some other + * problem occurs while executing the request + */ + protected String executeRequest(HttpRequestBase request) throws IOException { + HttpResponse response = this.execute(request); + HttpEntity entity = response.getEntity(); + if (response.getStatusLine().getStatusCode() == 200) { + return EntityUtils.toString(entity, "utf-8"); + } else if (response.getStatusLine().getStatusCode() == 500) { + ((BasicManagedEntity) entity).releaseConnection(); + throw new InvalidURLException(request.getURI().toString() + " is an Invalid URL"); + } else { + throw new IOException(response.getStatusLine().toString()); + } + } + + /** + * Perform a post against the WSAPI + * + * @param url the request url + * @param body the body of the post + * @return the JSON encoded string response + * @throws IOException if a non-200 response code is returned or if some other + * problem occurs while executing the request + */ + public String doPost(String url, String body) throws IOException { + HttpPost httpPost = new HttpPost(getWsapiUrl() + url); + httpPost.setEntity(new StringEntity(body, "utf-8")); + return doRequest(httpPost); + } + + + /** + * Perform a put against the WSAPI + * + * @param url the request url + * @param body the body of the put + * @return the JSON encoded string response + * @throws IOException if a non-200 response code is returned or if some other + * problem occurs while executing the request + */ + public String doPut(String url, String body) throws IOException { + HttpPut httpPut = new HttpPut(getWsapiUrl() + url); + httpPut.setEntity(new StringEntity(body, "utf-8")); + return doRequest(httpPut); + } + + /** + * Perform a delete against the WSAPI + * + * @param url the request url + * @return the JSON encoded string response + * @throws IOException if a non-200 response code is returned or if some other + * problem occurs while executing the request + */ + public String doDelete(String url) throws IOException { + HttpDelete httpDelete = new HttpDelete(getWsapiUrl() + url); + return doRequest(httpDelete); + } + + /** + * Perform a get against the WSAPI + * + * @param url the request url + * @return the JSON encoded string response + * @throws IOException if a non-200 response code is returned or if some other + * problem occurs while executing the request + */ + public String doGet(String url) throws IOException { + HttpGet httpGet = new HttpGet(getWsapiUrl() + url); + return doRequest(httpGet); + } + + /** + * Release all resources associated with this instance. + * + * @throws IOException if an error occurs releasing resources + */ + public void close() throws IOException { + this.getConnectionManager().shutdown(); + } + + protected Credentials setClientCredentials(URI server, String userName, String password) { + UsernamePasswordCredentials credentials = new UsernamePasswordCredentials(userName, password); + this.getCredentialsProvider().setCredentials(new AuthScope(server.getHost(), server.getPort()), credentials); + return credentials; + } + + /** + * Get the WSAPI base url based on the current server and WSAPI version + * + * @return the fully qualified WSAPI base url, e.g. https://rally1.rallydev.com/slm/webservice/1.33 + */ + public String getWsapiUrl() { + return getServer() + "/slm/webservice/" + getWsapiVersion(); + } +} diff --git a/src/main/java/com/rallydev/rest/client/package-info.java b/src/main/java/com/rallydev/rest/client/package-info.java new file mode 100644 index 0000000..2b38cbd --- /dev/null +++ b/src/main/java/com/rallydev/rest/client/package-info.java @@ -0,0 +1,4 @@ +/** + * Provides the underlying http client implementations. + */ +package com.rallydev.rest.client; \ No newline at end of file diff --git a/src/main/java/com/rallydev/rest/response/CreateResponse.java b/src/main/java/com/rallydev/rest/response/CreateResponse.java index 373797a..1a60045 100644 --- a/src/main/java/com/rallydev/rest/response/CreateResponse.java +++ b/src/main/java/com/rallydev/rest/response/CreateResponse.java @@ -4,13 +4,13 @@ import com.rallydev.rest.RallyRestApi; /** - * Represents a WSAPI response from creating an object + * Represents a WSAPI response from creating an object */ public class CreateResponse extends Response { /** * Create a new create response from the specified JSON encoded string. - * + * * @param createResponse the JSON encoded string */ public CreateResponse(String createResponse) { @@ -19,17 +19,17 @@ public CreateResponse(String createResponse) { /** * Get the name of the root JSON result - * + * * @return the root element name */ @Override protected String getRoot() { - return RallyRestApi.CREATE_RESULT_KEY; + return "CreateResult"; } /** * Get the created object. - *

Returns null if the operation was not successful

+ *

Returns null if the operation was not successful

* @return the created object */ public JsonObject getObject() { diff --git a/src/main/java/com/rallydev/rest/response/DeleteResponse.java b/src/main/java/com/rallydev/rest/response/DeleteResponse.java index a4e98c7..477efff 100644 --- a/src/main/java/com/rallydev/rest/response/DeleteResponse.java +++ b/src/main/java/com/rallydev/rest/response/DeleteResponse.java @@ -1,7 +1,5 @@ package com.rallydev.rest.response; -import static com.rallydev.rest.RallyRestApi.OPERATION_RESULT_KEY; - /** * Represents a WSAPI response from deleting an object */ @@ -23,6 +21,6 @@ public DeleteResponse(String deleteResponse) { */ @Override protected String getRoot() { - return OPERATION_RESULT_KEY; + return "OperationResult"; } } diff --git a/src/main/java/com/rallydev/rest/response/QueryResponse.java b/src/main/java/com/rallydev/rest/response/QueryResponse.java index 8dfecd3..148f92e 100644 --- a/src/main/java/com/rallydev/rest/response/QueryResponse.java +++ b/src/main/java/com/rallydev/rest/response/QueryResponse.java @@ -2,8 +2,6 @@ import com.google.gson.JsonArray; -import static com.rallydev.rest.RallyRestApi.QUERY_RESULT_KEY; - /** * Represents a WSAPI response from querying for objects. */ @@ -25,12 +23,12 @@ public QueryResponse(String queryResponse) { */ @Override protected String getRoot() { - return QUERY_RESULT_KEY; + return "QueryResult"; } /** * Get the total number of objects that matched the query - * + * * @return the total number of objects */ public int getTotalResultCount() { @@ -40,7 +38,7 @@ public int getTotalResultCount() { /** * Get the results of the query *

Depending on the limit of the original request this may include one or more pages.

- * + * * @return the results */ public JsonArray getResults() { @@ -49,7 +47,7 @@ public JsonArray getResults() { /** * Get the page size of the results - * + * * @return the page size */ public int getPageSize() { @@ -58,7 +56,7 @@ public int getPageSize() { /** * Get the start index of the results - * + * * @return the start index */ public int getStart() { diff --git a/src/main/java/com/rallydev/rest/response/UpdateResponse.java b/src/main/java/com/rallydev/rest/response/UpdateResponse.java index df4043e..0e12292 100644 --- a/src/main/java/com/rallydev/rest/response/UpdateResponse.java +++ b/src/main/java/com/rallydev/rest/response/UpdateResponse.java @@ -2,8 +2,6 @@ import com.google.gson.JsonObject; -import static com.rallydev.rest.RallyRestApi.OPERATION_RESULT_KEY; - /** * Represents a WSAPI response from updating an object. */ @@ -25,12 +23,12 @@ public UpdateResponse(String updateResponse) { */ @Override protected String getRoot() { - return OPERATION_RESULT_KEY; + return "OperationResult"; } /** * Get the updated object. - *

Returns null if the operation was not successful

+ *

Returns null if the operation was not successful

* @return the updated object */ public JsonObject getObject() { diff --git a/src/main/resources/doc/allclasses-frame.html b/src/main/resources/doc/allclasses-frame.html index 95c9880..c533fd9 100644 --- a/src/main/resources/doc/allclasses-frame.html +++ b/src/main/resources/doc/allclasses-frame.html @@ -2,9 +2,9 @@ - + All Classes (Rally Rest API for Java 2.0) - + diff --git a/src/main/resources/doc/allclasses-noframe.html b/src/main/resources/doc/allclasses-noframe.html index 28857f4..a8998b0 100644 --- a/src/main/resources/doc/allclasses-noframe.html +++ b/src/main/resources/doc/allclasses-noframe.html @@ -2,9 +2,9 @@ - + All Classes (Rally Rest API for Java 2.0) - + diff --git a/src/main/resources/doc/com/rallydev/rest/RallyRestApi.html b/src/main/resources/doc/com/rallydev/rest/RallyRestApi.html index c354c4f..6972313 100644 --- a/src/main/resources/doc/com/rallydev/rest/RallyRestApi.html +++ b/src/main/resources/doc/com/rallydev/rest/RallyRestApi.html @@ -2,9 +2,9 @@ - + RallyRestApi (Rally Rest API for Java 2.0) - + @@ -33,6 +33,7 @@
  • Index
  • Help
  • +
    Rally Rest API for Java 2.0