diff --git a/src/main/java/org/rundeck/api/ApiCall.java b/src/main/java/org/rundeck/api/ApiCall.java index 93993ad..f3e7596 100644 --- a/src/main/java/org/rundeck/api/ApiCall.java +++ b/src/main/java/org/rundeck/api/ApiCall.java @@ -15,6 +15,7 @@ */ package org.rundeck.api; +import org.apache.commons.io.IOUtils; import org.apache.commons.lang.StringUtils; import org.apache.http.*; import org.apache.http.client.HttpClient; @@ -42,10 +43,7 @@ import org.rundeck.api.parser.XmlNodeParser; import org.rundeck.api.util.AssertUtil; import org.rundeck.api.util.DocumentContentProducer; -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.UnsupportedEncodingException; +import java.io.*; import java.net.ProxySelector; import java.security.KeyManagementException; import java.security.KeyStoreException; @@ -356,13 +354,35 @@ class ApiCall { private T execute(HttpRequestBase request, XmlNodeParser parser) throws RundeckApiException, RundeckApiLoginException, RundeckApiTokenException { // execute the request - InputStream response = execute(request); - - // read and parse the response - Document xmlDocument = ParserHelper.loadDocument(response); - return parser.parseXmlNode(xmlDocument); + return new ParserHandler(parser).handle(execute(request, new ResultHandler())); } + /** + * Execute an HTTP GET request to the RunDeck instance, on the given path. We will login first, and then execute the + * API call. At the end, the given parser will be used to convert the response to a more useful result object. + * + * @param apiPath on which we will make the HTTP request - see {@link ApiPathBuilder} + * @param parser used to parse the response + * + * @return the result of the call, as formatted by the parser + * + * @throws RundeckApiException in case of error when calling the API + * @throws RundeckApiLoginException if the login fails (in case of login-based authentication) + * @throws RundeckApiTokenException if the token is invalid (in case of token-based authentication) + */ + public int get(ApiPathBuilder apiPath, OutputStream outputStream) throws RundeckApiException, + RundeckApiLoginException, RundeckApiTokenException, IOException { + HttpGet request = new HttpGet(client.getUrl() + client.getApiEndpoint() + apiPath); + if (null != apiPath.getAccept()) { + request.setHeader("Accept", apiPath.getAccept()); + } + WriteOutHandler handler = new WriteOutHandler(outputStream); + int wrote = execute(request, handler); + if(handler.thrown!=null){ + throw handler.thrown; + } + return wrote; + } /** * Execute an HTTP request to the RunDeck instance. We will login first, and then execute the API call. * @@ -374,6 +394,83 @@ class ApiCall { */ private ByteArrayInputStream execute(HttpUriRequest request) throws RundeckApiException, RundeckApiLoginException, RundeckApiTokenException { + return execute(request, new ResultHandler() ); + } + + /** + * Handles one type into another + * @param + * @param + */ + private static interface Handler{ + public V handle(T response); + } + + /** + * Handles parsing inputstream via a parser + * @param + */ + private static class ParserHandler implements Handler { + XmlNodeParser parser; + + private ParserHandler(XmlNodeParser parser) { + this.parser = parser; + } + + @Override + public S handle(InputStream response) { + // read and parse the response + return parser.parseXmlNode(ParserHelper.loadDocument(response)); + } + } + + /** + * Handles writing response to an output stream + */ + private static class WriteOutHandler implements Handler { + private WriteOutHandler(OutputStream writeOut) { + this.writeOut = writeOut; + } + + OutputStream writeOut; + IOException thrown; + @Override + public Integer handle(final HttpResponse response) { + try { + return IOUtils.copy(response.getEntity().getContent(), writeOut); + } catch (IOException e) { + thrown=e; + } + return -1; + } + } + + /** + * Handles reading response into a byte array stream + */ + private static class ResultHandler implements Handler { + @Override + public ByteArrayInputStream handle(final HttpResponse response) { + // return a new inputStream, so that we can close all network resources + try { + return new ByteArrayInputStream(EntityUtils.toByteArray(response.getEntity())); + } catch (IOException e) { + throw new RundeckApiException("Failed to consume entity and convert the inputStream", e); + } + } + } + /** + * Execute an HTTP request to the RunDeck instance. We will login first, and then execute the API call. + * + * @param request to execute. see {@link HttpGet}, {@link HttpDelete}, and so on... + * @return a new {@link InputStream} instance, not linked with network resources + * @throws RundeckApiException in case of error when calling the API + * @throws RundeckApiLoginException if the login fails (in case of login-based authentication) + * @throws RundeckApiTokenException if the token is invalid (in case of token-based authentication) + */ + private T execute(HttpUriRequest request, Handler handler) throws RundeckApiException, + RundeckApiLoginException, + RundeckApiTokenException { HttpClient httpClient = instantiateHttpClient(); try { // we only need to manually login in case of login-based authentication @@ -428,13 +525,7 @@ class ApiCall { throw new RundeckApiException("Empty RunDeck response ! HTTP status line is : " + response.getStatusLine()); } - - // return a new inputStream, so that we can close all network resources - try { - return new ByteArrayInputStream(EntityUtils.toByteArray(response.getEntity())); - } catch (IOException e) { - throw new RundeckApiException("Failed to consume entity and convert the inputStream", e); - } + return handler.handle(response); } finally { httpClient.getConnectionManager().shutdown(); }