From eed9f0257615325b97a705b01316ad298fade5e4 Mon Sep 17 00:00:00 2001 From: Cameron Porter Date: Fri, 29 Jan 2016 23:43:58 -0600 Subject: [PATCH 01/62] Check a type's base type to determine if it has an id or global identifier when generating service init methods. --- .../java/com/softlayer/api/gen/ClassWriter.java | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/gen/src/main/java/com/softlayer/api/gen/ClassWriter.java b/gen/src/main/java/com/softlayer/api/gen/ClassWriter.java index b51c04b..d449365 100644 --- a/gen/src/main/java/com/softlayer/api/gen/ClassWriter.java +++ b/gen/src/main/java/com/softlayer/api/gen/ClassWriter.java @@ -377,8 +377,14 @@ public ClassWriter emitType() throws IOException { // Now the service if (!type.meta.noservice) { - if (type.meta.properties.containsKey("id")) { - if (type.meta.properties.containsKey("globalIdentifier")) { + + Boolean containsId = type.meta.properties.containsKey("id") + || type.baseMeta.properties.containsKey("id"); + Boolean containsGlobalIdentifier = type.meta.properties.containsKey("globalIdentifier") + || type.baseMeta.properties.containsKey("globalIdentifier"); + + if (containsId) { + if (containsGlobalIdentifier) { beginMethod("Service", "asService", PUBLIC, TYPE_API_CLIENT, "client"). beginControlFlow("if (id != null)"). emitStatement("return service(client, id)"). @@ -395,11 +401,11 @@ public ClassWriter emitType() throws IOException { emitStatement("return client.createService(Service.class, null)"). endMethod().emitEmptyLine(); - if (type.meta.properties.containsKey("id")) { + if (containsId) { beginMethod("Service", "service", PUBLIC_STATIC, TYPE_API_CLIENT, "client", "Long", "id"). emitStatement("return client.createService(Service.class, id == null ? null : id.toString())"). endMethod().emitEmptyLine(); - if (type.meta.properties.containsKey("globalIdentifier")) { + if (containsGlobalIdentifier) { beginMethod("Service", "service", PUBLIC_STATIC, TYPE_API_CLIENT, "client", "String", "globalIdentifier"). emitStatement("return client.createService(Service.class, globalIdentifier)"). From d821ced53f2e889d1c17af8c5b3b1d44fe69529e Mon Sep 17 00:00:00 2001 From: Cameron Porter Date: Mon, 1 Feb 2016 21:47:41 -0600 Subject: [PATCH 02/62] Traverse down each type's parents to see if any parent type contains id or globalIdentifier properties. --- .../com/softlayer/api/gen/ClassWriter.java | 28 +++++++++++++------ .../java/com/softlayer/api/gen/Generator.java | 2 +- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/gen/src/main/java/com/softlayer/api/gen/ClassWriter.java b/gen/src/main/java/com/softlayer/api/gen/ClassWriter.java index d449365..06395a8 100644 --- a/gen/src/main/java/com/softlayer/api/gen/ClassWriter.java +++ b/gen/src/main/java/com/softlayer/api/gen/ClassWriter.java @@ -59,13 +59,13 @@ public static void emitPackageInfo(File baseDir, List classes) throws } } - public static void emitType(File baseDir, TypeClass type) throws IOException { + public static void emitType(File baseDir, TypeClass type, Meta meta) throws IOException { File fileDir = new File(baseDir, type.packageName.replace('.', '/')); fileDir.mkdirs(); Writer writer = new BufferedWriter(new OutputStreamWriter( new FileOutputStream(new File(fileDir, type.className + ".java")), "UTF-8")); try { - new ClassWriter(writer, type).emitType(); + new ClassWriter(writer, type, meta).emitType(); } finally { try { writer.close(); } catch (Exception e) { } } @@ -86,10 +86,12 @@ public static String getClassName(Meta.Type type) { } public final TypeClass type; + private final Meta meta; - public ClassWriter(Writer out, TypeClass type) { + public ClassWriter(Writer out, TypeClass type, Meta meta) { super(out); this.type = type; + this.meta = meta; setIndent(" "); } @@ -378,10 +380,20 @@ public ClassWriter emitType() throws IOException { // Now the service if (!type.meta.noservice) { - Boolean containsId = type.meta.properties.containsKey("id") - || type.baseMeta.properties.containsKey("id"); - Boolean containsGlobalIdentifier = type.meta.properties.containsKey("globalIdentifier") - || type.baseMeta.properties.containsKey("globalIdentifier"); + // Check if the type or any of its' parent types have id or globalIdentifier properties + Boolean containsId = false; + Boolean containsGlobalIdentifier = false; + Meta.Type searchType = type.meta; + while (searchType != null) { + if (searchType.properties.containsKey("id")) { + containsId = true; + if (searchType.properties.containsKey("globalIdentifier")) { + containsGlobalIdentifier = true; + } + break; + } + searchType = meta.types.get(searchType.base); + } if (containsId) { if (containsGlobalIdentifier) { @@ -415,7 +427,7 @@ public ClassWriter emitType() throws IOException { emitService(); } - + emitMask().endType(); return this; } diff --git a/gen/src/main/java/com/softlayer/api/gen/Generator.java b/gen/src/main/java/com/softlayer/api/gen/Generator.java index 16da038..79a41d8 100644 --- a/gen/src/main/java/com/softlayer/api/gen/Generator.java +++ b/gen/src/main/java/com/softlayer/api/gen/Generator.java @@ -35,7 +35,7 @@ public void buildClient() throws IOException { List classes = new ArrayList(meta.types.size()); for (Meta.Type type : meta.types.values()) { TypeClass typeClass = new MetaConverter(BASE_PKG, meta, type).buildTypeClass(); - ClassWriter.emitType(dir, typeClass); + ClassWriter.emitType(dir, typeClass, meta); classes.add(typeClass); } ClassWriter.emitPackageInfo(dir, classes); From 9cb60d92978c74d51635c81e7b3f194ec1712dc5 Mon Sep 17 00:00:00 2001 From: cmp Date: Mon, 19 Sep 2016 10:59:50 -0500 Subject: [PATCH 03/62] Use the meta name on masks instead of the name after converting for reserved keywords in java. --- gen/src/main/java/com/softlayer/api/gen/ClassWriter.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gen/src/main/java/com/softlayer/api/gen/ClassWriter.java b/gen/src/main/java/com/softlayer/api/gen/ClassWriter.java index 06395a8..cb626ae 100644 --- a/gen/src/main/java/com/softlayer/api/gen/ClassWriter.java +++ b/gen/src/main/java/com/softlayer/api/gen/ClassWriter.java @@ -114,12 +114,12 @@ public ClassWriter emitMask() throws IOException { for (TypeClass.Property property : type.properties) { if (property.nonArrayJavaType.startsWith("com.")) { beginMethod(property.nonArrayJavaType + ".Mask", property.name, PUBLIC). - emitStatement("return withSubMask(%s, %s.class)", stringLiteral(property.name), + emitStatement("return withSubMask(%s, %s.class)", stringLiteral(property.meta.name), compressType(property.nonArrayJavaType + ".Mask")). endMethod().emitEmptyLine(); } else { beginMethod("Mask", property.name, PUBLIC). - emitStatement("withLocalProperty(%s)", stringLiteral(property.name)). + emitStatement("withLocalProperty(%s)", stringLiteral(property.meta.name)). emitStatement("return this"). endMethod().emitEmptyLine(); } From 257e48ea43a36ed417f2a7760e4047db8fec7479 Mon Sep 17 00:00:00 2001 From: cmp Date: Tue, 15 Nov 2016 23:27:32 -0600 Subject: [PATCH 04/62] Add additional keyword replacement for 'native'. Correct invalid first characters for java identifiers assigned by MetaConverter. --- .../com/softlayer/api/gen/MetaConverter.java | 23 ++++++++++++++++--- .../com/softlayer/api/http/HttpClient.java | 2 +- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/gen/src/main/java/com/softlayer/api/gen/MetaConverter.java b/gen/src/main/java/com/softlayer/api/gen/MetaConverter.java index 6a328be..df3c9f5 100644 --- a/gen/src/main/java/com/softlayer/api/gen/MetaConverter.java +++ b/gen/src/main/java/com/softlayer/api/gen/MetaConverter.java @@ -18,6 +18,7 @@ public class MetaConverter { keywordReplacements = new HashMap(2); keywordReplacements.put("package", "pkg"); keywordReplacements.put("private", "priv"); + keywordReplacements.put("native", "nat"); keywords = new HashSet(Arrays.asList(new String[] { "abstract", "continue", "for", "new", "switch", "assert", "default", "goto", "package", "synchronized", @@ -57,7 +58,7 @@ public String getClassName(String typeName) { if (invalidClassNames.contains(name)) { name = pieces[pieces.length - 2] + name; } - return name; + return getValidJavaIdentifier(name); } public String getMethodOrPropertyName(String className, String name) { @@ -66,7 +67,7 @@ public String getMethodOrPropertyName(String className, String name) { name = Character.toLowerCase(className.charAt(0)) + className.substring(1) + Character.toUpperCase(name.charAt(0)) + name.substring(1); } - return name; + return getValidJavaIdentifier(name); } public String getPackageName(String typeName) { @@ -79,11 +80,27 @@ public String getPackageName(String typeName) { for (int i = 0; i < pieces.length - 1; i++) { String piece = pieces[i].toLowerCase(); String replacement = keywordReplacements.get(piece); - pkg.append('.').append(replacement != null ? replacement : piece); + piece = replacement != null ? replacement : piece; + pkg.append('.').append(getValidJavaIdentifier(piece)); } return pkg.toString(); } + /** + * Provides a valid java identifier from the given name. + * Currently only checks for a valid start character and adds 'z' + * if the name has an invalid character at the start. + * + * @param name The identifier name to use. + * @return The new name after validating. + */ + public String getValidJavaIdentifier(String name) { + if (!Character.isJavaIdentifierStart(name.charAt(0))) { + name = "z" + name; + } + return name; + } + public TypeClass buildTypeClass() { imports.clear(); String packageName = getPackageName(type.name); diff --git a/src/main/java/com/softlayer/api/http/HttpClient.java b/src/main/java/com/softlayer/api/http/HttpClient.java index 30fa8a2..b8c7602 100644 --- a/src/main/java/com/softlayer/api/http/HttpClient.java +++ b/src/main/java/com/softlayer/api/http/HttpClient.java @@ -22,6 +22,6 @@ public interface HttpClient extends Closeable { /** Make asynchronous HTTP invocation. All errors (inability to connect or API errors) are in the future. */ public Future invokeAsync(Callable setupBody); - /** Callback-form of {@link #invokeAsync()} */ + /** Callback-form of {@link #invokeAsync(Callable)} */ public Future invokeAsync(Callable setupBody, ResponseHandler callback); } From 8b11204f64a31d0606989126da7a5164a7a321a6 Mon Sep 17 00:00:00 2001 From: Cameron Porter Date: Mon, 28 Nov 2016 14:20:37 -0600 Subject: [PATCH 05/62] Release version 0.2.3 --- LICENSE | 2 +- README.md | 11 +++++++++-- examples/pom.xml | 2 +- gen/pom.xml | 2 +- pom.xml | 10 +++++----- 5 files changed, 17 insertions(+), 10 deletions(-) diff --git a/LICENSE b/LICENSE index 09c0bbd..bdaac14 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2015 The SoftLayer Developer Network +Copyright (c) 2016 The SoftLayer Developer Network Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index ce88df9..94978cf 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ To add the project you your Maven project, add the dependency: com.softlayer.api softlayer-api-client - 0.2.2 + 0.2.3 ``` @@ -230,6 +230,13 @@ The services are not guaranteed to be thread-safe on their own, so it is difficu style, the `ResponseHandlerWithHeaders` can be used instead of `ResponseHandler`. But the safest way is to only use a single service per thread. +### Differences from the API + +Due to restrictions on identifiers in Java, some properties, methods, classes, and packages will be named differently +from the naming used by the API. For example, an API property that starts with a number will be prepended with 'z'. +[Java keywords](https://docs.oracle.com/javase/specs/jls/se8/html/jls-3.html#jls-3.9) that appear in identifiers may +also be replaced. + ## Building This project is intentionally provided without all of the service code. Normal Maven `install` and `package` commands @@ -265,4 +272,4 @@ fully qualified class name of your implementation on a single line in a file in ## Copyright -This software is Copyright (c) 2015 The SoftLayer Developer Network. See the bundled LICENSE file for more information. +This software is Copyright (c) 2016 The SoftLayer Developer Network. See the bundled LICENSE file for more information. diff --git a/examples/pom.xml b/examples/pom.xml index 3d2d634..238f322 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -5,7 +5,7 @@ softlayer-api-client-examples jar - 0.2.2 + 0.2.3 softlayer-api-client-examples http://sldn.softlayer.com diff --git a/gen/pom.xml b/gen/pom.xml index 0b1b940..534fdb6 100644 --- a/gen/pom.xml +++ b/gen/pom.xml @@ -5,7 +5,7 @@ softlayer-api-client-gen jar - 0.2.2 + 0.2.3 softlayer-api-client-gen http://sldn.softlayer.com diff --git a/pom.xml b/pom.xml index 2caf573..fc8af13 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ softlayer-api-client jar - 0.2.2 + 0.2.3 SoftLayer API Client for Java API client for accessing the SoftLayer API http://sldn.softlayer.com @@ -17,9 +17,9 @@ - cretz - Chad Retz - cretz@softlayer.com + camporter + Cameron Porter + cmporter@softlayer.com SoftLayer http://softlayer.com @@ -33,7 +33,7 @@ scm:git:git@github.com:softlayer/softlayer-java.git scm:git:git@github.com:softlayer/softlayer-java.git git@github.com:softlayer/softlayer-java.git - 0.2.2 + 0.2.3 UTF-8 From 814494caebfdbafb924b0b747ed42550451464d5 Mon Sep 17 00:00:00 2001 From: Cameron Porter Date: Thu, 19 Oct 2017 13:59:07 -0500 Subject: [PATCH 06/62] Release version 0.2.4 --- LICENSE | 2 +- README.md | 4 ++-- examples/pom.xml | 2 +- gen/pom.xml | 2 +- pom.xml | 9 +++++++-- 5 files changed, 12 insertions(+), 7 deletions(-) diff --git a/LICENSE b/LICENSE index bdaac14..5e1a332 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2016 The SoftLayer Developer Network +Copyright (c) 2017 The SoftLayer Developer Network Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 94978cf..8dcbb44 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ To add the project you your Maven project, add the dependency: com.softlayer.api softlayer-api-client - 0.2.3 + 0.2.4 ``` @@ -272,4 +272,4 @@ fully qualified class name of your implementation on a single line in a file in ## Copyright -This software is Copyright (c) 2016 The SoftLayer Developer Network. See the bundled LICENSE file for more information. +This software is Copyright (c) 2017 The SoftLayer Developer Network. See the bundled LICENSE file for more information. diff --git a/examples/pom.xml b/examples/pom.xml index 238f322..07e73b1 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -5,7 +5,7 @@ softlayer-api-client-examples jar - 0.2.3 + 0.2.4 softlayer-api-client-examples http://sldn.softlayer.com diff --git a/gen/pom.xml b/gen/pom.xml index 534fdb6..d9df533 100644 --- a/gen/pom.xml +++ b/gen/pom.xml @@ -5,7 +5,7 @@ softlayer-api-client-gen jar - 0.2.3 + 0.2.4 softlayer-api-client-gen http://sldn.softlayer.com diff --git a/pom.xml b/pom.xml index fc8af13..0646ffa 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ softlayer-api-client jar - 0.2.3 + 0.2.4 SoftLayer API Client for Java API client for accessing the SoftLayer API http://sldn.softlayer.com @@ -33,7 +33,7 @@ scm:git:git@github.com:softlayer/softlayer-java.git scm:git:git@github.com:softlayer/softlayer-java.git git@github.com:softlayer/softlayer-java.git - 0.2.3 + 0.2.4 UTF-8 @@ -108,6 +108,11 @@ jar + + + + -Xdoclint:none + From ba9bd7f5c218e62d3f2a27d240b63eb83972f0cf Mon Sep 17 00:00:00 2001 From: Cameron Porter Date: Sat, 21 Oct 2017 21:54:21 -0500 Subject: [PATCH 07/62] Add maven central and javadoc badges. --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 8dcbb44..4598b41 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ # SoftLayer API Client for Java [![Build Status](https://travis-ci.org/softlayer/softlayer-java.svg)](https://travis-ci.org/softlayer/softlayer-java) +[![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.softlayer.api/softlayer-api-client/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.softlayer.api/softlayer-api-client) +[![Javadocs](https://www.javadoc.io/badge/com.softlayer.api/softlayer-api-client.svg)](https://www.javadoc.io/doc/com.softlayer.api/softlayer-api-client) ## Introduction @@ -263,7 +265,7 @@ name of your implementation on a single line in a file in the JAR at ### JSON Marshalling -The default JSON marshaller that is used is [Gson](https://code.google.com/p/google-gson/). In order to create your own, +The default JSON marshaller that is used is [Gson](https://github.com/google/gson). In order to create your own, alternative implementation you must implement `com.softlayer.api.json.JsonMarshallerFactyory`. Once implemented, this can be explicitly set on the `RestApiClient` by calling `setJsonMarshallerFactory`. Instead of setting the factory manually, you can also leverage Java's `ServiceLoader` mechanism to have it used by default. This involves adding the From 9d19560351b2070f2f079e883a04b99dae633ab0 Mon Sep 17 00:00:00 2001 From: Cameron Porter Date: Sat, 21 Oct 2017 22:06:47 -0500 Subject: [PATCH 08/62] Ignore Intellij IDEA folders and module files. --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 700d3af..2c04155 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,8 @@ /.classpath /.project /.settings +/.idea +*.iml /target /src/main/java/com/softlayer/api/service /gen/target From c71832b3b6714e2970d28d5c465e38445c999201 Mon Sep 17 00:00:00 2001 From: Cameron Porter Date: Mon, 30 Oct 2017 14:09:36 -0500 Subject: [PATCH 09/62] Make sure calls to createObjects explicitly include the method name in the URL to the REST API. Otherwise, the request may fail due to a bug with the SoftLayer API. --- .../java/com/softlayer/api/RestApiClient.java | 29 ++++++++++++++++--- .../com/softlayer/api/RestApiClientTest.java | 2 +- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/softlayer/api/RestApiClient.java b/src/main/java/com/softlayer/api/RestApiClient.java index c945442..35ea57b 100644 --- a/src/main/java/com/softlayer/api/RestApiClient.java +++ b/src/main/java/com/softlayer/api/RestApiClient.java @@ -41,6 +41,18 @@ public class RestApiClient implements ApiClient { static { HEADERS = Collections.singletonMap("SoftLayer-Include-Types", Collections.singletonList("true")); } + + /** + * A list of service methods that do not have to be added to the REST URL. + * createObjects is supposed to work, but does not. + */ + private static final List IMPLICIT_SERVICE_METHODS = Arrays.asList( + "getObject", + "deleteObject", + "createObject", + "editObject", + "editObjects" + ); private final String baseUrl; private HttpClientFactory httpClientFactory; @@ -135,7 +147,18 @@ protected String getHttpMethodFromMethodName(String methodName) { return "GET"; } } - + + /** + * Get the full REST URL required to make a request. + * + * @param serviceName The name of the API service. + * @param methodName The name of the method on the service to call. + * @param id The identifier of the object to make a call to, + * otherwise null if not making a request to a specific object. + * @param resultLimit The number of results to limit the request to. + * @param maskString The mask, in string form, to use on the request. + * @return String + */ protected String getFullUrl(String serviceName, String methodName, String id, ResultLimit resultLimit, String maskString) { StringBuilder url = new StringBuilder(baseUrl + serviceName); @@ -146,9 +169,7 @@ protected String getFullUrl(String serviceName, String methodName, String id, // Some method names are not included, others can have the "get" stripped if (methodName.startsWith("get") && !"getObject".equals(methodName)) { url.append('/').append(methodName.substring(3)); - } else if (!"getObject".equals(methodName) && !"deleteObject".equals(methodName) && - !"createObject".equals(methodName) && !"createObjects".equals(methodName) && - !"editObject".equals(methodName) && !"editObjects".equals(methodName)) { + } else if (!IMPLICIT_SERVICE_METHODS.contains(methodName)) { url.append('/').append(methodName); } url.append(".json"); diff --git a/src/test/java/com/softlayer/api/RestApiClientTest.java b/src/test/java/com/softlayer/api/RestApiClientTest.java index a141cb7..92c8a70 100644 --- a/src/test/java/com/softlayer/api/RestApiClientTest.java +++ b/src/test/java/com/softlayer/api/RestApiClientTest.java @@ -63,7 +63,7 @@ public void testGetFullUrl() { client.getFullUrl("SomeService", "deleteObject", null, null, null)); assertEquals("http://example.com/SomeService.json", client.getFullUrl("SomeService", "createObject", null, null, null)); - assertEquals("http://example.com/SomeService.json", + assertEquals("http://example.com/SomeService/createObjects.json", client.getFullUrl("SomeService", "createObjects", null, null, null)); assertEquals("http://example.com/SomeService.json", client.getFullUrl("SomeService", "editObject", null, null, null)); From ff1a845df4811536ab167ebdeb6b34b6f5ad91e5 Mon Sep 17 00:00:00 2001 From: Cameron Porter Date: Thu, 2 Nov 2017 18:29:39 -0500 Subject: [PATCH 10/62] Fix README JsonMarshallerFactory typo. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4598b41..7cd7054 100644 --- a/README.md +++ b/README.md @@ -266,7 +266,7 @@ name of your implementation on a single line in a file in the JAR at ### JSON Marshalling The default JSON marshaller that is used is [Gson](https://github.com/google/gson). In order to create your own, -alternative implementation you must implement `com.softlayer.api.json.JsonMarshallerFactyory`. Once implemented, this +alternative implementation you must implement `com.softlayer.api.json.JsonMarshallerFactory`. Once implemented, this can be explicitly set on the `RestApiClient` by calling `setJsonMarshallerFactory`. Instead of setting the factory manually, you can also leverage Java's `ServiceLoader` mechanism to have it used by default. This involves adding the fully qualified class name of your implementation on a single line in a file in the JAR at From d5c19da2586f32d3d700ec22d1d225cca8c73c18 Mon Sep 17 00:00:00 2001 From: Cameron Porter Date: Wed, 10 Jan 2018 11:08:34 -0600 Subject: [PATCH 11/62] Release version 0.2.5 --- LICENSE | 2 +- README.md | 4 ++-- examples/pom.xml | 2 +- gen/pom.xml | 2 +- pom.xml | 4 ++-- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/LICENSE b/LICENSE index 5e1a332..f1d9742 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2017 The SoftLayer Developer Network +Copyright (c) 2018 The SoftLayer Developer Network Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 7cd7054..fd42901 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ To add the project you your Maven project, add the dependency: com.softlayer.api softlayer-api-client - 0.2.4 + 0.2.5 ``` @@ -274,4 +274,4 @@ fully qualified class name of your implementation on a single line in a file in ## Copyright -This software is Copyright (c) 2017 The SoftLayer Developer Network. See the bundled LICENSE file for more information. +This software is Copyright (c) 2018 The SoftLayer Developer Network. See the bundled LICENSE file for more information. diff --git a/examples/pom.xml b/examples/pom.xml index 07e73b1..6f5a910 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -5,7 +5,7 @@ softlayer-api-client-examples jar - 0.2.4 + 0.2.5 softlayer-api-client-examples http://sldn.softlayer.com diff --git a/gen/pom.xml b/gen/pom.xml index d9df533..56bb119 100644 --- a/gen/pom.xml +++ b/gen/pom.xml @@ -5,7 +5,7 @@ softlayer-api-client-gen jar - 0.2.4 + 0.2.5 softlayer-api-client-gen http://sldn.softlayer.com diff --git a/pom.xml b/pom.xml index 0646ffa..8b2256f 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ softlayer-api-client jar - 0.2.4 + 0.2.5 SoftLayer API Client for Java API client for accessing the SoftLayer API http://sldn.softlayer.com @@ -33,7 +33,7 @@ scm:git:git@github.com:softlayer/softlayer-java.git scm:git:git@github.com:softlayer/softlayer-java.git git@github.com:softlayer/softlayer-java.git - 0.2.4 + 0.2.5 UTF-8 From 95b38417d5a5dcc7aa2dc866739998e6ac38291b Mon Sep 17 00:00:00 2001 From: John Unterholzner Date: Mon, 15 Jan 2018 17:00:25 -0600 Subject: [PATCH 12/62] add security group examples --- .../api/example/AddSecurityGroupRule.java | 45 +++++++++++++++++++ .../api/example/CreateSecurityGroup.java | 27 +++++++++++ .../api/example/ListSecurityGroups.java | 31 +++++++++++++ 3 files changed, 103 insertions(+) create mode 100644 examples/src/main/java/com/softlayer/api/example/AddSecurityGroupRule.java create mode 100644 examples/src/main/java/com/softlayer/api/example/CreateSecurityGroup.java create mode 100644 examples/src/main/java/com/softlayer/api/example/ListSecurityGroups.java diff --git a/examples/src/main/java/com/softlayer/api/example/AddSecurityGroupRule.java b/examples/src/main/java/com/softlayer/api/example/AddSecurityGroupRule.java new file mode 100644 index 0000000..c545f92 --- /dev/null +++ b/examples/src/main/java/com/softlayer/api/example/AddSecurityGroupRule.java @@ -0,0 +1,45 @@ +package com.softlayer.api.example; + +import com.softlayer.api.ApiClient; +import com.softlayer.api.service.network.SecurityGroup; +import com.softlayer.api.service.network.securitygroup.Rule; + +import java.util.ArrayList; +import java.util.List; + +/** Create a simple security group */ +public class AddSecurityGroupRule extends Example { + + @Override + public void run(ApiClient client) throws Exception { + SecurityGroup.Service service = SecurityGroup.service(client); + + // create a new security group + SecurityGroup sg = new SecurityGroup(); + sg.setName("javaTest"); + sg.setDescription("javaTestDescription"); + + // create that security group + SecurityGroup sg_out = service.createObject(sg); + System.out.format("Created security group with ID: %s\n", sg_out.getId()); + + // bind the service to the id of the newly created security group + service = sg_out.asService(client); + + // Create a security group rule + Rule rule = new Rule(); + rule.setDirection("ingress"); + rule.setProtocol("udp"); + + List newRules = new ArrayList(); + newRules.add(rule); + + // Now add the rule(s) to the security group + System.out.format("Adding rule(s) to security group"); + service.addRules(newRules); + } + + public static void main(String[] args) throws Exception { + new AddSecurityGroupRule().start(args); + } +} diff --git a/examples/src/main/java/com/softlayer/api/example/CreateSecurityGroup.java b/examples/src/main/java/com/softlayer/api/example/CreateSecurityGroup.java new file mode 100644 index 0000000..98a8cdd --- /dev/null +++ b/examples/src/main/java/com/softlayer/api/example/CreateSecurityGroup.java @@ -0,0 +1,27 @@ +package com.softlayer.api.example; + +import com.softlayer.api.ApiClient; +import com.softlayer.api.service.network.SecurityGroup; + +/** Create security group example. */ +public class CreateSecurityGroup extends Example { + + @Override + public void run(ApiClient client) throws Exception { + SecurityGroup.Service service = SecurityGroup.service(client); + + // Create a java object representing the new security group + SecurityGroup sg = new SecurityGroup(); + sg.setName("javaTest"); + sg.setDescription("javaTestDescription"); + + // Now call the security group service to create it + System.out.println("Make call to create security group"); + SecurityGroup sg_out = service.createObject(sg); + System.out.format("Created security group with name = %s\n", sg_out.getName()); + } + + public static void main(String[] args) throws Exception { + new CreateSecurityGroup().start(args); + } +} diff --git a/examples/src/main/java/com/softlayer/api/example/ListSecurityGroups.java b/examples/src/main/java/com/softlayer/api/example/ListSecurityGroups.java new file mode 100644 index 0000000..a7f4f4e --- /dev/null +++ b/examples/src/main/java/com/softlayer/api/example/ListSecurityGroups.java @@ -0,0 +1,31 @@ +package com.softlayer.api.example; + +import com.softlayer.api.ApiClient; +import com.softlayer.api.service.Account; +import com.softlayer.api.service.network.SecurityGroup; + +/** List all security groups for an account */ +public class ListSecurityGroups extends Example { + + @Override + public void run(ApiClient client) throws Exception { + // Get the Account service + Account.Service service = Account.service(client); + + // To get specific information on an account (security groups in this case) a mask is provided + service.withMask().securityGroups(); + + // Calling getObject will now use the mask + Account account = service.getObject(); + + System.out.format("\nFound %d security groups\n", account.getSecurityGroups().size()); + + for (SecurityGroup sg : account.getSecurityGroups()) { + System.out.format("id: %s name: %s \n", sg.getId(), sg.getName()); + } + } + + public static void main(String[] args) throws Exception { + new ListSecurityGroups().start(args); + } +} From 2e8195c0e9cb7d1f931b24b54fe0724f243da7d3 Mon Sep 17 00:00:00 2001 From: John Unterholzner Date: Mon, 22 Jan 2018 10:33:42 -0600 Subject: [PATCH 13/62] updates for PR review --- .../softlayer/api/example/AddSecurityGroupRule.java | 10 +++++----- .../com/softlayer/api/example/CreateSecurityGroup.java | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/examples/src/main/java/com/softlayer/api/example/AddSecurityGroupRule.java b/examples/src/main/java/com/softlayer/api/example/AddSecurityGroupRule.java index c545f92..50df35d 100644 --- a/examples/src/main/java/com/softlayer/api/example/AddSecurityGroupRule.java +++ b/examples/src/main/java/com/softlayer/api/example/AddSecurityGroupRule.java @@ -20,22 +20,22 @@ public void run(ApiClient client) throws Exception { sg.setDescription("javaTestDescription"); // create that security group - SecurityGroup sg_out = service.createObject(sg); - System.out.format("Created security group with ID: %s\n", sg_out.getId()); + SecurityGroup sgOut = service.createObject(sg); + System.out.format("Created security group with ID: %s\n", sgOut.getId()); // bind the service to the id of the newly created security group - service = sg_out.asService(client); + service = sgOut.asService(client); // Create a security group rule Rule rule = new Rule(); rule.setDirection("ingress"); rule.setProtocol("udp"); - List newRules = new ArrayList(); + List newRules = new ArrayList(); newRules.add(rule); // Now add the rule(s) to the security group - System.out.format("Adding rule(s) to security group"); + System.out.println("Adding rule(s) to security group"); service.addRules(newRules); } diff --git a/examples/src/main/java/com/softlayer/api/example/CreateSecurityGroup.java b/examples/src/main/java/com/softlayer/api/example/CreateSecurityGroup.java index 98a8cdd..eadbb99 100644 --- a/examples/src/main/java/com/softlayer/api/example/CreateSecurityGroup.java +++ b/examples/src/main/java/com/softlayer/api/example/CreateSecurityGroup.java @@ -17,8 +17,8 @@ public void run(ApiClient client) throws Exception { // Now call the security group service to create it System.out.println("Make call to create security group"); - SecurityGroup sg_out = service.createObject(sg); - System.out.format("Created security group with name = %s\n", sg_out.getName()); + SecurityGroup sgOut = service.createObject(sg); + System.out.format("Created security group with name = %s\n", sgOut.getName()); } public static void main(String[] args) throws Exception { From fa050f7c2aed85a8baf082d38c41cc4a06a1990f Mon Sep 17 00:00:00 2001 From: Cameron Porter Date: Sat, 3 Mar 2018 00:09:00 -0600 Subject: [PATCH 14/62] Initial commit for java 8 support. --- .travis.yml | 4 +- README.md | 2 +- examples/pom.xml | 4 +- .../api/example/AddSecurityGroupRule.java | 2 +- .../api/example/PaginationAsyncCallback.java | 10 +- .../api/example/PaginationAsyncPolling.java | 12 +- gen/pom.xml | 6 +- .../com/softlayer/api/gen/ClassWriter.java | 15 +- .../java/com/softlayer/api/gen/Generator.java | 39 +- .../main/java/com/softlayer/api/gen/Main.java | 14 +- .../main/java/com/softlayer/api/gen/Meta.java | 43 +- .../com/softlayer/api/gen/MetaConverter.java | 119 +- .../com/softlayer/api/gen/Restriction.java | 6 +- pom.xml | 26 +- .../java/com/softlayer/api/ApiClient.java | 4 +- src/main/java/com/softlayer/api/Mask.java | 4 +- src/main/java/com/softlayer/api/Maskable.java | 10 +- .../com/softlayer/api/ResponseHandler.java | 4 +- .../java/com/softlayer/api/RestApiClient.java | 68 +- .../com/softlayer/api/ResultLimitable.java | 6 +- src/main/java/com/softlayer/api/Service.java | 2 +- src/main/java/com/softlayer/api/Type.java | 2 +- .../api/http/BuiltInHttpClientFactory.java | 56 +- .../api/http/HttpBasicAuthCredentials.java | 23 +- .../com/softlayer/api/http/HttpClient.java | 8 +- .../com/softlayer/api/http/HttpResponse.java | 6 +- .../java/com/softlayer/api/json/Base64.java | 2067 ----------------- .../api/json/GsonJsonMarshallerFactory.java | 38 +- .../softlayer/api/json/JsonMarshaller.java | 4 +- .../com/softlayer/api/RestApiClientTest.java | 135 +- .../com/softlayer/api/ResultLimitTest.java | 26 + .../http/BuiltInHttpClientFactoryTest.java | 84 +- .../json/GsonJsonMarshallerFactoryTest.java | 18 +- 33 files changed, 474 insertions(+), 2393 deletions(-) delete mode 100644 src/main/java/com/softlayer/api/json/Base64.java create mode 100644 src/test/java/com/softlayer/api/ResultLimitTest.java diff --git a/.travis.yml b/.travis.yml index fa4eb57..342a614 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,5 @@ language: java jdk: - oraclejdk8 - - oraclejdk7 - - openjdk7 - - openjdk6 + - openjdk8 install: mvn install -DskipTests=true -Dmaven.javadoc.skip=true -Dgpg.skip=true \ No newline at end of file diff --git a/README.md b/README.md index fd42901..eed2cf1 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ ## Introduction This library provides a JVM client for the [SoftLayer API](http://sldn.softlayer.com/article/SoftLayer-API-Overview). It -has code generated and compiled via Maven. The client can work with any Java 6+ runtime. It uses the code generation +has code generated and compiled via Maven. The client can work with any Java 8+ runtime. It uses the code generation project in `gen/` to generate the service and type related code. Although likely to work in resource-constrained environments (i.e. Android, J2ME, etc), using this is not recommended; Use the [REST](http://sldn.softlayer.com/article/REST) API instead. diff --git a/examples/pom.xml b/examples/pom.xml index 6f5a910..de92d47 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -17,7 +17,7 @@ UTF-8 - 1.6 + 1.8 @@ -54,7 +54,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.0 + 3.7.0 ${java.version} ${java.version} diff --git a/examples/src/main/java/com/softlayer/api/example/AddSecurityGroupRule.java b/examples/src/main/java/com/softlayer/api/example/AddSecurityGroupRule.java index 50df35d..c5d21e6 100644 --- a/examples/src/main/java/com/softlayer/api/example/AddSecurityGroupRule.java +++ b/examples/src/main/java/com/softlayer/api/example/AddSecurityGroupRule.java @@ -31,7 +31,7 @@ public void run(ApiClient client) throws Exception { rule.setDirection("ingress"); rule.setProtocol("udp"); - List newRules = new ArrayList(); + List newRules = new ArrayList<>(); newRules.add(rule); // Now add the rule(s) to the security group diff --git a/examples/src/main/java/com/softlayer/api/example/PaginationAsyncCallback.java b/examples/src/main/java/com/softlayer/api/example/PaginationAsyncCallback.java index f7d74cd..8848d90 100644 --- a/examples/src/main/java/com/softlayer/api/example/PaginationAsyncCallback.java +++ b/examples/src/main/java/com/softlayer/api/example/PaginationAsyncCallback.java @@ -1,6 +1,5 @@ package com.softlayer.api.example; -import java.util.Comparator; import java.util.List; import java.util.NavigableSet; import java.util.Set; @@ -28,12 +27,9 @@ public void run(ApiClient client) throws Exception { ((ThreadPooledHttpClientFactory) HttpClientFactory.getDefault()).setThreadPool(threadPool); // A thread-safe set is needed to hold the resulting packages ordered by name - final NavigableSet packages = new ConcurrentSkipListSet(new Comparator() { - @Override - public int compare(Package pkg1, Package pkg2) { - return pkg1.getName().compareToIgnoreCase(pkg2.getName()); - } - }); + final NavigableSet packages = new ConcurrentSkipListSet<>( + (pkg1, pkg2) -> pkg1.getName().compareToIgnoreCase(pkg2.getName()) + ); // To know how many calls have to be made to get all items, an initial call is required to get the // first set of data AND the total count diff --git a/examples/src/main/java/com/softlayer/api/example/PaginationAsyncPolling.java b/examples/src/main/java/com/softlayer/api/example/PaginationAsyncPolling.java index c80d20d..7187441 100644 --- a/examples/src/main/java/com/softlayer/api/example/PaginationAsyncPolling.java +++ b/examples/src/main/java/com/softlayer/api/example/PaginationAsyncPolling.java @@ -1,7 +1,6 @@ package com.softlayer.api.example; import java.util.ArrayList; -import java.util.Comparator; import java.util.List; import java.util.NavigableSet; import java.util.TreeSet; @@ -27,7 +26,7 @@ public void run(ApiClient client) throws Exception { ((ThreadPooledHttpClientFactory) HttpClientFactory.getDefault()).setThreadPool(threadPool); // Asynchronous responses are held so they can be waited on once all are submitted - List responses = new ArrayList(); + List responses = new ArrayList<>(); // To know how many calls have to be made to get all items, an initial call is required to get the // first set of data AND the total count @@ -53,12 +52,9 @@ public void run(ApiClient client) throws Exception { threadPool.shutdown(); // A set is needed to hold the resulting packages ordered by name - final NavigableSet packages = new TreeSet(new Comparator() { - @Override - public int compare(Package pkg1, Package pkg2) { - return pkg1.getName().compareToIgnoreCase(pkg2.getName()); - } - }); + final NavigableSet packages = new TreeSet<>( + (pkg1, pkg2) -> pkg1.getName().compareToIgnoreCase(pkg2.getName()) + ); // Unlike the callback approach, this approach guarantees they come in the order requested since a blocking // call to get() is in the request order diff --git a/gen/pom.xml b/gen/pom.xml index 56bb119..78f41fb 100644 --- a/gen/pom.xml +++ b/gen/pom.xml @@ -17,7 +17,7 @@ UTF-8 - 1.6 + 1.8 com.softlayer.api.gen.Main @@ -58,12 +58,12 @@ org.apache.maven.plugins maven-javadoc-plugin - 2.9.1 + 3.0.0-M1 org.apache.maven.plugins maven-compiler-plugin - 3.0 + 3.7.0 ${java.version} ${java.version} diff --git a/gen/src/main/java/com/softlayer/api/gen/ClassWriter.java b/gen/src/main/java/com/softlayer/api/gen/ClassWriter.java index cb626ae..6e318c8 100644 --- a/gen/src/main/java/com/softlayer/api/gen/ClassWriter.java +++ b/gen/src/main/java/com/softlayer/api/gen/ClassWriter.java @@ -7,6 +7,7 @@ import java.io.IOException; import java.io.OutputStreamWriter; import java.io.Writer; +import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.EnumSet; import java.util.HashMap; @@ -21,7 +22,7 @@ public class ClassWriter extends JavaWriter { public static final String SLDN_URL_BASE_PATH = "http://sldn.softlayer.com/reference/"; - + public static final String TYPE_API_CLIENT = "com.softlayer.api.ApiClient"; public static final String TYPE_API_METHOD = "com.softlayer.api.annotation.ApiMethod"; public static final String TYPE_API_PROPERTY = "com.softlayer.api.annotation.ApiProperty"; @@ -63,7 +64,7 @@ public static void emitType(File baseDir, TypeClass type, Meta meta) throws IOEx File fileDir = new File(baseDir, type.packageName.replace('.', '/')); fileDir.mkdirs(); Writer writer = new BufferedWriter(new OutputStreamWriter( - new FileOutputStream(new File(fileDir, type.className + ".java")), "UTF-8")); + new FileOutputStream(new File(fileDir, type.className + ".java")), StandardCharsets.UTF_8)); try { new ClassWriter(writer, type, meta).emitType(); } finally { @@ -97,7 +98,7 @@ public ClassWriter(Writer out, TypeClass type, Meta meta) { public ClassWriter emitAnnotationWithAttrs(String annotationType, Object... attributes) throws IOException { int i = 0; - Map attrMap = new HashMap(attributes.length / 2 + 1); + Map attrMap = new HashMap<>(attributes.length / 2 + 1); while (i < attributes.length) { String key = attributes[i++].toString(); attrMap.put(key, attributes[i++]); @@ -134,7 +135,7 @@ public ClassWriter emitProperty(TypeClass.Property property) throws IOException emitJavadoc(property.meta.doc.replace("\n", "
\n")); } - Map params = new HashMap(2); + Map params = new HashMap<>(2); if (!property.name.equals(property.meta.name)) { params.put("value", stringLiteral(property.meta.name)); } @@ -267,7 +268,7 @@ public ClassWriter emitServiceMethod(TypeClass.Method method, boolean async) thr "/" + method.meta.name + "\">" + type.meta.name + "::" + method.meta.name + ""; emitJavadoc(javadoc); - Map params = new HashMap(2); + Map params = new HashMap<>(2); if (!method.name.equals(method.meta.name)) { params.put("value", stringLiteral(method.meta.name)); } @@ -433,8 +434,8 @@ public ClassWriter emitType() throws IOException { } public ClassWriter emitTypeImports() throws IOException { - Map imports = new HashMap(type.imports); - + Map imports = new HashMap<>(type.imports); + imports.remove("Mask"); imports.remove(type.className); imports.put("ApiType", TYPE_API_TYPE); diff --git a/gen/src/main/java/com/softlayer/api/gen/Generator.java b/gen/src/main/java/com/softlayer/api/gen/Generator.java index 79a41d8..1ba77d5 100644 --- a/gen/src/main/java/com/softlayer/api/gen/Generator.java +++ b/gen/src/main/java/com/softlayer/api/gen/Generator.java @@ -3,6 +3,11 @@ import java.io.File; import java.io.IOException; import java.net.URL; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; import java.util.ArrayList; import java.util.List; import java.util.Set; @@ -15,7 +20,13 @@ public class Generator { private final URL metadataUrl; private final Restriction whitelist; private final Restriction blacklist; - + + /** + * @param dir The directory to generate classes into. + * @param metadataUrl The metadata to generate from. + * @param whitelist + * @param blacklist + */ public Generator(File dir, URL metadataUrl, Restriction whitelist, Restriction blacklist) { this.dir = dir; this.metadataUrl = metadataUrl; @@ -32,7 +43,7 @@ public void buildClient() throws IOException { applyRestrictions(meta); log("Generating source code"); - List classes = new ArrayList(meta.types.size()); + List classes = new ArrayList<>(meta.types.size()); for (Meta.Type type : meta.types.values()) { TypeClass typeClass = new MetaConverter(BASE_PKG, meta, type).buildTypeClass(); ClassWriter.emitType(dir, typeClass, meta); @@ -77,16 +88,22 @@ protected void log(String contents) { System.out.println(contents); } - public void recursivelyDelete(File file) { - if (file.isDirectory()) { - for (File child : file.listFiles()) { - recursivelyDelete(child); + public void recursivelyDelete(File file) throws IOException { + if (!file.exists()) { + return; + } + Files.walkFileTree(file.toPath(), new SimpleFileVisitor() { + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + Files.delete(file); + return FileVisitResult.CONTINUE; } - if (!file.delete()) { - throw new RuntimeException("Unable to delete: " + file); + + @Override + public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { + Files.delete(dir); + return FileVisitResult.CONTINUE; } - } else if (file.exists() && !file.delete()) { - throw new RuntimeException("Unable to delete: " + file); - } + }); } } diff --git a/gen/src/main/java/com/softlayer/api/gen/Main.java b/gen/src/main/java/com/softlayer/api/gen/Main.java index 117dfba..879da43 100644 --- a/gen/src/main/java/com/softlayer/api/gen/Main.java +++ b/gen/src/main/java/com/softlayer/api/gen/Main.java @@ -13,6 +13,8 @@ /** Entry point for the code generator */ public class Main { + + protected static final String METADATA_URL = "https://api.softlayer.com/metadata/v3.1"; public static final String USAGE = "Arguments:\n\n" + @@ -35,7 +37,7 @@ public static void main(String[] args) throws Exception { Restriction whitelist; Restriction blacklist; try { - List argList = new ArrayList(Arrays.asList(args)); + List argList = Arrays.asList(args); if (argList.contains("--help")) { System.out.println(USAGE); return; @@ -43,7 +45,7 @@ public static void main(String[] args) throws Exception { String dirString = getArg("--src", argList); dir = new File(dirString != null ? dirString : "../src/main/java"); String urlString = getArg("--url", argList); - url = new URL(urlString != null ? urlString : "https://api.softlayer.com/metadata/v3.1"); + url = new URL(urlString != null ? urlString : METADATA_URL); whitelist = getRestriction(getArg("--whitelist", argList)); blacklist = getRestriction(getArg("--blacklist", argList)); if (whitelist != null && blacklist != null) { @@ -83,17 +85,13 @@ private static Restriction getRestriction(String filename) throws IOException { line = line.trim(); if (line.contains("::")) { String pieces[] = line.split("::", 2); - Set methods = restriction.methods.get(pieces[0]); - if (methods == null) { - methods = new HashSet(); - restriction.methods.put(pieces[0], methods); - } + Set methods = restriction.methods.computeIfAbsent(pieces[0], key -> new HashSet<>()); methods.add(pieces[1]); } else if (line.contains(".")) { String pieces[] = line.split(".", 2); Set properties = restriction.properties.get(pieces[0]); if (properties == null) { - properties = new HashSet(); + properties = new HashSet<>(); restriction.methods.put(pieces[0], properties); } properties.add(pieces[1]); diff --git a/gen/src/main/java/com/softlayer/api/gen/Meta.java b/gen/src/main/java/com/softlayer/api/gen/Meta.java index 6fb26ce..7a9ad8d 100644 --- a/gen/src/main/java/com/softlayer/api/gen/Meta.java +++ b/gen/src/main/java/com/softlayer/api/gen/Meta.java @@ -16,8 +16,17 @@ import com.google.gson.stream.JsonReader; import com.google.gson.stream.JsonWriter; +/** + * Represents the structure of the metadata provided by the API. + */ public class Meta { - + + /** + * Reads a JSON object from the given metadata URL and generates a new Meta object containing all types. + * + * @param url The API metadata URL. + * @return Meta + */ public static Meta fromUrl(URL url) { InputStream stream = null; try { @@ -33,8 +42,10 @@ public PropertyForm read(JsonReader in) throws IOException { return PropertyForm.valueOf(in.nextString().toUpperCase()); } }).create(); - Map types = gson.fromJson(new InputStreamReader(stream), - new TypeToken>(){ }.getType()); + Map types = gson.fromJson( + new InputStreamReader(stream), + new TypeToken>(){ }.getType() + ); return new Meta(types); } catch (IOException e) { throw new RuntimeException(e); @@ -44,14 +55,19 @@ public PropertyForm read(JsonReader in) throws IOException { } } } - + public final Map types; - + public Meta(Map types) { this.types = types; } + /** + * Representation of a type in the metadata API. + */ public static class Type { + public static final String BASE_TYPE_NAME = "SoftLayer_Entity"; + public String name; public String base; public String typeDoc; @@ -60,7 +76,10 @@ public static class Type { public Map methods = Collections.emptyMap(); public boolean noservice; } - + + /** + * Representation of a property in the metadata API. + */ public static class Property { public String name; public String type; @@ -69,12 +88,15 @@ public static class Property { public String doc; } - public static enum PropertyForm { + public enum PropertyForm { LOCAL, RELATIONAL, COUNT } - + + /** + * A representation of a method in the metadata API. + */ public static class Method { public String name; public String type; @@ -88,7 +110,10 @@ public static class Method { public boolean maskable; public List parameters = Collections.emptyList(); } - + + /** + * A representation of a parameter in the metadata API. + */ public static class Parameter { public String name; public String type; diff --git a/gen/src/main/java/com/softlayer/api/gen/MetaConverter.java b/gen/src/main/java/com/softlayer/api/gen/MetaConverter.java index df3c9f5..bf2fada 100644 --- a/gen/src/main/java/com/softlayer/api/gen/MetaConverter.java +++ b/gen/src/main/java/com/softlayer/api/gen/MetaConverter.java @@ -1,12 +1,8 @@ package com.softlayer.api.gen; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.*; public class MetaConverter { @@ -15,11 +11,11 @@ public class MetaConverter { protected static final Set invalidClassNames; static { - keywordReplacements = new HashMap(2); + keywordReplacements = new HashMap<>(2); keywordReplacements.put("package", "pkg"); keywordReplacements.put("private", "priv"); keywordReplacements.put("native", "nat"); - keywords = new HashSet(Arrays.asList(new String[] { + keywords = new HashSet<>(Arrays.asList( "abstract", "continue", "for", "new", "switch", "assert", "default", "goto", "package", "synchronized", "boolean", "do", "if", "private", "this", @@ -30,13 +26,13 @@ public class MetaConverter { "char", "final", "interface", "static", "void", "class", "finally", "long", "strictfp", "volatile", "const", "float", "native", "super", "while" - })); - invalidClassNames = new HashSet(Arrays.asList(new String[] { + )); + invalidClassNames = new HashSet<>(Arrays.asList( "Service" - })); + )); } - protected final Map imports = new HashMap(); + protected final Map imports = new HashMap<>(); protected final String basePackageName; protected final Meta meta; protected final Meta.Type type; @@ -46,10 +42,10 @@ public MetaConverter(String basePackageName, Meta meta, Meta.Type type) { this.basePackageName = basePackageName; this.meta = meta; this.type = type; - className = getClassName(type.name); + this.className = getClassName(type.name); } - public String getClassName(String typeName) { + protected String getClassName(String typeName) { String[] pieces = typeName.split("_"); // We want just the last, but add an extra piece if invalid. We don't go recursive // or check top-level when going back because it's a rare occurrence and we're safe @@ -76,13 +72,14 @@ public String getPackageName(String typeName) { typeName = typeName.substring(10); } String[] pieces = typeName.split("_"); - // Skip the last one, it's the class name - for (int i = 0; i < pieces.length - 1; i++) { - String piece = pieces[i].toLowerCase(); - String replacement = keywordReplacements.get(piece); - piece = replacement != null ? replacement : piece; - pkg.append('.').append(getValidJavaIdentifier(piece)); - } + Arrays.stream(pieces) + .limit(pieces.length - 1) // Skip the last, it's the class name + .map(String::toLowerCase) + .map(piece -> { + String replacement = keywordReplacements.get(piece); + return replacement != null ? replacement : piece; + }) + .forEach(piece -> pkg.append('.').append(getValidJavaIdentifier(piece))); return pkg.toString(); } @@ -108,7 +105,7 @@ public TypeClass buildTypeClass() { Meta.Type baseMeta = null; String baseService = null; Meta.Type baseServiceMeta = null; - if (type.base != null && !"SoftLayer_Entity".equals(type.name)) { + if (type.base != null && !Meta.Type.BASE_TYPE_NAME.equals(type.name)) { String baseClassName = getClassName(type.base); base = getPackageName(type.base) + '.' + baseClassName; imports.put(baseClassName, base); @@ -132,7 +129,7 @@ public TypeClass buildTypeClass() { } public List getProperties() { - List properties = new ArrayList(type.properties.size()); + List properties = new ArrayList<>(type.properties.size()); for (Meta.Property property : type.properties.values()) { String javaType = getJavaType(property.type, property.typeArray); if (javaType != null) { @@ -148,12 +145,12 @@ public List getProperties() { } public List getMethods(Meta.Type baseMeta) { - List methods = new ArrayList(type.methods.size()); + List methods = new ArrayList<>(type.methods.size()); for (Meta.Method method : type.methods.values()) { String javaType = getJavaType(method.type, method.typeArray); if (javaType != null) { boolean allParametersValid = true; - List parameters = new ArrayList(method.parameters.size()); + List parameters = new ArrayList<>(method.parameters.size()); for (Meta.Parameter parameter : method.parameters) { String paramJavaType = getJavaType(parameter.type, parameter.typeArray); if (paramJavaType == null) { @@ -206,33 +203,51 @@ public List getMethods(Meta.Type baseMeta) { public String getJavaType(String typeName, boolean array) { String javaType; + // Attempt primitives first - if ("base64Binary".equals(typeName)) { - javaType = "byte[]"; - } else if ("boolean".equals(typeName)) { - javaType = "Boolean"; - } else if ("dateTime".equals(typeName)) { - javaType = "java.util.GregorianCalendar"; - imports.put("GregorianCalendar", javaType); - } else if ("decimal".equals(typeName) || "float".equals(typeName)) { - javaType = "java.math.BigDecimal"; - imports.put("BigDecimal", javaType); - } else if ("enum".equals(typeName) || "json".equals(typeName) || "string".equals(typeName)) { - javaType = "String"; - } else if ("int".equals(typeName) || "integer".equals(typeName) || - "unsignedInt".equals(typeName) || "unsignedLong".equals(typeName)) { - javaType = "Long"; - } else if ("nonNegativeInteger".equals(typeName)) { - javaType = "java.math.BigInteger"; - imports.put("BigInteger", javaType); - } else if ("void".equals(typeName)) { - javaType = "Void"; - } else if (!meta.types.containsKey(typeName)) { - return null; - } else { - String className = getClassName(typeName); - javaType = getPackageName(typeName) + '.' + className; - imports.put(className, javaType); + switch (typeName) { + case "base64Binary": + javaType = "byte[]"; + break; + case "boolean": + javaType = "Boolean"; + break; + case "dateTime": + javaType = GregorianCalendar.class.getName(); + imports.put("GregorianCalendar", javaType); + break; + case "decimal": + case "float": + javaType = BigDecimal.class.getName(); + imports.put("BigDecimal", javaType); + break; + case "enum": + case "json": + case "string": + javaType = "String"; + break; + case "int": + case "integer": + case "unsignedInt": + case "unsignedLong": + javaType = "Long"; + break; + case "nonNegativeInteger": + javaType = BigInteger.class.getName(); + imports.put("BigInteger", javaType); + break; + case "void": + javaType = "Void"; + break; + default: + if (!meta.types.containsKey(typeName)) { + return null; + } else { + String className = getClassName(typeName); + javaType = getPackageName(typeName) + '.' + className; + imports.put(className, javaType); + } + break; } if (array) { imports.put("List", "java.util.List"); diff --git a/gen/src/main/java/com/softlayer/api/gen/Restriction.java b/gen/src/main/java/com/softlayer/api/gen/Restriction.java index 1483178..6fecd22 100644 --- a/gen/src/main/java/com/softlayer/api/gen/Restriction.java +++ b/gen/src/main/java/com/softlayer/api/gen/Restriction.java @@ -7,11 +7,11 @@ public class Restriction { /** If empty, there is no restriction on any type */ - public final Set types = new HashSet(); + public final Set types = new HashSet<>(); /** If empty, there is no restriction on any property */ - public final Map> properties = new HashMap>(); + public final Map> properties = new HashMap<>(); /** If empty, there is no restriction on any method */ - public final Map> methods = new HashMap>(); + public final Map> methods = new HashMap<>(); } diff --git a/pom.xml b/pom.xml index 8b2256f..372516e 100644 --- a/pom.xml +++ b/pom.xml @@ -15,6 +15,14 @@ repo
+ + GitHub Issues + https://github.com/softlayer/softlayer-java/issues + + + Travis CI + https://travis-ci.org/softlayer/softlayer-java + camporter @@ -37,7 +45,7 @@ UTF-8 - 1.6 + 1.8 @@ -59,18 +67,18 @@ com.google.code.gson gson - 2.3 + 2.6 junit junit - 4.11 + 4.12 test org.mockito mockito-core - 1.10.4 + 2.8.47 test @@ -79,7 +87,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.0 + 3.7.0 ${java.version} ${java.version} @@ -88,7 +96,7 @@ org.apache.maven.plugins maven-source-plugin - 2.2.1 + 3.0.1 attach-sources @@ -101,7 +109,7 @@ org.apache.maven.plugins maven-javadoc-plugin - 2.9.1 + 3.0.0-M1 attach-javadocs @@ -119,7 +127,7 @@ org.apache.maven.plugins maven-gpg-plugin - 1.5 + 1.6 sign-artifacts @@ -133,7 +141,7 @@ maven-invoker-plugin - 1.8 + 3.0.1 generate-services diff --git a/src/main/java/com/softlayer/api/ApiClient.java b/src/main/java/com/softlayer/api/ApiClient.java index 70cf4b3..092d686 100644 --- a/src/main/java/com/softlayer/api/ApiClient.java +++ b/src/main/java/com/softlayer/api/ApiClient.java @@ -8,12 +8,12 @@ public interface ApiClient { * * @return This instance */ - public ApiClient withCredentials(String username, String apiKey); + ApiClient withCredentials(String username, String apiKey); /** * Get a service for the given sets of classes and optional ID. It is not recommended to call this * directly, but rather invoke the service method on the type class. * E.g. {@link com.softlayer.api.service.Account#service(ApiClient)}. */ - public S createService(Class serviceClass, String id); + S createService(Class serviceClass, String id); } diff --git a/src/main/java/com/softlayer/api/Mask.java b/src/main/java/com/softlayer/api/Mask.java index a62c3bb..3c35061 100644 --- a/src/main/java/com/softlayer/api/Mask.java +++ b/src/main/java/com/softlayer/api/Mask.java @@ -7,8 +7,8 @@ /** Object mask parameter. See http://sldn.softlayer.com/article/Object-Masks */ public class Mask { - private final Set localProperties = new HashSet(); - private final Map subMasks = new HashMap(); + private final Set localProperties = new HashSet<>(); + private final Map subMasks = new HashMap<>(); /** Clear out all previously masked objects and local properties */ public void clear() { diff --git a/src/main/java/com/softlayer/api/Maskable.java b/src/main/java/com/softlayer/api/Maskable.java index 81c1c7a..4a3093e 100644 --- a/src/main/java/com/softlayer/api/Maskable.java +++ b/src/main/java/com/softlayer/api/Maskable.java @@ -4,17 +4,17 @@ public interface Maskable { /** Overwrite the existing mask on this service with a new one and return it */ - public Mask withNewMask(); + Mask withNewMask(); /** Use the existing mask on this service or create it if not present */ - public Mask withMask(); + Mask withMask(); /** Set the mask to the given object */ - public void setMask(Mask mask); + void setMask(Mask mask); /** Set the mask to a string, formatted according to http://sldn.softlayer.com/article/Object-Masks */ - public void setMask(String mask); + void setMask(String mask); /** Removes the mask from the service */ - public void clearMask(); + void clearMask(); } diff --git a/src/main/java/com/softlayer/api/ResponseHandler.java b/src/main/java/com/softlayer/api/ResponseHandler.java index edb25f3..a807a35 100644 --- a/src/main/java/com/softlayer/api/ResponseHandler.java +++ b/src/main/java/com/softlayer/api/ResponseHandler.java @@ -4,8 +4,8 @@ public interface ResponseHandler { /** Called when the method errored. This is NOT called when onSuccess errors. */ - public void onError(Exception ex); + void onError(Exception ex); /** Called when the method succeeds. */ - public void onSuccess(T value); + void onSuccess(T value); } diff --git a/src/main/java/com/softlayer/api/RestApiClient.java b/src/main/java/com/softlayer/api/RestApiClient.java index 35ea57b..46afa0e 100644 --- a/src/main/java/com/softlayer/api/RestApiClient.java +++ b/src/main/java/com/softlayer/api/RestApiClient.java @@ -9,11 +9,7 @@ import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.net.URLEncoder; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Scanner; +import java.util.*; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; @@ -29,7 +25,9 @@ import com.softlayer.api.json.JsonMarshallerFactory; import com.softlayer.api.service.Entity; -/** Implementation of API client for http://sldn.softlayer.com/article/REST */ +/** + * Implementation of API client for http://sldn.softlayer.com/article/REST + */ public class RestApiClient implements ApiClient { public static final String BASE_URL = "https://api.softlayer.com/rest/v3.1/"; @@ -53,17 +51,22 @@ public class RestApiClient implements ApiClient { "editObject", "editObjects" ); - + private final String baseUrl; private HttpClientFactory httpClientFactory; private JsonMarshallerFactory jsonMarshallerFactory; private boolean loggingEnabled = false; private HttpBasicAuthCredentials credentials; - + public RestApiClient() { this(BASE_URL); } - + + /** + * Create a Rest client with a custom URL. + * + * @param baseUrl The custom URL the REST client will use. + */ public RestApiClient(String baseUrl) { // Add trailing slash if not present if (!baseUrl.endsWith("/")) { @@ -71,7 +74,7 @@ public RestApiClient(String baseUrl) { } this.baseUrl = baseUrl; } - + public String getBaseUrl() { return baseUrl; } @@ -137,14 +140,17 @@ protected void writeParameterHttpBody(Object[] params, OutputStream out) { } protected String getHttpMethodFromMethodName(String methodName) { - if ("deleteObject".equals(methodName)) { - return "DELETE"; - } else if ("createObject".equals(methodName) || "createObjects".equals(methodName)) { - return "POST"; - } else if ("editObject".equals(methodName) || "editObjects".equals(methodName)) { - return "PUT"; - } else { - return "GET"; + switch (methodName) { + case "deleteObject": + return "DELETE"; + case "createObject": + case "createObjects": + return "POST"; + case "editObject": + case "editObjects": + return "PUT"; + default: + return "GET"; } } @@ -172,6 +178,7 @@ protected String getFullUrl(String serviceName, String methodName, String id, } else if (!IMPLICIT_SERVICE_METHODS.contains(methodName)) { url.append('/').append(methodName); } + url.append(".json"); if (resultLimit != null) { url.append("?resultLimit=").append(resultLimit.offset).append(',').append(resultLimit.limit); @@ -211,7 +218,7 @@ protected void logResponse(String url, int statusCode, String body) { @SuppressWarnings("unchecked") public S createService(Class serviceClass, String id) { return (S) Proxy.newProxyInstance(getClass().getClassLoader(), - new Class[] { serviceClass }, new ServiceProxy(serviceClass, id)); + new Class[] { serviceClass }, new ServiceProxy<>(serviceClass, id)); } class ServiceProxy implements InvocationHandler { @@ -310,12 +317,9 @@ public Object invokeService(Method method, final Object[] args) throws Throwable final HttpClient client = getHttpClientFactory().getHttpClient(credentials, httpMethod, url, HEADERS); // Invoke with response - HttpResponse response = client.invokeSync(new Callable() { - @Override - public Void call() throws Exception { - logRequestAndWriteBody(client, httpMethod, url, args); - return null; - } + HttpResponse response = client.invokeSync(() -> { + logRequestAndWriteBody(client, httpMethod, url, args); + return null; }); return logAndHandleResponse(response, url, method.getGenericReturnType()); @@ -347,13 +351,10 @@ public Object invokeServiceAsync(final Method asyncMethod, final Object[] args) final String url = getFullUrl(serviceClass.getAnnotation(ApiService.class).value(), methodName, methodId, resultLimit, mask == null ? maskString : mask.getMask()); final HttpClient client = getHttpClientFactory().getHttpClient(credentials, httpMethod, url, HEADERS); - - Callable setupBody = new Callable() { - @Override - public Void call() throws Exception { - logRequestAndWriteBody(client, httpMethod, url, trimmedArgs); - return null; - } + + Callable setupBody = () -> { + logRequestAndWriteBody(client, httpMethod, url, trimmedArgs); + return null; }; if (lastParamCallback) { @@ -439,8 +440,9 @@ public boolean isDone() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { boolean noParams = args == null || args.length == 0; + if ("asAsync".equals(method.getName()) && noParams) { - ServiceProxy asyncProxy = new ServiceProxy(serviceClass, id); + ServiceProxy asyncProxy = new ServiceProxy<>(serviceClass, id); asyncProxy.mask = mask; asyncProxy.maskString = maskString; asyncProxy.resultLimit = resultLimit; diff --git a/src/main/java/com/softlayer/api/ResultLimitable.java b/src/main/java/com/softlayer/api/ResultLimitable.java index a7a80f1..6dd8719 100644 --- a/src/main/java/com/softlayer/api/ResultLimitable.java +++ b/src/main/java/com/softlayer/api/ResultLimitable.java @@ -3,10 +3,10 @@ /** Interface implemented by services to support pagination */ public interface ResultLimitable { - public ResultLimit getResultLimit(); + ResultLimit getResultLimit(); - public ResultLimit setResultLimit(ResultLimit limit); + ResultLimit setResultLimit(ResultLimit limit); /** The non-paginated total item count. This can be overwritten if a service is reused */ - public Integer getLastResponseTotalItemCount(); + Integer getLastResponseTotalItemCount(); } diff --git a/src/main/java/com/softlayer/api/Service.java b/src/main/java/com/softlayer/api/Service.java index db71a62..57b30a6 100644 --- a/src/main/java/com/softlayer/api/Service.java +++ b/src/main/java/com/softlayer/api/Service.java @@ -4,5 +4,5 @@ public interface Service extends Maskable, ResultLimitable { /** Get an async version of this service */ - public ServiceAsync asAsync(); + ServiceAsync asAsync(); } diff --git a/src/main/java/com/softlayer/api/Type.java b/src/main/java/com/softlayer/api/Type.java index f3f277b..158ec49 100644 --- a/src/main/java/com/softlayer/api/Type.java +++ b/src/main/java/com/softlayer/api/Type.java @@ -25,6 +25,6 @@ public Map getUnknownProperties() { */ public void setUnknownProperties(Map unknownProperties) { this.unknownProperties = Collections.unmodifiableMap( - new HashMap(unknownProperties)); + new HashMap<>(unknownProperties)); } } diff --git a/src/main/java/com/softlayer/api/http/BuiltInHttpClientFactory.java b/src/main/java/com/softlayer/api/http/BuiltInHttpClientFactory.java index da13a30..06f5580 100644 --- a/src/main/java/com/softlayer/api/http/BuiltInHttpClientFactory.java +++ b/src/main/java/com/softlayer/api/http/BuiltInHttpClientFactory.java @@ -5,7 +5,6 @@ import java.io.OutputStream; import java.io.UnsupportedEncodingException; import java.net.HttpURLConnection; -import java.net.ProtocolException; import java.net.URL; import java.util.List; import java.util.Map; @@ -17,8 +16,6 @@ import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; -import javax.xml.bind.DatatypeConverter; - import com.softlayer.api.ResponseHandler; /** @@ -98,8 +95,12 @@ class BuiltInHttpClient implements HttpClient, HttpResponse { final Map> headers; HttpURLConnection connection; - public BuiltInHttpClient(HttpCredentials credentials, String method, - String fullUrl, Map> headers) { + public BuiltInHttpClient( + HttpCredentials credentials, + String method, + String fullUrl, + Map> headers + ) { // We only support basic auth if (credentials != null && !(credentials instanceof HttpBasicAuthCredentials)) { throw new UnsupportedOperationException("Only basic auth is supported, not " + credentials.getClass()); @@ -138,12 +139,11 @@ public HttpResponse invokeSync(Callable setupBody) { // fairly fast and safe. openConnection(); if (credentials != null) { - // XXX: Using JAXB datatype converter here because it's the only base 64 I trust to be around... - // should we embed a base 64 encoder in here? - HttpBasicAuthCredentials authCredentials = (HttpBasicAuthCredentials) credentials; try { - connection.addRequestProperty("Authorization", "Basic " + new String(DatatypeConverter. - printBase64Binary((authCredentials.username + ':' + authCredentials.apiKey).getBytes("UTF-8")))); + connection.addRequestProperty( + "Authorization", + credentials.getHeader() + ); } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); } @@ -153,14 +153,8 @@ public HttpResponse invokeSync(Callable setupBody) { connection.addRequestProperty(headerEntry.getKey(), headerValue); } } - if (!"GET".equals(method)) { - try { - connection.setRequestMethod(method); - } catch (ProtocolException e) { - throw new RuntimeException(e); - } - } try { + connection.setRequestMethod(method); setupBody.call(); } catch (Exception e) { throw new RuntimeException(e); @@ -170,31 +164,21 @@ public HttpResponse invokeSync(Callable setupBody) { @Override public Future invokeAsync(final Callable setupBody) { - return getThreadPool().submit(new Callable() { - @Override - public HttpResponse call() throws Exception { - // We let any exception here properly bubble out of the future - HttpResponse response = invokeSync(setupBody); - return response; - } - }); + return getThreadPool().submit(() -> invokeSync(setupBody)); } @Override public Future invokeAsync(final Callable setupBody, final ResponseHandler callback) { - return getThreadPool().submit(new Callable() { - @Override - public Void call() throws Exception { - HttpResponse resp; - try { - resp = invokeSync(setupBody); - } catch (Exception e) { - callback.onError(e); - return null; - } - callback.onSuccess(resp); + return getThreadPool().submit(() -> { + HttpResponse response; + try { + response = invokeSync(setupBody); + } catch (Exception e) { + callback.onError(e); return null; } + callback.onSuccess(response); + return null; }); } diff --git a/src/main/java/com/softlayer/api/http/HttpBasicAuthCredentials.java b/src/main/java/com/softlayer/api/http/HttpBasicAuthCredentials.java index 4752926..9c36cc8 100644 --- a/src/main/java/com/softlayer/api/http/HttpBasicAuthCredentials.java +++ b/src/main/java/com/softlayer/api/http/HttpBasicAuthCredentials.java @@ -1,6 +1,9 @@ package com.softlayer.api.http; -/** HTTP basic authentication support for username and API key */ +import java.io.UnsupportedEncodingException; +import java.util.Base64; + +/** HTTP basic authorization support for username and API key */ public class HttpBasicAuthCredentials implements HttpCredentials { public final String username; @@ -10,4 +13,22 @@ public HttpBasicAuthCredentials(String username, String apiKey) { this.username = username; this.apiKey = apiKey; } + + /** + * Gets the encoded representation of the basic authentication credentials + * for use in an HTTP Authorization header. + * + * @return String + * @throws UnsupportedEncodingException If encoding with UTF-8 fails. + */ + public String getHeader() throws UnsupportedEncodingException + { + String authPair = username + ':' + apiKey; + String result = "Basic "; + result += new String( + Base64.getEncoder().encode(authPair.getBytes("UTF-8")), + "UTF-8" + ); + return result; + } } diff --git a/src/main/java/com/softlayer/api/http/HttpClient.java b/src/main/java/com/softlayer/api/http/HttpClient.java index b8c7602..f3a2496 100644 --- a/src/main/java/com/softlayer/api/http/HttpClient.java +++ b/src/main/java/com/softlayer/api/http/HttpClient.java @@ -14,14 +14,14 @@ public interface HttpClient extends Closeable { /** Stream to write body contents to (if at all). When called, callers are expected to close it. */ - public OutputStream getBodyStream(); + OutputStream getBodyStream(); /** Make synchronous HTTP invocation. Throws if unable to connect. Errors from the API are returned normally. */ - public HttpResponse invokeSync(Callable setupBody); + HttpResponse invokeSync(Callable setupBody); /** Make asynchronous HTTP invocation. All errors (inability to connect or API errors) are in the future. */ - public Future invokeAsync(Callable setupBody); + Future invokeAsync(Callable setupBody); /** Callback-form of {@link #invokeAsync(Callable)} */ - public Future invokeAsync(Callable setupBody, ResponseHandler callback); + Future invokeAsync(Callable setupBody, ResponseHandler callback); } diff --git a/src/main/java/com/softlayer/api/http/HttpResponse.java b/src/main/java/com/softlayer/api/http/HttpResponse.java index 817081e..d444c46 100644 --- a/src/main/java/com/softlayer/api/http/HttpResponse.java +++ b/src/main/java/com/softlayer/api/http/HttpResponse.java @@ -7,10 +7,10 @@ /** Interface representing an HTTP response from the HTTP client */ public interface HttpResponse { - public int getStatusCode(); + int getStatusCode(); - public Map> getHeaders(); + Map> getHeaders(); /** When this is used by the caller, he is expected to close it */ - public InputStream getInputStream(); + InputStream getInputStream(); } diff --git a/src/main/java/com/softlayer/api/json/Base64.java b/src/main/java/com/softlayer/api/json/Base64.java deleted file mode 100644 index 872e34f..0000000 --- a/src/main/java/com/softlayer/api/json/Base64.java +++ /dev/null @@ -1,2067 +0,0 @@ -package com.softlayer.api.json; - -// We use this public domain version because we cannot assume the user is on Java 8 - -/** - *

Encodes and decodes to and from Base64 notation.

- *

Homepage: http://iharder.net/base64.

- * - *

Example:

- * - * String encoded = Base64.encode( myByteArray ); - *
- * byte[] myByteArray = Base64.decode( encoded ); - * - *

The options parameter, which appears in a few places, is used to pass - * several pieces of information to the encoder. In the "higher level" methods such as - * encodeBytes( bytes, options ) the options parameter can be used to indicate such - * things as first gzipping the bytes before encoding them, not inserting linefeeds, - * and encoding using the URL-safe and Ordered dialects.

- * - *

Note, according to RFC3548, - * Section 2.1, implementations should not add line feeds unless explicitly told - * to do so. I've got Base64 set to this behavior now, although earlier versions - * broke lines by default.

- * - *

The constants defined in Base64 can be OR-ed together to combine options, so you - * might make a call like this:

- * - * String encoded = Base64.encodeBytes( mybytes, Base64.GZIP | Base64.DO_BREAK_LINES ); - *

to compress the data before encoding it and then making the output have newline characters.

- *

Also...

- * String encoded = Base64.encodeBytes( crazyString.getBytes() ); - * - * - * - *

- * Change Log: - *

- *
    - *
  • v2.3.7 - Fixed subtle bug when base 64 input stream contained the - * value 01111111, which is an invalid base 64 character but should not - * throw an ArrayIndexOutOfBoundsException either. Led to discovery of - * mishandling (or potential for better handling) of other bad input - * characters. You should now get an IOException if you try decoding - * something that has bad characters in it.
  • - *
  • v2.3.6 - Fixed bug when breaking lines and the final byte of the encoded - * string ended in the last column; the buffer was not properly shrunk and - * contained an extra (null) byte that made it into the string.
  • - *
  • v2.3.5 - Fixed bug in {@link #encodeFromFile} where estimated buffer size - * was wrong for files of size 31, 34, and 37 bytes.
  • - *
  • v2.3.4 - Fixed bug when working with gzipped streams whereby flushing - * the Base64.OutputStream closed the Base64 encoding (by padding with equals - * signs) too soon. Also added an option to suppress the automatic decoding - * of gzipped streams. Also added experimental support for specifying a - * class loader when using the - * {@link #decodeToObject(java.lang.String, int, java.lang.ClassLoader)} - * method.
  • - *
  • v2.3.3 - Changed default char encoding to US-ASCII which reduces the internal Java - * footprint with its CharEncoders and so forth. Fixed some javadocs that were - * inconsistent. Removed imports and specified things like java.io.IOException - * explicitly inline.
  • - *
  • v2.3.2 - Reduced memory footprint! Finally refined the "guessing" of how big the - * final encoded data will be so that the code doesn't have to create two output - * arrays: an oversized initial one and then a final, exact-sized one. Big win - * when using the {@link #encodeBytesToBytes(byte[])} family of methods (and not - * using the gzip options which uses a different mechanism with streams and stuff).
  • - *
  • v2.3.1 - Added {@link #encodeBytesToBytes(byte[], int, int, int)} and some - * similar helper methods to be more efficient with memory by not returning a - * String but just a byte array.
  • - *
  • v2.3 - This is not a drop-in replacement! This is two years of comments - * and bug fixes queued up and finally executed. Thanks to everyone who sent - * me stuff, and I'm sorry I wasn't able to distribute your fixes to everyone else. - * Much bad coding was cleaned up including throwing exceptions where necessary - * instead of returning null values or something similar. Here are some changes - * that may affect you: - *
      - *
    • Does not break lines, by default. This is to keep in compliance with - * RFC3548.
    • - *
    • Throws exceptions instead of returning null values. Because some operations - * (especially those that may permit the GZIP option) use IO streams, there - * is a possiblity of an java.io.IOException being thrown. After some discussion and - * thought, I've changed the behavior of the methods to throw java.io.IOExceptions - * rather than return null if ever there's an error. I think this is more - * appropriate, though it will require some changes to your code. Sorry, - * it should have been done this way to begin with.
    • - *
    • Removed all references to System.out, System.err, and the like. - * Shame on me. All I can say is sorry they were ever there.
    • - *
    • Throws NullPointerExceptions and IllegalArgumentExceptions as needed - * such as when passed arrays are null or offsets are invalid.
    • - *
    • Cleaned up as much javadoc as I could to avoid any javadoc warnings. - * This was especially annoying before for people who were thorough in their - * own projects and then had gobs of javadoc warnings on this file.
    • - *
    - *
  • v2.2.1 - Fixed bug using URL_SAFE and ORDERED encodings. Fixed bug - * when using very small files (~< 40 bytes).
  • - *
  • v2.2 - Added some helper methods for encoding/decoding directly from - * one file to the next. Also added a main() method to support command line - * encoding/decoding from one file to the next. Also added these Base64 dialects: - *
      - *
    1. The default is RFC3548 format.
    2. - *
    3. Calling Base64.setFormat(Base64.BASE64_FORMAT.URLSAFE_FORMAT) generates - * URL and file name friendly format as described in Section 4 of RFC3548. - * http://www.faqs.org/rfcs/rfc3548.html
    4. - *
    5. Calling Base64.setFormat(Base64.BASE64_FORMAT.ORDERED_FORMAT) generates - * URL and file name friendly format that preserves lexical ordering as described - * in http://www.faqs.org/qa/rfcc-1940.html
    6. - *
    - * Special thanks to Jim Kellerman at http://www.powerset.com/ - * for contributing the new Base64 dialects. - *
  • - * - *
  • v2.1 - Cleaned up javadoc comments and unused variables and methods. Added - * some convenience methods for reading and writing to and from files.
  • - *
  • v2.0.2 - Now specifies UTF-8 encoding in places where the code fails on systems - * with other encodings (like EBCDIC).
  • - *
  • v2.0.1 - Fixed an error when decoding a single byte, that is, when the - * encoded data was a single byte.
  • - *
  • v2.0 - I got rid of methods that used booleans to set options. - * Now everything is more consolidated and cleaner. The code now detects - * when data that's being decoded is gzip-compressed and will decompress it - * automatically. Generally things are cleaner. You'll probably have to - * change some method calls that you were making to support the new - * options format (ints that you "OR" together).
  • - *
  • v1.5.1 - Fixed bug when decompressing and decoding to a - * byte[] using decode( String s, boolean gzipCompressed ). - * Added the ability to "suspend" encoding in the Output Stream so - * you can turn on and off the encoding if you need to embed base64 - * data in an otherwise "normal" stream (like an XML file).
  • - *
  • v1.5 - Output stream pases on flush() command but doesn't do anything itself. - * This helps when using GZIP streams. - * Added the ability to GZip-compress objects before encoding them.
  • - *
  • v1.4 - Added helper methods to read/write files.
  • - *
  • v1.3.6 - Fixed OutputStream.flush() so that 'position' is reset.
  • - *
  • v1.3.5 - Added flag to turn on and off line breaks. Fixed bug in input stream - * where last buffer being read, if not completely full, was not returned.
  • - *
  • v1.3.4 - Fixed when "improperly padded stream" error was thrown at the wrong time.
  • - *
  • v1.3.3 - Fixed I/O streams which were totally messed up.
  • - *
- * - *

- * I am placing this code in the Public Domain. Do with it as you will. - * This software comes with no guarantees or warranties but with - * plenty of well-wishing instead! - * Please visit http://iharder.net/base64 - * periodically to check for updates or to contribute improvements. - *

- * - * @author Robert Harder - * @author rob@iharder.net - * @version 2.3.7 - */ -class Base64 -{ - -/* ******** P U B L I C F I E L D S ******** */ - - - /** No options specified. Value is zero. */ - public final static int NO_OPTIONS = 0; - - /** Specify encoding in first bit. Value is one. */ - public final static int ENCODE = 1; - - - /** Specify decoding in first bit. Value is zero. */ - public final static int DECODE = 0; - - - /** Specify that data should be gzip-compressed in second bit. Value is two. */ - public final static int GZIP = 2; - - /** Specify that gzipped data should not be automatically gunzipped. */ - public final static int DONT_GUNZIP = 4; - - - /** Do break lines when encoding. Value is 8. */ - public final static int DO_BREAK_LINES = 8; - - /** - * Encode using Base64-like encoding that is URL- and Filename-safe as described - * in Section 4 of RFC3548: - * http://www.faqs.org/rfcs/rfc3548.html. - * It is important to note that data encoded this way is not officially valid Base64, - * or at the very least should not be called Base64 without also specifying that is - * was encoded using the URL- and Filename-safe dialect. - */ - public final static int URL_SAFE = 16; - - - /** - * Encode using the special "ordered" dialect of Base64 described here: - * http://www.faqs.org/qa/rfcc-1940.html. - */ - public final static int ORDERED = 32; - - -/* ******** P R I V A T E F I E L D S ******** */ - - - /** Maximum line length (76) of Base64 output. */ - private final static int MAX_LINE_LENGTH = 76; - - - /** The equals sign (=) as a byte. */ - private final static byte EQUALS_SIGN = (byte)'='; - - - /** The new line character (\n) as a byte. */ - private final static byte NEW_LINE = (byte)'\n'; - - - /** Preferred encoding. */ - private final static String PREFERRED_ENCODING = "US-ASCII"; - - - private final static byte WHITE_SPACE_ENC = -5; // Indicates white space in encoding - private final static byte EQUALS_SIGN_ENC = -1; // Indicates equals sign in encoding - - -/* ******** S T A N D A R D B A S E 6 4 A L P H A B E T ******** */ - - /** The 64 valid Base64 values. */ - /* Host platform me be something funny like EBCDIC, so we hardcode these values. */ - private final static byte[] _STANDARD_ALPHABET = { - (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F', (byte)'G', - (byte)'H', (byte)'I', (byte)'J', (byte)'K', (byte)'L', (byte)'M', (byte)'N', - (byte)'O', (byte)'P', (byte)'Q', (byte)'R', (byte)'S', (byte)'T', (byte)'U', - (byte)'V', (byte)'W', (byte)'X', (byte)'Y', (byte)'Z', - (byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e', (byte)'f', (byte)'g', - (byte)'h', (byte)'i', (byte)'j', (byte)'k', (byte)'l', (byte)'m', (byte)'n', - (byte)'o', (byte)'p', (byte)'q', (byte)'r', (byte)'s', (byte)'t', (byte)'u', - (byte)'v', (byte)'w', (byte)'x', (byte)'y', (byte)'z', - (byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', (byte)'5', - (byte)'6', (byte)'7', (byte)'8', (byte)'9', (byte)'+', (byte)'/' - }; - - - /** - * Translates a Base64 value to either its 6-bit reconstruction value - * or a negative number indicating some other meaning. - **/ - private final static byte[] _STANDARD_DECODABET = { - -9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 0 - 8 - -5,-5, // Whitespace: Tab and Linefeed - -9,-9, // Decimal 11 - 12 - -5, // Whitespace: Carriage Return - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 14 - 26 - -9,-9,-9,-9,-9, // Decimal 27 - 31 - -5, // Whitespace: Space - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 33 - 42 - 62, // Plus sign at decimal 43 - -9,-9,-9, // Decimal 44 - 46 - 63, // Slash at decimal 47 - 52,53,54,55,56,57,58,59,60,61, // Numbers zero through nine - -9,-9,-9, // Decimal 58 - 60 - -1, // Equals sign at decimal 61 - -9,-9,-9, // Decimal 62 - 64 - 0,1,2,3,4,5,6,7,8,9,10,11,12,13, // Letters 'A' through 'N' - 14,15,16,17,18,19,20,21,22,23,24,25, // Letters 'O' through 'Z' - -9,-9,-9,-9,-9,-9, // Decimal 91 - 96 - 26,27,28,29,30,31,32,33,34,35,36,37,38, // Letters 'a' through 'm' - 39,40,41,42,43,44,45,46,47,48,49,50,51, // Letters 'n' through 'z' - -9,-9,-9,-9,-9 // Decimal 123 - 127 - ,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 128 - 139 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255 - }; - - -/* ******** U R L S A F E B A S E 6 4 A L P H A B E T ******** */ - - /** - * Used in the URL- and Filename-safe dialect described in Section 4 of RFC3548: - * http://www.faqs.org/rfcs/rfc3548.html. - * Notice that the last two bytes become "hyphen" and "underscore" instead of "plus" and "slash." - */ - private final static byte[] _URL_SAFE_ALPHABET = { - (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F', (byte)'G', - (byte)'H', (byte)'I', (byte)'J', (byte)'K', (byte)'L', (byte)'M', (byte)'N', - (byte)'O', (byte)'P', (byte)'Q', (byte)'R', (byte)'S', (byte)'T', (byte)'U', - (byte)'V', (byte)'W', (byte)'X', (byte)'Y', (byte)'Z', - (byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e', (byte)'f', (byte)'g', - (byte)'h', (byte)'i', (byte)'j', (byte)'k', (byte)'l', (byte)'m', (byte)'n', - (byte)'o', (byte)'p', (byte)'q', (byte)'r', (byte)'s', (byte)'t', (byte)'u', - (byte)'v', (byte)'w', (byte)'x', (byte)'y', (byte)'z', - (byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', (byte)'5', - (byte)'6', (byte)'7', (byte)'8', (byte)'9', (byte)'-', (byte)'_' - }; - - /** - * Used in decoding URL- and Filename-safe dialects of Base64. - */ - private final static byte[] _URL_SAFE_DECODABET = { - -9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 0 - 8 - -5,-5, // Whitespace: Tab and Linefeed - -9,-9, // Decimal 11 - 12 - -5, // Whitespace: Carriage Return - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 14 - 26 - -9,-9,-9,-9,-9, // Decimal 27 - 31 - -5, // Whitespace: Space - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 33 - 42 - -9, // Plus sign at decimal 43 - -9, // Decimal 44 - 62, // Minus sign at decimal 45 - -9, // Decimal 46 - -9, // Slash at decimal 47 - 52,53,54,55,56,57,58,59,60,61, // Numbers zero through nine - -9,-9,-9, // Decimal 58 - 60 - -1, // Equals sign at decimal 61 - -9,-9,-9, // Decimal 62 - 64 - 0,1,2,3,4,5,6,7,8,9,10,11,12,13, // Letters 'A' through 'N' - 14,15,16,17,18,19,20,21,22,23,24,25, // Letters 'O' through 'Z' - -9,-9,-9,-9, // Decimal 91 - 94 - 63, // Underscore at decimal 95 - -9, // Decimal 96 - 26,27,28,29,30,31,32,33,34,35,36,37,38, // Letters 'a' through 'm' - 39,40,41,42,43,44,45,46,47,48,49,50,51, // Letters 'n' through 'z' - -9,-9,-9,-9,-9 // Decimal 123 - 127 - ,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 128 - 139 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255 - }; - - - -/* ******** O R D E R E D B A S E 6 4 A L P H A B E T ******** */ - - /** - * I don't get the point of this technique, but someone requested it, - * and it is described here: - * http://www.faqs.org/qa/rfcc-1940.html. - */ - private final static byte[] _ORDERED_ALPHABET = { - (byte)'-', - (byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', - (byte)'5', (byte)'6', (byte)'7', (byte)'8', (byte)'9', - (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F', (byte)'G', - (byte)'H', (byte)'I', (byte)'J', (byte)'K', (byte)'L', (byte)'M', (byte)'N', - (byte)'O', (byte)'P', (byte)'Q', (byte)'R', (byte)'S', (byte)'T', (byte)'U', - (byte)'V', (byte)'W', (byte)'X', (byte)'Y', (byte)'Z', - (byte)'_', - (byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e', (byte)'f', (byte)'g', - (byte)'h', (byte)'i', (byte)'j', (byte)'k', (byte)'l', (byte)'m', (byte)'n', - (byte)'o', (byte)'p', (byte)'q', (byte)'r', (byte)'s', (byte)'t', (byte)'u', - (byte)'v', (byte)'w', (byte)'x', (byte)'y', (byte)'z' - }; - - /** - * Used in decoding the "ordered" dialect of Base64. - */ - private final static byte[] _ORDERED_DECODABET = { - -9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 0 - 8 - -5,-5, // Whitespace: Tab and Linefeed - -9,-9, // Decimal 11 - 12 - -5, // Whitespace: Carriage Return - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 14 - 26 - -9,-9,-9,-9,-9, // Decimal 27 - 31 - -5, // Whitespace: Space - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 33 - 42 - -9, // Plus sign at decimal 43 - -9, // Decimal 44 - 0, // Minus sign at decimal 45 - -9, // Decimal 46 - -9, // Slash at decimal 47 - 1,2,3,4,5,6,7,8,9,10, // Numbers zero through nine - -9,-9,-9, // Decimal 58 - 60 - -1, // Equals sign at decimal 61 - -9,-9,-9, // Decimal 62 - 64 - 11,12,13,14,15,16,17,18,19,20,21,22,23, // Letters 'A' through 'M' - 24,25,26,27,28,29,30,31,32,33,34,35,36, // Letters 'N' through 'Z' - -9,-9,-9,-9, // Decimal 91 - 94 - 37, // Underscore at decimal 95 - -9, // Decimal 96 - 38,39,40,41,42,43,44,45,46,47,48,49,50, // Letters 'a' through 'm' - 51,52,53,54,55,56,57,58,59,60,61,62,63, // Letters 'n' through 'z' - -9,-9,-9,-9,-9 // Decimal 123 - 127 - ,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 128 - 139 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243 - -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255 - }; - - -/* ******** D E T E R M I N E W H I C H A L H A B E T ******** */ - - - /** - * Returns one of the _SOMETHING_ALPHABET byte arrays depending on - * the options specified. - * It's possible, though silly, to specify ORDERED and URLSAFE - * in which case one of them will be picked, though there is - * no guarantee as to which one will be picked. - */ - private final static byte[] getAlphabet( int options ) { - if ((options & URL_SAFE) == URL_SAFE) { - return _URL_SAFE_ALPHABET; - } else if ((options & ORDERED) == ORDERED) { - return _ORDERED_ALPHABET; - } else { - return _STANDARD_ALPHABET; - } - } // end getAlphabet - - - /** - * Returns one of the _SOMETHING_DECODABET byte arrays depending on - * the options specified. - * It's possible, though silly, to specify ORDERED and URL_SAFE - * in which case one of them will be picked, though there is - * no guarantee as to which one will be picked. - */ - private final static byte[] getDecodabet( int options ) { - if( (options & URL_SAFE) == URL_SAFE) { - return _URL_SAFE_DECODABET; - } else if ((options & ORDERED) == ORDERED) { - return _ORDERED_DECODABET; - } else { - return _STANDARD_DECODABET; - } - } // end getAlphabet - - - - /** Defeats instantiation. */ - private Base64(){} - - - - -/* ******** E N C O D I N G M E T H O D S ******** */ - - - /** - * Encodes up to the first three bytes of array threeBytes - * and returns a four-byte array in Base64 notation. - * The actual number of significant bytes in your array is - * given by numSigBytes. - * The array threeBytes needs only be as big as - * numSigBytes. - * Code can reuse a byte array by passing a four-byte array as b4. - * - * @param b4 A reusable byte array to reduce array instantiation - * @param threeBytes the array to convert - * @param numSigBytes the number of significant bytes in your array - * @return four byte array in Base64 notation. - * @since 1.5.1 - */ - private static byte[] encode3to4( byte[] b4, byte[] threeBytes, int numSigBytes, int options ) { - encode3to4( threeBytes, 0, numSigBytes, b4, 0, options ); - return b4; - } // end encode3to4 - - - /** - *

Encodes up to three bytes of the array source - * and writes the resulting four Base64 bytes to destination. - * The source and destination arrays can be manipulated - * anywhere along their length by specifying - * srcOffset and destOffset. - * This method does not check to make sure your arrays - * are large enough to accomodate srcOffset + 3 for - * the source array or destOffset + 4 for - * the destination array. - * The actual number of significant bytes in your array is - * given by numSigBytes.

- *

This is the lowest level of the encoding methods with - * all possible parameters.

- * - * @param source the array to convert - * @param srcOffset the index where conversion begins - * @param numSigBytes the number of significant bytes in your array - * @param destination the array to hold the conversion - * @param destOffset the index where output will be put - * @return the destination array - * @since 1.3 - */ - private static byte[] encode3to4( - byte[] source, int srcOffset, int numSigBytes, - byte[] destination, int destOffset, int options ) { - - byte[] ALPHABET = getAlphabet( options ); - - // 1 2 3 - // 01234567890123456789012345678901 Bit position - // --------000000001111111122222222 Array position from threeBytes - // --------| || || || | Six bit groups to index ALPHABET - // >>18 >>12 >> 6 >> 0 Right shift necessary - // 0x3f 0x3f 0x3f Additional AND - - // Create buffer with zero-padding if there are only one or two - // significant bytes passed in the array. - // We have to shift left 24 in order to flush out the 1's that appear - // when Java treats a value as negative that is cast from a byte to an int. - int inBuff = ( numSigBytes > 0 ? ((source[ srcOffset ] << 24) >>> 8) : 0 ) - | ( numSigBytes > 1 ? ((source[ srcOffset + 1 ] << 24) >>> 16) : 0 ) - | ( numSigBytes > 2 ? ((source[ srcOffset + 2 ] << 24) >>> 24) : 0 ); - - switch( numSigBytes ) - { - case 3: - destination[ destOffset ] = ALPHABET[ (inBuff >>> 18) ]; - destination[ destOffset + 1 ] = ALPHABET[ (inBuff >>> 12) & 0x3f ]; - destination[ destOffset + 2 ] = ALPHABET[ (inBuff >>> 6) & 0x3f ]; - destination[ destOffset + 3 ] = ALPHABET[ (inBuff ) & 0x3f ]; - return destination; - - case 2: - destination[ destOffset ] = ALPHABET[ (inBuff >>> 18) ]; - destination[ destOffset + 1 ] = ALPHABET[ (inBuff >>> 12) & 0x3f ]; - destination[ destOffset + 2 ] = ALPHABET[ (inBuff >>> 6) & 0x3f ]; - destination[ destOffset + 3 ] = EQUALS_SIGN; - return destination; - - case 1: - destination[ destOffset ] = ALPHABET[ (inBuff >>> 18) ]; - destination[ destOffset + 1 ] = ALPHABET[ (inBuff >>> 12) & 0x3f ]; - destination[ destOffset + 2 ] = EQUALS_SIGN; - destination[ destOffset + 3 ] = EQUALS_SIGN; - return destination; - - default: - return destination; - } // end switch - } // end encode3to4 - - - - /** - * Performs Base64 encoding on the raw ByteBuffer, - * writing it to the encoded ByteBuffer. - * This is an experimental feature. Currently it does not - * pass along any options (such as {@link #DO_BREAK_LINES} - * or {@link #GZIP}. - * - * @param raw input buffer - * @param encoded output buffer - * @since 2.3 - */ - public static void encode( java.nio.ByteBuffer raw, java.nio.ByteBuffer encoded ){ - byte[] raw3 = new byte[3]; - byte[] enc4 = new byte[4]; - - while( raw.hasRemaining() ){ - int rem = Math.min(3,raw.remaining()); - raw.get(raw3,0,rem); - Base64.encode3to4(enc4, raw3, rem, Base64.NO_OPTIONS ); - encoded.put(enc4); - } // end input remaining - } - - - /** - * Performs Base64 encoding on the raw ByteBuffer, - * writing it to the encoded CharBuffer. - * This is an experimental feature. Currently it does not - * pass along any options (such as {@link #DO_BREAK_LINES} - * or {@link #GZIP}. - * - * @param raw input buffer - * @param encoded output buffer - * @since 2.3 - */ - public static void encode( java.nio.ByteBuffer raw, java.nio.CharBuffer encoded ){ - byte[] raw3 = new byte[3]; - byte[] enc4 = new byte[4]; - - while( raw.hasRemaining() ){ - int rem = Math.min(3,raw.remaining()); - raw.get(raw3,0,rem); - Base64.encode3to4(enc4, raw3, rem, Base64.NO_OPTIONS ); - for( int i = 0; i < 4; i++ ){ - encoded.put( (char)(enc4[i] & 0xFF) ); - } - } // end input remaining - } - - - - - /** - * Serializes an object and returns the Base64-encoded - * version of that serialized object. - * - *

As of v 2.3, if the object - * cannot be serialized or there is another error, - * the method will throw an java.io.IOException. This is new to v2.3! - * In earlier versions, it just returned a null value, but - * in retrospect that's a pretty poor way to handle it.

- * - * The object is not GZip-compressed before being encoded. - * - * @param serializableObject The object to encode - * @return The Base64-encoded object - * @throws java.io.IOException if there is an error - * @throws NullPointerException if serializedObject is null - * @since 1.4 - */ - public static String encodeObject( java.io.Serializable serializableObject ) - throws java.io.IOException { - return encodeObject( serializableObject, NO_OPTIONS ); - } // end encodeObject - - - - /** - * Serializes an object and returns the Base64-encoded - * version of that serialized object. - * - *

As of v 2.3, if the object - * cannot be serialized or there is another error, - * the method will throw an java.io.IOException. This is new to v2.3! - * In earlier versions, it just returned a null value, but - * in retrospect that's a pretty poor way to handle it.

- * - * The object is not GZip-compressed before being encoded. - *

- * Example options:

-     *   GZIP: gzip-compresses object before encoding it.
-     *   DO_BREAK_LINES: break lines at 76 characters
-     * 
- *

- * Example: encodeObject( myObj, Base64.GZIP ) or - *

- * Example: encodeObject( myObj, Base64.GZIP | Base64.DO_BREAK_LINES ) - * - * @param serializableObject The object to encode - * @param options Specified options - * @return The Base64-encoded object - * @see Base64#GZIP - * @see Base64#DO_BREAK_LINES - * @throws java.io.IOException if there is an error - * @since 2.0 - */ - public static String encodeObject( java.io.Serializable serializableObject, int options ) - throws java.io.IOException { - - if( serializableObject == null ){ - throw new NullPointerException( "Cannot serialize a null object." ); - } // end if: null - - // Streams - java.io.ByteArrayOutputStream baos = null; - java.io.OutputStream b64os = null; - java.util.zip.GZIPOutputStream gzos = null; - java.io.ObjectOutputStream oos = null; - - - try { - // ObjectOutputStream -> (GZIP) -> Base64 -> ByteArrayOutputStream - baos = new java.io.ByteArrayOutputStream(); - b64os = new Base64.OutputStream( baos, ENCODE | options ); - if( (options & GZIP) != 0 ){ - // Gzip - gzos = new java.util.zip.GZIPOutputStream(b64os); - oos = new java.io.ObjectOutputStream( gzos ); - } else { - // Not gzipped - oos = new java.io.ObjectOutputStream( b64os ); - } - oos.writeObject( serializableObject ); - } // end try - catch( java.io.IOException e ) { - // Catch it and then throw it immediately so that - // the finally{} block is called for cleanup. - throw e; - } // end catch - finally { - try{ oos.close(); } catch( Exception e ){} - try{ gzos.close(); } catch( Exception e ){} - try{ b64os.close(); } catch( Exception e ){} - try{ baos.close(); } catch( Exception e ){} - } // end finally - - // Return value according to relevant encoding. - try { - return new String( baos.toByteArray(), PREFERRED_ENCODING ); - } // end try - catch (java.io.UnsupportedEncodingException uue){ - // Fall back to some Java default - return new String( baos.toByteArray() ); - } // end catch - - } // end encode - - - - /** - * Encodes a byte array into Base64 notation. - * Does not GZip-compress data. - * - * @param source The data to convert - * @return The data in Base64-encoded form - * @throws NullPointerException if source array is null - * @since 1.4 - */ - public static String encodeBytes( byte[] source ) { - // Since we're not going to have the GZIP encoding turned on, - // we're not going to have an java.io.IOException thrown, so - // we should not force the user to have to catch it. - String encoded = null; - try { - encoded = encodeBytes(source, 0, source.length, NO_OPTIONS); - } catch (java.io.IOException ex) { - assert false : ex.getMessage(); - } // end catch - assert encoded != null; - return encoded; - } // end encodeBytes - - - - /** - * Encodes a byte array into Base64 notation. - *

- * Example options:

-     *   GZIP: gzip-compresses object before encoding it.
-     *   DO_BREAK_LINES: break lines at 76 characters
-     *     Note: Technically, this makes your encoding non-compliant.
-     * 
- *

- * Example: encodeBytes( myData, Base64.GZIP ) or - *

- * Example: encodeBytes( myData, Base64.GZIP | Base64.DO_BREAK_LINES ) - * - * - *

As of v 2.3, if there is an error with the GZIP stream, - * the method will throw an java.io.IOException. This is new to v2.3! - * In earlier versions, it just returned a null value, but - * in retrospect that's a pretty poor way to handle it.

- * - * - * @param source The data to convert - * @param options Specified options - * @return The Base64-encoded data as a String - * @see Base64#GZIP - * @see Base64#DO_BREAK_LINES - * @throws java.io.IOException if there is an error - * @throws NullPointerException if source array is null - * @since 2.0 - */ - public static String encodeBytes( byte[] source, int options ) throws java.io.IOException { - return encodeBytes( source, 0, source.length, options ); - } // end encodeBytes - - - /** - * Encodes a byte array into Base64 notation. - * Does not GZip-compress data. - * - *

As of v 2.3, if there is an error, - * the method will throw an java.io.IOException. This is new to v2.3! - * In earlier versions, it just returned a null value, but - * in retrospect that's a pretty poor way to handle it.

- * - * - * @param source The data to convert - * @param off Offset in array where conversion should begin - * @param len Length of data to convert - * @return The Base64-encoded data as a String - * @throws NullPointerException if source array is null - * @throws IllegalArgumentException if source array, offset, or length are invalid - * @since 1.4 - */ - public static String encodeBytes( byte[] source, int off, int len ) { - // Since we're not going to have the GZIP encoding turned on, - // we're not going to have an java.io.IOException thrown, so - // we should not force the user to have to catch it. - String encoded = null; - try { - encoded = encodeBytes( source, off, len, NO_OPTIONS ); - } catch (java.io.IOException ex) { - assert false : ex.getMessage(); - } // end catch - assert encoded != null; - return encoded; - } // end encodeBytes - - - - /** - * Encodes a byte array into Base64 notation. - *

- * Example options:

-     *   GZIP: gzip-compresses object before encoding it.
-     *   DO_BREAK_LINES: break lines at 76 characters
-     *     Note: Technically, this makes your encoding non-compliant.
-     * 
- *

- * Example: encodeBytes( myData, Base64.GZIP ) or - *

- * Example: encodeBytes( myData, Base64.GZIP | Base64.DO_BREAK_LINES ) - * - * - *

As of v 2.3, if there is an error with the GZIP stream, - * the method will throw an java.io.IOException. This is new to v2.3! - * In earlier versions, it just returned a null value, but - * in retrospect that's a pretty poor way to handle it.

- * - * - * @param source The data to convert - * @param off Offset in array where conversion should begin - * @param len Length of data to convert - * @param options Specified options - * @return The Base64-encoded data as a String - * @see Base64#GZIP - * @see Base64#DO_BREAK_LINES - * @throws java.io.IOException if there is an error - * @throws NullPointerException if source array is null - * @throws IllegalArgumentException if source array, offset, or length are invalid - * @since 2.0 - */ - public static String encodeBytes( byte[] source, int off, int len, int options ) throws java.io.IOException { - byte[] encoded = encodeBytesToBytes( source, off, len, options ); - - // Return value according to relevant encoding. - try { - return new String( encoded, PREFERRED_ENCODING ); - } // end try - catch (java.io.UnsupportedEncodingException uue) { - return new String( encoded ); - } // end catch - - } // end encodeBytes - - - - - /** - * Similar to {@link #encodeBytes(byte[])} but returns - * a byte array instead of instantiating a String. This is more efficient - * if you're working with I/O streams and have large data sets to encode. - * - * - * @param source The data to convert - * @return The Base64-encoded data as a byte[] (of ASCII characters) - * @throws NullPointerException if source array is null - * @since 2.3.1 - */ - public static byte[] encodeBytesToBytes( byte[] source ) { - byte[] encoded = null; - try { - encoded = encodeBytesToBytes( source, 0, source.length, Base64.NO_OPTIONS ); - } catch( java.io.IOException ex ) { - assert false : "IOExceptions only come from GZipping, which is turned off: " + ex.getMessage(); - } - return encoded; - } - - - /** - * Similar to {@link #encodeBytes(byte[], int, int, int)} but returns - * a byte array instead of instantiating a String. This is more efficient - * if you're working with I/O streams and have large data sets to encode. - * - * - * @param source The data to convert - * @param off Offset in array where conversion should begin - * @param len Length of data to convert - * @param options Specified options - * @return The Base64-encoded data as a String - * @see Base64#GZIP - * @see Base64#DO_BREAK_LINES - * @throws java.io.IOException if there is an error - * @throws NullPointerException if source array is null - * @throws IllegalArgumentException if source array, offset, or length are invalid - * @since 2.3.1 - */ - public static byte[] encodeBytesToBytes( byte[] source, int off, int len, int options ) throws java.io.IOException { - - if( source == null ){ - throw new NullPointerException( "Cannot serialize a null array." ); - } // end if: null - - if( off < 0 ){ - throw new IllegalArgumentException( "Cannot have negative offset: " + off ); - } // end if: off < 0 - - if( len < 0 ){ - throw new IllegalArgumentException( "Cannot have length offset: " + len ); - } // end if: len < 0 - - if( off + len > source.length ){ - throw new IllegalArgumentException( - String.format( "Cannot have offset of %d and length of %d with array of length %d", off,len,source.length)); - } // end if: off < 0 - - - - // Compress? - if( (options & GZIP) != 0 ) { - java.io.ByteArrayOutputStream baos = null; - java.util.zip.GZIPOutputStream gzos = null; - Base64.OutputStream b64os = null; - - try { - // GZip -> Base64 -> ByteArray - baos = new java.io.ByteArrayOutputStream(); - b64os = new Base64.OutputStream( baos, ENCODE | options ); - gzos = new java.util.zip.GZIPOutputStream( b64os ); - - gzos.write( source, off, len ); - gzos.close(); - } // end try - catch( java.io.IOException e ) { - // Catch it and then throw it immediately so that - // the finally{} block is called for cleanup. - throw e; - } // end catch - finally { - try{ gzos.close(); } catch( Exception e ){} - try{ b64os.close(); } catch( Exception e ){} - try{ baos.close(); } catch( Exception e ){} - } // end finally - - return baos.toByteArray(); - } // end if: compress - - // Else, don't compress. Better not to use streams at all then. - else { - boolean breakLines = (options & DO_BREAK_LINES) != 0; - - //int len43 = len * 4 / 3; - //byte[] outBuff = new byte[ ( len43 ) // Main 4:3 - // + ( (len % 3) > 0 ? 4 : 0 ) // Account for padding - // + (breakLines ? ( len43 / MAX_LINE_LENGTH ) : 0) ]; // New lines - // Try to determine more precisely how big the array needs to be. - // If we get it right, we don't have to do an array copy, and - // we save a bunch of memory. - int encLen = ( len / 3 ) * 4 + ( len % 3 > 0 ? 4 : 0 ); // Bytes needed for actual encoding - if( breakLines ){ - encLen += encLen / MAX_LINE_LENGTH; // Plus extra newline characters - } - byte[] outBuff = new byte[ encLen ]; - - - int d = 0; - int e = 0; - int len2 = len - 2; - int lineLength = 0; - for( ; d < len2; d+=3, e+=4 ) { - encode3to4( source, d+off, 3, outBuff, e, options ); - - lineLength += 4; - if( breakLines && lineLength >= MAX_LINE_LENGTH ) - { - outBuff[e+4] = NEW_LINE; - e++; - lineLength = 0; - } // end if: end of line - } // en dfor: each piece of array - - if( d < len ) { - encode3to4( source, d+off, len - d, outBuff, e, options ); - e += 4; - } // end if: some padding needed - - - // Only resize array if we didn't guess it right. - if( e <= outBuff.length - 1 ){ - // If breaking lines and the last byte falls right at - // the line length (76 bytes per line), there will be - // one extra byte, and the array will need to be resized. - // Not too bad of an estimate on array size, I'd say. - byte[] finalOut = new byte[e]; - System.arraycopy(outBuff,0, finalOut,0,e); - //System.err.println("Having to resize array from " + outBuff.length + " to " + e ); - return finalOut; - } else { - //System.err.println("No need to resize array."); - return outBuff; - } - - } // end else: don't compress - - } // end encodeBytesToBytes - - - - - -/* ******** D E C O D I N G M E T H O D S ******** */ - - - /** - * Decodes four bytes from array source - * and writes the resulting bytes (up to three of them) - * to destination. - * The source and destination arrays can be manipulated - * anywhere along their length by specifying - * srcOffset and destOffset. - * This method does not check to make sure your arrays - * are large enough to accomodate srcOffset + 4 for - * the source array or destOffset + 3 for - * the destination array. - * This method returns the actual number of bytes that - * were converted from the Base64 encoding. - *

This is the lowest level of the decoding methods with - * all possible parameters.

- * - * - * @param source the array to convert - * @param srcOffset the index where conversion begins - * @param destination the array to hold the conversion - * @param destOffset the index where output will be put - * @param options alphabet type is pulled from this (standard, url-safe, ordered) - * @return the number of decoded bytes converted - * @throws NullPointerException if source or destination arrays are null - * @throws IllegalArgumentException if srcOffset or destOffset are invalid - * or there is not enough room in the array. - * @since 1.3 - */ - private static int decode4to3( - byte[] source, int srcOffset, - byte[] destination, int destOffset, int options ) { - - // Lots of error checking and exception throwing - if( source == null ){ - throw new NullPointerException( "Source array was null." ); - } // end if - if( destination == null ){ - throw new NullPointerException( "Destination array was null." ); - } // end if - if( srcOffset < 0 || srcOffset + 3 >= source.length ){ - throw new IllegalArgumentException( String.format( - "Source array with length %d cannot have offset of %d and still process four bytes.", source.length, srcOffset ) ); - } // end if - if( destOffset < 0 || destOffset +2 >= destination.length ){ - throw new IllegalArgumentException( String.format( - "Destination array with length %d cannot have offset of %d and still store three bytes.", destination.length, destOffset ) ); - } // end if - - - byte[] DECODABET = getDecodabet( options ); - - // Example: Dk== - if( source[ srcOffset + 2] == EQUALS_SIGN ) { - // Two ways to do the same thing. Don't know which way I like best. - //int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 ) - // | ( ( DECODABET[ source[ srcOffset + 1] ] << 24 ) >>> 12 ); - int outBuff = ( ( DECODABET[ source[ srcOffset ] ] & 0xFF ) << 18 ) - | ( ( DECODABET[ source[ srcOffset + 1] ] & 0xFF ) << 12 ); - - destination[ destOffset ] = (byte)( outBuff >>> 16 ); - return 1; - } - - // Example: DkL= - else if( source[ srcOffset + 3 ] == EQUALS_SIGN ) { - // Two ways to do the same thing. Don't know which way I like best. - //int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 ) - // | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 ) - // | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 ); - int outBuff = ( ( DECODABET[ source[ srcOffset ] ] & 0xFF ) << 18 ) - | ( ( DECODABET[ source[ srcOffset + 1 ] ] & 0xFF ) << 12 ) - | ( ( DECODABET[ source[ srcOffset + 2 ] ] & 0xFF ) << 6 ); - - destination[ destOffset ] = (byte)( outBuff >>> 16 ); - destination[ destOffset + 1 ] = (byte)( outBuff >>> 8 ); - return 2; - } - - // Example: DkLE - else { - // Two ways to do the same thing. Don't know which way I like best. - //int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 ) - // | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 ) - // | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 ) - // | ( ( DECODABET[ source[ srcOffset + 3 ] ] << 24 ) >>> 24 ); - int outBuff = ( ( DECODABET[ source[ srcOffset ] ] & 0xFF ) << 18 ) - | ( ( DECODABET[ source[ srcOffset + 1 ] ] & 0xFF ) << 12 ) - | ( ( DECODABET[ source[ srcOffset + 2 ] ] & 0xFF ) << 6) - | ( ( DECODABET[ source[ srcOffset + 3 ] ] & 0xFF ) ); - - - destination[ destOffset ] = (byte)( outBuff >> 16 ); - destination[ destOffset + 1 ] = (byte)( outBuff >> 8 ); - destination[ destOffset + 2 ] = (byte)( outBuff ); - - return 3; - } - } // end decodeToBytes - - - - - - /** - * Low-level access to decoding ASCII characters in - * the form of a byte array. Ignores GUNZIP option, if - * it's set. This is not generally a recommended method, - * although it is used internally as part of the decoding process. - * Special case: if len = 0, an empty array is returned. Still, - * if you need more speed and reduced memory footprint (and aren't - * gzipping), consider this method. - * - * @param source The Base64 encoded data - * @return decoded data - * @since 2.3.1 - */ - public static byte[] decode( byte[] source ) - throws java.io.IOException { - byte[] decoded = null; -// try { - decoded = decode( source, 0, source.length, Base64.NO_OPTIONS ); -// } catch( java.io.IOException ex ) { -// assert false : "IOExceptions only come from GZipping, which is turned off: " + ex.getMessage(); -// } - return decoded; - } - - - - /** - * Low-level access to decoding ASCII characters in - * the form of a byte array. Ignores GUNZIP option, if - * it's set. This is not generally a recommended method, - * although it is used internally as part of the decoding process. - * Special case: if len = 0, an empty array is returned. Still, - * if you need more speed and reduced memory footprint (and aren't - * gzipping), consider this method. - * - * @param source The Base64 encoded data - * @param off The offset of where to begin decoding - * @param len The length of characters to decode - * @param options Can specify options such as alphabet type to use - * @return decoded data - * @throws java.io.IOException If bogus characters exist in source data - * @since 1.3 - */ - public static byte[] decode( byte[] source, int off, int len, int options ) - throws java.io.IOException { - - // Lots of error checking and exception throwing - if( source == null ){ - throw new NullPointerException( "Cannot decode null source array." ); - } // end if - if( off < 0 || off + len > source.length ){ - throw new IllegalArgumentException( String.format( - "Source array with length %d cannot have offset of %d and process %d bytes.", source.length, off, len ) ); - } // end if - - if( len == 0 ){ - return new byte[0]; - }else if( len < 4 ){ - throw new IllegalArgumentException( - "Base64-encoded string must have at least four characters, but length specified was " + len ); - } // end if - - byte[] DECODABET = getDecodabet( options ); - - int len34 = len * 3 / 4; // Estimate on array size - byte[] outBuff = new byte[ len34 ]; // Upper limit on size of output - int outBuffPosn = 0; // Keep track of where we're writing - - byte[] b4 = new byte[4]; // Four byte buffer from source, eliminating white space - int b4Posn = 0; // Keep track of four byte input buffer - int i = 0; // Source array counter - byte sbiDecode = 0; // Special value from DECODABET - - for( i = off; i < off+len; i++ ) { // Loop through source - - sbiDecode = DECODABET[ source[i]&0xFF ]; - - // White space, Equals sign, or legit Base64 character - // Note the values such as -5 and -9 in the - // DECODABETs at the top of the file. - if( sbiDecode >= WHITE_SPACE_ENC ) { - if( sbiDecode >= EQUALS_SIGN_ENC ) { - b4[ b4Posn++ ] = source[i]; // Save non-whitespace - if( b4Posn > 3 ) { // Time to decode? - outBuffPosn += decode4to3( b4, 0, outBuff, outBuffPosn, options ); - b4Posn = 0; - - // If that was the equals sign, break out of 'for' loop - if( source[i] == EQUALS_SIGN ) { - break; - } // end if: equals sign - } // end if: quartet built - } // end if: equals sign or better - } // end if: white space, equals sign or better - else { - // There's a bad input character in the Base64 stream. - throw new java.io.IOException( String.format( - "Bad Base64 input character decimal %d in array position %d", ((int)source[i])&0xFF, i ) ); - } // end else: - } // each input character - - byte[] out = new byte[ outBuffPosn ]; - System.arraycopy( outBuff, 0, out, 0, outBuffPosn ); - return out; - } // end decode - - - - - /** - * Decodes data from Base64 notation, automatically - * detecting gzip-compressed data and decompressing it. - * - * @param s the string to decode - * @return the decoded data - * @throws java.io.IOException If there is a problem - * @since 1.4 - */ - public static byte[] decode( String s ) throws java.io.IOException { - return decode( s, NO_OPTIONS ); - } - - - - /** - * Decodes data from Base64 notation, automatically - * detecting gzip-compressed data and decompressing it. - * - * @param s the string to decode - * @param options encode options such as URL_SAFE - * @return the decoded data - * @throws java.io.IOException if there is an error - * @throws NullPointerException if s is null - * @since 1.4 - */ - public static byte[] decode( String s, int options ) throws java.io.IOException { - - if( s == null ){ - throw new NullPointerException( "Input string was null." ); - } // end if - - byte[] bytes; - try { - bytes = s.getBytes( PREFERRED_ENCODING ); - } // end try - catch( java.io.UnsupportedEncodingException uee ) { - bytes = s.getBytes(); - } // end catch - // - - // Decode - bytes = decode( bytes, 0, bytes.length, options ); - - // Check to see if it's gzip-compressed - // GZIP Magic Two-Byte Number: 0x8b1f (35615) - boolean dontGunzip = (options & DONT_GUNZIP) != 0; - if( (bytes != null) && (bytes.length >= 4) && (!dontGunzip) ) { - - int head = ((int)bytes[0] & 0xff) | ((bytes[1] << 8) & 0xff00); - if( java.util.zip.GZIPInputStream.GZIP_MAGIC == head ) { - java.io.ByteArrayInputStream bais = null; - java.util.zip.GZIPInputStream gzis = null; - java.io.ByteArrayOutputStream baos = null; - byte[] buffer = new byte[2048]; - int length = 0; - - try { - baos = new java.io.ByteArrayOutputStream(); - bais = new java.io.ByteArrayInputStream( bytes ); - gzis = new java.util.zip.GZIPInputStream( bais ); - - while( ( length = gzis.read( buffer ) ) >= 0 ) { - baos.write(buffer,0,length); - } // end while: reading input - - // No error? Get new bytes. - bytes = baos.toByteArray(); - - } // end try - catch( java.io.IOException e ) { - e.printStackTrace(); - // Just return originally-decoded bytes - } // end catch - finally { - try{ baos.close(); } catch( Exception e ){} - try{ gzis.close(); } catch( Exception e ){} - try{ bais.close(); } catch( Exception e ){} - } // end finally - - } // end if: gzipped - } // end if: bytes.length >= 2 - - return bytes; - } // end decode - - - - /** - * Attempts to decode Base64 data and deserialize a Java - * Object within. Returns null if there was an error. - * - * @param encodedObject The Base64 data to decode - * @return The decoded and deserialized object - * @throws NullPointerException if encodedObject is null - * @throws java.io.IOException if there is a general error - * @throws ClassNotFoundException if the decoded object is of a - * class that cannot be found by the JVM - * @since 1.5 - */ - public static Object decodeToObject( String encodedObject ) - throws java.io.IOException, java.lang.ClassNotFoundException { - return decodeToObject(encodedObject,NO_OPTIONS,null); - } - - - /** - * Attempts to decode Base64 data and deserialize a Java - * Object within. Returns null if there was an error. - * If loader is not null, it will be the class loader - * used when deserializing. - * - * @param encodedObject The Base64 data to decode - * @param options Various parameters related to decoding - * @param loader Optional class loader to use in deserializing classes. - * @return The decoded and deserialized object - * @throws NullPointerException if encodedObject is null - * @throws java.io.IOException if there is a general error - * @throws ClassNotFoundException if the decoded object is of a - * class that cannot be found by the JVM - * @since 2.3.4 - */ - public static Object decodeToObject( - String encodedObject, int options, final ClassLoader loader ) - throws java.io.IOException, java.lang.ClassNotFoundException { - - // Decode and gunzip if necessary - byte[] objBytes = decode( encodedObject, options ); - - java.io.ByteArrayInputStream bais = null; - java.io.ObjectInputStream ois = null; - Object obj = null; - - try { - bais = new java.io.ByteArrayInputStream( objBytes ); - - // If no custom class loader is provided, use Java's builtin OIS. - if( loader == null ){ - ois = new java.io.ObjectInputStream( bais ); - } // end if: no loader provided - - // Else make a customized object input stream that uses - // the provided class loader. - else { - ois = new java.io.ObjectInputStream(bais){ - @Override - public Class resolveClass(java.io.ObjectStreamClass streamClass) - throws java.io.IOException, ClassNotFoundException { - Class c = Class.forName(streamClass.getName(), false, loader); - if( c == null ){ - return super.resolveClass(streamClass); - } else { - return c; // Class loader knows of this class. - } // end else: not null - } // end resolveClass - }; // end ois - } // end else: no custom class loader - - obj = ois.readObject(); - } // end try - catch( java.io.IOException e ) { - throw e; // Catch and throw in order to execute finally{} - } // end catch - catch( java.lang.ClassNotFoundException e ) { - throw e; // Catch and throw in order to execute finally{} - } // end catch - finally { - try{ bais.close(); } catch( Exception e ){} - try{ ois.close(); } catch( Exception e ){} - } // end finally - - return obj; - } // end decodeObject - - - - /** - * Convenience method for encoding data to a file. - * - *

As of v 2.3, if there is a error, - * the method will throw an java.io.IOException. This is new to v2.3! - * In earlier versions, it just returned false, but - * in retrospect that's a pretty poor way to handle it.

- * - * @param dataToEncode byte array of data to encode in base64 form - * @param filename Filename for saving encoded data - * @throws java.io.IOException if there is an error - * @throws NullPointerException if dataToEncode is null - * @since 2.1 - */ - public static void encodeToFile( byte[] dataToEncode, String filename ) - throws java.io.IOException { - - if( dataToEncode == null ){ - throw new NullPointerException( "Data to encode was null." ); - } // end iff - - Base64.OutputStream bos = null; - try { - bos = new Base64.OutputStream( - new java.io.FileOutputStream( filename ), Base64.ENCODE ); - bos.write( dataToEncode ); - } // end try - catch( java.io.IOException e ) { - throw e; // Catch and throw to execute finally{} block - } // end catch: java.io.IOException - finally { - try{ bos.close(); } catch( Exception e ){} - } // end finally - - } // end encodeToFile - - - /** - * Convenience method for decoding data to a file. - * - *

As of v 2.3, if there is a error, - * the method will throw an java.io.IOException. This is new to v2.3! - * In earlier versions, it just returned false, but - * in retrospect that's a pretty poor way to handle it.

- * - * @param dataToDecode Base64-encoded data as a string - * @param filename Filename for saving decoded data - * @throws java.io.IOException if there is an error - * @since 2.1 - */ - public static void decodeToFile( String dataToDecode, String filename ) - throws java.io.IOException { - - Base64.OutputStream bos = null; - try{ - bos = new Base64.OutputStream( - new java.io.FileOutputStream( filename ), Base64.DECODE ); - bos.write( dataToDecode.getBytes( PREFERRED_ENCODING ) ); - } // end try - catch( java.io.IOException e ) { - throw e; // Catch and throw to execute finally{} block - } // end catch: java.io.IOException - finally { - try{ bos.close(); } catch( Exception e ){} - } // end finally - - } // end decodeToFile - - - - - /** - * Convenience method for reading a base64-encoded - * file and decoding it. - * - *

As of v 2.3, if there is a error, - * the method will throw an java.io.IOException. This is new to v2.3! - * In earlier versions, it just returned false, but - * in retrospect that's a pretty poor way to handle it.

- * - * @param filename Filename for reading encoded data - * @return decoded byte array - * @throws java.io.IOException if there is an error - * @since 2.1 - */ - public static byte[] decodeFromFile( String filename ) - throws java.io.IOException { - - byte[] decodedData = null; - Base64.InputStream bis = null; - try - { - // Set up some useful variables - java.io.File file = new java.io.File( filename ); - byte[] buffer = null; - int length = 0; - int numBytes = 0; - - // Check for size of file - if( file.length() > Integer.MAX_VALUE ) - { - throw new java.io.IOException( "File is too big for this convenience method (" + file.length() + " bytes)." ); - } // end if: file too big for int index - buffer = new byte[ (int)file.length() ]; - - // Open a stream - bis = new Base64.InputStream( - new java.io.BufferedInputStream( - new java.io.FileInputStream( file ) ), Base64.DECODE ); - - // Read until done - while( ( numBytes = bis.read( buffer, length, 4096 ) ) >= 0 ) { - length += numBytes; - } // end while - - // Save in a variable to return - decodedData = new byte[ length ]; - System.arraycopy( buffer, 0, decodedData, 0, length ); - - } // end try - catch( java.io.IOException e ) { - throw e; // Catch and release to execute finally{} - } // end catch: java.io.IOException - finally { - try{ bis.close(); } catch( Exception e) {} - } // end finally - - return decodedData; - } // end decodeFromFile - - - - /** - * Convenience method for reading a binary file - * and base64-encoding it. - * - *

As of v 2.3, if there is a error, - * the method will throw an java.io.IOException. This is new to v2.3! - * In earlier versions, it just returned false, but - * in retrospect that's a pretty poor way to handle it.

- * - * @param filename Filename for reading binary data - * @return base64-encoded string - * @throws java.io.IOException if there is an error - * @since 2.1 - */ - public static String encodeFromFile( String filename ) - throws java.io.IOException { - - String encodedData = null; - Base64.InputStream bis = null; - try - { - // Set up some useful variables - java.io.File file = new java.io.File( filename ); - byte[] buffer = new byte[ Math.max((int)(file.length() * 1.4+1),40) ]; // Need max() for math on small files (v2.2.1); Need +1 for a few corner cases (v2.3.5) - int length = 0; - int numBytes = 0; - - // Open a stream - bis = new Base64.InputStream( - new java.io.BufferedInputStream( - new java.io.FileInputStream( file ) ), Base64.ENCODE ); - - // Read until done - while( ( numBytes = bis.read( buffer, length, 4096 ) ) >= 0 ) { - length += numBytes; - } // end while - - // Save in a variable to return - encodedData = new String( buffer, 0, length, Base64.PREFERRED_ENCODING ); - - } // end try - catch( java.io.IOException e ) { - throw e; // Catch and release to execute finally{} - } // end catch: java.io.IOException - finally { - try{ bis.close(); } catch( Exception e) {} - } // end finally - - return encodedData; - } // end encodeFromFile - - /** - * Reads infile and encodes it to outfile. - * - * @param infile Input file - * @param outfile Output file - * @throws java.io.IOException if there is an error - * @since 2.2 - */ - public static void encodeFileToFile( String infile, String outfile ) - throws java.io.IOException { - - String encoded = Base64.encodeFromFile( infile ); - java.io.OutputStream out = null; - try{ - out = new java.io.BufferedOutputStream( - new java.io.FileOutputStream( outfile ) ); - out.write( encoded.getBytes("US-ASCII") ); // Strict, 7-bit output. - } // end try - catch( java.io.IOException e ) { - throw e; // Catch and release to execute finally{} - } // end catch - finally { - try { out.close(); } - catch( Exception ex ){} - } // end finally - } // end encodeFileToFile - - - /** - * Reads infile and decodes it to outfile. - * - * @param infile Input file - * @param outfile Output file - * @throws java.io.IOException if there is an error - * @since 2.2 - */ - public static void decodeFileToFile( String infile, String outfile ) - throws java.io.IOException { - - byte[] decoded = Base64.decodeFromFile( infile ); - java.io.OutputStream out = null; - try{ - out = new java.io.BufferedOutputStream( - new java.io.FileOutputStream( outfile ) ); - out.write( decoded ); - } // end try - catch( java.io.IOException e ) { - throw e; // Catch and release to execute finally{} - } // end catch - finally { - try { out.close(); } - catch( Exception ex ){} - } // end finally - } // end decodeFileToFile - - - /* ******** I N N E R C L A S S I N P U T S T R E A M ******** */ - - - - /** - * A {@link Base64.InputStream} will read data from another - * java.io.InputStream, given in the constructor, - * and encode/decode to/from Base64 notation on the fly. - * - * @see Base64 - * @since 1.3 - */ - public static class InputStream extends java.io.FilterInputStream { - - private boolean encode; // Encoding or decoding - private int position; // Current position in the buffer - private byte[] buffer; // Small buffer holding converted data - private int bufferLength; // Length of buffer (3 or 4) - private int numSigBytes; // Number of meaningful bytes in the buffer - private int lineLength; - private boolean breakLines; // Break lines at less than 80 characters - private int options; // Record options used to create the stream. - private byte[] decodabet; // Local copies to avoid extra method calls - - - /** - * Constructs a {@link Base64.InputStream} in DECODE mode. - * - * @param in the java.io.InputStream from which to read data. - * @since 1.3 - */ - public InputStream( java.io.InputStream in ) { - this( in, DECODE ); - } // end constructor - - - /** - * Constructs a {@link Base64.InputStream} in - * either ENCODE or DECODE mode. - *

- * Valid options:

-         *   ENCODE or DECODE: Encode or Decode as data is read.
-         *   DO_BREAK_LINES: break lines at 76 characters
-         *     (only meaningful when encoding)
-         * 
- *

- * Example: new Base64.InputStream( in, Base64.DECODE ) - * - * - * @param in the java.io.InputStream from which to read data. - * @param options Specified options - * @see Base64#ENCODE - * @see Base64#DECODE - * @see Base64#DO_BREAK_LINES - * @since 2.0 - */ - public InputStream( java.io.InputStream in, int options ) { - - super( in ); - this.options = options; // Record for later - this.breakLines = (options & DO_BREAK_LINES) > 0; - this.encode = (options & ENCODE) > 0; - this.bufferLength = encode ? 4 : 3; - this.buffer = new byte[ bufferLength ]; - this.position = -1; - this.lineLength = 0; - this.decodabet = getDecodabet(options); - } // end constructor - - /** - * Reads enough of the input stream to convert - * to/from Base64 and returns the next byte. - * - * @return next byte - * @since 1.3 - */ - @Override - public int read() throws java.io.IOException { - - // Do we need to get data? - if( position < 0 ) { - if( encode ) { - byte[] b3 = new byte[3]; - int numBinaryBytes = 0; - for( int i = 0; i < 3; i++ ) { - int b = in.read(); - - // If end of stream, b is -1. - if( b >= 0 ) { - b3[i] = (byte)b; - numBinaryBytes++; - } else { - break; // out of for loop - } // end else: end of stream - - } // end for: each needed input byte - - if( numBinaryBytes > 0 ) { - encode3to4( b3, 0, numBinaryBytes, buffer, 0, options ); - position = 0; - numSigBytes = 4; - } // end if: got data - else { - return -1; // Must be end of stream - } // end else - } // end if: encoding - - // Else decoding - else { - byte[] b4 = new byte[4]; - int i = 0; - for( i = 0; i < 4; i++ ) { - // Read four "meaningful" bytes: - int b = 0; - do{ b = in.read(); } - while( b >= 0 && decodabet[ b & 0x7f ] <= WHITE_SPACE_ENC ); - - if( b < 0 ) { - break; // Reads a -1 if end of stream - } // end if: end of stream - - b4[i] = (byte)b; - } // end for: each needed input byte - - if( i == 4 ) { - numSigBytes = decode4to3( b4, 0, buffer, 0, options ); - position = 0; - } // end if: got four characters - else if( i == 0 ){ - return -1; - } // end else if: also padded correctly - else { - // Must have broken out from above. - throw new java.io.IOException( "Improperly padded Base64 input." ); - } // end - - } // end else: decode - } // end else: get data - - // Got data? - if( position >= 0 ) { - // End of relevant data? - if( /*!encode &&*/ position >= numSigBytes ){ - return -1; - } // end if: got data - - if( encode && breakLines && lineLength >= MAX_LINE_LENGTH ) { - lineLength = 0; - return '\n'; - } // end if - else { - lineLength++; // This isn't important when decoding - // but throwing an extra "if" seems - // just as wasteful. - - int b = buffer[ position++ ]; - - if( position >= bufferLength ) { - position = -1; - } // end if: end - - return b & 0xFF; // This is how you "cast" a byte that's - // intended to be unsigned. - } // end else - } // end if: position >= 0 - - // Else error - else { - throw new java.io.IOException( "Error in Base64 code reading stream." ); - } // end else - } // end read - - - /** - * Calls {@link #read()} repeatedly until the end of stream - * is reached or len bytes are read. - * Returns number of bytes read into array or -1 if - * end of stream is encountered. - * - * @param dest array to hold values - * @param off offset for array - * @param len max number of bytes to read into array - * @return bytes read into array or -1 if end of stream is encountered. - * @since 1.3 - */ - @Override - public int read( byte[] dest, int off, int len ) - throws java.io.IOException { - int i; - int b; - for( i = 0; i < len; i++ ) { - b = read(); - - if( b >= 0 ) { - dest[off + i] = (byte) b; - } - else if( i == 0 ) { - return -1; - } - else { - break; // Out of 'for' loop - } // Out of 'for' loop - } // end for: each byte read - return i; - } // end read - - } // end inner class InputStream - - - - - - - /* ******** I N N E R C L A S S O U T P U T S T R E A M ******** */ - - - - /** - * A {@link Base64.OutputStream} will write data to another - * java.io.OutputStream, given in the constructor, - * and encode/decode to/from Base64 notation on the fly. - * - * @see Base64 - * @since 1.3 - */ - public static class OutputStream extends java.io.FilterOutputStream { - - private boolean encode; - private int position; - private byte[] buffer; - private int bufferLength; - private int lineLength; - private boolean breakLines; - private byte[] b4; // Scratch used in a few places - private boolean suspendEncoding; - private int options; // Record for later - private byte[] decodabet; // Local copies to avoid extra method calls - - /** - * Constructs a {@link Base64.OutputStream} in ENCODE mode. - * - * @param out the java.io.OutputStream to which data will be written. - * @since 1.3 - */ - public OutputStream( java.io.OutputStream out ) { - this( out, ENCODE ); - } // end constructor - - - /** - * Constructs a {@link Base64.OutputStream} in - * either ENCODE or DECODE mode. - *

- * Valid options:

-         *   ENCODE or DECODE: Encode or Decode as data is read.
-         *   DO_BREAK_LINES: don't break lines at 76 characters
-         *     (only meaningful when encoding)
-         * 
- *

- * Example: new Base64.OutputStream( out, Base64.ENCODE ) - * - * @param out the java.io.OutputStream to which data will be written. - * @param options Specified options. - * @see Base64#ENCODE - * @see Base64#DECODE - * @see Base64#DO_BREAK_LINES - * @since 1.3 - */ - public OutputStream( java.io.OutputStream out, int options ) { - super( out ); - this.breakLines = (options & DO_BREAK_LINES) != 0; - this.encode = (options & ENCODE) != 0; - this.bufferLength = encode ? 3 : 4; - this.buffer = new byte[ bufferLength ]; - this.position = 0; - this.lineLength = 0; - this.suspendEncoding = false; - this.b4 = new byte[4]; - this.options = options; - this.decodabet = getDecodabet(options); - } // end constructor - - - /** - * Writes the byte to the output stream after - * converting to/from Base64 notation. - * When encoding, bytes are buffered three - * at a time before the output stream actually - * gets a write() call. - * When decoding, bytes are buffered four - * at a time. - * - * @param theByte the byte to write - * @since 1.3 - */ - @Override - public void write(int theByte) - throws java.io.IOException { - // Encoding suspended? - if( suspendEncoding ) { - this.out.write( theByte ); - return; - } // end if: supsended - - // Encode? - if( encode ) { - buffer[ position++ ] = (byte)theByte; - if( position >= bufferLength ) { // Enough to encode. - - this.out.write( encode3to4( b4, buffer, bufferLength, options ) ); - - lineLength += 4; - if( breakLines && lineLength >= MAX_LINE_LENGTH ) { - this.out.write( NEW_LINE ); - lineLength = 0; - } // end if: end of line - - position = 0; - } // end if: enough to output - } // end if: encoding - - // Else, Decoding - else { - // Meaningful Base64 character? - if( decodabet[ theByte & 0x7f ] > WHITE_SPACE_ENC ) { - buffer[ position++ ] = (byte)theByte; - if( position >= bufferLength ) { // Enough to output. - - int len = Base64.decode4to3( buffer, 0, b4, 0, options ); - out.write( b4, 0, len ); - position = 0; - } // end if: enough to output - } // end if: meaningful base64 character - else if( decodabet[ theByte & 0x7f ] != WHITE_SPACE_ENC ) { - throw new java.io.IOException( "Invalid character in Base64 data." ); - } // end else: not white space either - } // end else: decoding - } // end write - - - - /** - * Calls {@link #write(int)} repeatedly until len - * bytes are written. - * - * @param theBytes array from which to read bytes - * @param off offset for array - * @param len max number of bytes to read into array - * @since 1.3 - */ - @Override - public void write( byte[] theBytes, int off, int len ) - throws java.io.IOException { - // Encoding suspended? - if( suspendEncoding ) { - this.out.write( theBytes, off, len ); - return; - } // end if: supsended - - for( int i = 0; i < len; i++ ) { - write( theBytes[ off + i ] ); - } // end for: each byte written - - } // end write - - - - /** - * Method added by PHIL. [Thanks, PHIL. -Rob] - * This pads the buffer without closing the stream. - * @throws java.io.IOException if there's an error. - */ - public void flushBase64() throws java.io.IOException { - if( position > 0 ) { - if( encode ) { - out.write( encode3to4( b4, buffer, position, options ) ); - position = 0; - } // end if: encoding - else { - throw new java.io.IOException( "Base64 input not properly padded." ); - } // end else: decoding - } // end if: buffer partially full - - } // end flush - - - /** - * Flushes and closes (I think, in the superclass) the stream. - * - * @since 1.3 - */ - @Override - public void close() throws java.io.IOException { - // 1. Ensure that pending characters are written - flushBase64(); - - // 2. Actually close the stream - // Base class both flushes and closes. - super.close(); - - buffer = null; - out = null; - } // end close - - - - /** - * Suspends encoding of the stream. - * May be helpful if you need to embed a piece of - * base64-encoded data in a stream. - * - * @throws java.io.IOException if there's an error flushing - * @since 1.5.1 - */ - public void suspendEncoding() throws java.io.IOException { - flushBase64(); - this.suspendEncoding = true; - } // end suspendEncoding - - - /** - * Resumes encoding of the stream. - * May be helpful if you need to embed a piece of - * base64-encoded data in a stream. - * - * @since 1.5.1 - */ - public void resumeEncoding() { - this.suspendEncoding = false; - } // end resumeEncoding - - - - } // end inner class OutputStream - - -} // end class Base64 diff --git a/src/main/java/com/softlayer/api/json/GsonJsonMarshallerFactory.java b/src/main/java/com/softlayer/api/json/GsonJsonMarshallerFactory.java index 946652e..87bac33 100644 --- a/src/main/java/com/softlayer/api/json/GsonJsonMarshallerFactory.java +++ b/src/main/java/com/softlayer/api/json/GsonJsonMarshallerFactory.java @@ -15,6 +15,7 @@ import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; +import java.util.Base64; import java.util.GregorianCalendar; import java.util.HashMap; import java.util.List; @@ -55,7 +56,7 @@ class GsonJsonMarshallerFactory extends JsonMarshallerFactory implements JsonMar create(); ApiTypes types = Entity.class.getPackage().getAnnotation(ApiTypes.class); - typeClasses = new HashMap>(types.value().length); + typeClasses = new HashMap<>(types.value().length); for (Class clazz : types.value()) { typeClasses.put(clazz.getAnnotation(ApiType.class).value(), clazz); } @@ -93,7 +94,7 @@ public TypeAdapter create(Gson gson, TypeToken type) { return null; } // Obtain all ApiProperty fields and make them accessible... - Map fields = new HashMap(); + Map fields = new HashMap<>(); loadFields(typeClass, fields); return (TypeAdapter) new EntityTypeAdapter((Class) typeClass, fields); } @@ -208,12 +209,10 @@ private Entity readForThisType(JsonReader in) throws IOException { Entity entity; try { entity = typeClass.newInstance(); - } catch (InstantiationException e) { - throw new RuntimeException(e); - } catch (IllegalAccessException e) { + } catch (InstantiationException | IllegalAccessException e) { throw new RuntimeException(e); } - Map unknownProperties = new HashMap(); + Map unknownProperties = new HashMap<>(); while (in.hasNext()) { String propertyName = in.nextName(); EntityJsonField field = fields.get(propertyName); @@ -245,23 +244,16 @@ static class GregorianCalendarTypeAdapter extends TypeAdapter // can just remove/add the colon as necessary. This is a better solution than using // JAXB libraries. // Ref: http://stackoverflow.com/questions/2201925/converting-iso-8601-compliant-string-to-java-util-date - - final ThreadLocal secondFormat = new ThreadLocal() { - @Override - protected DateFormat initialValue() { - return new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ"); - } - }; + final ThreadLocal secondFormat = ThreadLocal.withInitial( + () -> new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ") + ); // Some times come back with fractions of a second all the way down to 6 digits. // Luckily we can just use the presence of a decimal point as a discriminator between // this format and the one above. - final ThreadLocal subSecondFormat = new ThreadLocal() { - @Override - protected DateFormat initialValue() { - return new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ"); - } - }; + final ThreadLocal subSecondFormat = ThreadLocal.withInitial( + () -> new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ") + ); @Override public void write(JsonWriter out, GregorianCalendar value) throws IOException { @@ -357,7 +349,7 @@ public void write(JsonWriter out, byte[] value) throws IOException { if (value == null) { out.nullValue(); } else { - out.value(Base64.encodeBytes(value)); + out.value(Base64.getEncoder().encodeToString(value)); } } @@ -366,8 +358,8 @@ public byte[] read(JsonReader in) throws IOException { if (in.peek() == JsonToken.NULL) { in.nextNull(); return null; - } - return Base64.decode(in.nextString()); + } + return Base64.getDecoder().decode(in.nextString()); } } @@ -409,7 +401,7 @@ public List read(JsonReader in) throws IOException { // We only take over if it's the beginning of an object, otherwise delegate if (in.peek() == JsonToken.BEGIN_OBJECT) { // Send back a mutable list of 1 - List result = new ArrayList(1); + List result = new ArrayList<>(1); result.add(instanceDelegate.read(in)); return result; } diff --git a/src/main/java/com/softlayer/api/json/JsonMarshaller.java b/src/main/java/com/softlayer/api/json/JsonMarshaller.java index b5f4b0f..0ee3848 100644 --- a/src/main/java/com/softlayer/api/json/JsonMarshaller.java +++ b/src/main/java/com/softlayer/api/json/JsonMarshaller.java @@ -11,8 +11,8 @@ public interface JsonMarshaller { /** Convert the given object to JSON on the stream. The output stream is closed by this marshaller */ - public void toJson(Object object, OutputStream out); + void toJson(Object object, OutputStream out); /** Convert the JSON stream to the given type. The input stream is closed by this marshaller */ - public T fromJson(Type type, InputStream in); + T fromJson(Type type, InputStream in); } diff --git a/src/test/java/com/softlayer/api/RestApiClientTest.java b/src/test/java/com/softlayer/api/RestApiClientTest.java index 92c8a70..481d030 100644 --- a/src/test/java/com/softlayer/api/RestApiClientTest.java +++ b/src/test/java/com/softlayer/api/RestApiClientTest.java @@ -85,39 +85,38 @@ private String withOutputCaptured(Callable closure) throws Exception { @Test public void testLogRequest() throws Exception { - assertEquals("Running VERB on URL with no body\n", withOutputCaptured(new Callable() { - @Override - public Void call() throws Exception { + assertEquals( + "Running VERB on URL with no body\n", + withOutputCaptured(() -> { new RestApiClient().logRequest("VERB", "URL", new Object[0]); return null; - } - })); - assertEquals("Running VERB on URL with body: {\"parameters\":[{\"key\":\"value\"}]}\n", - withOutputCaptured(new Callable() { - @Override - public Void call() throws Exception { - new RestApiClient().logRequest("VERB", "URL", - new Object[] { Collections.singletonMap("key", "value") }); - return null; - } - })); + }) + ); + assertEquals( + "Running VERB on URL with body: {\"parameters\":[{\"key\":\"value\"}]}\n", + withOutputCaptured(() -> { + new RestApiClient().logRequest("VERB", "URL", + new Object[]{Collections.singletonMap("key", "value")}); + return null; + }) + ); } @Test public void testLogResponse() throws Exception { - assertEquals("Got 123 on URL with body: some body\n", withOutputCaptured(new Callable() { - @Override - public Void call() throws Exception { + assertEquals( + "Got 123 on URL with body: some body\n", + withOutputCaptured(() -> { new RestApiClient().logResponse("URL", 123, "some body"); return null; - } - })); + }) + ); } @Test(expected = IllegalStateException.class) public void testFailedIfCallingNonStaticWithoutId() { FakeHttpClientFactory http = new FakeHttpClientFactory(123, - Collections.>emptyMap(), "some response"); + Collections.emptyMap(), "some response"); RestApiClient client = new RestApiClient("http://example.com/"); client.setHttpClientFactory(http); TestEntity.service(client).doSomethingNonStatic(new GregorianCalendar()); @@ -126,7 +125,7 @@ public void testFailedIfCallingNonStaticWithoutId() { @Test(expected = IllegalStateException.class) public void testFailedIfCallingNonStaticAsyncWithoutId() { FakeHttpClientFactory http = new FakeHttpClientFactory(123, - Collections.>emptyMap(), "some response"); + Collections.emptyMap(), "some response"); RestApiClient client = new RestApiClient("http://example.com/"); client.setHttpClientFactory(http); TestEntity.service(client).asAsync().doSomethingNonStatic(new GregorianCalendar()); @@ -135,7 +134,7 @@ public void testFailedIfCallingNonStaticAsyncWithoutId() { @Test public void testSuccess() throws Exception { FakeHttpClientFactory http = new FakeHttpClientFactory(200, - Collections.>emptyMap(), "\"some response\""); + Collections.emptyMap(), "\"some response\""); RestApiClient client = new RestApiClient("http://example.com/").withCredentials("user", "key"); client.setHttpClientFactory(http); TestEntity entity = new TestEntity(); @@ -154,9 +153,10 @@ public void testSuccess() throws Exception { @Test public void testBadRequestFailure() throws Exception { FakeHttpClientFactory http = new FakeHttpClientFactory(ApiException.BadRequest.STATUS, - Collections.>emptyMap(), + Collections.emptyMap(), "{\"error\": \"some error\", \"code\": \"some code\"}"); - RestApiClient client = new RestApiClient("http://example.com/").withCredentials("user", "key"); + RestApiClient client = new RestApiClient("http://example.com/") + .withCredentials("user", "key"); client.setHttpClientFactory(http); TestEntity entity = new TestEntity(); entity.setFoo("blah"); @@ -172,11 +172,14 @@ public void testBadRequestFailure() throws Exception { @Test public void testAsyncFutureSuccess() throws Exception { FakeHttpClientFactory http = new FakeHttpClientFactory(200, - Collections.>emptyMap(), "\"some response\""); - RestApiClient client = new RestApiClient("http://example.com/").withCredentials("user", "key"); + Collections.emptyMap(), "\"some response\""); + RestApiClient client = new RestApiClient("http://example.com/") + .withCredentials("user", "key"); client.setHttpClientFactory(http); + TestEntity entity = new TestEntity(); entity.setFoo("blah"); + assertEquals("some response", TestEntity.service(client).asAsync().doSomethingStatic(123L, entity).get()); assertEquals("user", ((HttpBasicAuthCredentials) http.credentials).username); assertEquals("key", ((HttpBasicAuthCredentials) http.credentials).apiKey); @@ -191,10 +194,12 @@ public void testAsyncFutureSuccess() throws Exception { @Test public void testAsyncFutureFailure() throws Exception { FakeHttpClientFactory http = new FakeHttpClientFactory(ApiException.BadRequest.STATUS, - Collections.>emptyMap(), + Collections.emptyMap(), "{\"error\": \"some error\", \"code\": \"some code\"}"); - RestApiClient client = new RestApiClient("http://example.com/").withCredentials("user", "key"); + RestApiClient client = new RestApiClient("http://example.com/") + .withCredentials("user", "key"); client.setHttpClientFactory(http); + TestEntity entity = new TestEntity(); entity.setFoo("blah"); try { @@ -209,9 +214,11 @@ public void testAsyncFutureFailure() throws Exception { @Test public void testAsyncCallbackSuccess() throws Exception { final FakeHttpClientFactory http = new FakeHttpClientFactory(200, - Collections.>emptyMap(), "\"some response\""); - RestApiClient client = new RestApiClient("http://example.com/").withCredentials("user", "key"); + Collections.emptyMap(), "\"some response\""); + RestApiClient client = new RestApiClient("http://example.com/") + .withCredentials("user", "key"); client.setHttpClientFactory(http); + TestEntity entity = new TestEntity(); entity.setFoo("blah"); final AtomicBoolean successCalled = new AtomicBoolean(); @@ -227,6 +234,7 @@ public void onSuccess(String value) { successCalled.set(true); } }).get(); + assertEquals("user", ((HttpBasicAuthCredentials) http.credentials).username); assertEquals("key", ((HttpBasicAuthCredentials) http.credentials).apiKey); assertEquals("GET", http.method); @@ -241,10 +249,12 @@ public void onSuccess(String value) { @Test public void testAsyncCallbackFailure() throws Exception { FakeHttpClientFactory http = new FakeHttpClientFactory(ApiException.BadRequest.STATUS, - Collections.>emptyMap(), + Collections.emptyMap(), "{\"error\": \"some error\", \"code\": \"some code\"}"); - RestApiClient client = new RestApiClient("http://example.com/").withCredentials("user", "key"); + RestApiClient client = new RestApiClient("http://example.com/") + .withCredentials("user", "key"); client.setHttpClientFactory(http); + TestEntity entity = new TestEntity(); entity.setFoo("blah"); final AtomicBoolean errorCalled = new AtomicBoolean(); @@ -267,19 +277,19 @@ public void onSuccess(String value) { @Test public void testCallWithLog() throws Exception { final FakeHttpClientFactory http = new FakeHttpClientFactory(200, - Collections.>emptyMap(), "\"some response\""); - String output = withOutputCaptured(new Callable() { - @Override - public Void call() throws Exception { - RestApiClient client = new RestApiClient("http://example.com/").withCredentials("user", "key"). - withLoggingEnabled(); + Collections.emptyMap(), "\"some response\""); + String output = withOutputCaptured(() -> { + RestApiClient client = new RestApiClient("http://example.com/") + .withCredentials("user", "key") + .withLoggingEnabled(); client.setHttpClientFactory(http); + TestEntity entity = new TestEntity(); entity.setFoo("blah"); TestEntity.service(client).doSomethingStatic(123L, entity); return null; - } }); + assertTrue(http.invokeSyncCalled); assertEquals( "Running GET on http://example.com/SoftLayer_TestEntity/doSomethingStatic.json with body: " @@ -292,9 +302,11 @@ public Void call() throws Exception { @Test public void testDifferentMethodName() throws Exception { FakeHttpClientFactory http = new FakeHttpClientFactory(200, - Collections.>emptyMap(), "[]"); - RestApiClient client = new RestApiClient("http://example.com/").withCredentials("user", "key"); + Collections.emptyMap(), "[]"); + RestApiClient client = new RestApiClient("http://example.com/") + .withCredentials("user", "key"); client.setHttpClientFactory(http); + assertEquals(Collections.emptyList(), TestEntity.service(client).fakeName()); assertEquals("http://example.com/SoftLayer_TestEntity/actualName.json", http.fullUrl); assertNull(http.outStream); @@ -305,14 +317,17 @@ public void testDifferentMethodName() throws Exception { @Test public void testWithMask() throws Exception { FakeHttpClientFactory http = new FakeHttpClientFactory(200, - Collections.>emptyMap(), "\"some response\""); - RestApiClient client = new RestApiClient("http://example.com/").withCredentials("user", "key"); + Collections.emptyMap(), "\"some response\""); + RestApiClient client = new RestApiClient("http://example.com/") + .withCredentials("user", "key"); client.setHttpClientFactory(http); + TestEntity entity = new TestEntity(); entity.setFoo("blah"); TestEntity.Service service = TestEntity.service(client); service.withMask().foo().child().date(); service.withMask().child().baz(); + assertEquals("some response", service.doSomethingStatic(123L, entity)); assertEquals("http://example.com/SoftLayer_TestEntity/doSomethingStatic.json" + "?objectMask=" + URLEncoder.encode(service.withMask().getMask(), "UTF-8"), http.fullUrl); @@ -322,9 +337,11 @@ public void testWithMask() throws Exception { @Test public void testSetObjectMask() throws Exception { FakeHttpClientFactory http = new FakeHttpClientFactory(200, - Collections.>emptyMap(), "\"some response\""); - RestApiClient client = new RestApiClient("http://example.com/").withCredentials("user", "key"); + Collections.emptyMap(), "\"some response\""); + RestApiClient client = new RestApiClient("http://example.com/") + .withCredentials("user", "key"); client.setHttpClientFactory(http); + TestEntity entity = new TestEntity(); entity.setFoo("blah"); TestEntity.Service service = TestEntity.service(client); @@ -332,6 +349,7 @@ public void testSetObjectMask() throws Exception { mask.foo().child().date(); mask.child().baz(); service.setMask(mask); + assertEquals("some response", service.doSomethingStatic(123L, entity)); assertEquals("http://example.com/SoftLayer_TestEntity/doSomethingStatic.json" + "?objectMask=" + URLEncoder.encode(mask.getMask(), "UTF-8"), http.fullUrl); @@ -341,13 +359,16 @@ public void testSetObjectMask() throws Exception { @Test public void testSetStringMask() throws Exception { FakeHttpClientFactory http = new FakeHttpClientFactory(200, - Collections.>emptyMap(), "\"some response\""); - RestApiClient client = new RestApiClient("http://example.com/").withCredentials("user", "key"); + Collections.emptyMap(), "\"some response\""); + RestApiClient client = new RestApiClient("http://example.com/") + .withCredentials("user", "key"); client.setHttpClientFactory(http); + TestEntity entity = new TestEntity(); entity.setFoo("blah"); TestEntity.Service service = TestEntity.service(client); service.setMask("yay-a-mask"); + assertEquals("some response", service.doSomethingStatic(123L, entity)); assertEquals("http://example.com/SoftLayer_TestEntity/doSomethingStatic.json" + "?objectMask=yay-a-mask", http.fullUrl); @@ -357,13 +378,16 @@ public void testSetStringMask() throws Exception { @Test public void testWithResultLimit() throws Exception { FakeHttpClientFactory http = new FakeHttpClientFactory(200, - Collections.>emptyMap(), "\"some response\""); - RestApiClient client = new RestApiClient("http://example.com/").withCredentials("user", "key"); + Collections.emptyMap(), "\"some response\""); + RestApiClient client = new RestApiClient("http://example.com/") + .withCredentials("user", "key"); client.setHttpClientFactory(http); + TestEntity entity = new TestEntity(); entity.setFoo("blah"); TestEntity.Service service = TestEntity.service(client); service.setResultLimit(new ResultLimit(1, 2)); + assertEquals(1, service.getResultLimit().offset); assertEquals(2, service.getResultLimit().limit); assertEquals("some response", service.doSomethingStatic(123L, entity)); @@ -377,11 +401,14 @@ public void testWithTotalItemsResponseHeader() throws Exception { FakeHttpClientFactory http = new FakeHttpClientFactory(200, Collections.singletonMap("SoftLayer-Total-Items", Collections.singletonList("234")), "\"some response\""); - RestApiClient client = new RestApiClient("http://example.com/").withCredentials("user", "key"); + RestApiClient client = new RestApiClient("http://example.com/") + .withCredentials("user", "key"); client.setHttpClientFactory(http); + TestEntity entity = new TestEntity(); entity.setFoo("blah"); TestEntity.Service service = TestEntity.service(client); + assertEquals("some response", service.doSomethingStatic(123L, entity)); assertTrue(http.invokeSyncCalled); assertEquals(234, service.getLastResponseTotalItemCount().intValue()); @@ -392,11 +419,14 @@ public void testWithTotalItemsAsyncFutureResponseHeader() throws Exception { FakeHttpClientFactory http = new FakeHttpClientFactory(200, Collections.singletonMap("SoftLayer-Total-Items", Collections.singletonList("234")), "\"some response\""); - RestApiClient client = new RestApiClient("http://example.com/").withCredentials("user", "key"); + RestApiClient client = new RestApiClient("http://example.com/") + .withCredentials("user", "key"); client.setHttpClientFactory(http); + TestEntity entity = new TestEntity(); entity.setFoo("blah"); TestEntity.ServiceAsync service = TestEntity.service(client).asAsync(); + assertEquals("some response", service.doSomethingStatic(123L, entity).get()); assertTrue(http.invokeAsyncFutureCalled); assertEquals(234, service.getLastResponseTotalItemCount().intValue()); @@ -407,7 +437,8 @@ public void testWithTotalItemsAsyncCallbackResponseHeader() throws Exception { FakeHttpClientFactory http = new FakeHttpClientFactory(200, Collections.singletonMap("SoftLayer-Total-Items", Collections.singletonList("234")), "\"some response\""); - RestApiClient client = new RestApiClient("http://example.com/").withCredentials("user", "key"); + RestApiClient client = new RestApiClient("http://example.com/") + .withCredentials("user", "key"); client.setHttpClientFactory(http); TestEntity entity = new TestEntity(); entity.setFoo("blah"); diff --git a/src/test/java/com/softlayer/api/ResultLimitTest.java b/src/test/java/com/softlayer/api/ResultLimitTest.java new file mode 100644 index 0000000..e53d921 --- /dev/null +++ b/src/test/java/com/softlayer/api/ResultLimitTest.java @@ -0,0 +1,26 @@ +package com.softlayer.api; + +import static org.junit.Assert.*; + +import org.junit.Test; + +public class ResultLimitTest { + @Test + public void testConstructorWithLimit() + { + int limit = 123; + ResultLimit resultLimit = new ResultLimit(limit); + assertEquals(0, resultLimit.offset); + assertEquals(limit, resultLimit.limit); + } + + @Test + public void testConstructorWithOffsetAndLimit() + { + int limit = 456; + int offset = 789; + ResultLimit resultLimit = new ResultLimit(offset, limit); + assertEquals(offset, resultLimit.offset); + assertEquals(limit, resultLimit.limit); + } +} diff --git a/src/test/java/com/softlayer/api/http/BuiltInHttpClientFactoryTest.java b/src/test/java/com/softlayer/api/http/BuiltInHttpClientFactoryTest.java index 786c604..ee40c56 100644 --- a/src/test/java/com/softlayer/api/http/BuiltInHttpClientFactoryTest.java +++ b/src/test/java/com/softlayer/api/http/BuiltInHttpClientFactoryTest.java @@ -5,7 +5,6 @@ import java.net.HttpURLConnection; import java.util.Collections; -import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; @@ -19,8 +18,12 @@ public class BuiltInHttpClientFactoryTest { @Test public void testGetHttpClientWithoutBasicAuth() { try { - new BuiltInHttpClientFactory().getHttpClient(new HttpCredentials() { }, "GET", - "http://example.com", Collections.>emptyMap()); + new BuiltInHttpClientFactory().getHttpClient( + new HttpCredentials() { }, + "GET", + "http://example.com", + Collections.emptyMap() + ); fail(); } catch (UnsupportedOperationException e) { assertTrue(e.getMessage().contains("basic auth")); @@ -29,12 +32,9 @@ public void testGetHttpClientWithoutBasicAuth() { @Test public void testGetThreadPoolDefaultsToDaemonThreads() throws Exception { - boolean daemon = new BuiltInHttpClientFactory().getThreadPool().submit(new Callable() { - @Override - public Boolean call() throws Exception { - return Thread.currentThread().isDaemon(); - } - }).get(); + boolean daemon = new BuiltInHttpClientFactory().getThreadPool().submit( + () -> Thread.currentThread().isDaemon() + ).get(); assertTrue(daemon); } @@ -60,9 +60,14 @@ public void testSetThreadPoolDoesNotShutDownUserDefined() { @Test public void testInvokeSyncSetsUpProperly() throws Exception { - BuiltInHttpClient client = spy(new BuiltInHttpClientFactory().getHttpClient( - new HttpBasicAuthCredentials("some user", "some key"), "NOTGET", "http://example.com", - Collections.singletonMap("header", Collections.singletonList("some header value")))); + BuiltInHttpClient client = spy( + new BuiltInHttpClientFactory().getHttpClient( + new HttpBasicAuthCredentials("some user", "some key"), + "NOTGET", + "http://example.com", + Collections.singletonMap("header", Collections.singletonList("some header value")) + ) + ); client.connection = mock(HttpURLConnection.class); // Skip opening connection doNothing().when(client).openConnection(); @@ -82,9 +87,14 @@ public void testInvokeSyncSetsUpProperly() throws Exception { @Test public void testInvokeAsyncFutureResult() throws Exception { - BuiltInHttpClient client = spy(new BuiltInHttpClientFactory().getHttpClient( - new HttpBasicAuthCredentials("some user", "some key"), "GET", "http://example.com", - Collections.>emptyMap())); + BuiltInHttpClient client = spy( + new BuiltInHttpClientFactory().getHttpClient( + new HttpBasicAuthCredentials("some user", "some key"), + "GET", + "http://example.com", + Collections.emptyMap() + ) + ); Callable callable = mock(Callable.class); doReturn(client).when(client).invokeSync(callable); assertEquals(client, client.invokeAsync(callable).get()); @@ -94,9 +104,14 @@ public void testInvokeAsyncFutureResult() throws Exception { @Test @SuppressWarnings("unchecked") public void testInvokeAsyncCallbackSuccess() throws Exception { - BuiltInHttpClient client = spy(new BuiltInHttpClientFactory().getHttpClient( - new HttpBasicAuthCredentials("some user", "some key"), "GET", "http://example.com", - Collections.>emptyMap())); + BuiltInHttpClient client = spy( + new BuiltInHttpClientFactory().getHttpClient( + new HttpBasicAuthCredentials("some user", "some key"), + "GET", + "http://example.com", + Collections.emptyMap() + ) + ); Callable callable = mock(Callable.class); doReturn(client).when(client).invokeSync(callable); ResponseHandler handler = mock(ResponseHandler.class); @@ -109,9 +124,14 @@ public void testInvokeAsyncCallbackSuccess() throws Exception { @Test @SuppressWarnings("unchecked") public void testInvokeAsyncCallbackError() throws Exception { - BuiltInHttpClient client = spy(new BuiltInHttpClientFactory().getHttpClient( - new HttpBasicAuthCredentials("some user", "some key"), "GET", "http://example.com", - Collections.>emptyMap())); + BuiltInHttpClient client = spy( + new BuiltInHttpClientFactory().getHttpClient( + new HttpBasicAuthCredentials("some user", "some key"), + "GET", + "http://example.com", + Collections.emptyMap() + ) + ); Callable callable = mock(Callable.class); doThrow(RuntimeException.class).when(client).invokeSync(callable); ResponseHandler handler = mock(ResponseHandler.class); @@ -123,9 +143,14 @@ public void testInvokeAsyncCallbackError() throws Exception { @Test public void testGetInputStreamOnSuccess() throws Exception { - BuiltInHttpClient client = spy(new BuiltInHttpClientFactory().getHttpClient( - new HttpBasicAuthCredentials("some user", "some key"), "GET", "http://example.com", - Collections.>emptyMap())); + BuiltInHttpClient client = spy( + new BuiltInHttpClientFactory().getHttpClient( + new HttpBasicAuthCredentials("some user", "some key"), + "GET", + "http://example.com", + Collections.emptyMap() + ) + ); client.connection = mock(HttpURLConnection.class); when(client.connection.getResponseCode()).thenReturn(250); client.getInputStream(); @@ -135,9 +160,14 @@ public void testGetInputStreamOnSuccess() throws Exception { @Test public void testGetErrorStreamOnFailure() throws Exception { - BuiltInHttpClient client = spy(new BuiltInHttpClientFactory().getHttpClient( - new HttpBasicAuthCredentials("some user", "some key"), "GET", "http://example.com", - Collections.>emptyMap())); + BuiltInHttpClient client = spy( + new BuiltInHttpClientFactory().getHttpClient( + new HttpBasicAuthCredentials("some user", "some key"), + "GET", + "http://example.com", + Collections.emptyMap() + ) + ); client.connection = mock(HttpURLConnection.class); when(client.connection.getResponseCode()).thenReturn(450); client.getInputStream(); diff --git a/src/test/java/com/softlayer/api/json/GsonJsonMarshallerFactoryTest.java b/src/test/java/com/softlayer/api/json/GsonJsonMarshallerFactoryTest.java index c10392e..06ea291 100644 --- a/src/test/java/com/softlayer/api/json/GsonJsonMarshallerFactoryTest.java +++ b/src/test/java/com/softlayer/api/json/GsonJsonMarshallerFactoryTest.java @@ -80,6 +80,7 @@ public void testRead() throws Exception { assertEquals("bad value", obj.getUnknownProperties().get("notApiProperty")); assertEquals("child string", obj.getChild().getFoo()); assertNull(obj.getChild().getBaz()); + assertEquals(0, obj.getChild().getUnknownProperties().size()); assertFalse(obj.getChild().isBazSpecified()); assertEquals(2, obj.getMoreChildren().size()); assertEquals("child 1", obj.getMoreChildren().get(0).getFoo()); @@ -102,7 +103,7 @@ public void testWrite() throws Exception { obj.getMoreChildren().get(1).setFoo("child 2"); Map actual = fromJson(new TypeToken>() { }.getType(), toJson(obj)); - Map expected = new HashMap(6); + Map expected = new HashMap<>(6); expected.put("complexType", "SoftLayer_TestEntity"); expected.put("bar", "some string"); expected.put("baz", null); @@ -111,14 +112,14 @@ public void testWrite() throws Exception { (offsetMinutes < 0 ? '-' : '+') + String.format("%1$02d:%2$02d", Math.abs(offsetMinutes / 60), Math.abs(offsetMinutes % 60)); expected.put("date", "1984-02-25T20:15:25" + expectedTimeZone); - Map childMap = new HashMap(); + Map childMap = new HashMap<>(); childMap.put("complexType", "SoftLayer_TestEntity"); childMap.put("bar", "child string"); expected.put("child", childMap); - Map child1Map = new HashMap(); + Map child1Map = new HashMap<>(); child1Map.put("complexType", "SoftLayer_TestEntity"); child1Map.put("bar", "child 1"); - Map child2Map = new HashMap(); + Map child2Map = new HashMap<>(); child2Map.put("complexType", "SoftLayer_TestEntity"); child2Map.put("bar", "child 2"); expected.put("moreChildren", Arrays.asList(child1Map, child2Map)); @@ -128,7 +129,14 @@ public void testWrite() throws Exception { @Test public void testReadBothDateFormats() throws Exception { String regular = "\"1984-02-25T20:15:25-06:00\""; - Calendar expected = new GregorianCalendar(1984, Calendar.FEBRUARY, 25, 20, 15, 25); + Calendar expected = new GregorianCalendar( + 1984, + Calendar.FEBRUARY, + 25, + 20, + 15, + 25 + ); expected.setTimeZone(TimeZone.getTimeZone("GMT-06:00")); assertEquals(expected.getTimeInMillis(), fromJson(GregorianCalendar.class, regular).getTimeInMillis()); From fc3504b2cb9596b70d1ad4fc2ac640f9d9db4f3e Mon Sep 17 00:00:00 2001 From: Cameron Porter Date: Sat, 3 Mar 2018 01:00:34 -0600 Subject: [PATCH 15/62] Fix invoke plugin path issue. Move default gen source path. --- gen/src/main/java/com/softlayer/api/gen/Main.java | 3 ++- pom.xml | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/gen/src/main/java/com/softlayer/api/gen/Main.java b/gen/src/main/java/com/softlayer/api/gen/Main.java index 879da43..ee75d34 100644 --- a/gen/src/main/java/com/softlayer/api/gen/Main.java +++ b/gen/src/main/java/com/softlayer/api/gen/Main.java @@ -15,6 +15,7 @@ public class Main { protected static final String METADATA_URL = "https://api.softlayer.com/metadata/v3.1"; + protected static final String DEFAULT_SOURCE_PATH = "../src/main/java"; public static final String USAGE = "Arguments:\n\n" + @@ -43,7 +44,7 @@ public static void main(String[] args) throws Exception { return; } String dirString = getArg("--src", argList); - dir = new File(dirString != null ? dirString : "../src/main/java"); + dir = new File(dirString != null ? dirString : DEFAULT_SOURCE_PATH); String urlString = getArg("--url", argList); url = new URL(urlString != null ? urlString : METADATA_URL); whitelist = getRestriction(getArg("--whitelist", argList)); diff --git a/pom.xml b/pom.xml index 372516e..46c6aca 100644 --- a/pom.xml +++ b/pom.xml @@ -141,7 +141,8 @@ maven-invoker-plugin - 3.0.1 + + 2.0.0 generate-services From 0d26bc9367b667200aa7fe9f5177dbc52f468d48 Mon Sep 17 00:00:00 2001 From: Cameron Porter Date: Mon, 26 Mar 2018 11:46:12 -0500 Subject: [PATCH 16/62] Changes mentioned from pull request. Added tests for the HTTP basic auth class. --- .../com/softlayer/api/gen/MetaConverter.java | 15 ++++++------ .../api/http/BuiltInHttpClientFactory.java | 12 ++++------ .../api/http/HttpBasicAuthCredentials.java | 11 ++------- .../http/HttpBasicAuthCredentialsTest.java | 23 +++++++++++++++++++ 4 files changed, 36 insertions(+), 25 deletions(-) create mode 100644 src/test/java/com/softlayer/api/http/HttpBasicAuthCredentialsTest.java diff --git a/gen/src/main/java/com/softlayer/api/gen/MetaConverter.java b/gen/src/main/java/com/softlayer/api/gen/MetaConverter.java index bf2fada..f123dae 100644 --- a/gen/src/main/java/com/softlayer/api/gen/MetaConverter.java +++ b/gen/src/main/java/com/softlayer/api/gen/MetaConverter.java @@ -72,14 +72,13 @@ public String getPackageName(String typeName) { typeName = typeName.substring(10); } String[] pieces = typeName.split("_"); - Arrays.stream(pieces) - .limit(pieces.length - 1) // Skip the last, it's the class name - .map(String::toLowerCase) - .map(piece -> { - String replacement = keywordReplacements.get(piece); - return replacement != null ? replacement : piece; - }) - .forEach(piece -> pkg.append('.').append(getValidJavaIdentifier(piece))); + // Skip the last one, it's the class name + for (int i = 0; i < pieces.length - 1; i++) { + String piece = pieces[i].toLowerCase(); + String replacement = keywordReplacements.get(piece); + piece = replacement != null ? replacement : piece; + pkg.append('.').append(getValidJavaIdentifier(piece)); + } return pkg.toString(); } diff --git a/src/main/java/com/softlayer/api/http/BuiltInHttpClientFactory.java b/src/main/java/com/softlayer/api/http/BuiltInHttpClientFactory.java index 06f5580..5cf49f0 100644 --- a/src/main/java/com/softlayer/api/http/BuiltInHttpClientFactory.java +++ b/src/main/java/com/softlayer/api/http/BuiltInHttpClientFactory.java @@ -139,14 +139,10 @@ public HttpResponse invokeSync(Callable setupBody) { // fairly fast and safe. openConnection(); if (credentials != null) { - try { - connection.addRequestProperty( - "Authorization", - credentials.getHeader() - ); - } catch (UnsupportedEncodingException e) { - throw new RuntimeException(e); - } + connection.addRequestProperty( + "Authorization", + credentials.getHeader() + ); } for (Map.Entry> headerEntry : headers.entrySet()) { for (String headerValue : headerEntry.getValue()) { diff --git a/src/main/java/com/softlayer/api/http/HttpBasicAuthCredentials.java b/src/main/java/com/softlayer/api/http/HttpBasicAuthCredentials.java index 9c36cc8..2ae9432 100644 --- a/src/main/java/com/softlayer/api/http/HttpBasicAuthCredentials.java +++ b/src/main/java/com/softlayer/api/http/HttpBasicAuthCredentials.java @@ -1,6 +1,5 @@ package com.softlayer.api.http; -import java.io.UnsupportedEncodingException; import java.util.Base64; /** HTTP basic authorization support for username and API key */ @@ -19,16 +18,10 @@ public HttpBasicAuthCredentials(String username, String apiKey) { * for use in an HTTP Authorization header. * * @return String - * @throws UnsupportedEncodingException If encoding with UTF-8 fails. */ - public String getHeader() throws UnsupportedEncodingException + public String getHeader() { String authPair = username + ':' + apiKey; - String result = "Basic "; - result += new String( - Base64.getEncoder().encode(authPair.getBytes("UTF-8")), - "UTF-8" - ); - return result; + return "Basic " + Base64.getEncoder().encodeToString(authPair.getBytes()); } } diff --git a/src/test/java/com/softlayer/api/http/HttpBasicAuthCredentialsTest.java b/src/test/java/com/softlayer/api/http/HttpBasicAuthCredentialsTest.java new file mode 100644 index 0000000..477ae04 --- /dev/null +++ b/src/test/java/com/softlayer/api/http/HttpBasicAuthCredentialsTest.java @@ -0,0 +1,23 @@ +package com.softlayer.api.http; + +import static org.junit.Assert.*; + +import org.junit.Test; + +public class HttpBasicAuthCredentialsTest { + + @Test + public void testConstructor() + { + HttpBasicAuthCredentials authCredentials = new HttpBasicAuthCredentials("username", "apiKey"); + assertEquals("username", authCredentials.username); + assertEquals("apiKey", authCredentials.apiKey); + } + + @Test + public void testGetHeader() + { + HttpBasicAuthCredentials authCredentials = new HttpBasicAuthCredentials("username", "apiKey"); + assertEquals("Basic dXNlcm5hbWU6YXBpS2V5", authCredentials.getHeader()); + } +} From 4bc484445f4b22bdd1022a0dacaabc105cbdbccf Mon Sep 17 00:00:00 2001 From: Cameron Porter Date: Mon, 26 Mar 2018 11:59:58 -0500 Subject: [PATCH 17/62] Fix braces. --- .../com/softlayer/api/http/HttpBasicAuthCredentials.java | 3 +-- .../softlayer/api/http/HttpBasicAuthCredentialsTest.java | 6 ++---- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/softlayer/api/http/HttpBasicAuthCredentials.java b/src/main/java/com/softlayer/api/http/HttpBasicAuthCredentials.java index 2ae9432..1e10ab4 100644 --- a/src/main/java/com/softlayer/api/http/HttpBasicAuthCredentials.java +++ b/src/main/java/com/softlayer/api/http/HttpBasicAuthCredentials.java @@ -19,8 +19,7 @@ public HttpBasicAuthCredentials(String username, String apiKey) { * * @return String */ - public String getHeader() - { + public String getHeader() { String authPair = username + ':' + apiKey; return "Basic " + Base64.getEncoder().encodeToString(authPair.getBytes()); } diff --git a/src/test/java/com/softlayer/api/http/HttpBasicAuthCredentialsTest.java b/src/test/java/com/softlayer/api/http/HttpBasicAuthCredentialsTest.java index 477ae04..051523e 100644 --- a/src/test/java/com/softlayer/api/http/HttpBasicAuthCredentialsTest.java +++ b/src/test/java/com/softlayer/api/http/HttpBasicAuthCredentialsTest.java @@ -7,16 +7,14 @@ public class HttpBasicAuthCredentialsTest { @Test - public void testConstructor() - { + public void testConstructor() { HttpBasicAuthCredentials authCredentials = new HttpBasicAuthCredentials("username", "apiKey"); assertEquals("username", authCredentials.username); assertEquals("apiKey", authCredentials.apiKey); } @Test - public void testGetHeader() - { + public void testGetHeader() { HttpBasicAuthCredentials authCredentials = new HttpBasicAuthCredentials("username", "apiKey"); assertEquals("Basic dXNlcm5hbWU6YXBpS2V5", authCredentials.getHeader()); } From 5adc2e1908b834cad1d953dd5103a6f3c42e620d Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Fri, 8 Jun 2018 15:39:17 -0400 Subject: [PATCH 18/62] Fixed the object mask to work with result limit. --- src/main/java/com/softlayer/api/Mask.java | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/softlayer/api/Mask.java b/src/main/java/com/softlayer/api/Mask.java index 3c35061..cabf6de 100644 --- a/src/main/java/com/softlayer/api/Mask.java +++ b/src/main/java/com/softlayer/api/Mask.java @@ -4,6 +4,7 @@ import java.util.HashSet; import java.util.Map; import java.util.Set; +import java.util.regex.Pattern; /** Object mask parameter. See http://sldn.softlayer.com/article/Object-Masks */ public class Mask { @@ -44,7 +45,26 @@ protected String getMask() { @Override public String toString() { - return toString(new StringBuilder()).toString(); + String objectMask = new String(); + String builtedMask = toString(new StringBuilder()).toString(); + if(builtedMask.contains("[")){ + String [] mask = builtedMask.split(Pattern.quote("[")); + for (int count = 0; count < mask.length; count ++ ) { + if (count != 0){ + objectMask = new StringBuilder().append(objectMask).append(mask[count]).toString(); + } + } + } + else { + String [] mask = builtedMask.split(Pattern.quote(".")); + for (int count = 0; count < mask.length; count ++ ) { + if (count != 0){ + objectMask = new StringBuilder().append(objectMask).append(mask[count]).append(".").toString(); + } + } + } + String resultMask = objectMask.substring(0, objectMask.length()-1); + return resultMask; } /** Append this mask's string representation to the given builder and return it */ From f7f3f915025fdd934a372075358d5007d21a0fb8 Mon Sep 17 00:00:00 2001 From: albert camacho Date: Mon, 11 Jun 2018 16:59:36 -0400 Subject: [PATCH 19/62] Fix for issue #53 --- gen/src/main/java/com/softlayer/api/gen/ClassWriter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gen/src/main/java/com/softlayer/api/gen/ClassWriter.java b/gen/src/main/java/com/softlayer/api/gen/ClassWriter.java index 6e318c8..671c04f 100644 --- a/gen/src/main/java/com/softlayer/api/gen/ClassWriter.java +++ b/gen/src/main/java/com/softlayer/api/gen/ClassWriter.java @@ -272,7 +272,7 @@ public ClassWriter emitServiceMethod(TypeClass.Method method, boolean async) thr if (!method.name.equals(method.meta.name)) { params.put("value", stringLiteral(method.meta.name)); } - if (!method.meta.isstatic) { + if (!method.meta.isstatic && !"SoftLayer_Resource_Metadata".equals(type.meta.name)) { params.put("instanceRequired", true); } emitAnnotation(TYPE_API_METHOD, params); From 16571b82e6934e3640061a5c71c91849991454bc Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Thu, 14 Jun 2018 09:54:28 -0400 Subject: [PATCH 20/62] Fixed the object mask to work with result limit. --- src/main/java/com/softlayer/api/Mask.java | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/softlayer/api/Mask.java b/src/main/java/com/softlayer/api/Mask.java index cabf6de..2839bb1 100644 --- a/src/main/java/com/softlayer/api/Mask.java +++ b/src/main/java/com/softlayer/api/Mask.java @@ -56,11 +56,15 @@ public String toString() { } } else { - String [] mask = builtedMask.split(Pattern.quote(".")); - for (int count = 0; count < mask.length; count ++ ) { - if (count != 0){ - objectMask = new StringBuilder().append(objectMask).append(mask[count]).append(".").toString(); + if(builtedMask.contains(".")){ + String [] mask = builtedMask.split(Pattern.quote(".")); + for (int count = 0; count < mask.length; count ++ ) { + if (count != 0){ + objectMask = new StringBuilder().append(objectMask).append(mask[count]).append(".").toString(); + } } + }else { + objectMask = builtedMask + "."; } } String resultMask = objectMask.substring(0, objectMask.length()-1); From be56b0da04656d5f769230a50f1937ecc9c8e39c Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Thu, 14 Jun 2018 16:23:06 -0400 Subject: [PATCH 21/62] Fixed the object mask to work with result limit. --- src/main/java/com/softlayer/api/Mask.java | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/softlayer/api/Mask.java b/src/main/java/com/softlayer/api/Mask.java index 2839bb1..d457c82 100644 --- a/src/main/java/com/softlayer/api/Mask.java +++ b/src/main/java/com/softlayer/api/Mask.java @@ -45,26 +45,28 @@ protected String getMask() { @Override public String toString() { + String subMask = new String(); String objectMask = new String(); - String builtedMask = toString(new StringBuilder()).toString(); - if(builtedMask.contains("[")){ - String [] mask = builtedMask.split(Pattern.quote("[")); + String builtMask = toString(new StringBuilder()).toString(); + if(builtMask.contains("[")){ + String [] mask = builtMask.split(Pattern.quote("[")); for (int count = 0; count < mask.length; count ++ ) { if (count != 0){ - objectMask = new StringBuilder().append(objectMask).append(mask[count]).toString(); + subMask = new StringBuilder().append(subMask).append("[").append(mask[count]).toString(); } } + objectMask = subMask.substring(1, subMask.length()); } else { - if(builtedMask.contains(".")){ - String [] mask = builtedMask.split(Pattern.quote(".")); + if(builtMask.contains(".")){ + String [] mask = builtMask.split(Pattern.quote(".")); for (int count = 0; count < mask.length; count ++ ) { if (count != 0){ objectMask = new StringBuilder().append(objectMask).append(mask[count]).append(".").toString(); } } }else { - objectMask = builtedMask + "."; + objectMask = builtMask + "."; } } String resultMask = objectMask.substring(0, objectMask.length()-1); From 18ff77919ff34c46dd4d8dfb3c8ae80a980cd43d Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Thu, 14 Jun 2018 15:42:47 -0500 Subject: [PATCH 22/62] Splitting out Mask tests to a new file. --- src/test/java/com/softlayer/api/MaskTest.java | 108 ++++++++++++++++++ .../com/softlayer/api/RestApiClientTest.java | 78 +------------ 2 files changed, 109 insertions(+), 77 deletions(-) create mode 100644 src/test/java/com/softlayer/api/MaskTest.java diff --git a/src/test/java/com/softlayer/api/MaskTest.java b/src/test/java/com/softlayer/api/MaskTest.java new file mode 100644 index 0000000..b55a193 --- /dev/null +++ b/src/test/java/com/softlayer/api/MaskTest.java @@ -0,0 +1,108 @@ +package com.softlayer.api; + +import static org.junit.Assert.*; + + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.lang.reflect.Proxy; +import java.net.URLEncoder; +import java.util.Collections; +import java.util.GregorianCalendar; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.junit.Test; + +import com.softlayer.api.http.FakeHttpClientFactory; +import com.softlayer.api.http.HttpBasicAuthCredentials; +import com.softlayer.api.json.GsonJsonMarshallerFactoryTest; +import com.softlayer.api.service.TestEntity; + + + +public class MaskTest { + static { + GsonJsonMarshallerFactoryTest.addTestEntityToGson(); + } + @Test + public void testWithMask() throws Exception { + FakeHttpClientFactory http = new FakeHttpClientFactory(200, + Collections.emptyMap(), "\"some response\""); + RestApiClient client = new RestApiClient("http://example.com/") + .withCredentials("user", "key"); + client.setHttpClientFactory(http); + + TestEntity entity = new TestEntity(); + entity.setFoo("blah"); + TestEntity.Service service = TestEntity.service(client); + service.withMask().foo().child().date(); + service.withMask().child().baz(); + + assertEquals("some response", service.doSomethingStatic(123L, entity)); + assertEquals("http://example.com/SoftLayer_TestEntity/doSomethingStatic.json" + + "?objectMask=" + URLEncoder.encode(service.withMask().getMask(), "UTF-8"), http.fullUrl); + assertTrue(http.invokeSyncCalled); + } + + @Test + public void testSetObjectMask() throws Exception { + FakeHttpClientFactory http = new FakeHttpClientFactory(200, + Collections.emptyMap(), "\"some response\""); + RestApiClient client = new RestApiClient("http://example.com/") + .withCredentials("user", "key"); + client.setHttpClientFactory(http); + + TestEntity entity = new TestEntity(); + entity.setFoo("blah"); + TestEntity.Service service = TestEntity.service(client); + TestEntity.Mask mask = new TestEntity.Mask(); + mask.foo().child().date(); + mask.child().baz(); + service.setMask(mask); + + assertEquals("some response", service.doSomethingStatic(123L, entity)); + assertEquals("http://example.com/SoftLayer_TestEntity/doSomethingStatic.json" + + "?objectMask=" + URLEncoder.encode(mask.getMask(), "UTF-8"), http.fullUrl); + assertTrue(http.invokeSyncCalled); + } + + @Test + public void testSetStringMask() throws Exception { + FakeHttpClientFactory http = new FakeHttpClientFactory(200, + Collections.emptyMap(), "\"some response\""); + RestApiClient client = new RestApiClient("http://example.com/") + .withCredentials("user", "key"); + client.setHttpClientFactory(http); + + TestEntity entity = new TestEntity(); + entity.setFoo("blah"); + TestEntity.Service service = TestEntity.service(client); + service.setMask("yay-a-mask"); + + assertEquals("some response", service.doSomethingStatic(123L, entity)); + assertEquals("http://example.com/SoftLayer_TestEntity/doSomethingStatic.json" + + "?objectMask=yay-a-mask", http.fullUrl); + assertTrue(http.invokeSyncCalled); + } + + @Test(expected = IllegalArgumentException.class) + public void testMaskMustNotBeNull() { + RestApiClient client = new RestApiClient("http://example.com/"); + TestEntity.Service service = TestEntity.service(client); + service.setMask((Mask) null); + } + + @Test + public void testMaskRemoval() { + RestApiClient client = new RestApiClient("http://example.com/"); + TestEntity.Service service = TestEntity.service(client); + service.withMask().baz(); + assertEquals("baz", service.withMask().toString()); + service.clearMask(); + assertEquals("", service.withMask().toString()); + } +} + diff --git a/src/test/java/com/softlayer/api/RestApiClientTest.java b/src/test/java/com/softlayer/api/RestApiClientTest.java index 481d030..1a0b565 100644 --- a/src/test/java/com/softlayer/api/RestApiClientTest.java +++ b/src/test/java/com/softlayer/api/RestApiClientTest.java @@ -313,68 +313,7 @@ public void testDifferentMethodName() throws Exception { assertEquals(RestApiClient.HEADERS, http.headers); assertTrue(http.invokeSyncCalled); } - - @Test - public void testWithMask() throws Exception { - FakeHttpClientFactory http = new FakeHttpClientFactory(200, - Collections.emptyMap(), "\"some response\""); - RestApiClient client = new RestApiClient("http://example.com/") - .withCredentials("user", "key"); - client.setHttpClientFactory(http); - TestEntity entity = new TestEntity(); - entity.setFoo("blah"); - TestEntity.Service service = TestEntity.service(client); - service.withMask().foo().child().date(); - service.withMask().child().baz(); - - assertEquals("some response", service.doSomethingStatic(123L, entity)); - assertEquals("http://example.com/SoftLayer_TestEntity/doSomethingStatic.json" - + "?objectMask=" + URLEncoder.encode(service.withMask().getMask(), "UTF-8"), http.fullUrl); - assertTrue(http.invokeSyncCalled); - } - - @Test - public void testSetObjectMask() throws Exception { - FakeHttpClientFactory http = new FakeHttpClientFactory(200, - Collections.emptyMap(), "\"some response\""); - RestApiClient client = new RestApiClient("http://example.com/") - .withCredentials("user", "key"); - client.setHttpClientFactory(http); - - TestEntity entity = new TestEntity(); - entity.setFoo("blah"); - TestEntity.Service service = TestEntity.service(client); - TestEntity.Mask mask = new TestEntity.Mask(); - mask.foo().child().date(); - mask.child().baz(); - service.setMask(mask); - - assertEquals("some response", service.doSomethingStatic(123L, entity)); - assertEquals("http://example.com/SoftLayer_TestEntity/doSomethingStatic.json" - + "?objectMask=" + URLEncoder.encode(mask.getMask(), "UTF-8"), http.fullUrl); - assertTrue(http.invokeSyncCalled); - } - - @Test - public void testSetStringMask() throws Exception { - FakeHttpClientFactory http = new FakeHttpClientFactory(200, - Collections.emptyMap(), "\"some response\""); - RestApiClient client = new RestApiClient("http://example.com/") - .withCredentials("user", "key"); - client.setHttpClientFactory(http); - - TestEntity entity = new TestEntity(); - entity.setFoo("blah"); - TestEntity.Service service = TestEntity.service(client); - service.setMask("yay-a-mask"); - - assertEquals("some response", service.doSomethingStatic(123L, entity)); - assertEquals("http://example.com/SoftLayer_TestEntity/doSomethingStatic.json" - + "?objectMask=yay-a-mask", http.fullUrl); - assertTrue(http.invokeSyncCalled); - } - @Test public void testWithResultLimit() throws Exception { FakeHttpClientFactory http = new FakeHttpClientFactory(200, @@ -461,22 +400,7 @@ public void onSuccess(String value) { assertTrue(successCalled.get()); } - @Test(expected = IllegalArgumentException.class) - public void testMaskMustNotBeNull() { - RestApiClient client = new RestApiClient("http://example.com/"); - TestEntity.Service service = TestEntity.service(client); - service.setMask((Mask) null); - } - - @Test - public void testMaskRemoval() { - RestApiClient client = new RestApiClient("http://example.com/"); - TestEntity.Service service = TestEntity.service(client); - service.withMask().baz(); - assertEquals("baz", service.withMask().toString()); - service.clearMask(); - assertEquals("", service.withMask().toString()); - } + @Test public void testNormalObjectMethodsOnService() { From bb92e07dc8ae23f3007d5a897f5a41a15ae6a475 Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Mon, 18 Jun 2018 18:19:15 -0500 Subject: [PATCH 23/62] #52 some unit tests around objectMasks, groundwork for withMask() improvements --- .gitignore | 1 + src/main/java/com/softlayer/api/Mask.java | 28 +----- .../java/com/softlayer/api/RestApiClient.java | 3 +- .../java/com/softlayer/api/TestApiClient.java | 61 ++++++++++++ src/test/java/com/softlayer/api/MaskTest.java | 57 ++++++++++- .../com/softlayer/api/service/TestEntity.java | 94 ++++++++++++++++++- 6 files changed, 214 insertions(+), 30 deletions(-) create mode 100644 src/main/java/com/softlayer/api/TestApiClient.java diff --git a/.gitignore b/.gitignore index 2c04155..05900eb 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ /gen/build.log /examples/target settings.xml +.DS_Store diff --git a/src/main/java/com/softlayer/api/Mask.java b/src/main/java/com/softlayer/api/Mask.java index d457c82..3c35061 100644 --- a/src/main/java/com/softlayer/api/Mask.java +++ b/src/main/java/com/softlayer/api/Mask.java @@ -4,7 +4,6 @@ import java.util.HashSet; import java.util.Map; import java.util.Set; -import java.util.regex.Pattern; /** Object mask parameter. See http://sldn.softlayer.com/article/Object-Masks */ public class Mask { @@ -45,32 +44,7 @@ protected String getMask() { @Override public String toString() { - String subMask = new String(); - String objectMask = new String(); - String builtMask = toString(new StringBuilder()).toString(); - if(builtMask.contains("[")){ - String [] mask = builtMask.split(Pattern.quote("[")); - for (int count = 0; count < mask.length; count ++ ) { - if (count != 0){ - subMask = new StringBuilder().append(subMask).append("[").append(mask[count]).toString(); - } - } - objectMask = subMask.substring(1, subMask.length()); - } - else { - if(builtMask.contains(".")){ - String [] mask = builtMask.split(Pattern.quote(".")); - for (int count = 0; count < mask.length; count ++ ) { - if (count != 0){ - objectMask = new StringBuilder().append(objectMask).append(mask[count]).append(".").toString(); - } - } - }else { - objectMask = builtMask + "."; - } - } - String resultMask = objectMask.substring(0, objectMask.length()-1); - return resultMask; + return toString(new StringBuilder()).toString(); } /** Append this mask's string representation to the given builder and return it */ diff --git a/src/main/java/com/softlayer/api/RestApiClient.java b/src/main/java/com/softlayer/api/RestApiClient.java index 46afa0e..4bbdf6b 100644 --- a/src/main/java/com/softlayer/api/RestApiClient.java +++ b/src/main/java/com/softlayer/api/RestApiClient.java @@ -304,6 +304,7 @@ public Object logAndHandleResponse(HttpResponse response, String url, } public Object invokeService(Method method, final Object[] args) throws Throwable { + System.out.print("invokeService: " + method + "\n"); ApiMethod methodInfo = method.getAnnotation(ApiMethod.class); // Must have ID if instance is required if (methodInfo.instanceRequired() && id == null) { @@ -440,7 +441,7 @@ public boolean isDone() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { boolean noParams = args == null || args.length == 0; - + System.out.print("Invoking: " + method.getName() + "\n"); if ("asAsync".equals(method.getName()) && noParams) { ServiceProxy asyncProxy = new ServiceProxy<>(serviceClass, id); asyncProxy.mask = mask; diff --git a/src/main/java/com/softlayer/api/TestApiClient.java b/src/main/java/com/softlayer/api/TestApiClient.java new file mode 100644 index 0000000..cfc0dca --- /dev/null +++ b/src/main/java/com/softlayer/api/TestApiClient.java @@ -0,0 +1,61 @@ +package com.softlayer.api; + +import com.softlayer.api.annotation.ApiMethod; +import com.softlayer.api.annotation.ApiService; +import com.softlayer.api.http.HttpBasicAuthCredentials; +import com.softlayer.api.http.HttpClient; +import com.softlayer.api.http.HttpResponse; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; + +public class TestApiClient extends RestApiClient{ + + + private HttpBasicAuthCredentials credentials; + public HttpBasicAuthCredentials getCredentials() { + return credentials; + } + + public TestApiClient(String baseUrl) { + super(baseUrl); + } + + public S createService(Class serviceClass, String id) { + return (S) Proxy.newProxyInstance(getClass().getClassLoader(), + new Class[] { serviceClass }, new TestServiceProxy<>(serviceClass, id)); + } + + class TestServiceProxy extends ServiceProxy { + + final Class serviceClass; + final String id; + Mask mask; + String maskString; + ResultLimit resultLimit; + Integer lastResponseTotalItemCount; + + public TestServiceProxy(Class serviceClass, String id) { + super(serviceClass, id); + this.serviceClass = serviceClass; + this.id = id; + + } + + public String invokeService(Method method, final Object[] args) throws Throwable { + System.out.print("invokeTESTService: " + method + "\n"); + ApiMethod methodInfo = method.getAnnotation(ApiMethod.class); + // Must have ID if instance is required + if (methodInfo.instanceRequired() && id == null) { + throw new IllegalStateException("ID is required to invoke " + method); + } + String methodName = methodInfo.value().isEmpty() ? method.getName() : methodInfo.value(); + final String httpMethod = getHttpMethodFromMethodName(methodName); + String methodId = methodInfo.instanceRequired() ? this.id : null; + final String url = getFullUrl(serviceClass.getAnnotation(ApiService.class).value(), + methodName, methodId, resultLimit, mask == null ? maskString : mask.getMask()); + return url; + } + } +} diff --git a/src/test/java/com/softlayer/api/MaskTest.java b/src/test/java/com/softlayer/api/MaskTest.java index b55a193..1c90d42 100644 --- a/src/test/java/com/softlayer/api/MaskTest.java +++ b/src/test/java/com/softlayer/api/MaskTest.java @@ -70,7 +70,7 @@ public void testSetObjectMask() throws Exception { } @Test - public void testSetStringMask() throws Exception { + public void testSetStringMask() { FakeHttpClientFactory http = new FakeHttpClientFactory(200, Collections.emptyMap(), "\"some response\""); RestApiClient client = new RestApiClient("http://example.com/") @@ -104,5 +104,60 @@ public void testMaskRemoval() { service.clearMask(); assertEquals("", service.withMask().toString()); } + + @Test + public void testRecursiveMaskandLocal() { + RestApiClient client = new RestApiClient("http://example.com/"); + TestEntity.Service service = TestEntity.service(client); + service.withMask().recursiveProperty().recursiveProperty().baz(); + service.withMask().recursiveProperty().recursiveProperty().foo(); + service.withMask().recursiveProperty().date(); + assertEquals("recursiveProperty[date,recursiveProperty[foo,baz]]", + service.withMask().toString()); + } + + @Test + public void testRecursiveMask() { + RestApiClient client = new RestApiClient("http://example.com/"); + TestEntity.Service service = TestEntity.service(client); + service.withMask().recursiveProperty().baz(); + service.withMask().recursiveProperty().foo(); + service.withMask().recursiveProperty().date(); + + assertEquals("recursiveProperty[date,foo,baz]", + service.withMask().toString()); + } + + @Test + public void testMultiLevelMask() { + RestApiClient client = new RestApiClient("http://example.com/"); + TestEntity.Service service = TestEntity.service(client); + service.withMask().recursiveProperty().baz(); + service.withMask().recursiveProperty().foo(); + + service.withMask().moreChildren().recursiveProperty().baz(); + service.withMask().moreChildren().date(); + + assertEquals("moreChildren[date,recursiveProperty.baz],recursiveProperty[foo,baz]", + service.withMask().toString()); + } + + @Test + public void testChangeMaskScope() { + RestApiClient client = new TestApiClient("http://example.com/"); + client.setLoggingEnabled(true); + System.out.print("Hello"); + TestEntity.Service service = TestEntity.service(client); + service.withMask().recursiveProperty().baz(); + service.withMask().recursiveProperty().foo(); + + String result = service.getRecursiveProperty(); + System.out.print(result); +// RestApiClient.ServiceProxy serviceProxy = client.createService(TestEntity, 1234); +// serviceProxy.invoke(service, service.getRecursiveProperty(),null); +// assertEquals("http://example.com/SomeService/1234/someMethod.json?objectMask=someMask%26%26", +// client.ServiceProxy. +// ) + } } diff --git a/src/test/java/com/softlayer/api/service/TestEntity.java b/src/test/java/com/softlayer/api/service/TestEntity.java index 66411e2..e852c96 100644 --- a/src/test/java/com/softlayer/api/service/TestEntity.java +++ b/src/test/java/com/softlayer/api/service/TestEntity.java @@ -6,7 +6,9 @@ import java.util.concurrent.Future; import com.softlayer.api.ApiClient; +import com.softlayer.api.Mask; import com.softlayer.api.ResponseHandler; +import com.softlayer.api.ResultLimit; import com.softlayer.api.annotation.ApiMethod; import com.softlayer.api.annotation.ApiProperty; import com.softlayer.api.annotation.ApiService; @@ -113,6 +115,15 @@ public List getMoreChildren() { return moreChildren; } + @ApiProperty + protected List recursiveProperty; + + public List getrecursiveProperty() { + if (recursiveProperty == null) { + recursiveProperty = new ArrayList(); + } + return recursiveProperty; + } public Service asService(ApiClient client) { return service(client, id); } @@ -141,6 +152,9 @@ public static interface Service extends com.softlayer.api.Service { @ApiMethod(instanceRequired = true) public Void doSomethingNonStatic(GregorianCalendar param1); + + @ApiMethod("getRecursiveProperty") + public String getRecursiveProperty(); } public static interface ServiceAsync extends com.softlayer.api.ServiceAsync { @@ -162,7 +176,7 @@ public static interface ServiceAsync extends com.softlayer.api.ServiceAsync { public static class Mask extends Entity.Mask { public Mask foo() { - withLocalProperty("bar"); + withLocalProperty("foo"); return this; } @@ -183,5 +197,83 @@ public Mask child() { public Mask moreChildren() { return withSubMask("moreChildren", Mask.class); } + + public Mask recursiveProperty() { + return withSubMask("recursiveProperty", Mask.class); + } + } + + public class TestService implements Service { + + @Override + public ServiceAsync asAsync() { + return null; + } + + @Override + public Mask withNewMask() { + return null; + } + + @Override + public Mask withMask() { + return null; + } + + @Override + public void setMask(com.softlayer.api.Mask mask) { + + } + + @Override + public void setMask(String mask) { + + } + + @Override + public void clearMask() { + + } + + @Override + public void setMask(Mask mask) { + + } + + @Override + public String doSomethingStatic(Long param1, TestEntity param2) { + return null; + } + + @Override + public List fakeName() { + return null; + } + + @Override + public Void doSomethingNonStatic(GregorianCalendar param1) { + return null; + } + + @Override + public String getRecursiveProperty() { + System.out.print("This is playing"); + return "Thats playing to win"; + } + + @Override + public ResultLimit getResultLimit() { + return null; + } + + @Override + public ResultLimit setResultLimit(ResultLimit limit) { + return null; + } + + @Override + public Integer getLastResponseTotalItemCount() { + return null; + } } } \ No newline at end of file From 1e6ea3483723cb389f9fa8ffafaf195004d1b2c6 Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Wed, 20 Jun 2018 17:43:25 -0500 Subject: [PATCH 24/62] a LOT of groundwork for fancier testing of api calls --- .../java/com/softlayer/api/RestApiClient.java | 6 +- .../java/com/softlayer/api/TestApiClient.java | 61 ------ src/test/java/com/softlayer/api/MaskTest.java | 27 ++- .../java/com/softlayer/api/TestApiClient.java | 108 ++++++++++ .../api/http/FakeHttpClientFactory.java | 10 +- .../com/softlayer/api/service/TestEntity.java | 57 +++++- .../com/softlayer/api/service/TestThing.java | 185 ++++++++++++++++++ 7 files changed, 373 insertions(+), 81 deletions(-) delete mode 100644 src/main/java/com/softlayer/api/TestApiClient.java create mode 100644 src/test/java/com/softlayer/api/TestApiClient.java create mode 100644 src/test/java/com/softlayer/api/service/TestThing.java diff --git a/src/main/java/com/softlayer/api/RestApiClient.java b/src/main/java/com/softlayer/api/RestApiClient.java index 4bbdf6b..8a85e8d 100644 --- a/src/main/java/com/softlayer/api/RestApiClient.java +++ b/src/main/java/com/softlayer/api/RestApiClient.java @@ -225,10 +225,11 @@ class ServiceProxy implements InvocationHandler { final Class serviceClass; final String id; - Mask mask; + public Mask mask; String maskString; ResultLimit resultLimit; Integer lastResponseTotalItemCount; + public ServiceProxy(Class serviceClass, String id) { this.serviceClass = serviceClass; @@ -304,7 +305,7 @@ public Object logAndHandleResponse(HttpResponse response, String url, } public Object invokeService(Method method, final Object[] args) throws Throwable { - System.out.print("invokeService: " + method + "\n"); + ApiMethod methodInfo = method.getAnnotation(ApiMethod.class); // Must have ID if instance is required if (methodInfo.instanceRequired() && id == null) { @@ -441,7 +442,6 @@ public boolean isDone() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { boolean noParams = args == null || args.length == 0; - System.out.print("Invoking: " + method.getName() + "\n"); if ("asAsync".equals(method.getName()) && noParams) { ServiceProxy asyncProxy = new ServiceProxy<>(serviceClass, id); asyncProxy.mask = mask; diff --git a/src/main/java/com/softlayer/api/TestApiClient.java b/src/main/java/com/softlayer/api/TestApiClient.java deleted file mode 100644 index cfc0dca..0000000 --- a/src/main/java/com/softlayer/api/TestApiClient.java +++ /dev/null @@ -1,61 +0,0 @@ -package com.softlayer.api; - -import com.softlayer.api.annotation.ApiMethod; -import com.softlayer.api.annotation.ApiService; -import com.softlayer.api.http.HttpBasicAuthCredentials; -import com.softlayer.api.http.HttpClient; -import com.softlayer.api.http.HttpResponse; - -import java.lang.reflect.InvocationHandler; -import java.lang.reflect.Method; -import java.lang.reflect.Proxy; - -public class TestApiClient extends RestApiClient{ - - - private HttpBasicAuthCredentials credentials; - public HttpBasicAuthCredentials getCredentials() { - return credentials; - } - - public TestApiClient(String baseUrl) { - super(baseUrl); - } - - public S createService(Class serviceClass, String id) { - return (S) Proxy.newProxyInstance(getClass().getClassLoader(), - new Class[] { serviceClass }, new TestServiceProxy<>(serviceClass, id)); - } - - class TestServiceProxy extends ServiceProxy { - - final Class serviceClass; - final String id; - Mask mask; - String maskString; - ResultLimit resultLimit; - Integer lastResponseTotalItemCount; - - public TestServiceProxy(Class serviceClass, String id) { - super(serviceClass, id); - this.serviceClass = serviceClass; - this.id = id; - - } - - public String invokeService(Method method, final Object[] args) throws Throwable { - System.out.print("invokeTESTService: " + method + "\n"); - ApiMethod methodInfo = method.getAnnotation(ApiMethod.class); - // Must have ID if instance is required - if (methodInfo.instanceRequired() && id == null) { - throw new IllegalStateException("ID is required to invoke " + method); - } - String methodName = methodInfo.value().isEmpty() ? method.getName() : methodInfo.value(); - final String httpMethod = getHttpMethodFromMethodName(methodName); - String methodId = methodInfo.instanceRequired() ? this.id : null; - final String url = getFullUrl(serviceClass.getAnnotation(ApiService.class).value(), - methodName, methodId, resultLimit, mask == null ? maskString : mask.getMask()); - return url; - } - } -} diff --git a/src/test/java/com/softlayer/api/MaskTest.java b/src/test/java/com/softlayer/api/MaskTest.java index 1c90d42..5ac60bd 100644 --- a/src/test/java/com/softlayer/api/MaskTest.java +++ b/src/test/java/com/softlayer/api/MaskTest.java @@ -106,7 +106,7 @@ public void testMaskRemoval() { } @Test - public void testRecursiveMaskandLocal() { + public void testRecursiveMaskAndLocal() { RestApiClient client = new RestApiClient("http://example.com/"); TestEntity.Service service = TestEntity.service(client); service.withMask().recursiveProperty().recursiveProperty().baz(); @@ -137,16 +137,37 @@ public void testMultiLevelMask() { service.withMask().moreChildren().recursiveProperty().baz(); service.withMask().moreChildren().date(); + String result = service.getRecursiveProperty(); assertEquals("moreChildren[date,recursiveProperty.baz],recursiveProperty[foo,baz]", service.withMask().toString()); } + @Test + public void testNoChangeMaskScope() { + + + TestApiClient client = new TestApiClient("http://example.com/"); + + client.setLoggingEnabled(true); + + TestEntity.Service service = TestEntity.service(client); + service.withMask().testThing().id(); + service.withMask().testThing().first(); + + TestEntity result = service.getObject(); + assertEquals("testThing[id,first]", service.withMask().toString()); + String expected = "http://example.com/SoftLayer_TestEntity.json?objectMask=mask%5BtestThing%5Bid%2Cfirst%5D%5D"; + assertEquals(expected, client.httpClientFactory.fullUrl); + + + } + @Test public void testChangeMaskScope() { - RestApiClient client = new TestApiClient("http://example.com/"); + TestApiClient client = new TestApiClient("http://example.com/"); client.setLoggingEnabled(true); - System.out.print("Hello"); + TestEntity.Service service = TestEntity.service(client); service.withMask().recursiveProperty().baz(); service.withMask().recursiveProperty().foo(); diff --git a/src/test/java/com/softlayer/api/TestApiClient.java b/src/test/java/com/softlayer/api/TestApiClient.java new file mode 100644 index 0000000..1dc7caf --- /dev/null +++ b/src/test/java/com/softlayer/api/TestApiClient.java @@ -0,0 +1,108 @@ +package com.softlayer.api; + +import com.softlayer.api.annotation.ApiMethod; +import com.softlayer.api.annotation.ApiService; +import com.softlayer.api.http.*; +import com.softlayer.api.json.GsonJsonMarshallerFactoryTest; + +import java.io.ByteArrayOutputStream; +import java.io.OutputStream; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.Collections; + +import org.mockito.Mockito; + +public class TestApiClient extends RestApiClient{ + + + private HttpBasicAuthCredentials credentials; + public HttpBasicAuthCredentials getCredentials() { + return credentials; + } + public FakeHttpClientFactory httpClientFactory; + public TestApiClient(String baseUrl) { + super(baseUrl); + } + + public FakeHttpClientFactory getHttpClientFactory() { + if (httpClientFactory == null) { + httpClientFactory = new FakeHttpClientFactory(200, + Collections.emptyMap(), ""); + } + return httpClientFactory; + } + public void setHttpClientFactory(FakeHttpClientFactory httpClientFactory) { + this.httpClientFactory = httpClientFactory; + } + + public S createService(Class serviceClass, String id) { + return (S) Proxy.newProxyInstance(getClass().getClassLoader(), + new Class[] { serviceClass }, new TestServiceProxy<>(serviceClass, id)); + } + + class TestServiceProxy extends ServiceProxy { + + final Class serviceClass; + final String id; + + public Mask mask; + public String url; + String maskString; + ResultLimit resultLimit; + Integer lastResponseTotalItemCount; + + + public TestServiceProxy(Class serviceClass, String id) { + super(serviceClass, id); + Class c = serviceClass; + try { + c = Class.forName("com.softlayer.api.service.TestEntity$ServiceTester"); + }catch (ClassNotFoundException x) { + x.printStackTrace(); + } + + this.serviceClass = c; + this.id = id; + } + + @Override + public Object invokeService(Method method, final Object[] args) throws Throwable { + this.mask = super.mask; + this.maskString = super.maskString; + + ApiMethod methodInfo = method.getAnnotation(ApiMethod.class); + // Must have ID if instance is required + + if (methodInfo.instanceRequired() && id == null) { + throw new IllegalStateException("ID is required to invoke " + method); + } + String methodName = methodInfo.value().isEmpty() ? method.getName() : methodInfo.value(); + final String httpMethod = getHttpMethodFromMethodName(methodName); + String methodId = methodInfo.instanceRequired() ? this.id : null; + final String url = getFullUrl(serviceClass.getAnnotation(ApiService.class).value(), + methodName, methodId, resultLimit, mask == null ? maskString : mask.getMask()); + + this.url = url; + + Method toCall = serviceClass.getDeclaredMethod(method.getName()); + Object resultInvoke = toCall.invoke(serviceClass.newInstance()); + OutputStream output = new ByteArrayOutputStream(); + getJsonMarshallerFactory().getJsonMarshaller().toJson( + resultInvoke, output); + FakeHttpClientFactory faketory = new FakeHttpClientFactory(200, + Collections.emptyMap(), output.toString()); + setHttpClientFactory(faketory); + + final HttpClient client = getHttpClientFactory().getHttpClient(credentials, httpMethod, url, HEADERS); + HttpResponse response = client.invokeSync(() -> { + logRequestAndWriteBody(client, httpMethod, url, args); + return null; + }); + return logAndHandleResponse(response, url, method.getGenericReturnType()); + } + } + + +} diff --git a/src/test/java/com/softlayer/api/http/FakeHttpClientFactory.java b/src/test/java/com/softlayer/api/http/FakeHttpClientFactory.java index 4f78ac0..85bb91b 100644 --- a/src/test/java/com/softlayer/api/http/FakeHttpClientFactory.java +++ b/src/test/java/com/softlayer/api/http/FakeHttpClientFactory.java @@ -81,11 +81,11 @@ public OutputStream getBodyStream() { @Override public HttpResponse invokeSync(Callable setupBody) { invokeSyncCalled = true; - try { - setupBody.call(); - } catch (Exception e) { - throw new RuntimeException(e); - } +// try { +// setupBody.call(); +// } catch (Exception e) { +// throw new RuntimeException(e); +// } return this; } diff --git a/src/test/java/com/softlayer/api/service/TestEntity.java b/src/test/java/com/softlayer/api/service/TestEntity.java index e852c96..909bc17 100644 --- a/src/test/java/com/softlayer/api/service/TestEntity.java +++ b/src/test/java/com/softlayer/api/service/TestEntity.java @@ -6,7 +6,6 @@ import java.util.concurrent.Future; import com.softlayer.api.ApiClient; -import com.softlayer.api.Mask; import com.softlayer.api.ResponseHandler; import com.softlayer.api.ResultLimit; import com.softlayer.api.annotation.ApiMethod; @@ -118,12 +117,23 @@ public List getMoreChildren() { @ApiProperty protected List recursiveProperty; - public List getrecursiveProperty() { + public List getRecursiveProperty() { if (recursiveProperty == null) { recursiveProperty = new ArrayList(); } return recursiveProperty; } + + @ApiProperty + protected TestThing testThing; + + public TestThing getTestThing() { + if (testThing == null) { + testThing = new TestThing(); + } + return testThing; + } + public Service asService(ApiClient client) { return service(client, id); } @@ -155,6 +165,12 @@ public static interface Service extends com.softlayer.api.Service { @ApiMethod("getRecursiveProperty") public String getRecursiveProperty(); + + @ApiMethod("getObject") + public TestEntity getObject(); + + @ApiMethod("getTestThing") + public TestThing getTestThing(); } public static interface ServiceAsync extends com.softlayer.api.ServiceAsync { @@ -174,7 +190,7 @@ public static interface ServiceAsync extends com.softlayer.api.ServiceAsync { } public static class Mask extends Entity.Mask { - + public Mask foo() { withLocalProperty("foo"); return this; @@ -199,11 +215,15 @@ public Mask moreChildren() { } public Mask recursiveProperty() { - return withSubMask("recursiveProperty", Mask.class); + return withSubMask("recursiveProperty", com.softlayer.api.service.TestEntity.Mask.class); + } + public TestThing.Mask testThing() { + return withSubMask("testThing", com.softlayer.api.service.TestThing.Mask.class); } } - public class TestService implements Service { + @ApiService("SoftLayer_TestEntity") + public static class ServiceTester implements Service { @Override public ServiceAsync asAsync() { @@ -212,12 +232,13 @@ public ServiceAsync asAsync() { @Override public Mask withNewMask() { - return null; + return new Mask(); } @Override public Mask withMask() { - return null; + + return new Mask(); } @Override @@ -257,10 +278,28 @@ public Void doSomethingNonStatic(GregorianCalendar param1) { @Override public String getRecursiveProperty() { - System.out.print("This is playing"); - return "Thats playing to win"; + System.out.print("getRecursiveProperty\n"); + System.out.print("MASK: " + withMask().toString() +"\n"); + + return "Hello World"; } + @Override + public TestEntity getObject() { + TestEntity toReturn = new TestEntity(); + toReturn.id = Long.valueOf(12345); + toReturn.baz = "GotBaz"; + return toReturn; + + } + + @Override + public TestThing getTestThing() { + TestThing toReturn = new TestThing(); + toReturn.id = Long.valueOf(5555); + toReturn.first = "First Test Thing"; + return toReturn; + } @Override public ResultLimit getResultLimit() { return null; diff --git a/src/test/java/com/softlayer/api/service/TestThing.java b/src/test/java/com/softlayer/api/service/TestThing.java new file mode 100644 index 0000000..ca7f5ae --- /dev/null +++ b/src/test/java/com/softlayer/api/service/TestThing.java @@ -0,0 +1,185 @@ +package com.softlayer.api.service; + +import java.util.ArrayList; +import java.util.GregorianCalendar; +import java.util.List; +import java.util.concurrent.Future; + + +import com.softlayer.api.ApiClient; +import com.softlayer.api.Mask; +import com.softlayer.api.ResponseHandler; +import com.softlayer.api.ResultLimit; +import com.softlayer.api.annotation.ApiMethod; +import com.softlayer.api.annotation.ApiProperty; +import com.softlayer.api.annotation.ApiService; +import com.softlayer.api.annotation.ApiType; +import com.softlayer.api.service.TestEntity; +import org.junit.Test; + + +public class TestThing extends Entity { + + @ApiProperty(canBeNullOrNotSet = true) + protected Long id; + + public Long getId() { + return id; + } + + public void setId(Long id) { + idSpecified = true; + this.id = id; + } + + protected boolean idSpecified; + + public boolean isIdSpecified() { + return idSpecified; + } + + public void unsetId() { + id = null; + idSpecified = false; + } + + @ApiProperty("first") + protected String first; + + public String getFirst() { return first; } + + public void setFirst(String first) { + this.first = first; + } + + @ApiProperty("second") + protected String second; + + public String getSecond() { return second; } + + public void setSecond(String second) { + this.second = second; + } + + @ApiProperty("testEntity") + protected List testEntity; + + public List getTestEntity() { + if (testEntity == null) { + testEntity = new ArrayList(); + } + return testEntity; + } + public Service asService(ApiClient client) { + return service(client, id); + } + + public static Service service(ApiClient client) { + return client.createService(Service.class, null); + } + + public static Service service(ApiClient client, Long id) { + return client.createService(Service.class, id == null ? null : id.toString()); + } + + @ApiService("SoftLayer_TestThing") + public static interface Service extends com.softlayer.api.Service { + public ServiceAsync asAsync(); + public Mask withNewMask(); + public Mask withMask(); + public void setMask(Mask mask); + + @ApiMethod("getObject") + public TestThing getObject(); + + @ApiMethod("getTestEntity") + public List getTestEntity(); + } + + public static interface ServiceAsync extends com.softlayer.api.ServiceAsync { + public Mask withNewMask(); + public Mask withMask(); + public void setMask(Mask mask); + + public Future getObject(); + public Future getObject(ResponseHandler handler); + + public Future> getTestEntity(); + public Future getTestEntity(ResponseHandler> handler); + } + + public static class Mask extends Entity.Mask { + + public Mask id() { + withLocalProperty("id"); + return this; + } + public Mask first() { + withLocalProperty("first"); + return this; + } + public Mask second() { + withLocalProperty("second"); + return this; + } + public Mask testEntity() { + return withSubMask("testEntity", Mask.class); + } + + } + + @ApiService("SoftLayer_TestThing") + public static class ServiceTester implements Service { + + @Override + public ServiceAsync asAsync() { return null; } + + @Override + public Mask withNewMask() { return new Mask(); } + + @Override + public Mask withMask() { return new Mask(); } + + @Override + public void setMask(com.softlayer.api.Mask mask) { + + } + + @Override + public void setMask(String mask) { + + } + + @Override + public void clearMask() { + + } + + @Override + public void setMask(Mask mask) { + + } + + @Override + public TestThing getObject() { + TestThing toReturn = new TestThing(); + toReturn.id = Long.valueOf(1234); + toReturn.first = "First Property"; + + return toReturn; + } + + @Override + public List getTestEntity() { return null; } + + @Override + public ResultLimit getResultLimit() { return null; } + + @Override + public ResultLimit setResultLimit(ResultLimit limit) { return null; } + + @Override + public Integer getLastResponseTotalItemCount() { return null; } + } +} + From 21494f74e06745928436316ca149dea0c87355bf Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Wed, 20 Jun 2018 18:08:02 -0500 Subject: [PATCH 25/62] fixed some unit tests --- .../java/com/softlayer/api/RestApiClient.java | 5 ++-- src/test/java/com/softlayer/api/MaskTest.java | 24 +++++++++---------- .../api/http/FakeHttpClientFactory.java | 10 ++++---- .../com/softlayer/api/service/TestEntity.java | 3 --- 4 files changed, 18 insertions(+), 24 deletions(-) diff --git a/src/main/java/com/softlayer/api/RestApiClient.java b/src/main/java/com/softlayer/api/RestApiClient.java index 8a85e8d..46afa0e 100644 --- a/src/main/java/com/softlayer/api/RestApiClient.java +++ b/src/main/java/com/softlayer/api/RestApiClient.java @@ -225,11 +225,10 @@ class ServiceProxy implements InvocationHandler { final Class serviceClass; final String id; - public Mask mask; + Mask mask; String maskString; ResultLimit resultLimit; Integer lastResponseTotalItemCount; - public ServiceProxy(Class serviceClass, String id) { this.serviceClass = serviceClass; @@ -305,7 +304,6 @@ public Object logAndHandleResponse(HttpResponse response, String url, } public Object invokeService(Method method, final Object[] args) throws Throwable { - ApiMethod methodInfo = method.getAnnotation(ApiMethod.class); // Must have ID if instance is required if (methodInfo.instanceRequired() && id == null) { @@ -442,6 +440,7 @@ public boolean isDone() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { boolean noParams = args == null || args.length == 0; + if ("asAsync".equals(method.getName()) && noParams) { ServiceProxy asyncProxy = new ServiceProxy<>(serviceClass, id); asyncProxy.mask = mask; diff --git a/src/test/java/com/softlayer/api/MaskTest.java b/src/test/java/com/softlayer/api/MaskTest.java index 5ac60bd..4b8f766 100644 --- a/src/test/java/com/softlayer/api/MaskTest.java +++ b/src/test/java/com/softlayer/api/MaskTest.java @@ -90,14 +90,14 @@ public void testSetStringMask() { @Test(expected = IllegalArgumentException.class) public void testMaskMustNotBeNull() { - RestApiClient client = new RestApiClient("http://example.com/"); + RestApiClient client = new TestApiClient("http://example.com/"); TestEntity.Service service = TestEntity.service(client); service.setMask((Mask) null); } @Test public void testMaskRemoval() { - RestApiClient client = new RestApiClient("http://example.com/"); + RestApiClient client = new TestApiClient("http://example.com/"); TestEntity.Service service = TestEntity.service(client); service.withMask().baz(); assertEquals("baz", service.withMask().toString()); @@ -107,7 +107,7 @@ public void testMaskRemoval() { @Test public void testRecursiveMaskAndLocal() { - RestApiClient client = new RestApiClient("http://example.com/"); + RestApiClient client = new TestApiClient("http://example.com/"); TestEntity.Service service = TestEntity.service(client); service.withMask().recursiveProperty().recursiveProperty().baz(); service.withMask().recursiveProperty().recursiveProperty().foo(); @@ -118,7 +118,7 @@ public void testRecursiveMaskAndLocal() { @Test public void testRecursiveMask() { - RestApiClient client = new RestApiClient("http://example.com/"); + RestApiClient client = new TestApiClient("http://example.com/"); TestEntity.Service service = TestEntity.service(client); service.withMask().recursiveProperty().baz(); service.withMask().recursiveProperty().foo(); @@ -130,7 +130,7 @@ public void testRecursiveMask() { @Test public void testMultiLevelMask() { - RestApiClient client = new RestApiClient("http://example.com/"); + RestApiClient client = new TestApiClient("http://example.com/"); TestEntity.Service service = TestEntity.service(client); service.withMask().recursiveProperty().baz(); service.withMask().recursiveProperty().foo(); @@ -149,12 +149,13 @@ public void testNoChangeMaskScope() { TestApiClient client = new TestApiClient("http://example.com/"); - client.setLoggingEnabled(true); +// client.setLoggingEnabled(true); TestEntity.Service service = TestEntity.service(client); service.withMask().testThing().id(); service.withMask().testThing().first(); + TestEntity result = service.getObject(); assertEquals("testThing[id,first]", service.withMask().toString()); String expected = "http://example.com/SoftLayer_TestEntity.json?objectMask=mask%5BtestThing%5Bid%2Cfirst%5D%5D"; @@ -165,20 +166,17 @@ public void testNoChangeMaskScope() { @Test public void testChangeMaskScope() { +// https://github.com/softlayer/softlayer-java/issues/19 + TestApiClient client = new TestApiClient("http://example.com/"); - client.setLoggingEnabled(true); +// client.setLoggingEnabled(true); TestEntity.Service service = TestEntity.service(client); service.withMask().recursiveProperty().baz(); service.withMask().recursiveProperty().foo(); String result = service.getRecursiveProperty(); - System.out.print(result); -// RestApiClient.ServiceProxy serviceProxy = client.createService(TestEntity, 1234); -// serviceProxy.invoke(service, service.getRecursiveProperty(),null); -// assertEquals("http://example.com/SomeService/1234/someMethod.json?objectMask=someMask%26%26", -// client.ServiceProxy. -// ) +// assertEquals("baz,foo", service.withMask()); } } diff --git a/src/test/java/com/softlayer/api/http/FakeHttpClientFactory.java b/src/test/java/com/softlayer/api/http/FakeHttpClientFactory.java index 85bb91b..4f78ac0 100644 --- a/src/test/java/com/softlayer/api/http/FakeHttpClientFactory.java +++ b/src/test/java/com/softlayer/api/http/FakeHttpClientFactory.java @@ -81,11 +81,11 @@ public OutputStream getBodyStream() { @Override public HttpResponse invokeSync(Callable setupBody) { invokeSyncCalled = true; -// try { -// setupBody.call(); -// } catch (Exception e) { -// throw new RuntimeException(e); -// } + try { + setupBody.call(); + } catch (Exception e) { + throw new RuntimeException(e); + } return this; } diff --git a/src/test/java/com/softlayer/api/service/TestEntity.java b/src/test/java/com/softlayer/api/service/TestEntity.java index 909bc17..db3ac7f 100644 --- a/src/test/java/com/softlayer/api/service/TestEntity.java +++ b/src/test/java/com/softlayer/api/service/TestEntity.java @@ -278,9 +278,6 @@ public Void doSomethingNonStatic(GregorianCalendar param1) { @Override public String getRecursiveProperty() { - System.out.print("getRecursiveProperty\n"); - System.out.print("MASK: " + withMask().toString() +"\n"); - return "Hello World"; } From 7c67c40b0172abdb817d8b0054c46136c34cdc9f Mon Sep 17 00:00:00 2001 From: Cameron Porter Date: Tue, 4 Dec 2018 11:31:41 -0600 Subject: [PATCH 26/62] Release version 0.2.6 --- README.md | 2 +- examples/pom.xml | 11 ++++------- gen/pom.xml | 12 ++++++------ pom.xml | 25 ++++++++++++------------- 4 files changed, 23 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index eed2cf1..ec3e9fe 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ To add the project you your Maven project, add the dependency: com.softlayer.api softlayer-api-client - 0.2.5 + 0.2.6 ``` diff --git a/examples/pom.xml b/examples/pom.xml index de92d47..6c9becd 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -5,7 +5,7 @@ softlayer-api-client-examples jar - 0.2.5 + 0.2.6 softlayer-api-client-examples http://sldn.softlayer.com @@ -28,18 +28,15 @@ - org.apache.maven.plugins maven-resources-plugin - 2.6 + 3.1.0 org.codehaus.mojo exec-maven-plugin - 1.2.1 + 1.6.0 @@ -54,7 +51,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.7.0 + 3.8.0 ${java.version} ${java.version} diff --git a/gen/pom.xml b/gen/pom.xml index 78f41fb..a69cfe9 100644 --- a/gen/pom.xml +++ b/gen/pom.xml @@ -5,7 +5,7 @@ softlayer-api-client-gen jar - 0.2.5 + 0.2.6 softlayer-api-client-gen http://sldn.softlayer.com @@ -29,12 +29,12 @@ com.google.code.gson gson - 2.3 + 2.8.5 junit junit - 4.11 + 4.12 test @@ -43,7 +43,7 @@ org.codehaus.mojo exec-maven-plugin - 1.2.1 + 1.6.0 @@ -58,12 +58,12 @@ org.apache.maven.plugins maven-javadoc-plugin - 3.0.0-M1 + 3.0.1 org.apache.maven.plugins maven-compiler-plugin - 3.7.0 + 3.8.0 ${java.version} ${java.version} diff --git a/pom.xml b/pom.xml index 46c6aca..b6dee25 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ softlayer-api-client jar - 0.2.5 + 0.2.6 SoftLayer API Client for Java API client for accessing the SoftLayer API http://sldn.softlayer.com @@ -41,7 +41,7 @@ scm:git:git@github.com:softlayer/softlayer-java.git scm:git:git@github.com:softlayer/softlayer-java.git git@github.com:softlayer/softlayer-java.git - 0.2.5 + 0.2.6 UTF-8 @@ -67,7 +67,7 @@ com.google.code.gson gson - 2.6 + 2.8.5 junit @@ -78,7 +78,7 @@ org.mockito mockito-core - 2.8.47 + 2.23.4 test @@ -87,7 +87,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.7.0 + 3.8.0 ${java.version} ${java.version} @@ -109,18 +109,18 @@ org.apache.maven.plugins maven-javadoc-plugin - 3.0.0-M1 + 3.0.1 + + + + none + attach-javadocs jar - - - - -Xdoclint:none - @@ -141,8 +141,7 @@ maven-invoker-plugin - - 2.0.0 + 3.1.0 generate-services From 6c018a2c5f3677b32988afff013c7f26ea825256 Mon Sep 17 00:00:00 2001 From: Cameron Porter Date: Tue, 4 Dec 2018 12:19:36 -0600 Subject: [PATCH 27/62] Update travis configuration to test on java 10 and 11 where available and supported by OpenJDK and Oracle. --- .travis.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.travis.yml b/.travis.yml index 342a614..53b61d0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,8 @@ language: java jdk: - oraclejdk8 + - oraclejdk11 - openjdk8 + - openjdk10 + - openjdk11 install: mvn install -DskipTests=true -Dmaven.javadoc.skip=true -Dgpg.skip=true \ No newline at end of file From c3d535488b16ccd1cfd59c0a590ffee20d3670aa Mon Sep 17 00:00:00 2001 From: Cameron Porter Date: Tue, 19 Mar 2019 12:54:12 -0500 Subject: [PATCH 28/62] Code cleanup. The ServiceTester classes don't really work the way the client expects, so they've been removed in favor of using the FakeHttpClientFactory in place where it is needed. Additionally the TestApiClient duplicates a lot of the same code as the RestApiClient and is not flexible for service types other than the TestEntity ServiceTester class. --- src/test/java/com/softlayer/api/MaskTest.java | 77 ++++---- .../com/softlayer/api/RestApiClientTest.java | 4 - .../java/com/softlayer/api/TestApiClient.java | 108 ----------- .../com/softlayer/api/service/TestEntity.java | 174 +++++------------- .../com/softlayer/api/service/TestThing.java | 82 ++------- 5 files changed, 97 insertions(+), 348 deletions(-) delete mode 100644 src/test/java/com/softlayer/api/TestApiClient.java diff --git a/src/test/java/com/softlayer/api/MaskTest.java b/src/test/java/com/softlayer/api/MaskTest.java index 4b8f766..928b0be 100644 --- a/src/test/java/com/softlayer/api/MaskTest.java +++ b/src/test/java/com/softlayer/api/MaskTest.java @@ -2,37 +2,27 @@ import static org.junit.Assert.*; - -import java.io.ByteArrayOutputStream; -import java.io.PrintStream; -import java.lang.reflect.Proxy; import java.net.URLEncoder; import java.util.Collections; -import java.util.GregorianCalendar; -import java.util.List; -import java.util.concurrent.Callable; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.atomic.AtomicBoolean; +import org.junit.Ignore; import org.junit.Test; import com.softlayer.api.http.FakeHttpClientFactory; -import com.softlayer.api.http.HttpBasicAuthCredentials; import com.softlayer.api.json.GsonJsonMarshallerFactoryTest; import com.softlayer.api.service.TestEntity; - - public class MaskTest { static { GsonJsonMarshallerFactoryTest.addTestEntityToGson(); } + @Test public void testWithMask() throws Exception { FakeHttpClientFactory http = new FakeHttpClientFactory(200, - Collections.emptyMap(), "\"some response\""); + Collections.emptyMap(), "\"some response\""); RestApiClient client = new RestApiClient("http://example.com/") - .withCredentials("user", "key"); + .withCredentials("user", "key"); client.setHttpClientFactory(http); TestEntity entity = new TestEntity(); @@ -43,16 +33,16 @@ public void testWithMask() throws Exception { assertEquals("some response", service.doSomethingStatic(123L, entity)); assertEquals("http://example.com/SoftLayer_TestEntity/doSomethingStatic.json" - + "?objectMask=" + URLEncoder.encode(service.withMask().getMask(), "UTF-8"), http.fullUrl); + + "?objectMask=" + URLEncoder.encode(service.withMask().getMask(), "UTF-8"), http.fullUrl); assertTrue(http.invokeSyncCalled); } @Test public void testSetObjectMask() throws Exception { FakeHttpClientFactory http = new FakeHttpClientFactory(200, - Collections.emptyMap(), "\"some response\""); + Collections.emptyMap(), "\"some response\""); RestApiClient client = new RestApiClient("http://example.com/") - .withCredentials("user", "key"); + .withCredentials("user", "key"); client.setHttpClientFactory(http); TestEntity entity = new TestEntity(); @@ -65,16 +55,16 @@ public void testSetObjectMask() throws Exception { assertEquals("some response", service.doSomethingStatic(123L, entity)); assertEquals("http://example.com/SoftLayer_TestEntity/doSomethingStatic.json" - + "?objectMask=" + URLEncoder.encode(mask.getMask(), "UTF-8"), http.fullUrl); + + "?objectMask=" + URLEncoder.encode(mask.getMask(), "UTF-8"), http.fullUrl); assertTrue(http.invokeSyncCalled); } @Test public void testSetStringMask() { FakeHttpClientFactory http = new FakeHttpClientFactory(200, - Collections.emptyMap(), "\"some response\""); + Collections.emptyMap(), "\"some response\""); RestApiClient client = new RestApiClient("http://example.com/") - .withCredentials("user", "key"); + .withCredentials("user", "key"); client.setHttpClientFactory(http); TestEntity entity = new TestEntity(); @@ -84,20 +74,20 @@ public void testSetStringMask() { assertEquals("some response", service.doSomethingStatic(123L, entity)); assertEquals("http://example.com/SoftLayer_TestEntity/doSomethingStatic.json" - + "?objectMask=yay-a-mask", http.fullUrl); + + "?objectMask=yay-a-mask", http.fullUrl); assertTrue(http.invokeSyncCalled); } @Test(expected = IllegalArgumentException.class) public void testMaskMustNotBeNull() { - RestApiClient client = new TestApiClient("http://example.com/"); + RestApiClient client = new RestApiClient("http://example.com/"); TestEntity.Service service = TestEntity.service(client); service.setMask((Mask) null); } @Test public void testMaskRemoval() { - RestApiClient client = new TestApiClient("http://example.com/"); + RestApiClient client = new RestApiClient("http://example.com/"); TestEntity.Service service = TestEntity.service(client); service.withMask().baz(); assertEquals("baz", service.withMask().toString()); @@ -107,30 +97,34 @@ public void testMaskRemoval() { @Test public void testRecursiveMaskAndLocal() { - RestApiClient client = new TestApiClient("http://example.com/"); + RestApiClient client = new RestApiClient("http://example.com/"); TestEntity.Service service = TestEntity.service(client); service.withMask().recursiveProperty().recursiveProperty().baz(); service.withMask().recursiveProperty().recursiveProperty().foo(); service.withMask().recursiveProperty().date(); assertEquals("recursiveProperty[date,recursiveProperty[foo,baz]]", - service.withMask().toString()); + service.withMask().toString()); } @Test public void testRecursiveMask() { - RestApiClient client = new TestApiClient("http://example.com/"); + RestApiClient client = new RestApiClient("http://example.com/"); TestEntity.Service service = TestEntity.service(client); service.withMask().recursiveProperty().baz(); service.withMask().recursiveProperty().foo(); service.withMask().recursiveProperty().date(); assertEquals("recursiveProperty[date,foo,baz]", - service.withMask().toString()); + service.withMask().toString()); } @Test public void testMultiLevelMask() { - RestApiClient client = new TestApiClient("http://example.com/"); + FakeHttpClientFactory http = new FakeHttpClientFactory(200, + Collections.emptyMap(),""); + RestApiClient client = new RestApiClient("http://example.com/"); + client.setHttpClientFactory(http); + TestEntity.Service service = TestEntity.service(client); service.withMask().recursiveProperty().baz(); service.withMask().recursiveProperty().foo(); @@ -140,43 +134,40 @@ public void testMultiLevelMask() { String result = service.getRecursiveProperty(); assertEquals("moreChildren[date,recursiveProperty.baz],recursiveProperty[foo,baz]", - service.withMask().toString()); + service.withMask().toString()); } @Test public void testNoChangeMaskScope() { - - - TestApiClient client = new TestApiClient("http://example.com/"); - -// client.setLoggingEnabled(true); + FakeHttpClientFactory http = new FakeHttpClientFactory(200, + Collections.emptyMap(),""); + RestApiClient client = new RestApiClient("http://example.com/"); + client.setHttpClientFactory(http); TestEntity.Service service = TestEntity.service(client); service.withMask().testThing().id(); service.withMask().testThing().first(); - TestEntity result = service.getObject(); assertEquals("testThing[id,first]", service.withMask().toString()); String expected = "http://example.com/SoftLayer_TestEntity.json?objectMask=mask%5BtestThing%5Bid%2Cfirst%5D%5D"; - assertEquals(expected, client.httpClientFactory.fullUrl); - - + assertEquals(expected, http.fullUrl); } + /** + * This doesn't work due to the issues mentioned in https://github.com/softlayer/softlayer-java/issues/19 + */ @Test + @Ignore public void testChangeMaskScope() { -// https://github.com/softlayer/softlayer-java/issues/19 - - TestApiClient client = new TestApiClient("http://example.com/"); -// client.setLoggingEnabled(true); + RestApiClient client = new RestApiClient("http://example.com/"); TestEntity.Service service = TestEntity.service(client); service.withMask().recursiveProperty().baz(); service.withMask().recursiveProperty().foo(); String result = service.getRecursiveProperty(); -// assertEquals("baz,foo", service.withMask()); + assertEquals("baz,foo", service.withMask().toString()); } } diff --git a/src/test/java/com/softlayer/api/RestApiClientTest.java b/src/test/java/com/softlayer/api/RestApiClientTest.java index 1a0b565..e44b7e5 100644 --- a/src/test/java/com/softlayer/api/RestApiClientTest.java +++ b/src/test/java/com/softlayer/api/RestApiClientTest.java @@ -5,10 +5,8 @@ import java.io.ByteArrayOutputStream; import java.io.PrintStream; import java.lang.reflect.Proxy; -import java.net.URLEncoder; import java.util.Collections; import java.util.GregorianCalendar; -import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.atomic.AtomicBoolean; @@ -400,8 +398,6 @@ public void onSuccess(String value) { assertTrue(successCalled.get()); } - - @Test public void testNormalObjectMethodsOnService() { RestApiClient client = new RestApiClient("http://example.com/"); diff --git a/src/test/java/com/softlayer/api/TestApiClient.java b/src/test/java/com/softlayer/api/TestApiClient.java deleted file mode 100644 index 1dc7caf..0000000 --- a/src/test/java/com/softlayer/api/TestApiClient.java +++ /dev/null @@ -1,108 +0,0 @@ -package com.softlayer.api; - -import com.softlayer.api.annotation.ApiMethod; -import com.softlayer.api.annotation.ApiService; -import com.softlayer.api.http.*; -import com.softlayer.api.json.GsonJsonMarshallerFactoryTest; - -import java.io.ByteArrayOutputStream; -import java.io.OutputStream; -import java.lang.reflect.InvocationHandler; -import java.lang.reflect.Method; -import java.lang.reflect.Proxy; -import java.util.Collections; - -import org.mockito.Mockito; - -public class TestApiClient extends RestApiClient{ - - - private HttpBasicAuthCredentials credentials; - public HttpBasicAuthCredentials getCredentials() { - return credentials; - } - public FakeHttpClientFactory httpClientFactory; - public TestApiClient(String baseUrl) { - super(baseUrl); - } - - public FakeHttpClientFactory getHttpClientFactory() { - if (httpClientFactory == null) { - httpClientFactory = new FakeHttpClientFactory(200, - Collections.emptyMap(), ""); - } - return httpClientFactory; - } - public void setHttpClientFactory(FakeHttpClientFactory httpClientFactory) { - this.httpClientFactory = httpClientFactory; - } - - public S createService(Class serviceClass, String id) { - return (S) Proxy.newProxyInstance(getClass().getClassLoader(), - new Class[] { serviceClass }, new TestServiceProxy<>(serviceClass, id)); - } - - class TestServiceProxy extends ServiceProxy { - - final Class serviceClass; - final String id; - - public Mask mask; - public String url; - String maskString; - ResultLimit resultLimit; - Integer lastResponseTotalItemCount; - - - public TestServiceProxy(Class serviceClass, String id) { - super(serviceClass, id); - Class c = serviceClass; - try { - c = Class.forName("com.softlayer.api.service.TestEntity$ServiceTester"); - }catch (ClassNotFoundException x) { - x.printStackTrace(); - } - - this.serviceClass = c; - this.id = id; - } - - @Override - public Object invokeService(Method method, final Object[] args) throws Throwable { - this.mask = super.mask; - this.maskString = super.maskString; - - ApiMethod methodInfo = method.getAnnotation(ApiMethod.class); - // Must have ID if instance is required - - if (methodInfo.instanceRequired() && id == null) { - throw new IllegalStateException("ID is required to invoke " + method); - } - String methodName = methodInfo.value().isEmpty() ? method.getName() : methodInfo.value(); - final String httpMethod = getHttpMethodFromMethodName(methodName); - String methodId = methodInfo.instanceRequired() ? this.id : null; - final String url = getFullUrl(serviceClass.getAnnotation(ApiService.class).value(), - methodName, methodId, resultLimit, mask == null ? maskString : mask.getMask()); - - this.url = url; - - Method toCall = serviceClass.getDeclaredMethod(method.getName()); - Object resultInvoke = toCall.invoke(serviceClass.newInstance()); - OutputStream output = new ByteArrayOutputStream(); - getJsonMarshallerFactory().getJsonMarshaller().toJson( - resultInvoke, output); - FakeHttpClientFactory faketory = new FakeHttpClientFactory(200, - Collections.emptyMap(), output.toString()); - setHttpClientFactory(faketory); - - final HttpClient client = getHttpClientFactory().getHttpClient(credentials, httpMethod, url, HEADERS); - HttpResponse response = client.invokeSync(() -> { - logRequestAndWriteBody(client, httpMethod, url, args); - return null; - }); - return logAndHandleResponse(response, url, method.getGenericReturnType()); - } - } - - -} diff --git a/src/test/java/com/softlayer/api/service/TestEntity.java b/src/test/java/com/softlayer/api/service/TestEntity.java index db3ac7f..ce85bf6 100644 --- a/src/test/java/com/softlayer/api/service/TestEntity.java +++ b/src/test/java/com/softlayer/api/service/TestEntity.java @@ -5,17 +5,17 @@ import java.util.List; import java.util.concurrent.Future; -import com.softlayer.api.ApiClient; -import com.softlayer.api.ResponseHandler; -import com.softlayer.api.ResultLimit; import com.softlayer.api.annotation.ApiMethod; import com.softlayer.api.annotation.ApiProperty; import com.softlayer.api.annotation.ApiService; import com.softlayer.api.annotation.ApiType; +import com.softlayer.api.ApiClient; +import com.softlayer.api.ResponseHandler; +import com.softlayer.api.ResultLimit; @ApiType("SoftLayer_TestEntity") public class TestEntity extends Entity { - + @ApiProperty(canBeNullOrNotSet = true) protected Long id; @@ -38,75 +38,75 @@ public void unsetId() { id = null; idSpecified = false; } - + @ApiProperty("bar") protected String foo; - + public String getFoo() { return foo; } - + public void setFoo(String foo) { this.foo = foo; } - + @ApiProperty(canBeNullOrNotSet = true) protected String baz; protected boolean bazSpecified; - + public String getBaz() { return baz; } - + public void setBaz(String baz) { bazSpecified = true; this.baz = baz; } - + public boolean isBazSpecified() { return bazSpecified; } - + public void unsetBaz() { baz = null; bazSpecified = false; } - + @ApiProperty protected GregorianCalendar date; - + public GregorianCalendar getDate() { return date; } - + public void setDate(GregorianCalendar date) { this.date = date; } - + protected String notApiProperty; - + public String getNotApiProperty() { return notApiProperty; } - + public void setNotApiProperty(String notApiProperty) { this.notApiProperty = notApiProperty; } - + @ApiProperty protected TestEntity child; - + public TestEntity getChild() { return child; } - + public void setChild(TestEntity child) { this.child = child; } - + @ApiProperty protected List moreChildren; - + public List getMoreChildren() { if (moreChildren == null) { moreChildren = new ArrayList(); @@ -145,21 +145,24 @@ public static Service service(ApiClient client) { public static Service service(ApiClient client, Long id) { return client.createService(Service.class, id == null ? null : id.toString()); } - + @ApiService("SoftLayer_TestEntity") public static interface Service extends com.softlayer.api.Service { public ServiceAsync asAsync(); + public Mask withNewMask(); + public Mask withMask(); + public void setMask(Mask mask); - + @ApiMethod public String doSomethingStatic(Long param1, TestEntity param2); - + @ApiMethod("actualName") public List fakeName(); - + @ApiMethod(instanceRequired = true) public Void doSomethingNonStatic(GregorianCalendar param1); @@ -172,44 +175,49 @@ public static interface Service extends com.softlayer.api.Service { @ApiMethod("getTestThing") public TestThing getTestThing(); } - + public static interface ServiceAsync extends com.softlayer.api.ServiceAsync { public Mask withNewMask(); + public Mask withMask(); + public void setMask(Mask mask); - + public Future doSomethingStatic(Long param1, TestEntity param2); + public Future doSomethingStatic(Long param1, TestEntity param2, ResponseHandler handler); - + public Future fakeName(); + public Future fakeName(ResponseHandler handler); - + public Future doSomethingNonStatic(GregorianCalendar param1); + public Future doSomethingNonStatic(GregorianCalendar param1, ResponseHandler handler); } - + public static class Mask extends Entity.Mask { public Mask foo() { withLocalProperty("foo"); return this; } - + public Mask baz() { withLocalProperty("baz"); return this; } - + public Mask date() { withLocalProperty("date"); return this; } - + public Mask child() { return withSubMask("child", Mask.class); } - + public Mask moreChildren() { return withSubMask("moreChildren", Mask.class); } @@ -217,99 +225,9 @@ public Mask moreChildren() { public Mask recursiveProperty() { return withSubMask("recursiveProperty", com.softlayer.api.service.TestEntity.Mask.class); } + public TestThing.Mask testThing() { return withSubMask("testThing", com.softlayer.api.service.TestThing.Mask.class); } } - - @ApiService("SoftLayer_TestEntity") - public static class ServiceTester implements Service { - - @Override - public ServiceAsync asAsync() { - return null; - } - - @Override - public Mask withNewMask() { - return new Mask(); - } - - @Override - public Mask withMask() { - - return new Mask(); - } - - @Override - public void setMask(com.softlayer.api.Mask mask) { - - } - - @Override - public void setMask(String mask) { - - } - - @Override - public void clearMask() { - - } - - @Override - public void setMask(Mask mask) { - - } - - @Override - public String doSomethingStatic(Long param1, TestEntity param2) { - return null; - } - - @Override - public List fakeName() { - return null; - } - - @Override - public Void doSomethingNonStatic(GregorianCalendar param1) { - return null; - } - - @Override - public String getRecursiveProperty() { - return "Hello World"; - } - - @Override - public TestEntity getObject() { - TestEntity toReturn = new TestEntity(); - toReturn.id = Long.valueOf(12345); - toReturn.baz = "GotBaz"; - return toReturn; - - } - - @Override - public TestThing getTestThing() { - TestThing toReturn = new TestThing(); - toReturn.id = Long.valueOf(5555); - toReturn.first = "First Test Thing"; - return toReturn; - } - @Override - public ResultLimit getResultLimit() { - return null; - } - - @Override - public ResultLimit setResultLimit(ResultLimit limit) { - return null; - } - - @Override - public Integer getLastResponseTotalItemCount() { - return null; - } - } -} \ No newline at end of file +} diff --git a/src/test/java/com/softlayer/api/service/TestThing.java b/src/test/java/com/softlayer/api/service/TestThing.java index ca7f5ae..2eac363 100644 --- a/src/test/java/com/softlayer/api/service/TestThing.java +++ b/src/test/java/com/softlayer/api/service/TestThing.java @@ -1,22 +1,15 @@ package com.softlayer.api.service; import java.util.ArrayList; -import java.util.GregorianCalendar; import java.util.List; import java.util.concurrent.Future; - import com.softlayer.api.ApiClient; -import com.softlayer.api.Mask; import com.softlayer.api.ResponseHandler; import com.softlayer.api.ResultLimit; import com.softlayer.api.annotation.ApiMethod; import com.softlayer.api.annotation.ApiProperty; import com.softlayer.api.annotation.ApiService; -import com.softlayer.api.annotation.ApiType; -import com.softlayer.api.service.TestEntity; -import org.junit.Test; - public class TestThing extends Entity { @@ -46,7 +39,9 @@ public void unsetId() { @ApiProperty("first") protected String first; - public String getFirst() { return first; } + public String getFirst() { + return first; + } public void setFirst(String first) { this.first = first; @@ -55,7 +50,9 @@ public void setFirst(String first) { @ApiProperty("second") protected String second; - public String getSecond() { return second; } + public String getSecond() { + return second; + } public void setSecond(String second) { this.second = second; @@ -70,6 +67,7 @@ public List getTestEntity() { } return testEntity; } + public Service asService(ApiClient client) { return service(client, id); } @@ -85,8 +83,11 @@ public static Service service(ApiClient client, Long id) { @ApiService("SoftLayer_TestThing") public static interface Service extends com.softlayer.api.Service { public ServiceAsync asAsync(); + public Mask withNewMask(); + public Mask withMask(); + public void setMask(Mask mask); @ApiMethod("getObject") @@ -98,13 +99,17 @@ public static interface Service extends com.softlayer.api.Service { public static interface ServiceAsync extends com.softlayer.api.ServiceAsync { public Mask withNewMask(); + public Mask withMask(); + public void setMask(Mask mask); public Future getObject(); + public Future getObject(ResponseHandler handler); public Future> getTestEntity(); + public Future getTestEntity(ResponseHandler> handler); } @@ -114,72 +119,19 @@ public Mask id() { withLocalProperty("id"); return this; } + public Mask first() { withLocalProperty("first"); return this; } + public Mask second() { withLocalProperty("second"); return this; } + public Mask testEntity() { return withSubMask("testEntity", Mask.class); } - - } - - @ApiService("SoftLayer_TestThing") - public static class ServiceTester implements Service { - - @Override - public ServiceAsync asAsync() { return null; } - - @Override - public Mask withNewMask() { return new Mask(); } - - @Override - public Mask withMask() { return new Mask(); } - - @Override - public void setMask(com.softlayer.api.Mask mask) { - - } - - @Override - public void setMask(String mask) { - - } - - @Override - public void clearMask() { - - } - - @Override - public void setMask(Mask mask) { - - } - - @Override - public TestThing getObject() { - TestThing toReturn = new TestThing(); - toReturn.id = Long.valueOf(1234); - toReturn.first = "First Property"; - - return toReturn; - } - - @Override - public List getTestEntity() { return null; } - - @Override - public ResultLimit getResultLimit() { return null; } - - @Override - public ResultLimit setResultLimit(ResultLimit limit) { return null; } - - @Override - public Integer getLastResponseTotalItemCount() { return null; } } } - From 0bb17f4b520aae0d0878f3dcaad0d9ee96472363 Mon Sep 17 00:00:00 2001 From: Cameron Porter Date: Thu, 21 Mar 2019 15:56:22 -0500 Subject: [PATCH 29/62] Release version 0.2.7 --- README.md | 25 ++++++++++++++++++++----- examples/pom.xml | 2 +- gen/pom.xml | 2 +- pom.xml | 4 ++-- 4 files changed, 24 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index ec3e9fe..eea475a 100644 --- a/README.md +++ b/README.md @@ -27,19 +27,34 @@ servers and may charge your account. ## Using -To add the project you your Maven project, add the dependency: +Add the library as a dependency using your favorite build tooling. + +Note that the published client library is built upon the state of the API at the time of the version's release. +It will contain the generated artifacts as of that time only. +See "Building" for more information on how to regenerate the artifacts to get regular +additions to the SoftLayer API. + +### Maven ```xml com.softlayer.api softlayer-api-client - 0.2.6 + 0.2.7 ``` -Note, the client published to Maven is built upon version change of this project. It will contain the generated -artifacts as of that time only. See "Building" for more information on how to regenerate the artifacts to get regular -additions to the SoftLayer API. +### Gradle + +```groovy +implementation 'com.softlayer.api:softlayer-api-client:0.2.7' +``` + +### Kotlin + +```kotlin +compile("com.softlayer.api:softlayer-api-client:0.2.7") +``` ### Creating a Client diff --git a/examples/pom.xml b/examples/pom.xml index 6c9becd..bf99322 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -5,7 +5,7 @@ softlayer-api-client-examples jar - 0.2.6 + 0.2.7 softlayer-api-client-examples http://sldn.softlayer.com diff --git a/gen/pom.xml b/gen/pom.xml index a69cfe9..7582733 100644 --- a/gen/pom.xml +++ b/gen/pom.xml @@ -5,7 +5,7 @@ softlayer-api-client-gen jar - 0.2.6 + 0.2.7 softlayer-api-client-gen http://sldn.softlayer.com diff --git a/pom.xml b/pom.xml index b6dee25..62e9be7 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ softlayer-api-client jar - 0.2.6 + 0.2.7 SoftLayer API Client for Java API client for accessing the SoftLayer API http://sldn.softlayer.com @@ -41,7 +41,7 @@ scm:git:git@github.com:softlayer/softlayer-java.git scm:git:git@github.com:softlayer/softlayer-java.git git@github.com:softlayer/softlayer-java.git - 0.2.6 + 0.2.7 UTF-8 From 6a94e4615776e1eb26ace554c211ed7ecdf7ae18 Mon Sep 17 00:00:00 2001 From: Cameron Porter Date: Fri, 13 Sep 2019 15:04:32 -0500 Subject: [PATCH 30/62] Update travis java versions. --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 53b61d0..0343321 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,9 @@ language: java jdk: - - oraclejdk8 + - oraclejdk9 - oraclejdk11 - openjdk8 - openjdk10 - openjdk11 + - openjdk12 install: mvn install -DskipTests=true -Dmaven.javadoc.skip=true -Dgpg.skip=true \ No newline at end of file From b9c7bf3c20e2fc5782fee397d9a13287277068d9 Mon Sep 17 00:00:00 2001 From: Cameron Porter Date: Mon, 16 Sep 2019 09:25:45 -0500 Subject: [PATCH 31/62] Fix building under java 11 and 12, and remove oracle java 9. --- .travis.yml | 2 +- examples/pom.xml | 2 +- gen/pom.xml | 7 +++++-- pom.xml | 9 +++++---- 4 files changed, 12 insertions(+), 8 deletions(-) diff --git a/.travis.yml b/.travis.yml index 0343321..0bb046d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,8 @@ language: java jdk: - - oraclejdk9 - oraclejdk11 - openjdk8 + - openjdk9 - openjdk10 - openjdk11 - openjdk12 diff --git a/examples/pom.xml b/examples/pom.xml index bf99322..87c864c 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -51,7 +51,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.8.0 + 3.8.1 ${java.version} ${java.version} diff --git a/gen/pom.xml b/gen/pom.xml index 7582733..ba022cc 100644 --- a/gen/pom.xml +++ b/gen/pom.xml @@ -58,12 +58,15 @@ org.apache.maven.plugins maven-javadoc-plugin - 3.0.1 + 3.1.1 + + ${java.version} + org.apache.maven.plugins maven-compiler-plugin - 3.8.0 + 3.8.1 ${java.version} ${java.version} diff --git a/pom.xml b/pom.xml index 62e9be7..0cde3bd 100644 --- a/pom.xml +++ b/pom.xml @@ -87,7 +87,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.8.0 + 3.8.1 ${java.version} ${java.version} @@ -96,7 +96,7 @@ org.apache.maven.plugins maven-source-plugin - 3.0.1 + 3.1.0 attach-sources @@ -109,11 +109,12 @@ org.apache.maven.plugins maven-javadoc-plugin - 3.0.1 + 3.1.1 none + ${java.version} @@ -141,7 +142,7 @@ maven-invoker-plugin - 3.1.0 + 3.2.1 generate-services From d709a2912f388df834d55794109642e4a1fa4dfb Mon Sep 17 00:00:00 2001 From: Cameron Porter Date: Mon, 16 Sep 2019 10:03:56 -0500 Subject: [PATCH 32/62] Release version 0.2.8 --- README.md | 6 +++--- examples/pom.xml | 2 +- gen/pom.xml | 2 +- pom.xml | 6 +++--- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index eea475a..3157553 100644 --- a/README.md +++ b/README.md @@ -40,20 +40,20 @@ additions to the SoftLayer API. com.softlayer.api softlayer-api-client - 0.2.7 + 0.2.8 ``` ### Gradle ```groovy -implementation 'com.softlayer.api:softlayer-api-client:0.2.7' +implementation 'com.softlayer.api:softlayer-api-client:0.2.8' ``` ### Kotlin ```kotlin -compile("com.softlayer.api:softlayer-api-client:0.2.7") +compile("com.softlayer.api:softlayer-api-client:0.2.8") ``` ### Creating a Client diff --git a/examples/pom.xml b/examples/pom.xml index 87c864c..94fb261 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -5,7 +5,7 @@ softlayer-api-client-examples jar - 0.2.7 + 0.2.8 softlayer-api-client-examples http://sldn.softlayer.com diff --git a/gen/pom.xml b/gen/pom.xml index ba022cc..d775d66 100644 --- a/gen/pom.xml +++ b/gen/pom.xml @@ -5,7 +5,7 @@ softlayer-api-client-gen jar - 0.2.7 + 0.2.8 softlayer-api-client-gen http://sldn.softlayer.com diff --git a/pom.xml b/pom.xml index 0cde3bd..9e9cfdd 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ softlayer-api-client jar - 0.2.7 + 0.2.8 SoftLayer API Client for Java API client for accessing the SoftLayer API http://sldn.softlayer.com @@ -41,7 +41,7 @@ scm:git:git@github.com:softlayer/softlayer-java.git scm:git:git@github.com:softlayer/softlayer-java.git git@github.com:softlayer/softlayer-java.git - 0.2.7 + 0.2.8 UTF-8 @@ -78,7 +78,7 @@ org.mockito mockito-core - 2.23.4 + 2.28.2 test From a679c9a18fdd41fe8845c2877c1ab2a4bbf8532d Mon Sep 17 00:00:00 2001 From: Cameron Porter Date: Mon, 30 Dec 2019 13:27:48 -0600 Subject: [PATCH 33/62] Update dates. Use pandoc in travis to update the github pages. --- .travis.yml | 24 +++++++++++++++++++++++- LICENSE | 2 +- README.md | 2 +- 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 0bb046d..47fc8c1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,11 @@ language: java + +addons: + apt: + update: true + packages: + - pandoc + jdk: - oraclejdk11 - openjdk8 @@ -6,4 +13,19 @@ jdk: - openjdk10 - openjdk11 - openjdk12 -install: mvn install -DskipTests=true -Dmaven.javadoc.skip=true -Dgpg.skip=true \ No newline at end of file + +install: + - mvn install -DskipTests=true -Dmaven.javadoc.skip=true -Dgpg.skip=true + - mkdir ghpages + - pandoc -o ghpages/index.html README.md + - printf "%s" "$GH_PAGES_DEPLOYMENT_KEY" > gh_pages_deployment_key + +deploy: + provider: pages:git + edge: true + skip_cleanup: true + local_dir: ghpages + deploy_key: gh_pages_deployment_key + keep_history: true + on: + branch: master diff --git a/LICENSE b/LICENSE index f1d9742..d8f5d64 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2018 The SoftLayer Developer Network +Copyright (c) 2020 The SoftLayer Developer Network Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 3157553..398738e 100644 --- a/README.md +++ b/README.md @@ -289,4 +289,4 @@ fully qualified class name of your implementation on a single line in a file in ## Copyright -This software is Copyright (c) 2018 The SoftLayer Developer Network. See the bundled LICENSE file for more information. +This software is Copyright (c) 2020 The SoftLayer Developer Network. See the bundled LICENSE file for more information. From b9e21e0dc692abbb7df59557fd31c786f4f0c994 Mon Sep 17 00:00:00 2001 From: Cameron Porter Date: Thu, 2 Jan 2020 18:41:58 -0600 Subject: [PATCH 34/62] Temporary fix for the metadata API issues. --- .travis.yml | 2 +- gen/src/main/java/com/softlayer/api/gen/Main.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 47fc8c1..a6ce414 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,7 +18,7 @@ install: - mvn install -DskipTests=true -Dmaven.javadoc.skip=true -Dgpg.skip=true - mkdir ghpages - pandoc -o ghpages/index.html README.md - - printf "%s" "$GH_PAGES_DEPLOYMENT_KEY" > gh_pages_deployment_key + - printf "$GH_PAGES_DEPLOYMENT_KEY" > gh_pages_deployment_key deploy: provider: pages:git diff --git a/gen/src/main/java/com/softlayer/api/gen/Main.java b/gen/src/main/java/com/softlayer/api/gen/Main.java index ee75d34..3fa541a 100644 --- a/gen/src/main/java/com/softlayer/api/gen/Main.java +++ b/gen/src/main/java/com/softlayer/api/gen/Main.java @@ -14,7 +14,7 @@ /** Entry point for the code generator */ public class Main { - protected static final String METADATA_URL = "https://api.softlayer.com/metadata/v3.1"; + protected static final String METADATA_URL = "https://api.softlayer.com/metadata/v3.1/"; protected static final String DEFAULT_SOURCE_PATH = "../src/main/java"; public static final String USAGE = @@ -23,7 +23,7 @@ public class Main { " --src DIR - Optional directory to generate source into. The com.softlayer.api.service package\n" + " underneath this directory will be cleaned before code is generated. If not given,\n" + " ../src/main/java is used\n" + - " --url URL - Optional metadata URL. If not given, http://api.softlayer.com/metadata is used.\n" + + " --url URL - Optional metadata URL. If not given, http://api.softlayer.com/metadata/v3.1/ is used.\n" + " --whitelist FILENAME - Optional set of types, properties, and methods to whitelist. It is one\n" + " entry per line and anything not entered will not be included in the generated client. Simply\n" + " give the type name, the property as type_name.propertyName, or the method as type_name::methodName.\n" + From cf2d6b8196bbad9dbaacb826311d830ca9611cd2 Mon Sep 17 00:00:00 2001 From: Cameron Porter Date: Thu, 2 Jan 2020 19:00:15 -0600 Subject: [PATCH 35/62] Remove gh-pages updating for now --- .travis.yml | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/.travis.yml b/.travis.yml index a6ce414..d7cbd69 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,16 +16,3 @@ jdk: install: - mvn install -DskipTests=true -Dmaven.javadoc.skip=true -Dgpg.skip=true - - mkdir ghpages - - pandoc -o ghpages/index.html README.md - - printf "$GH_PAGES_DEPLOYMENT_KEY" > gh_pages_deployment_key - -deploy: - provider: pages:git - edge: true - skip_cleanup: true - local_dir: ghpages - deploy_key: gh_pages_deployment_key - keep_history: true - on: - branch: master From 073d3532cc423b64fe18db09811adfa4c16e1138 Mon Sep 17 00:00:00 2001 From: Cameron Porter Date: Thu, 2 Jan 2020 19:23:43 -0600 Subject: [PATCH 36/62] Set the maven compiler plugin release config to version 8. Replace usages of Class.newInstance(), which is deprecated in Java 9. --- examples/pom.xml | 3 ++- gen/pom.xml | 3 ++- pom.xml | 3 ++- src/main/java/com/softlayer/api/Mask.java | 2 +- src/main/java/com/softlayer/api/RestApiClient.java | 4 ++-- .../com/softlayer/api/json/GsonJsonMarshallerFactory.java | 4 ++-- 6 files changed, 11 insertions(+), 8 deletions(-) diff --git a/examples/pom.xml b/examples/pom.xml index 94fb261..1e2ec67 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -17,7 +17,7 @@ UTF-8 - 1.8 + 8 @@ -55,6 +55,7 @@ ${java.version} ${java.version} + ${java.version} diff --git a/gen/pom.xml b/gen/pom.xml index d775d66..f7b8a1f 100644 --- a/gen/pom.xml +++ b/gen/pom.xml @@ -17,7 +17,7 @@ UTF-8 - 1.8 + 8 com.softlayer.api.gen.Main @@ -70,6 +70,7 @@ ${java.version} ${java.version} + ${java.version} diff --git a/pom.xml b/pom.xml index 9e9cfdd..8ab09d3 100644 --- a/pom.xml +++ b/pom.xml @@ -45,7 +45,7 @@ UTF-8 - 1.8 + 8 @@ -91,6 +91,7 @@ ${java.version} ${java.version} + ${java.version} diff --git a/src/main/java/com/softlayer/api/Mask.java b/src/main/java/com/softlayer/api/Mask.java index 3c35061..9fae4d8 100644 --- a/src/main/java/com/softlayer/api/Mask.java +++ b/src/main/java/com/softlayer/api/Mask.java @@ -29,7 +29,7 @@ protected T withSubMask(String name, Class maskClass) { T subMask = (T) subMasks.get(name); if (subMask == null) { try { - subMask = maskClass.newInstance(); + subMask = maskClass.getDeclaredConstructor().newInstance(); } catch (Exception e) { throw new RuntimeException(); } diff --git a/src/main/java/com/softlayer/api/RestApiClient.java b/src/main/java/com/softlayer/api/RestApiClient.java index 46afa0e..5c11e2e 100644 --- a/src/main/java/com/softlayer/api/RestApiClient.java +++ b/src/main/java/com/softlayer/api/RestApiClient.java @@ -449,12 +449,12 @@ public Object invoke(Object proxy, Method method, Object[] args) throws Throwabl return Proxy.newProxyInstance(getClass().getClassLoader(), new Class[] { method.getReturnType() }, asyncProxy); } else if ("withNewMask".equals(method.getName()) && noParams) { - mask = (Mask) method.getReturnType().newInstance(); + mask = (Mask) method.getReturnType().getDeclaredConstructor().newInstance(); maskString = null; return mask; } else if ("withMask".equals(method.getName()) && noParams) { if (mask == null) { - mask = (Mask) method.getReturnType().newInstance(); + mask = (Mask) method.getReturnType().getDeclaredConstructor().newInstance(); maskString = null; } return mask; diff --git a/src/main/java/com/softlayer/api/json/GsonJsonMarshallerFactory.java b/src/main/java/com/softlayer/api/json/GsonJsonMarshallerFactory.java index 87bac33..f77eb2b 100644 --- a/src/main/java/com/softlayer/api/json/GsonJsonMarshallerFactory.java +++ b/src/main/java/com/softlayer/api/json/GsonJsonMarshallerFactory.java @@ -208,8 +208,8 @@ private Entity readForThisType(JsonReader in) throws IOException { // Begin/end object (and the first "complexType" property) are done outside of here Entity entity; try { - entity = typeClass.newInstance(); - } catch (InstantiationException | IllegalAccessException e) { + entity = typeClass.getDeclaredConstructor().newInstance(); + } catch (ReflectiveOperationException e) { throw new RuntimeException(e); } Map unknownProperties = new HashMap<>(); From 86df33d80313420051ef06d026ef8ab668867572 Mon Sep 17 00:00:00 2001 From: Cameron Porter Date: Wed, 8 Jan 2020 10:11:54 -0600 Subject: [PATCH 37/62] Readd doc generation since an issue with travis-ci/dpl has been addressed. --- .travis.yml | 13 +++++++++++++ examples/pom.xml | 1 - gen/pom.xml | 1 - pom.xml | 1 - 4 files changed, 13 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index d7cbd69..a6ce414 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,3 +16,16 @@ jdk: install: - mvn install -DskipTests=true -Dmaven.javadoc.skip=true -Dgpg.skip=true + - mkdir ghpages + - pandoc -o ghpages/index.html README.md + - printf "$GH_PAGES_DEPLOYMENT_KEY" > gh_pages_deployment_key + +deploy: + provider: pages:git + edge: true + skip_cleanup: true + local_dir: ghpages + deploy_key: gh_pages_deployment_key + keep_history: true + on: + branch: master diff --git a/examples/pom.xml b/examples/pom.xml index 1e2ec67..38b3175 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -55,7 +55,6 @@ ${java.version} ${java.version} - ${java.version} diff --git a/gen/pom.xml b/gen/pom.xml index f7b8a1f..08bed14 100644 --- a/gen/pom.xml +++ b/gen/pom.xml @@ -70,7 +70,6 @@ ${java.version} ${java.version} - ${java.version} diff --git a/pom.xml b/pom.xml index 8ab09d3..aca1da3 100644 --- a/pom.xml +++ b/pom.xml @@ -91,7 +91,6 @@ ${java.version} ${java.version} - ${java.version} From 580f8bcef1e950de551a1ab555307e5517e8c8aa Mon Sep 17 00:00:00 2001 From: Cameron Porter Date: Thu, 9 Jan 2020 10:10:51 -0600 Subject: [PATCH 38/62] Use a different deployment method instead. --- .travis.yml | 4 ++-- ghpages_secret.enc | Bin 0 -> 1680 bytes 2 files changed, 2 insertions(+), 2 deletions(-) create mode 100644 ghpages_secret.enc diff --git a/.travis.yml b/.travis.yml index a6ce414..6a06ba8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,14 +18,14 @@ install: - mvn install -DskipTests=true -Dmaven.javadoc.skip=true -Dgpg.skip=true - mkdir ghpages - pandoc -o ghpages/index.html README.md - - printf "$GH_PAGES_DEPLOYMENT_KEY" > gh_pages_deployment_key + - openssl aes-256-cbc -K $encrypted_60afd10366bc_key -iv $encrypted_60afd10366bc_iv -in ghpages_secret.enc -out ghpages_secret -d deploy: provider: pages:git edge: true skip_cleanup: true local_dir: ghpages - deploy_key: gh_pages_deployment_key + deploy_key: ghpages_secret keep_history: true on: branch: master diff --git a/ghpages_secret.enc b/ghpages_secret.enc new file mode 100644 index 0000000000000000000000000000000000000000..00f2d2dd1621942d5fd86858ba6e2dd70a7e21cf GIT binary patch literal 1680 zcmV;B25a!HSY;t5VW`I zj6X`@zbB5p)CeOBuJ%$+ShwS zYkCh)e1&w4!u5TLrI)`IoV-ddzk~@oxTVIPO=2SiEh)#g{*5VoT9bspE*6U3&#NSs~*XnCBQLB-BlKgQl)F} z9wf4yghr3;d8z?_lxHUF^mAVTR7vDOCofyKqNd;*eY@#Rn7THUF1fV>jRHu}3_v zYoXRgB9(ybXVB=JpGRBaa%V4Ku>_+Sl?Ka@W8oyb42zf{=j~noMFdS0rT(0V`HeGd zje(1e2YL5liZ*X{iLoH;|2jrN{T+x?uWv$=xjB=k8?Y{m2P$FdBI_Yqc(8Bd3!+Mv zXW59n0NdrT90Oz>8i%7p+N>Xwq&=U+M5HKR(jL>Kr=MmU1$0_ujCIZFOo(*nTx7CC z7pTJY0;L0!X)m@gz{(xQJGM5@o*Q2cDx{LXl=if}O8QG=#!Xh$c(?_`!a<~)BvGgb z(c;hUsjZW^X%c1!4*k7Xgdqa*SO=;{I2u62%Vcd#PVPt%d=R{|S zzQ{7vbeXBSBQ!3?PVhk{OTQp~_p?bv*Fu{xtrN&bbc)oAlswcZv%Y4es8|tbP$w{A^KQov6K3@2E#yi%0L8KA>p)bk1G$>x zsWbvy8bA(HB`@5aM_(|`4nnU?T}W}Z{^WsbCi~J}t%zC$L5!Bz%bI^nO}AQI4m(l+ zP!G;NoquANSytz;aj*_u_`j@Pgzx;1u=*J2YWHEDFs<}a46lb$kB6)xO;eep!~Jo3 z&h*as7yA<%)c`%E%50MzBT=zRPIxZp5yo=7WV8>bh@uaQ^CxycvBpcVt3=q|&L49C zLNrPJ6%K8XDi_PW7d6!(>astWJ(Xd{b5wx9ywPhNJQRKhBm>y3Io?!gud)FEFBh#* zkRjQWyUL{QOS4?hOL=WkvOp=4A=PC(s{BEk(5|F z;8O)SDofHf|5J;5gAtXe4gzw+Ll!6DYfCpQY`DA=S z``}??k({vhbx?dBgXm^NoGy47nK$b-kyTkg8N}-j9Ct(lW1Br^Ds(wF)|oZ7W+-kF zW27J?o@IHuCK3!63(CDK1&qDk2o)ySEYka3v@ Date: Tue, 21 Jan 2020 13:43:39 -0600 Subject: [PATCH 39/62] Release 0.2.9 --- README.md | 6 +++--- examples/pom.xml | 2 +- gen/pom.xml | 4 ++-- pom.xml | 7 ++++--- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 398738e..6c79ee7 100644 --- a/README.md +++ b/README.md @@ -40,20 +40,20 @@ additions to the SoftLayer API. com.softlayer.api softlayer-api-client - 0.2.8 + 0.2.9 ``` ### Gradle ```groovy -implementation 'com.softlayer.api:softlayer-api-client:0.2.8' +implementation 'com.softlayer.api:softlayer-api-client:0.2.9' ``` ### Kotlin ```kotlin -compile("com.softlayer.api:softlayer-api-client:0.2.8") +compile("com.softlayer.api:softlayer-api-client:0.2.9") ``` ### Creating a Client diff --git a/examples/pom.xml b/examples/pom.xml index 38b3175..b9d43ba 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -5,7 +5,7 @@ softlayer-api-client-examples jar - 0.2.8 + 0.2.9 softlayer-api-client-examples http://sldn.softlayer.com diff --git a/gen/pom.xml b/gen/pom.xml index 08bed14..005927a 100644 --- a/gen/pom.xml +++ b/gen/pom.xml @@ -5,7 +5,7 @@ softlayer-api-client-gen jar - 0.2.8 + 0.2.9 softlayer-api-client-gen http://sldn.softlayer.com @@ -34,7 +34,7 @@ junit junit - 4.12 + 4.13 test diff --git a/pom.xml b/pom.xml index aca1da3..b1854bf 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ softlayer-api-client jar - 0.2.8 + 0.2.9 SoftLayer API Client for Java API client for accessing the SoftLayer API http://sldn.softlayer.com @@ -41,7 +41,7 @@ scm:git:git@github.com:softlayer/softlayer-java.git scm:git:git@github.com:softlayer/softlayer-java.git git@github.com:softlayer/softlayer-java.git - 0.2.8 + 0.2.9 UTF-8 @@ -72,7 +72,7 @@ junit junit - 4.12 + 4.13 test @@ -115,6 +115,7 @@ none ${java.version} + --allow-script-in-comments From dd5d0232524db013d8d2d00284008b086d873fcf Mon Sep 17 00:00:00 2001 From: Cameron Porter Date: Mon, 23 Mar 2020 18:01:24 -0500 Subject: [PATCH 40/62] Allow type coercion when determining APi types When we read objects from the API, we want to ensure the type matches either the type class that the client is aware of, or whatever the complexType property on the object tells us. In some cases, the API returns a type for a property that is not the same or a subtype of the type defined by the API. The API should not do this, however the previous behavior was to throw an exception in these cases, which is quite annoying. Instead, we will now coerce the object we get into the type that the definition says. If properties are missing, they will remain unset, and any extra properties will be added to the unknown properties. --- .../api/json/GsonJsonMarshallerFactory.java | 14 +-- .../json/GsonJsonMarshallerFactoryTest.java | 95 ++++++++++++++++--- .../com/softlayer/api/service/TestEntity.java | 1 - .../com/softlayer/api/service/TestThing.java | 2 + 4 files changed, 87 insertions(+), 25 deletions(-) diff --git a/src/main/java/com/softlayer/api/json/GsonJsonMarshallerFactory.java b/src/main/java/com/softlayer/api/json/GsonJsonMarshallerFactory.java index f77eb2b..e47d558 100644 --- a/src/main/java/com/softlayer/api/json/GsonJsonMarshallerFactory.java +++ b/src/main/java/com/softlayer/api/json/GsonJsonMarshallerFactory.java @@ -14,12 +14,8 @@ import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Base64; -import java.util.GregorianCalendar; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; +import java.util.function.Supplier; import com.google.gson.Gson; import com.google.gson.GsonBuilder; @@ -191,12 +187,12 @@ public Entity read(JsonReader in) throws IOException { // we're an adapter for. So if we have SoftLayer_Something and a newer release of the // API has a type extending it but we don't have a generated class for it, it will get // properly serialized to a SoftLayer_Something. + // If the API returns a type that isn't the same or a subtype of the type class, + // try as best we can to fit the data within the type class. Class clazz = typeClasses.get(apiTypeName); Entity result; - if (clazz == null) { + if (clazz == null || !typeClass.isAssignableFrom(clazz)) { result = readForThisType(in); - } else if (!typeClass.isAssignableFrom(clazz)) { - throw new RuntimeException("Expecting " + typeClass + " to be super type of " + clazz); } else { result = ((EntityTypeAdapter) gson.getAdapter(clazz)).readForThisType(in); } diff --git a/src/test/java/com/softlayer/api/json/GsonJsonMarshallerFactoryTest.java b/src/test/java/com/softlayer/api/json/GsonJsonMarshallerFactoryTest.java index 06ea291..5233db7 100644 --- a/src/test/java/com/softlayer/api/json/GsonJsonMarshallerFactoryTest.java +++ b/src/test/java/com/softlayer/api/json/GsonJsonMarshallerFactoryTest.java @@ -21,6 +21,7 @@ import com.google.gson.reflect.TypeToken; import com.softlayer.api.service.Entity; import com.softlayer.api.service.TestEntity; +import com.softlayer.api.service.TestThing; public class GsonJsonMarshallerFactoryTest { @@ -51,21 +52,22 @@ private String toJson(Object obj) throws Exception { public void testRead() throws Exception { Entity entity = fromJson(Entity.class, "{" - + "\"complexType\": \"SoftLayer_TestEntity\"," - + "\"bar\": \"some string\"," - + "\"foo\": \"another string\"," - + "\"baz\": null," - + "\"date\": \"1984-02-25T20:15:25-06:00\"," - + "\"notApiProperty\": \"bad value\"," - + "\"child\": {" - + " \"complexType\": \"SoftLayer_TestEntity\"," - + " \"bar\": \"child string\"" - + "}," - + "\"moreChildren\": [" - + " { \"complexType\": \"SoftLayer_TestEntity\", \"bar\": \"child 1\" }," - + " { \"complexType\": \"SoftLayer_TestEntity\", \"bar\": \"child 2\" }" - + "]" - + "}"); + + "\"complexType\": \"SoftLayer_TestEntity\"," + + "\"bar\": \"some string\"," + + "\"foo\": \"another string\"," + + "\"baz\": null," + + "\"date\": \"1984-02-25T20:15:25-06:00\"," + + "\"notApiProperty\": \"bad value\"," + + "\"child\": {" + + " \"complexType\": \"SoftLayer_TestEntity\"," + + " \"bar\": \"child string\"" + + "}," + + "\"moreChildren\": [" + + " { \"complexType\": \"SoftLayer_TestEntity\", \"bar\": \"child 1\" }," + + " { \"complexType\": \"SoftLayer_TestEntity\", \"bar\": \"child 2\" }" + + "]," + + "\"testThing\": {\"complexType\": \"SoftLayer_TestThing\", \"id\": 123}" + + "}"); assertEquals(TestEntity.class, entity.getClass()); TestEntity obj = (TestEntity) entity; assertEquals("some string", obj.getFoo()); @@ -85,6 +87,69 @@ public void testRead() throws Exception { assertEquals(2, obj.getMoreChildren().size()); assertEquals("child 1", obj.getMoreChildren().get(0).getFoo()); assertEquals("child 2", obj.getMoreChildren().get(1).getFoo()); + assertEquals(TestThing.class, obj.getTestThing().getClass()); + assertEquals(123, obj.getTestThing().getId().intValue()); + } + + @Test + public void testReadPropertyWithIncorrectComplexTypeCoercesTheType() throws Exception { + Entity entity = fromJson(Entity.class, + "{" + + "\"complexType\": \"SoftLayer_TestEntity\"," + + "\"testThing\": {\"complexType\": \"SoftLayer_TestEntity\", \"id\": 123, \"foo\": \"unknown!\"}" + + "}"); + assertEquals(TestEntity.class, entity.getClass()); + TestEntity obj = (TestEntity) entity; + assertEquals(0, obj.getUnknownProperties().size()); + assertEquals(TestThing.class, obj.getTestThing().getClass()); + assertEquals(123, obj.getTestThing().getId().intValue()); + assertEquals(1, obj.getTestThing().getUnknownProperties().size()); + assertEquals("unknown!", obj.getTestThing().getUnknownProperties().get("foo")); + } + + + @Test + public void testReadPropertyWithUnknownComplexTypeCoercesTheType() throws Exception { + Entity entity = fromJson(Entity.class, + "{" + + "\"complexType\": \"SoftLayer_TestEntity\"," + + "\"testThing\": {\"complexType\": \"WhoKnows\", \"id\": 123, \"foo\": \"unknown!\"}" + + "}"); + assertEquals(TestEntity.class, entity.getClass()); + TestEntity obj = (TestEntity) entity; + assertEquals(0, obj.getUnknownProperties().size()); + assertEquals(TestThing.class, obj.getTestThing().getClass()); + assertEquals(123, obj.getTestThing().getId().intValue()); + assertEquals(1, obj.getTestThing().getUnknownProperties().size()); + assertEquals("unknown!", obj.getTestThing().getUnknownProperties().get("foo")); + } + + @Test + public void testReadPropertyThrowsExceptionWithoutComplexType() { + + Exception e = assertThrows(RuntimeException.class, () -> { + Entity entity = fromJson(Entity.class, + "{" + + "\"complexType\": \"SoftLayer_TestEntity\"," + + "\"testThing\": {\"id\": 123}" + + "}"); + }); + + assertEquals("Expected 'complexType' as first property", e.getMessage()); + } + + @Test + public void testReadPropertyThrowsExceptioWithComplexTypeNotFirst() { + + Exception e = assertThrows(RuntimeException.class, () -> { + Entity entity = fromJson(Entity.class, + "{" + + "\"testThing\": {\"id\": 123}," + + "\"complexType\": \"SoftLayer_TestEntity\"" + + "}"); + }); + + assertEquals("Expected 'complexType' as first property", e.getMessage()); } @Test diff --git a/src/test/java/com/softlayer/api/service/TestEntity.java b/src/test/java/com/softlayer/api/service/TestEntity.java index ce85bf6..df3f7d8 100644 --- a/src/test/java/com/softlayer/api/service/TestEntity.java +++ b/src/test/java/com/softlayer/api/service/TestEntity.java @@ -11,7 +11,6 @@ import com.softlayer.api.annotation.ApiType; import com.softlayer.api.ApiClient; import com.softlayer.api.ResponseHandler; -import com.softlayer.api.ResultLimit; @ApiType("SoftLayer_TestEntity") public class TestEntity extends Entity { diff --git a/src/test/java/com/softlayer/api/service/TestThing.java b/src/test/java/com/softlayer/api/service/TestThing.java index 2eac363..548b87c 100644 --- a/src/test/java/com/softlayer/api/service/TestThing.java +++ b/src/test/java/com/softlayer/api/service/TestThing.java @@ -10,7 +10,9 @@ import com.softlayer.api.annotation.ApiMethod; import com.softlayer.api.annotation.ApiProperty; import com.softlayer.api.annotation.ApiService; +import com.softlayer.api.annotation.ApiType; +@ApiType("SoftLayer_TestThing") public class TestThing extends Entity { @ApiProperty(canBeNullOrNotSet = true) From 5c1a5a1b112af06530ca94cd9bb6ed01704ad0dc Mon Sep 17 00:00:00 2001 From: Cameron Porter Date: Wed, 25 Mar 2020 10:06:23 -0500 Subject: [PATCH 41/62] Update RestApiClient docs and add a BASE_SERVICE_URL constant. --- src/main/java/com/softlayer/api/RestApiClient.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/main/java/com/softlayer/api/RestApiClient.java b/src/main/java/com/softlayer/api/RestApiClient.java index 5c11e2e..f587b33 100644 --- a/src/main/java/com/softlayer/api/RestApiClient.java +++ b/src/main/java/com/softlayer/api/RestApiClient.java @@ -30,7 +30,15 @@ */ public class RestApiClient implements ApiClient { + /** + * The publically available API URL. + */ public static final String BASE_URL = "https://api.softlayer.com/rest/v3.1/"; + + /** + * The API URL that should be used when connecting via the softlayer/classic infrastructure private network. + */ + public static final String BASE_SERVICE_URL = "https://api.service.softlayer.com/rest/v3.1/"; static final String BASE_PKG = Entity.class.getPackage().getName(); @@ -58,6 +66,9 @@ public class RestApiClient implements ApiClient { private boolean loggingEnabled = false; private HttpBasicAuthCredentials credentials; + /** + * Create a Rest client that uses the publically available API. + */ public RestApiClient() { this(BASE_URL); } From 4b6df6f494464437374d0bc2c3f9906de3005086 Mon Sep 17 00:00:00 2001 From: Cameron Porter Date: Wed, 25 Mar 2020 10:13:02 -0500 Subject: [PATCH 42/62] Update README with service API information. --- README.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 6c79ee7..e963476 100644 --- a/README.md +++ b/README.md @@ -68,7 +68,14 @@ ApiClient client = new RestApiClient().withCredentials("my user", "my api key"); ``` If the end point isn't at the normal SoftLayer API, you can provide the prefix to the constructor of the -`RestApiClient`. By default it is set to `https://api.softlayer.com/rest/v3.1/`. +`RestApiClient`. By default it is set to the public API endpoint, `https://api.softlayer.com/rest/v3.1/`. + +If you are using the classic infrastructure private network, you can communicate with the API over that network by using the service URL instead: + +```java +ApiClient client = new RestApiClient(RestApiClient.BASE_SERVICE_URL) + .withCredentials("my user", "my api key"); +``` ### Making API Calls From 5eace401e1a1954760d32ddafc33cf77bfb5059f Mon Sep 17 00:00:00 2001 From: Cameron Porter Date: Wed, 25 Mar 2020 12:15:00 -0500 Subject: [PATCH 43/62] Release version 0.3.0 --- README.md | 6 +++--- examples/pom.xml | 2 +- gen/pom.xml | 2 +- pom.xml | 6 +++--- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index e963476..95261b2 100644 --- a/README.md +++ b/README.md @@ -40,20 +40,20 @@ additions to the SoftLayer API. com.softlayer.api softlayer-api-client - 0.2.9 + 0.3.0 ``` ### Gradle ```groovy -implementation 'com.softlayer.api:softlayer-api-client:0.2.9' +implementation 'com.softlayer.api:softlayer-api-client:0.3.0' ``` ### Kotlin ```kotlin -compile("com.softlayer.api:softlayer-api-client:0.2.9") +compile("com.softlayer.api:softlayer-api-client:0.3.0") ``` ### Creating a Client diff --git a/examples/pom.xml b/examples/pom.xml index b9d43ba..9cf4371 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -5,7 +5,7 @@ softlayer-api-client-examples jar - 0.2.9 + 0.3.0 softlayer-api-client-examples http://sldn.softlayer.com diff --git a/gen/pom.xml b/gen/pom.xml index 005927a..58a5eb2 100644 --- a/gen/pom.xml +++ b/gen/pom.xml @@ -5,7 +5,7 @@ softlayer-api-client-gen jar - 0.2.9 + 0.3.0 softlayer-api-client-gen http://sldn.softlayer.com diff --git a/pom.xml b/pom.xml index b1854bf..f003f80 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ softlayer-api-client jar - 0.2.9 + 0.3.0 SoftLayer API Client for Java API client for accessing the SoftLayer API http://sldn.softlayer.com @@ -41,7 +41,7 @@ scm:git:git@github.com:softlayer/softlayer-java.git scm:git:git@github.com:softlayer/softlayer-java.git git@github.com:softlayer/softlayer-java.git - 0.2.9 + 0.3.0 UTF-8 @@ -78,7 +78,7 @@ org.mockito mockito-core - 2.28.2 + 3.3.3 test From 8a0e0d36ee7c7c7563c572e18f7866612c90f2a7 Mon Sep 17 00:00:00 2001 From: Cameron Porter Date: Wed, 25 Mar 2020 14:59:41 -0500 Subject: [PATCH 44/62] Use shields.io for maven badge. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 95261b2..6612f93 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # SoftLayer API Client for Java [![Build Status](https://travis-ci.org/softlayer/softlayer-java.svg)](https://travis-ci.org/softlayer/softlayer-java) -[![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.softlayer.api/softlayer-api-client/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.softlayer.api/softlayer-api-client) +[![Maven Central](https://img.shields.io/maven-central/v/com.softlayer.api/softlayer-api-client)](https://search.maven.org/artifact/com.softlayer.api/softlayer-api-client) [![Javadocs](https://www.javadoc.io/badge/com.softlayer.api/softlayer-api-client.svg)](https://www.javadoc.io/doc/com.softlayer.api/softlayer-api-client) ## Introduction From 961e406f2f51394681e674e35fa52b57e7c587e2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 13 Oct 2020 02:47:46 +0000 Subject: [PATCH 45/62] Bump junit from 4.13 to 4.13.1 Bumps [junit](https://github.com/junit-team/junit4) from 4.13 to 4.13.1. - [Release notes](https://github.com/junit-team/junit4/releases) - [Changelog](https://github.com/junit-team/junit4/blob/main/doc/ReleaseNotes4.13.1.md) - [Commits](https://github.com/junit-team/junit4/compare/r4.13...r4.13.1) Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index f003f80..8bb7ed9 100644 --- a/pom.xml +++ b/pom.xml @@ -72,7 +72,7 @@ junit junit - 4.13 + 4.13.1 test From 280931d170b2ff1a8c34ebc990cd9094270259e1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 13 Oct 2020 02:51:11 +0000 Subject: [PATCH 46/62] Bump junit from 4.13 to 4.13.1 in /gen Bumps [junit](https://github.com/junit-team/junit4) from 4.13 to 4.13.1. - [Release notes](https://github.com/junit-team/junit4/releases) - [Changelog](https://github.com/junit-team/junit4/blob/main/doc/ReleaseNotes4.13.1.md) - [Commits](https://github.com/junit-team/junit4/compare/r4.13...r4.13.1) Signed-off-by: dependabot[bot] --- gen/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gen/pom.xml b/gen/pom.xml index 58a5eb2..c646e34 100644 --- a/gen/pom.xml +++ b/gen/pom.xml @@ -34,7 +34,7 @@ junit junit - 4.13 + 4.13.1 test From b70f9535dc1dea23a084a360aea0ae0ffb1e1dea Mon Sep 17 00:00:00 2001 From: Cameron Porter Date: Mon, 9 Nov 2020 15:34:30 -0600 Subject: [PATCH 47/62] Release 0.3.1 --- README.md | 6 +++--- examples/pom.xml | 2 +- gen/pom.xml | 4 ++-- pom.xml | 15 +++++++-------- 4 files changed, 13 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 6612f93..2563f68 100644 --- a/README.md +++ b/README.md @@ -40,20 +40,20 @@ additions to the SoftLayer API. com.softlayer.api softlayer-api-client - 0.3.0 + 0.3.1 ``` ### Gradle ```groovy -implementation 'com.softlayer.api:softlayer-api-client:0.3.0' +implementation 'com.softlayer.api:softlayer-api-client:0.3.1' ``` ### Kotlin ```kotlin -compile("com.softlayer.api:softlayer-api-client:0.3.0") +compile("com.softlayer.api:softlayer-api-client:0.3.1") ``` ### Creating a Client diff --git a/examples/pom.xml b/examples/pom.xml index 9cf4371..21d766c 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -5,7 +5,7 @@ softlayer-api-client-examples jar - 0.3.0 + 0.3.1 softlayer-api-client-examples http://sldn.softlayer.com diff --git a/gen/pom.xml b/gen/pom.xml index c646e34..f1becb3 100644 --- a/gen/pom.xml +++ b/gen/pom.xml @@ -5,7 +5,7 @@ softlayer-api-client-gen jar - 0.3.0 + 0.3.1 softlayer-api-client-gen http://sldn.softlayer.com @@ -29,7 +29,7 @@ com.google.code.gson gson - 2.8.5 + 2.8.6 junit diff --git a/pom.xml b/pom.xml index 8bb7ed9..ac69f4e 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ softlayer-api-client jar - 0.3.0 + 0.3.1 SoftLayer API Client for Java API client for accessing the SoftLayer API http://sldn.softlayer.com @@ -27,9 +27,8 @@ camporter Cameron Porter - cmporter@softlayer.com - SoftLayer - http://softlayer.com + IBM + http://ibm.com/cloud owner developer @@ -41,7 +40,7 @@ scm:git:git@github.com:softlayer/softlayer-java.git scm:git:git@github.com:softlayer/softlayer-java.git git@github.com:softlayer/softlayer-java.git - 0.3.0 + 0.3.1 UTF-8 @@ -50,7 +49,7 @@ jcenter - http://jcenter.bintray.com/ + https://jcenter.bintray.com/ @@ -67,7 +66,7 @@ com.google.code.gson gson - 2.8.5 + 2.8.6 junit @@ -78,7 +77,7 @@ org.mockito mockito-core - 3.3.3 + 3.6.0 test From 94db1a6eeaf4fca2ec44263b25f66d93d086d460 Mon Sep 17 00:00:00 2001 From: cmp Date: Thu, 19 Nov 2020 19:22:52 -0600 Subject: [PATCH 48/62] Add maven workflow action --- .github/workflows/maven.yml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 .github/workflows/maven.yml diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml new file mode 100644 index 0000000..6f18e58 --- /dev/null +++ b/.github/workflows/maven.yml @@ -0,0 +1,20 @@ +name: Java CI + +on: + push: + branches: [ master ] + pull_request: +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + java: [8, 11, 15] + steps: + - uses: actions/checkout@v2 + - name: Set up JDK + uses: actions/setup-java@v1 + with: + java-version: ${{matrix.java}} + - name: Build with Maven + run: mvn -B package --file pom.xml From d51f8a17a2e63690ce6bf1328634877b75e34315 Mon Sep 17 00:00:00 2001 From: cmp Date: Tue, 8 Dec 2020 17:28:11 -0600 Subject: [PATCH 49/62] Replace travisci badge with github actions --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2563f68..34b8046 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # SoftLayer API Client for Java -[![Build Status](https://travis-ci.org/softlayer/softlayer-java.svg)](https://travis-ci.org/softlayer/softlayer-java) +![Java CI](https://github.com/softlayer/softlayer-java/workflows/Java%20CI/badge.svg) [![Maven Central](https://img.shields.io/maven-central/v/com.softlayer.api/softlayer-api-client)](https://search.maven.org/artifact/com.softlayer.api/softlayer-api-client) [![Javadocs](https://www.javadoc.io/badge/com.softlayer.api/softlayer-api-client.svg)](https://www.javadoc.io/doc/com.softlayer.api/softlayer-api-client) From c0cf8e9107fa521f45c9b7da37639990992e4fbc Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Mon, 4 Jan 2021 16:41:23 -0600 Subject: [PATCH 50/62] #70 examples for Bearer Authentication --- .../com/softlayer/api/example/Example.java | 9 +++++- .../com/softlayer/api/example/QuickTest.java | 28 +++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 examples/src/main/java/com/softlayer/api/example/QuickTest.java diff --git a/examples/src/main/java/com/softlayer/api/example/Example.java b/examples/src/main/java/com/softlayer/api/example/Example.java index db2d386..32566fc 100644 --- a/examples/src/main/java/com/softlayer/api/example/Example.java +++ b/examples/src/main/java/com/softlayer/api/example/Example.java @@ -17,7 +17,14 @@ public void start(String[] args) throws Exception { baseUrl += '/'; } - run(new RestApiClient(baseUrl).withCredentials(args[0], args[1])); + RestApiClient client; + // mvn -e -q compile exec:java -Dexec.args="QuickTest Bearer eyJraWQ..... + if (args[0].trim().equals("Bearer")) { + client = new RestApiClient(baseUrl).withBearerToken(args[1]); + } else { + client = new RestApiClient(baseUrl).withCredentials(args[0], args[1]); + } + run(client); } /** Run the example with the given client */ diff --git a/examples/src/main/java/com/softlayer/api/example/QuickTest.java b/examples/src/main/java/com/softlayer/api/example/QuickTest.java new file mode 100644 index 0000000..24701da --- /dev/null +++ b/examples/src/main/java/com/softlayer/api/example/QuickTest.java @@ -0,0 +1,28 @@ +package com.softlayer.api.example; + +import com.softlayer.api.ApiClient; +import com.softlayer.api.RestApiClient; +import com.softlayer.api.service.Account; + + +/** A quick example for testing if authentication works. + +cd softlayer-java/examples +mvn -e -q compile exec:java -Dexec.args="QuickTest Bearer eyJraWQ..... +*/ +public class QuickTest extends Example { + + @Override + public void run(ApiClient client) throws Exception { + client.withLoggingEnabled(); + System.out.format("Authorization: %s\n", client.getCredentials()); + Account.Service service = Account.service(client); + + Account account = service.getObject(); + System.out.format("Account Name: %s\n", account.getCompanyName()); + } + + public static void main(String[] args) throws Exception { + new QuickTest().start(args); + } +} From 0cdfb8d1c3845ad77569df8e66977595c5f8dacb Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Mon, 4 Jan 2021 16:42:50 -0600 Subject: [PATCH 51/62] #70 support for Bearer HTTP authorization --- .../java/com/softlayer/api/ApiClient.java | 25 ++++++++++++++++++- .../java/com/softlayer/api/RestApiClient.java | 14 +++++++++-- .../api/http/BuiltInHttpClientFactory.java | 8 ++---- .../api/http/HttpBearerCredentials.java | 20 +++++++++++++++ .../softlayer/api/http/HttpCredentials.java | 1 + .../http/BuiltInHttpClientFactoryTest.java | 15 ----------- .../api/http/HttpBearerCredentialsTest.java | 22 ++++++++++++++++ 7 files changed, 81 insertions(+), 24 deletions(-) create mode 100644 src/main/java/com/softlayer/api/http/HttpBearerCredentials.java create mode 100644 src/test/java/com/softlayer/api/http/HttpBearerCredentialsTest.java diff --git a/src/main/java/com/softlayer/api/ApiClient.java b/src/main/java/com/softlayer/api/ApiClient.java index 092d686..a637995 100644 --- a/src/main/java/com/softlayer/api/ApiClient.java +++ b/src/main/java/com/softlayer/api/ApiClient.java @@ -1,5 +1,7 @@ package com.softlayer.api; +import com.softlayer.api.http.HttpCredentials; + /** Common interface for all API clients. {@link RestApiClient} is the preferred implementation */ public interface ApiClient { @@ -9,7 +11,28 @@ public interface ApiClient { * @return This instance */ ApiClient withCredentials(String username, String apiKey); - + + /** + * Uses a HTTP Bearer token for authentication instead of API key. + * + * @return This instance + */ + ApiClient withBearerToken(String token); + + /** + * Enables logging for client API calls + * + * @return This instance + */ + ApiClient withLoggingEnabled(); + + /** + * Returns the HTTP Authorization header + * + * @return This instance + */ + HttpCredentials getCredentials(); + /** * Get a service for the given sets of classes and optional ID. It is not recommended to call this * directly, but rather invoke the service method on the type class. diff --git a/src/main/java/com/softlayer/api/RestApiClient.java b/src/main/java/com/softlayer/api/RestApiClient.java index f587b33..43450c8 100644 --- a/src/main/java/com/softlayer/api/RestApiClient.java +++ b/src/main/java/com/softlayer/api/RestApiClient.java @@ -18,7 +18,9 @@ import com.softlayer.api.annotation.ApiMethod; import com.softlayer.api.annotation.ApiService; +import com.softlayer.api.http.HttpCredentials; import com.softlayer.api.http.HttpBasicAuthCredentials; +import com.softlayer.api.http.HttpBearerCredentials; import com.softlayer.api.http.HttpClient; import com.softlayer.api.http.HttpClientFactory; import com.softlayer.api.http.HttpResponse; @@ -64,7 +66,7 @@ public class RestApiClient implements ApiClient { private HttpClientFactory httpClientFactory; private JsonMarshallerFactory jsonMarshallerFactory; private boolean loggingEnabled = false; - private HttpBasicAuthCredentials credentials; + private HttpCredentials credentials; /** * Create a Rest client that uses the publically available API. @@ -114,6 +116,7 @@ public void setLoggingEnabled(boolean loggingEnabled) { this.loggingEnabled = loggingEnabled; } + @Override public RestApiClient withLoggingEnabled() { this.loggingEnabled = true; return this; @@ -141,7 +144,14 @@ public RestApiClient withCredentials(String username, String apiKey) { return this; } - public HttpBasicAuthCredentials getCredentials() { + @Override + public RestApiClient withBearerToken(String token) { + credentials = new HttpBearerCredentials(token); + return this; + } + + @Override + public HttpCredentials getCredentials() { return credentials; } diff --git a/src/main/java/com/softlayer/api/http/BuiltInHttpClientFactory.java b/src/main/java/com/softlayer/api/http/BuiltInHttpClientFactory.java index 5cf49f0..2d96b32 100644 --- a/src/main/java/com/softlayer/api/http/BuiltInHttpClientFactory.java +++ b/src/main/java/com/softlayer/api/http/BuiltInHttpClientFactory.java @@ -89,7 +89,7 @@ public void setThreadPool(ExecutorService threadPool) { class BuiltInHttpClient implements HttpClient, HttpResponse { - final HttpBasicAuthCredentials credentials; + final HttpCredentials credentials; final String method; final String fullUrl; final Map> headers; @@ -101,11 +101,7 @@ public BuiltInHttpClient( String fullUrl, Map> headers ) { - // We only support basic auth - if (credentials != null && !(credentials instanceof HttpBasicAuthCredentials)) { - throw new UnsupportedOperationException("Only basic auth is supported, not " + credentials.getClass()); - } - this.credentials = (HttpBasicAuthCredentials) credentials; + this.credentials = credentials; this.method = method; this.fullUrl = fullUrl; this.headers = headers; diff --git a/src/main/java/com/softlayer/api/http/HttpBearerCredentials.java b/src/main/java/com/softlayer/api/http/HttpBearerCredentials.java new file mode 100644 index 0000000..fd80aa1 --- /dev/null +++ b/src/main/java/com/softlayer/api/http/HttpBearerCredentials.java @@ -0,0 +1,20 @@ +package com.softlayer.api.http; + +/** HTTP bearer authorization support for bearer token fromhttps://iam.cloud.ibm.com/identity/token */ +public class HttpBearerCredentials implements HttpCredentials { + + public final String token; + + public HttpBearerCredentials(String token) { + this.token = token; + } + + /** + * Formats the token into a HTTP Authorization header. + * + * @return String + */ + public String getHeader() { + return "Bearer " + token; + } +} diff --git a/src/main/java/com/softlayer/api/http/HttpCredentials.java b/src/main/java/com/softlayer/api/http/HttpCredentials.java index a0ae093..a588f83 100644 --- a/src/main/java/com/softlayer/api/http/HttpCredentials.java +++ b/src/main/java/com/softlayer/api/http/HttpCredentials.java @@ -2,4 +2,5 @@ /** Base interface for all accepted HTTP credentials */ public interface HttpCredentials { + public String getHeader(); } diff --git a/src/test/java/com/softlayer/api/http/BuiltInHttpClientFactoryTest.java b/src/test/java/com/softlayer/api/http/BuiltInHttpClientFactoryTest.java index ee40c56..75ff90f 100644 --- a/src/test/java/com/softlayer/api/http/BuiltInHttpClientFactoryTest.java +++ b/src/test/java/com/softlayer/api/http/BuiltInHttpClientFactoryTest.java @@ -15,21 +15,6 @@ public class BuiltInHttpClientFactoryTest { - @Test - public void testGetHttpClientWithoutBasicAuth() { - try { - new BuiltInHttpClientFactory().getHttpClient( - new HttpCredentials() { }, - "GET", - "http://example.com", - Collections.emptyMap() - ); - fail(); - } catch (UnsupportedOperationException e) { - assertTrue(e.getMessage().contains("basic auth")); - } - } - @Test public void testGetThreadPoolDefaultsToDaemonThreads() throws Exception { boolean daemon = new BuiltInHttpClientFactory().getThreadPool().submit( diff --git a/src/test/java/com/softlayer/api/http/HttpBearerCredentialsTest.java b/src/test/java/com/softlayer/api/http/HttpBearerCredentialsTest.java new file mode 100644 index 0000000..0a1d67d --- /dev/null +++ b/src/test/java/com/softlayer/api/http/HttpBearerCredentialsTest.java @@ -0,0 +1,22 @@ +package com.softlayer.api.http; + +import static org.junit.Assert.*; + +import org.junit.Test; + +public class HttpBearerCredentialsTest { + + public final String bearerToken = "qqqqwwwweeerrttyyuuiiooppasddfgfgjghjkjklZXxcvcvbvbnnbm"; + @Test + public void testConstructor() { + HttpBearerCredentials authCredentials = new HttpBearerCredentials(bearerToken); + assertEquals(bearerToken, authCredentials.token); + } + + @Test + public void testGetHeader() { + HttpBearerCredentials authCredentials = new HttpBearerCredentials(bearerToken); + String header = "Bearer " + bearerToken; + assertEquals(header, authCredentials.getHeader()); + } +} From 194e7166f667622fa858f208f404b92eb5e45ce5 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 5 Jan 2021 16:37:02 -0600 Subject: [PATCH 52/62] #70 added code coverage tool to pom.xml, and a BuiltinHttpClientFactoryTest test --- README.md | 20 ++++++++++--- pom.xml | 28 +++++++++++++++++-- .../http/BuiltInHttpClientFactoryTest.java | 8 ++++++ 3 files changed, 50 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 34b8046..ca73258 100644 --- a/README.md +++ b/README.md @@ -40,20 +40,20 @@ additions to the SoftLayer API. com.softlayer.api softlayer-api-client - 0.3.1 + 0.3.2 ``` ### Gradle ```groovy -implementation 'com.softlayer.api:softlayer-api-client:0.3.1' +implementation 'com.softlayer.api:softlayer-api-client:0.3.2' ``` ### Kotlin ```kotlin -compile("com.softlayer.api:softlayer-api-client:0.3.1") +compile("com.softlayer.api:softlayer-api-client:0.3.2") ``` ### Creating a Client @@ -61,12 +61,24 @@ compile("com.softlayer.api:softlayer-api-client:0.3.1") All clients are instances of `ApiClient`. Currently there is only one implementation, the `RestApiClient`. Simply instantiate it and provide your credentials: + +#### Username and API Key +For using a Classic Infrastructure or IBM Cloud API key. When using the IBM Cloud Api key, your username is litterally `apikey`, more information about that can be found on the SLDN [Authenticating to the SoftLayer API](https://sldn.softlayer.com/article/authenticating-softlayer-api/#cloud-api) article. + ```java import com.softlayer.api.*; ApiClient client = new RestApiClient().withCredentials("my user", "my api key"); ``` +#### Acesses Token +Information on how to get a temoprary api token can be found on the SLDN [Authenticating to the SoftLayer API](https://sldn.softlayer.com/article/authenticating-softlayer-api/#temp-token) article. + +```java +import com.softlayer.api.*; +ApiClient client = new RestApiClient().withBearerToken("qqqqwwwweeeaaassddd...."); +``` + If the end point isn't at the normal SoftLayer API, you can provide the prefix to the constructor of the `RestApiClient`. By default it is set to the public API endpoint, `https://api.softlayer.com/rest/v3.1/`. @@ -296,4 +308,4 @@ fully qualified class name of your implementation on a single line in a file in ## Copyright -This software is Copyright (c) 2020 The SoftLayer Developer Network. See the bundled LICENSE file for more information. +This software is Copyright (c) 2021 The SoftLayer Developer Network. See the bundled LICENSE file for more information. diff --git a/pom.xml b/pom.xml index ac69f4e..32ae219 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ softlayer-api-client jar - 0.3.1 + 0.3.2 SoftLayer API Client for Java API client for accessing the SoftLayer API http://sldn.softlayer.com @@ -40,7 +40,7 @@ scm:git:git@github.com:softlayer/softlayer-java.git scm:git:git@github.com:softlayer/softlayer-java.git git@github.com:softlayer/softlayer-java.git - 0.3.1 + 0.3.2 UTF-8 @@ -161,6 +161,30 @@ + + org.jacoco + jacoco-maven-plugin + 0.8.6 + + + **/*com/softlayer/api/service/**/* + + + + + + prepare-agent + + + + report + prepare-package + + report + + + + diff --git a/src/test/java/com/softlayer/api/http/BuiltInHttpClientFactoryTest.java b/src/test/java/com/softlayer/api/http/BuiltInHttpClientFactoryTest.java index 75ff90f..15a355c 100644 --- a/src/test/java/com/softlayer/api/http/BuiltInHttpClientFactoryTest.java +++ b/src/test/java/com/softlayer/api/http/BuiltInHttpClientFactoryTest.java @@ -23,6 +23,14 @@ public void testGetThreadPoolDefaultsToDaemonThreads() throws Exception { assertTrue(daemon); } + @Test + public void testGetThreadPoolLazyLoading() { + BuiltInHttpClientFactory factory = new BuiltInHttpClientFactory(); + ExecutorService threadPool = factory.getThreadPool(); + assertNotNull(threadPool); + assertEquals(threadPool, factory.getThreadPool()); + } + @Test public void testSetThreadPoolShutsDownNonUserDefined() { BuiltInHttpClientFactory factory = new BuiltInHttpClientFactory(); From 80ed0e74cb3286d4b530934ed7f43dbf0b80aa66 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Thu, 7 Jan 2021 13:03:32 -0600 Subject: [PATCH 53/62] cleared up some documentation for BearerAuthorization --- README.md | 2 +- .../com/softlayer/api/http/HttpBearerCredentials.java | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index ca73258..1944b81 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,7 @@ instantiate it and provide your credentials: #### Username and API Key -For using a Classic Infrastructure or IBM Cloud API key. When using the IBM Cloud Api key, your username is litterally `apikey`, more information about that can be found on the SLDN [Authenticating to the SoftLayer API](https://sldn.softlayer.com/article/authenticating-softlayer-api/#cloud-api) article. +For using a Classic Infrastructure or IBM Cloud API key. When using the IBM Cloud Api key, your username is the literal string `apikey`, more information about that can be found on the SLDN [Authenticating to the SoftLayer API](https://sldn.softlayer.com/article/authenticating-softlayer-api/#cloud-api) article. ```java import com.softlayer.api.*; diff --git a/src/main/java/com/softlayer/api/http/HttpBearerCredentials.java b/src/main/java/com/softlayer/api/http/HttpBearerCredentials.java index fd80aa1..951f4f2 100644 --- a/src/main/java/com/softlayer/api/http/HttpBearerCredentials.java +++ b/src/main/java/com/softlayer/api/http/HttpBearerCredentials.java @@ -1,9 +1,13 @@ package com.softlayer.api.http; -/** HTTP bearer authorization support for bearer token fromhttps://iam.cloud.ibm.com/identity/token */ +/** HTTP Bearer authorization support for IBM IAM Tokens. + * + * @see IAM Tokens + * @see Authenticating SoftLayer API + */ public class HttpBearerCredentials implements HttpCredentials { - public final String token; + protected final String token; public HttpBearerCredentials(String token) { this.token = token; From 1e82fa35d5cca13b11ff2be045fde93ac9ef098a Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Thu, 14 Jan 2021 14:45:18 -0600 Subject: [PATCH 54/62] fixed pull request feedback --- LICENSE | 2 +- README.md | 2 +- examples/pom.xml | 2 +- gen/pom.xml | 2 +- src/main/java/com/softlayer/api/http/HttpCredentials.java | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/LICENSE b/LICENSE index d8f5d64..213959f 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2020 The SoftLayer Developer Network +Copyright (c) 2021 The SoftLayer Developer Network Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 1944b81..c99f64f 100644 --- a/README.md +++ b/README.md @@ -71,7 +71,7 @@ import com.softlayer.api.*; ApiClient client = new RestApiClient().withCredentials("my user", "my api key"); ``` -#### Acesses Token +#### Access Token Information on how to get a temoprary api token can be found on the SLDN [Authenticating to the SoftLayer API](https://sldn.softlayer.com/article/authenticating-softlayer-api/#temp-token) article. ```java diff --git a/examples/pom.xml b/examples/pom.xml index 21d766c..7a988fc 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -5,7 +5,7 @@ softlayer-api-client-examples jar - 0.3.1 + 0.3.2 softlayer-api-client-examples http://sldn.softlayer.com diff --git a/gen/pom.xml b/gen/pom.xml index f1becb3..c62354f 100644 --- a/gen/pom.xml +++ b/gen/pom.xml @@ -5,7 +5,7 @@ softlayer-api-client-gen jar - 0.3.1 + 0.3.2 softlayer-api-client-gen http://sldn.softlayer.com diff --git a/src/main/java/com/softlayer/api/http/HttpCredentials.java b/src/main/java/com/softlayer/api/http/HttpCredentials.java index a588f83..a751a46 100644 --- a/src/main/java/com/softlayer/api/http/HttpCredentials.java +++ b/src/main/java/com/softlayer/api/http/HttpCredentials.java @@ -2,5 +2,5 @@ /** Base interface for all accepted HTTP credentials */ public interface HttpCredentials { - public String getHeader(); + String getHeader(); } From 7492c132e5930c0964af1f7ab952fca896e4e26a Mon Sep 17 00:00:00 2001 From: cmp Date: Thu, 21 Jan 2021 10:49:02 -0600 Subject: [PATCH 55/62] Add credential usage warning --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index c99f64f..363c06f 100644 --- a/README.md +++ b/README.md @@ -65,10 +65,12 @@ instantiate it and provide your credentials: #### Username and API Key For using a Classic Infrastructure or IBM Cloud API key. When using the IBM Cloud Api key, your username is the literal string `apikey`, more information about that can be found on the SLDN [Authenticating to the SoftLayer API](https://sldn.softlayer.com/article/authenticating-softlayer-api/#cloud-api) article. +:warning: Make sure to avoid hard coding your username and API key when using the client! Always pull credentials from the environment, secure config, or other source. + ```java import com.softlayer.api.*; -ApiClient client = new RestApiClient().withCredentials("my user", "my api key"); +ApiClient client = new RestApiClient().withCredentials(myUser, myApiKey); ``` #### Access Token From 76029c8040c709cc98b95fd1806047bae259a2f8 Mon Sep 17 00:00:00 2001 From: Cameron Porter Date: Tue, 14 Sep 2021 21:41:04 -0500 Subject: [PATCH 56/62] Remove Travis CI config. Prep version 0.3.3. * Update maven workflow and add github pages workflow. * Update dependencies. * Fix links in README and credential misuse examples. --- .github/workflows/maven.yml | 18 ++++++++++-------- .github/workflows/pages.yml | 23 +++++++++++++++++++++++ .travis.yml | 31 ------------------------------- README.md | 26 ++++++++++++++------------ examples/pom.xml | 4 ++-- gen/pom.xml | 10 +++++----- ghpages_secret.enc | Bin 1680 -> 0 bytes pom.xml | 22 +++++++++++----------- 8 files changed, 65 insertions(+), 69 deletions(-) create mode 100644 .github/workflows/pages.yml delete mode 100644 .travis.yml delete mode 100644 ghpages_secret.enc diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index 6f18e58..67b51dc 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -9,12 +9,14 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - java: [8, 11, 15] + java: [8, 11, 16] steps: - - uses: actions/checkout@v2 - - name: Set up JDK - uses: actions/setup-java@v1 - with: - java-version: ${{matrix.java}} - - name: Build with Maven - run: mvn -B package --file pom.xml + - name: Checkout + uses: actions/checkout@v2 + - name: Set up JDK + uses: actions/setup-java@v2.3.0 + with: + distribution: 'temurin' + java-version: ${{matrix.java}} + - name: Build with Maven + run: mvn verify -B -e -V --file pom.xml diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml new file mode 100644 index 0000000..3e50206 --- /dev/null +++ b/.github/workflows/pages.yml @@ -0,0 +1,23 @@ +name: Publish Pages +on: + push: + branches: + - master +jobs: + build-and-deploy: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Build + run: | + apt-get update && apt-get install -y pandoc + mkdir ghpages + pandoc -o ghpages/index.html README.md + + - name: Deploy + uses: JamesIves/github-pages-deploy-action@4.1.5 + with: + branch: gh-pages + folder: ghpages diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 6a06ba8..0000000 --- a/.travis.yml +++ /dev/null @@ -1,31 +0,0 @@ -language: java - -addons: - apt: - update: true - packages: - - pandoc - -jdk: - - oraclejdk11 - - openjdk8 - - openjdk9 - - openjdk10 - - openjdk11 - - openjdk12 - -install: - - mvn install -DskipTests=true -Dmaven.javadoc.skip=true -Dgpg.skip=true - - mkdir ghpages - - pandoc -o ghpages/index.html README.md - - openssl aes-256-cbc -K $encrypted_60afd10366bc_key -iv $encrypted_60afd10366bc_iv -in ghpages_secret.enc -out ghpages_secret -d - -deploy: - provider: pages:git - edge: true - skip_cleanup: true - local_dir: ghpages - deploy_key: ghpages_secret - keep_history: true - on: - branch: master diff --git a/README.md b/README.md index 363c06f..2baff25 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,19 @@ # SoftLayer API Client for Java -![Java CI](https://github.com/softlayer/softlayer-java/workflows/Java%20CI/badge.svg) +[![Java CI](https://github.com/softlayer/softlayer-java/actions/workflows/maven.yml/badge.svg)](https://github.com/softlayer/softlayer-java/actions/workflows/maven.yml) [![Maven Central](https://img.shields.io/maven-central/v/com.softlayer.api/softlayer-api-client)](https://search.maven.org/artifact/com.softlayer.api/softlayer-api-client) [![Javadocs](https://www.javadoc.io/badge/com.softlayer.api/softlayer-api-client.svg)](https://www.javadoc.io/doc/com.softlayer.api/softlayer-api-client) ## Introduction -This library provides a JVM client for the [SoftLayer API](http://sldn.softlayer.com/article/SoftLayer-API-Overview). It +This library provides a JVM client for the [SoftLayer API](https://sldn.softlayer.com/article/getting-started/). It has code generated and compiled via Maven. The client can work with any Java 8+ runtime. It uses the code generation project in `gen/` to generate the service and type related code. Although likely to work in resource-constrained environments (i.e. Android, J2ME, etc), using this is not recommended; Use the -[REST](http://sldn.softlayer.com/article/REST) API instead. +[REST](https://sldn.softlayer.com/article/rest/) API instead. By default the HTTP client is the Java `HttpUrlConnection` and the JSON marshalling is done by -[Gson](https://code.google.com/p/google-gson/). Both of these pieces can be exchanged for alternative implementations +[Gson](https://github.com/google/gson). Both of these pieces can be exchanged for alternative implementations (see below). The `examples/` project has sample uses of the API. It can be executed from Maven while inside the `examples/` folder @@ -40,20 +40,20 @@ additions to the SoftLayer API. com.softlayer.api softlayer-api-client - 0.3.2 + 0.3.3 ``` ### Gradle ```groovy -implementation 'com.softlayer.api:softlayer-api-client:0.3.2' +implementation 'com.softlayer.api:softlayer-api-client:0.3.3' ``` ### Kotlin ```kotlin -compile("com.softlayer.api:softlayer-api-client:0.3.2") +compile("com.softlayer.api:softlayer-api-client:0.3.3") ``` ### Creating a Client @@ -74,21 +74,23 @@ ApiClient client = new RestApiClient().withCredentials(myUser, myApiKey); ``` #### Access Token -Information on how to get a temoprary api token can be found on the SLDN [Authenticating to the SoftLayer API](https://sldn.softlayer.com/article/authenticating-softlayer-api/#temp-token) article. +Information on how to get a temoprary api token can be found on the SLDN +[Authenticating to the SoftLayer API](https://sldn.softlayer.com/article/authenticating-softlayer-api/#temp-token) +article. ```java import com.softlayer.api.*; -ApiClient client = new RestApiClient().withBearerToken("qqqqwwwweeeaaassddd...."); +ApiClient client = new RestApiClient().withBearerToken(myBearerToken); ``` If the end point isn't at the normal SoftLayer API, you can provide the prefix to the constructor of the -`RestApiClient`. By default it is set to the public API endpoint, `https://api.softlayer.com/rest/v3.1/`. +`RestApiClient`. By default, it is set to the public API endpoint, `https://api.softlayer.com/rest/v3.1/`. If you are using the classic infrastructure private network, you can communicate with the API over that network by using the service URL instead: ```java ApiClient client = new RestApiClient(RestApiClient.BASE_SERVICE_URL) - .withCredentials("my user", "my api key"); + .withCredentials(myUser, myApiKey); ``` ### Making API Calls @@ -170,7 +172,7 @@ for (Vlan vlan : service.getObject().getNetworkVlans()) { All values of a type can be masked upon. If a value represents a primitive or collection of primitives, the same mask it is called on is returned. Otherwise the mask of the other type is given. These translate into SoftLayer's -[string-based object mask format](http://sldn.softlayer.com/article/Object-Masks). A string or an instance of a mask +[string-based object mask format](https://sldn.softlayer.com/article/object-masks/). A string or an instance of a mask can be given directly by calling `setMask` on the service. Note, when object masks are added on a service object, they will be sent with every service call unless removed via `clearMask` or overwritten via `withNewMask` or `setMask`. diff --git a/examples/pom.xml b/examples/pom.xml index 7a988fc..ca8db44 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -5,7 +5,7 @@ softlayer-api-client-examples jar - 0.3.2 + 0.3.3 softlayer-api-client-examples http://sldn.softlayer.com @@ -36,7 +36,7 @@ org.codehaus.mojo exec-maven-plugin - 1.6.0 + 3.0.0 diff --git a/gen/pom.xml b/gen/pom.xml index c62354f..9a7961f 100644 --- a/gen/pom.xml +++ b/gen/pom.xml @@ -5,7 +5,7 @@ softlayer-api-client-gen jar - 0.3.2 + 0.3.3 softlayer-api-client-gen http://sldn.softlayer.com @@ -29,12 +29,12 @@ com.google.code.gson gson - 2.8.6 + 2.8.8 junit junit - 4.13.1 + 4.13.2 test @@ -43,7 +43,7 @@ org.codehaus.mojo exec-maven-plugin - 1.6.0 + 3.0.0 @@ -58,7 +58,7 @@ org.apache.maven.plugins maven-javadoc-plugin - 3.1.1 + 3.3.1 ${java.version} diff --git a/ghpages_secret.enc b/ghpages_secret.enc deleted file mode 100644 index 00f2d2dd1621942d5fd86858ba6e2dd70a7e21cf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1680 zcmV;B25a!HSY;t5VW`I zj6X`@zbB5p)CeOBuJ%$+ShwS zYkCh)e1&w4!u5TLrI)`IoV-ddzk~@oxTVIPO=2SiEh)#g{*5VoT9bspE*6U3&#NSs~*XnCBQLB-BlKgQl)F} z9wf4yghr3;d8z?_lxHUF^mAVTR7vDOCofyKqNd;*eY@#Rn7THUF1fV>jRHu}3_v zYoXRgB9(ybXVB=JpGRBaa%V4Ku>_+Sl?Ka@W8oyb42zf{=j~noMFdS0rT(0V`HeGd zje(1e2YL5liZ*X{iLoH;|2jrN{T+x?uWv$=xjB=k8?Y{m2P$FdBI_Yqc(8Bd3!+Mv zXW59n0NdrT90Oz>8i%7p+N>Xwq&=U+M5HKR(jL>Kr=MmU1$0_ujCIZFOo(*nTx7CC z7pTJY0;L0!X)m@gz{(xQJGM5@o*Q2cDx{LXl=if}O8QG=#!Xh$c(?_`!a<~)BvGgb z(c;hUsjZW^X%c1!4*k7Xgdqa*SO=;{I2u62%Vcd#PVPt%d=R{|S zzQ{7vbeXBSBQ!3?PVhk{OTQp~_p?bv*Fu{xtrN&bbc)oAlswcZv%Y4es8|tbP$w{A^KQov6K3@2E#yi%0L8KA>p)bk1G$>x zsWbvy8bA(HB`@5aM_(|`4nnU?T}W}Z{^WsbCi~J}t%zC$L5!Bz%bI^nO}AQI4m(l+ zP!G;NoquANSytz;aj*_u_`j@Pgzx;1u=*J2YWHEDFs<}a46lb$kB6)xO;eep!~Jo3 z&h*as7yA<%)c`%E%50MzBT=zRPIxZp5yo=7WV8>bh@uaQ^CxycvBpcVt3=q|&L49C zLNrPJ6%K8XDi_PW7d6!(>astWJ(Xd{b5wx9ywPhNJQRKhBm>y3Io?!gud)FEFBh#* zkRjQWyUL{QOS4?hOL=WkvOp=4A=PC(s{BEk(5|F z;8O)SDofHf|5J;5gAtXe4gzw+Ll!6DYfCpQY`DA=S z``}??k({vhbx?dBgXm^NoGy47nK$b-kyTkg8N}-j9Ct(lW1Br^Ds(wF)|oZ7W+-kF zW27J?o@IHuCK3!63(CDK1&qDk2o)ySEYka3v@softlayer-api-client jar - 0.3.2 + 0.3.3 SoftLayer API Client for Java API client for accessing the SoftLayer API - http://sldn.softlayer.com + https://sldn.softlayer.com The MIT License (MIT) - http://opensource.org/licenses/MIT + https://opensource.org/licenses/MIT repo @@ -28,7 +28,7 @@ camporter Cameron Porter IBM - http://ibm.com/cloud + https://ibm.com/cloud owner developer @@ -40,7 +40,7 @@ scm:git:git@github.com:softlayer/softlayer-java.git scm:git:git@github.com:softlayer/softlayer-java.git git@github.com:softlayer/softlayer-java.git - 0.3.2 + 0.3.3 UTF-8 @@ -66,12 +66,12 @@ com.google.code.gson gson - 2.8.6 + 2.8.8 junit junit - 4.13.1 + 4.13.2 test @@ -108,7 +108,7 @@ org.apache.maven.plugins maven-javadoc-plugin - 3.1.1 + 3.3.1 @@ -128,7 +128,7 @@ org.apache.maven.plugins maven-gpg-plugin - 1.6 + 3.0.1 sign-artifacts @@ -142,7 +142,7 @@ maven-invoker-plugin - 3.2.1 + 3.2.2 generate-services @@ -164,7 +164,7 @@ org.jacoco jacoco-maven-plugin - 0.8.6 + 0.8.7 **/*com/softlayer/api/service/**/* From d2e3ddad640186f42c79a166a1bd87b59a75fe98 Mon Sep 17 00:00:00 2001 From: Cameron Porter Date: Wed, 15 Sep 2021 12:41:26 -0500 Subject: [PATCH 57/62] Skip gpg signing for the maven workflow. --- .github/workflows/maven.yml | 2 +- pom.xml | 6 ------ 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index 67b51dc..5a48c50 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -19,4 +19,4 @@ jobs: distribution: 'temurin' java-version: ${{matrix.java}} - name: Build with Maven - run: mvn verify -B -e -V --file pom.xml + run: mvn verify -B -e -V --file pom.xml -Dgpg.skip=true diff --git a/pom.xml b/pom.xml index 7612382..32b563f 100644 --- a/pom.xml +++ b/pom.xml @@ -46,12 +46,6 @@ UTF-8 8 - - - jcenter - https://jcenter.bintray.com/ - - ossrh From a1e38e9fa97b771797a9546ae751f7f4635ed6f1 Mon Sep 17 00:00:00 2001 From: Cameron Porter Date: Wed, 15 Sep 2021 12:50:01 -0500 Subject: [PATCH 58/62] Use maven cache for java setup. --- .github/workflows/maven.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index 5a48c50..36a45c6 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -18,5 +18,6 @@ jobs: with: distribution: 'temurin' java-version: ${{matrix.java}} + cache: 'maven' - name: Build with Maven run: mvn verify -B -e -V --file pom.xml -Dgpg.skip=true From 7a337d9d8a72a6bb517105ce9c4e986e84ce9502 Mon Sep 17 00:00:00 2001 From: Cameron Porter Date: Wed, 15 Sep 2021 13:46:35 -0500 Subject: [PATCH 59/62] Publishing requires using passwordless sudo to install pandoc. --- .github/workflows/pages.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml index 3e50206..fb9fbe9 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -12,7 +12,7 @@ jobs: - name: Build run: | - apt-get update && apt-get install -y pandoc + sudo apt-get update && sudo apt-get install -y pandoc mkdir ghpages pandoc -o ghpages/index.html README.md From 7b1a5a0bec48d2753e8445ae53c6ee802c4e6839 Mon Sep 17 00:00:00 2001 From: Cameron Porter Date: Thu, 16 Dec 2021 16:57:28 -0600 Subject: [PATCH 60/62] Process deprecations from the API metadata and annotate types, methods, and properties if they are deprecated. --- .github/workflows/maven.yml | 4 +- examples/pom.xml | 7 ++- gen/pom.xml | 7 ++- .../com/softlayer/api/gen/ClassWriter.java | 50 +++++++++++++++---- .../main/java/com/softlayer/api/gen/Meta.java | 3 ++ pom.xml | 25 +++++++++- 6 files changed, 78 insertions(+), 18 deletions(-) diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index 36a45c6..2581fba 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -9,12 +9,12 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - java: [8, 11, 16] + java: [8, 11, 16, 17] steps: - name: Checkout uses: actions/checkout@v2 - name: Set up JDK - uses: actions/setup-java@v2.3.0 + uses: actions/setup-java@v2.4.0 with: distribution: 'temurin' java-version: ${{matrix.java}} diff --git a/examples/pom.xml b/examples/pom.xml index ca8db44..09cd982 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -1,17 +1,20 @@ 4.0.0 + + 3.3.9 + com.softlayer.api softlayer-api-client-examples jar 0.3.3 softlayer-api-client-examples - http://sldn.softlayer.com + https://sldn.softlayer.com/ The MIT License (MIT) - http://opensource.org/licenses/MIT + https://opensource.org/licenses/MIT repo diff --git a/gen/pom.xml b/gen/pom.xml index 9a7961f..0181564 100644 --- a/gen/pom.xml +++ b/gen/pom.xml @@ -1,13 +1,16 @@ 4.0.0 + + 3.3.9 + com.softlayer.api softlayer-api-client-gen jar 0.3.3 softlayer-api-client-gen - http://sldn.softlayer.com + https://sldn.softlayer.com/ The MIT License (MIT) @@ -29,7 +32,7 @@ com.google.code.gson gson - 2.8.8 + 2.8.9 junit diff --git a/gen/src/main/java/com/softlayer/api/gen/ClassWriter.java b/gen/src/main/java/com/softlayer/api/gen/ClassWriter.java index 671c04f..2d4ef46 100644 --- a/gen/src/main/java/com/softlayer/api/gen/ClassWriter.java +++ b/gen/src/main/java/com/softlayer/api/gen/ClassWriter.java @@ -134,6 +134,10 @@ public ClassWriter emitProperty(TypeClass.Property property) throws IOException if (property.meta.doc != null) { emitJavadoc(property.meta.doc.replace("\n", "
\n")); } + + if (property.meta.deprecated) { + emitAnnotation("Deprecated"); + } Map params = new HashMap<>(2); if (!property.name.equals(property.meta.name)) { @@ -267,6 +271,10 @@ public ClassWriter emitServiceMethod(TypeClass.Method method, boolean async) thr javadoc += "@see " + type.meta.name + "::" + method.meta.name + ""; emitJavadoc(javadoc); + + if (method.meta.deprecated) { + emitAnnotation("Deprecated"); + } Map params = new HashMap<>(2); if (!method.name.equals(method.meta.name)) { @@ -279,6 +287,10 @@ public ClassWriter emitServiceMethod(TypeClass.Method method, boolean async) thr } else { // Otherwise, just a javadoc link emitJavadoc("Async version of {@link Service#" + method.name + "}"); + + if (method.meta.deprecated) { + emitAnnotation("Deprecated"); + } } String[] parameters = new String[method.parameters.size() * 2]; @@ -296,6 +308,9 @@ public ClassWriter emitServiceMethod(TypeClass.Method method, boolean async) thr // Async has an extra callback method if (async) { + if (method.meta.deprecated) { + emitAnnotation("Deprecated"); + } parameters = Arrays.copyOf(parameters, parameters.length + 2); parameters[parameters.length - 2] = TYPE_RESPONSE_HANDLER + '<' + method.javaType + '>'; parameters[parameters.length - 1] = "callback"; @@ -324,6 +339,10 @@ public ClassWriter emitServiceMethod(TypeClass.Property property, boolean async) javadoc += "@see " + type.meta.name + "::" + name + ""; emitJavadoc(javadoc); + + if (property.meta.deprecated) { + emitAnnotation("Deprecated"); + } // Instance is only required if it's not an account property if ("SoftLayer_Account".equals(type.meta.name)) { @@ -334,6 +353,10 @@ public ClassWriter emitServiceMethod(TypeClass.Property property, boolean async) } else { // Otherwise, just a javadoc link emitJavadoc("Async version of {@link Service#" + name + "}"); + + if (property.meta.deprecated) { + emitAnnotation("Deprecated"); + } } String returnType = property.javaType; @@ -355,17 +378,12 @@ public ClassWriter emitType() throws IOException { emitPackage(type.packageName); emitTypeImports(); - - // Javadoc - String javadoc = type.meta.typeDoc != null ? type.meta.typeDoc : type.meta.serviceDoc; - if (javadoc == null) { - javadoc = ""; - } else { - javadoc = javadoc.replace("\n", "
\n") + "\n\n"; + + emitJavadoc(getTypeJavadoc()); + + if (type.meta.deprecated) { + emitAnnotation("Deprecated"); } - javadoc += "@see " + type.meta.name + ""; - emitJavadoc(javadoc); // Each type has a type attribute emitAnnotation("ApiType", stringLiteral(type.meta.name)); @@ -432,6 +450,18 @@ public ClassWriter emitType() throws IOException { emitMask().endType(); return this; } + + protected String getTypeJavadoc() { + String javadoc = type.meta.typeDoc != null ? type.meta.typeDoc : type.meta.serviceDoc; + if (javadoc == null) { + javadoc = ""; + } else { + javadoc = javadoc.replace("\n", "
\n") + "\n\n"; + } + javadoc += "@see " + type.meta.name + ""; + return javadoc; + } public ClassWriter emitTypeImports() throws IOException { Map imports = new HashMap<>(type.imports); diff --git a/gen/src/main/java/com/softlayer/api/gen/Meta.java b/gen/src/main/java/com/softlayer/api/gen/Meta.java index 7a9ad8d..838af47 100644 --- a/gen/src/main/java/com/softlayer/api/gen/Meta.java +++ b/gen/src/main/java/com/softlayer/api/gen/Meta.java @@ -75,6 +75,7 @@ public static class Type { public String serviceDoc; public Map methods = Collections.emptyMap(); public boolean noservice; + public boolean deprecated; } /** @@ -86,6 +87,7 @@ public static class Property { public boolean typeArray; public PropertyForm form; public String doc; + public boolean deprecated; } public enum PropertyForm { @@ -109,6 +111,7 @@ public static class Method { public boolean filterable; public boolean maskable; public List parameters = Collections.emptyList(); + public boolean deprecated; } /** diff --git a/pom.xml b/pom.xml index 32b563f..314cd06 100644 --- a/pom.xml +++ b/pom.xml @@ -1,5 +1,8 @@ 4.0.0 + + 3.3.9 + com.softlayer.api softlayer-api-client jar @@ -60,7 +63,7 @@ com.google.code.gson gson - 2.8.8 + 2.8.9 junit @@ -71,7 +74,7 @@ org.mockito mockito-core - 3.6.0 + 4.2.0 test @@ -181,4 +184,22 @@
+ + + + org.codehaus.mojo + versions-maven-plugin + 2.8.1 + + + + dependency-updates-report + plugin-updates-report + property-updates-report + + + + + + From ecc49545c1d487ae1261d3b4e6e30ce0817d1c28 Mon Sep 17 00:00:00 2001 From: Cameron Porter Date: Fri, 17 Dec 2021 12:08:09 -0600 Subject: [PATCH 61/62] Release version 0.3.4 --- CHANGELOG.md | 56 ++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 8 +++---- examples/pom.xml | 2 +- gen/pom.xml | 2 +- pom.xml | 4 ++-- 5 files changed, 64 insertions(+), 8 deletions(-) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..e123418 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,56 @@ +# Changelog + +Note that new releases update the API types and services and may change or remove classes, methods, and types without +notice. + +## [Unreleased] + +## [0.3.4] - 2021-12-17 + +### Changed +* Add deprecation annotations to types, properties, and methods. Deprecations may be removed in future changes to the + API. + +### Changed +* New service definitions. + +## [0.3.3] - 2021-09-15 + +### Changed +* Updated services and types. +* Updated dependencies. + +## [0.3.2] - 2021-01-20 + +### Added +* Added support for Bearer Authentication Token Support. + +```java +import com.softlayer.api.*; +ApiClient client = new RestApiClient().withBearerToken("qqqqwwwweeeaaassddd...."); +``` + +### Changed +* Updated services and types. + +## [0.3.1] - 2021-11-09 + +### Changed +* Updated services and types. + +## [0.3.0] - 2021-03-25 + +### Added +* Added a new `RestApiClient.BASE_SERVICE_URL` constant to use the client with the classic infrastructure private + network. + +### Changed +* A breaking change has been made. Coerce return types to whatever the API metadata says should be returned, even if + the type returned by the API does not match (#64). + +* Updated services and types. + +## [0.2.9] - 2020-01-21 + +### Changed +* Updated generated services and types. diff --git a/README.md b/README.md index 2baff25..a771bf3 100644 --- a/README.md +++ b/README.md @@ -40,20 +40,20 @@ additions to the SoftLayer API. com.softlayer.api softlayer-api-client - 0.3.3 + 0.3.4 ``` ### Gradle ```groovy -implementation 'com.softlayer.api:softlayer-api-client:0.3.3' +implementation 'com.softlayer.api:softlayer-api-client:0.3.4' ``` ### Kotlin ```kotlin -compile("com.softlayer.api:softlayer-api-client:0.3.3") +compile("com.softlayer.api:softlayer-api-client:0.3.4") ``` ### Creating a Client @@ -74,7 +74,7 @@ ApiClient client = new RestApiClient().withCredentials(myUser, myApiKey); ``` #### Access Token -Information on how to get a temoprary api token can be found on the SLDN +Information on how to get a temporary api token can be found on the SLDN [Authenticating to the SoftLayer API](https://sldn.softlayer.com/article/authenticating-softlayer-api/#temp-token) article. diff --git a/examples/pom.xml b/examples/pom.xml index 09cd982..f90f564 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -8,7 +8,7 @@ softlayer-api-client-examples jar - 0.3.3 + 0.3.4 softlayer-api-client-examples https://sldn.softlayer.com/ diff --git a/gen/pom.xml b/gen/pom.xml index 0181564..768a3a5 100644 --- a/gen/pom.xml +++ b/gen/pom.xml @@ -8,7 +8,7 @@ softlayer-api-client-gen jar - 0.3.3 + 0.3.4 softlayer-api-client-gen https://sldn.softlayer.com/ diff --git a/pom.xml b/pom.xml index 314cd06..23bd176 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ softlayer-api-client jar - 0.3.3 + 0.3.4 SoftLayer API Client for Java API client for accessing the SoftLayer API https://sldn.softlayer.com @@ -43,7 +43,7 @@ scm:git:git@github.com:softlayer/softlayer-java.git scm:git:git@github.com:softlayer/softlayer-java.git git@github.com:softlayer/softlayer-java.git - 0.3.3 + 0.3.4 UTF-8 From 6185d58b1d7c34146a66922c0bbe440df8ca6941 Mon Sep 17 00:00:00 2001 From: Cameron Porter Date: Fri, 17 Dec 2021 12:17:52 -0600 Subject: [PATCH 62/62] Correct some changelog release dates --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e123418..b8b5301 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,12 +33,12 @@ ApiClient client = new RestApiClient().withBearerToken("qqqqwwwweeeaaassddd...." ### Changed * Updated services and types. -## [0.3.1] - 2021-11-09 +## [0.3.1] - 2020-11-09 ### Changed * Updated services and types. -## [0.3.0] - 2021-03-25 +## [0.3.0] - 2020-03-25 ### Added * Added a new `RestApiClient.BASE_SERVICE_URL` constant to use the client with the classic infrastructure private