diff --git a/pom.xml b/pom.xml index 3638e84..3bd8b3d 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ com.ciscospark ciscospark-client - 1.0-SNAPSHOT + 1.0-elibra.0 diff --git a/src/main/java/com/ciscospark/Client.java b/src/main/java/com/ciscospark/Client.java index 69f6ca2..accd6ec 100644 --- a/src/main/java/com/ciscospark/Client.java +++ b/src/main/java/com/ciscospark/Client.java @@ -12,6 +12,8 @@ import java.lang.reflect.Type; import java.math.BigDecimal; import java.net.*; +import java.nio.file.Path; +import java.nio.file.Paths; import java.security.SecureRandom; import javax.net.ssl.HttpsURLConnection; @@ -85,6 +87,15 @@ Iterator list(Class clazz, URL url) { return new PagingIterator(clazz, url); } + T head(Class clazz, String path, List params){ + return readHeaders(clazz, requestHeaders("HEAD", path, params, null)); + } + + T head(Class clazz, URL url){ + return readHeaders(clazz, request(url, "HEAD", null).connection.getHeaderFields()); + } + + void delete(String path) { delete(getUrl(path, null)); } @@ -135,6 +146,14 @@ InputStream request(String method, String path, List params, T bod URL url = getUrl(path, params); return request(url, method, body).inputStream; } + + Map> requestHeaders(String method, String path, List params, T body) { + URL url = getUrl(path, params); + return request(url, method, body).connection.getHeaderFields(); + } + + + static class Response { HttpsURLConnection connection; InputStream inputStream; @@ -167,7 +186,7 @@ private boolean authenticate() { if (clientId != null && clientSecret != null) { if (authCode != null && redirectUri != null) { log(Level.FINE, "Requesting access token"); - URL url = getUrl("/access_token",null); + URL url = getUrl("/access_token", null); AccessTokenRequest body = new AccessTokenRequest(); body.setGrant_type("authorization_code"); body.setClient_id(clientId); @@ -182,7 +201,7 @@ private boolean authenticate() { return true; } else if (refreshToken != null) { log(Level.FINE, "Refreshing access token"); - URL url = getUrl("/access_token",null); + URL url = getUrl("/access_token", null); AccessTokenRequest body = new AccessTokenRequest(); body.setClient_id(clientId); body.setClient_secret(clientSecret); @@ -202,15 +221,18 @@ private void log(Level level, String msg, Object... args) { logger.log(level, msg, args); } } - private Response doRequest(URL url, String method, T body) { + return doRequest(url, method, body, 0); + } + + private Response doRequest(URL url, String method, T body, int retryNumber) { try { HttpsURLConnection connection = getConnection(url); String trackingId = connection.getRequestProperty(TRACKING_ID); connection.setRequestMethod(method); if (logger != null && logger.isLoggable(Level.FINE)) { logger.log(Level.FINE, "Request {0}: {1} {2}", - new Object[] { trackingId, method, connection.getURL().toString() }); + new Object[]{trackingId, method, connection.getURL().toString()}); } if (body != null) { connection.setDoOutput(true); @@ -218,7 +240,7 @@ private Response doRequest(URL url, String method, T body) { ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); writeJson(body, byteArrayOutputStream); logger.log(Level.FINEST, "Request Body {0}: {1}", - new Object[] { trackingId, byteArrayOutputStream.toString() }); + new Object[]{trackingId, byteArrayOutputStream.toString()}); byteArrayOutputStream.writeTo(connection.getOutputStream()); } else { writeJson(body, connection.getOutputStream()); @@ -228,9 +250,23 @@ private Response doRequest(URL url, String method, T body) { int responseCode = connection.getResponseCode(); if (logger != null && logger.isLoggable(Level.FINE)) { logger.log(Level.FINE, "Response {0}: {1} {2}", - new Object[] { trackingId, responseCode, connection.getResponseMessage() }); + new Object[]{trackingId, responseCode, connection.getResponseMessage()}); + } + if (responseCode == 429) { + if(retryNumber>5) + throw new RuntimeException("Too many retry after HTTP response 429"); + + // retry based of Retry-After response header + final String retryAfter = connection.getHeaderField("Retry-After"); + try { + Thread.sleep(Long.parseLong(retryAfter)); + return doRequest(url, method, body, retryNumber+1); + } catch (InterruptedException e) { + log(Level.SEVERE, e.getMessage()); + } + } else { + checkForErrorResponse(connection, responseCode); } - checkForErrorResponse(connection, responseCode); if (logger != null && logger.isLoggable(Level.FINEST)) { InputStream inputStream = logResponse(trackingId, connection.getInputStream()); @@ -337,6 +373,25 @@ private HttpsURLConnection getConnection(URL url) throws IOException { } + private static T readHeaders(Class clazz, Map> headerFields){ + try{ + T result = clazz.newInstance(); + for (Field field : clazz.getDeclaredFields()){ + field.setAccessible(true); + Object fieldObject = field.get(result); + if (fieldObject instanceof HeaderField && + headerFields.containsKey(((HeaderField) fieldObject).getHeaderName())){ + ((HeaderField) fieldObject).setHeaderValue(headerFields.get(((HeaderField) fieldObject).getHeaderName())); + } + } + return result; + } catch (Exception ex) { + throw new SparkException(ex); + } + } + + + private static T readJson(Class clazz, InputStream inputStream) { JsonParser parser = Json.createParser(inputStream); parser.next(); @@ -349,7 +404,8 @@ private static T readObject(Class clazz, JsonParser parser) { List list = null; Field field = null; String key = ""; - PARSER_LOOP: while (parser.hasNext()) { + PARSER_LOOP: + while (parser.hasNext()) { JsonParser.Event event = parser.next(); switch (event) { case KEY_NAME: @@ -413,7 +469,7 @@ private static T readObject(Class clazz, JsonParser parser) { Object next = iterator.next(); iterator.set(URI.create(next.toString())); } - } else if (field.getType().getComponentType() != null /* this is an array class */ ) { + } else if (field.getType().getComponentType() != null /* this is an array class */) { itemClazz = field.getType().getComponentType(); // this would also cover the String array we had previously } else { throw new SparkException("bad field class: " + field.getType()); @@ -429,7 +485,7 @@ private static T readObject(Class clazz, JsonParser parser) { // the field type points us in the direction of the class to instantiate - + if (null != field) { if (null != list) { // we are in a list - we likely have a s at the end, which we should drop @@ -600,12 +656,11 @@ public void remove() { } - private void scrollToItemsArray(JsonParser parser) { JsonParser.Event event; while (parser.hasNext()) { event = parser.next(); - if (event == JsonParser.Event.KEY_NAME && parser.getString().equals("items")) { + if (event == JsonParser.Event.KEY_NAME && parser.getString().equals("items")) { break; } } @@ -617,7 +672,6 @@ private void scrollToItemsArray(JsonParser parser) { } - private static final Pattern linkPattern = Pattern.compile("\\s*<(\\S+)>\\s*;\\s*rel=\"(\\S+)\",?"); private HttpsURLConnection getLink(HttpsURLConnection connection, String rel) throws IOException { @@ -640,4 +694,70 @@ private HttpsURLConnection parseLinkHeader(String link, String desiredRel) throw } return result; } + + public File getFile(URL url) { + File file = this.doRequest4File(url); + return file; + } + + private File doRequest4File(URL url) { + try { + HttpURLConnection connection = this.getConnection(url); + String trackingId = connection.getRequestProperty("TrackingID"); + connection.setRequestMethod("GET"); + if (this.logger != null && this.logger.isLoggable(Level.FINE)) { + this.logger.log(Level.FINE, "Request {0}: {1} {2}", new Object[]{trackingId, "GET", connection.getURL().toString()}); + } + + int responseCode = connection.getResponseCode(); + if (this.logger != null && this.logger.isLoggable(Level.FINE)) { + this.logger.log(Level.FINE, "Response {0}: {1} {2}", new Object[]{trackingId, responseCode, connection.getResponseMessage()}); + } + + if (responseCode != 200) { + this.logger.info("No file to download. Server replied HTTP code: " + responseCode); + connection.disconnect(); + return null; + } else { + String fileName = ""; + String disposition = connection.getHeaderField("Content-Disposition"); + String contentType = connection.getContentType(); + int contentLength = connection.getContentLength(); + if (disposition != null) { + int index = disposition.indexOf("filename="); + if (index > 0) { + fileName = disposition.substring(index + 10, disposition.length() - 1); + } + } else { + String fileURL = url.toString(); + fileName = fileURL.substring(fileURL.lastIndexOf("/") + 1); + } + + if (this.logger != null && this.logger.isLoggable(Level.FINE)) { + this.logger.info("Content-Type = " + contentType); + this.logger.info("Content-Disposition = " + disposition); + this.logger.info("Content-Length = " + contentLength); + this.logger.info("fileName = " + fileName); + } + + InputStream inputStream = connection.getInputStream(); + String tempDirectory = System.getProperty("java.io.tmpdir"); + Path saveFilePath = Paths.get(tempDirectory, fileName); + File file = saveFilePath.toFile(); + FileOutputStream outputStream = new FileOutputStream(file); + byte[] buffer = new byte[512]; + + int bytesRead; + while((bytesRead = inputStream.read(buffer)) != -1) { + outputStream.write(buffer, 0, bytesRead); + } + + outputStream.close(); + inputStream.close(); + return file; + } + } catch (IOException var16) { + throw new SparkException("io error", var16); + } + } } diff --git a/src/main/java/com/ciscospark/Content.java b/src/main/java/com/ciscospark/Content.java new file mode 100644 index 0000000..71ee395 --- /dev/null +++ b/src/main/java/com/ciscospark/Content.java @@ -0,0 +1,29 @@ +package com.ciscospark; + + +import java.util.List; + +public class Content { + + private HeaderField cacheControl = new HeaderField("Cache-Control"); + private HeaderField contentDisposition = new HeaderField("Content-Disposition"); + private HeaderField contentLength = new HeaderField("Content-Length"); + private HeaderField contentType = new HeaderField("Content-Type"); + + public List getCacheControl() { + return cacheControl.getHeaderValue(); + } + + public List getContentDisposition() { + return contentDisposition.getHeaderValue(); + } + + public List getContentLength() { + return contentLength.getHeaderValue(); + } + + public List getContentType() { + return contentType.getHeaderValue(); + } + +} diff --git a/src/main/java/com/ciscospark/HeaderField.java b/src/main/java/com/ciscospark/HeaderField.java new file mode 100644 index 0000000..3be9447 --- /dev/null +++ b/src/main/java/com/ciscospark/HeaderField.java @@ -0,0 +1,25 @@ +package com.ciscospark; + +import java.util.List; + +public class HeaderField { + + private String headerName; + private List headerValue; + + public HeaderField(String headerName) { + this.headerName = headerName; + } + + public String getHeaderName() { + return headerName; + } + + public List getHeaderValue() { + return headerValue; + } + + public void setHeaderValue(List headerValue) { + this.headerValue = headerValue; + } +} diff --git a/src/main/java/com/ciscospark/RequestBuilder.java b/src/main/java/com/ciscospark/RequestBuilder.java index fe9feb8..22b1e11 100644 --- a/src/main/java/com/ciscospark/RequestBuilder.java +++ b/src/main/java/com/ciscospark/RequestBuilder.java @@ -1,5 +1,6 @@ package com.ciscospark; +import java.io.File; import java.net.URL; import java.util.Iterator; import java.util.List; @@ -15,7 +16,12 @@ public interface RequestBuilder { T post(T body); T put(T body); T get(); + + T head(); + Iterator iterate(); LinkedResponse> paginate(); void delete(); + + File getFile(); } diff --git a/src/main/java/com/ciscospark/RequestBuilderImpl.java b/src/main/java/com/ciscospark/RequestBuilderImpl.java index bdd2d1d..49ba9dc 100644 --- a/src/main/java/com/ciscospark/RequestBuilderImpl.java +++ b/src/main/java/com/ciscospark/RequestBuilderImpl.java @@ -1,5 +1,7 @@ package com.ciscospark; +import java.io.File; +import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; import java.util.Iterator; @@ -79,6 +81,15 @@ public T get() { } } + @Override + public T head() { + if (url != null) { + return client.head(clazz, url); + } else { + return client.head(clazz, pathBuilder.toString(), params); + } + } + @Override public Iterator iterate() { if (url != null) { @@ -97,6 +108,7 @@ public LinkedResponse> paginate() { } } + @Override public void delete() { if (url != null) { @@ -105,4 +117,22 @@ public void delete() { client.delete(pathBuilder.toString()); } } + + @Override + public File getFile() { + if (this.url != null) { + return this.client.getFile(this.url); + } else { + try { + String path = this.pathBuilder.toString(); + if (path.startsWith("/")) { + path = path.substring(path.indexOf("http")); + } + + return this.client.getFile(new URL(path)); + } catch (MalformedURLException var2) { + return null; + } + } + } } diff --git a/src/main/java/com/ciscospark/Spark.java b/src/main/java/com/ciscospark/Spark.java index cd69b16..c340270 100644 --- a/src/main/java/com/ciscospark/Spark.java +++ b/src/main/java/com/ciscospark/Spark.java @@ -17,6 +17,7 @@ public abstract class Spark { public abstract RequestBuilder organizations(); public abstract RequestBuilder licenses(); public abstract RequestBuilder roles(); + public abstract RequestBuilder contents(); /** diff --git a/src/main/java/com/ciscospark/SparkImpl.java b/src/main/java/com/ciscospark/SparkImpl.java index 03d35bd..d938a9a 100644 --- a/src/main/java/com/ciscospark/SparkImpl.java +++ b/src/main/java/com/ciscospark/SparkImpl.java @@ -60,5 +60,9 @@ public RequestBuilder licenses() { public RequestBuilder roles() { return new RequestBuilderImpl(Role.class, client, "/roles"); } + @Override + public RequestBuilder contents() { + return new RequestBuilderImpl(Content.class, client, "/contents"); + } }