From 4d7c3c85ef35c4c9eb93219ae49c2891517c9f69 Mon Sep 17 00:00:00 2001 From: David Bonomels Date: Tue, 21 Jan 2020 10:40:07 +0100 Subject: [PATCH 1/7] Added retry after 429 http response with maximum of 5 --- src/main/java/com/ciscospark/Client.java | 43 +++++++++++++++++------- 1 file changed, 30 insertions(+), 13 deletions(-) diff --git a/src/main/java/com/ciscospark/Client.java b/src/main/java/com/ciscospark/Client.java index 69f6ca2..6e8a5ae 100644 --- a/src/main/java/com/ciscospark/Client.java +++ b/src/main/java/com/ciscospark/Client.java @@ -135,6 +135,7 @@ InputStream request(String method, String path, List params, T bod URL url = getUrl(path, params); return request(url, method, body).inputStream; } + static class Response { HttpsURLConnection connection; InputStream inputStream; @@ -167,7 +168,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 +183,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 +203,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 +222,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 +232,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()); @@ -349,7 +367,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 +432,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 +448,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 +619,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 +635,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 { From 1ecba9815d194122b74b5e77f7576d1835f26360 Mon Sep 17 00:00:00 2001 From: David Bonomels Date: Tue, 21 Jan 2020 10:40:19 +0100 Subject: [PATCH 2/7] changed POM version --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 3638e84..4d1d2d0 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ com.ciscospark ciscospark-client - 1.0-SNAPSHOT + 1.1-SNAPSHOT From f7e3b819e6865fc71a191dfa53efa09bc7041b44 Mon Sep 17 00:00:00 2001 From: Marco Morselli Date: Fri, 24 Jan 2020 11:47:47 +0100 Subject: [PATCH 3/7] Added head method support for contents API --- src/main/java/com/ciscospark/Client.java | 34 +++++++++++++++++++ src/main/java/com/ciscospark/Content.java | 29 ++++++++++++++++ src/main/java/com/ciscospark/HeaderField.java | 25 ++++++++++++++ src/main/java/com/ciscospark/Spark.java | 1 + src/main/java/com/ciscospark/SparkImpl.java | 4 +++ 5 files changed, 93 insertions(+) create mode 100644 src/main/java/com/ciscospark/Content.java create mode 100644 src/main/java/com/ciscospark/HeaderField.java diff --git a/src/main/java/com/ciscospark/Client.java b/src/main/java/com/ciscospark/Client.java index 6e8a5ae..9d51162 100644 --- a/src/main/java/com/ciscospark/Client.java +++ b/src/main/java/com/ciscospark/Client.java @@ -85,6 +85,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)); } @@ -136,6 +145,13 @@ InputStream request(String method, String path, List params, T bod 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; @@ -355,6 +371,24 @@ 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()){ + 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(); 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/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"); + } } From ec303e8e27ab0212eeae8d937346a7e92c9a9cef Mon Sep 17 00:00:00 2001 From: Marco Morselli Date: Fri, 24 Jan 2020 12:03:04 +0100 Subject: [PATCH 4/7] Added head method to RequestBuilder --- src/main/java/com/ciscospark/RequestBuilder.java | 3 +++ src/main/java/com/ciscospark/RequestBuilderImpl.java | 9 +++++++++ 2 files changed, 12 insertions(+) diff --git a/src/main/java/com/ciscospark/RequestBuilder.java b/src/main/java/com/ciscospark/RequestBuilder.java index fe9feb8..e6724db 100644 --- a/src/main/java/com/ciscospark/RequestBuilder.java +++ b/src/main/java/com/ciscospark/RequestBuilder.java @@ -15,6 +15,9 @@ public interface RequestBuilder { T post(T body); T put(T body); T get(); + + T head(); + Iterator iterate(); LinkedResponse> paginate(); void delete(); diff --git a/src/main/java/com/ciscospark/RequestBuilderImpl.java b/src/main/java/com/ciscospark/RequestBuilderImpl.java index bdd2d1d..2752f34 100644 --- a/src/main/java/com/ciscospark/RequestBuilderImpl.java +++ b/src/main/java/com/ciscospark/RequestBuilderImpl.java @@ -79,6 +79,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) { From e88c017453a9835e484831a40205389c1089e7c2 Mon Sep 17 00:00:00 2001 From: Marco Morselli Date: Fri, 24 Jan 2020 12:43:40 +0100 Subject: [PATCH 5/7] added access to private field during header parsing --- src/main/java/com/ciscospark/Client.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/com/ciscospark/Client.java b/src/main/java/com/ciscospark/Client.java index 9d51162..6a4d6ca 100644 --- a/src/main/java/com/ciscospark/Client.java +++ b/src/main/java/com/ciscospark/Client.java @@ -375,6 +375,7 @@ private static T readHeaders(Class clazz, Map> heade 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())){ From 6b024879adad98e74538ae8282281c9413ee2523 Mon Sep 17 00:00:00 2001 From: Marco Morselli Date: Fri, 24 Jan 2020 15:20:11 +0100 Subject: [PATCH 6/7] updated version to 1.2 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 4d1d2d0..20775dc 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ com.ciscospark ciscospark-client - 1.1-SNAPSHOT + 1.2 From 7d85437e26a0f883286f896eb02377a367f49e63 Mon Sep 17 00:00:00 2001 From: Marco Morselli Date: Thu, 14 May 2020 12:08:08 +0200 Subject: [PATCH 7/7] added getFile method --- pom.xml | 2 +- src/main/java/com/ciscospark/Client.java | 68 +++++++++++++++++++ .../java/com/ciscospark/RequestBuilder.java | 3 + .../com/ciscospark/RequestBuilderImpl.java | 21 ++++++ 4 files changed, 93 insertions(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 20775dc..3bd8b3d 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ com.ciscospark ciscospark-client - 1.2 + 1.0-elibra.0 diff --git a/src/main/java/com/ciscospark/Client.java b/src/main/java/com/ciscospark/Client.java index 6a4d6ca..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; @@ -692,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/RequestBuilder.java b/src/main/java/com/ciscospark/RequestBuilder.java index e6724db..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; @@ -21,4 +22,6 @@ public interface RequestBuilder { 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 2752f34..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; @@ -106,6 +108,7 @@ public LinkedResponse> paginate() { } } + @Override public void delete() { if (url != null) { @@ -114,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; + } + } + } }