diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml deleted file mode 100644 index 2581fba..0000000 --- a/.github/workflows/maven.yml +++ /dev/null @@ -1,23 +0,0 @@ -name: Java CI - -on: - push: - branches: [ master ] - pull_request: -jobs: - build: - runs-on: ubuntu-latest - strategy: - matrix: - java: [8, 11, 16, 17] - steps: - - name: Checkout - uses: actions/checkout@v2 - - name: Set up JDK - uses: actions/setup-java@v2.4.0 - 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 diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml deleted file mode 100644 index fb9fbe9..0000000 --- a/.github/workflows/pages.yml +++ /dev/null @@ -1,23 +0,0 @@ -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: | - sudo apt-get update && sudo 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/.gitignore b/.gitignore deleted file mode 100644 index 05900eb..0000000 --- a/.gitignore +++ /dev/null @@ -1,12 +0,0 @@ -/.classpath -/.project -/.settings -/.idea -*.iml -/target -/src/main/java/com/softlayer/api/service -/gen/target -/gen/build.log -/examples/target -settings.xml -.DS_Store diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index b8b5301..0000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,56 +0,0 @@ -# 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] - 2020-11-09 - -### Changed -* Updated services and types. - -## [0.3.0] - 2020-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/LICENSE b/LICENSE deleted file mode 100644 index 213959f..0000000 --- a/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -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 -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/README.md b/README.md deleted file mode 100644 index a771bf3..0000000 --- a/README.md +++ /dev/null @@ -1,315 +0,0 @@ -# SoftLayer API Client for Java - -[![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](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](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://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 -via a command: - - mvn -q compile exec:java -Dexec.args="EXAMPLE_NAME API_USER API_KEY" - -Where `EXAMPLE_NAME` is the unqualified class name of an example in the `com.softlayer.api.example` package (e.g. -`ListServers`), `API_USER` is your API username, and `API_KEY` is your API key. NOTE: Some examples order virtual -servers and may charge your account. - -## Using - -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.3.4 - -``` - -### Gradle - -```groovy -implementation 'com.softlayer.api:softlayer-api-client:0.3.4' -``` - -### Kotlin - -```kotlin -compile("com.softlayer.api:softlayer-api-client:0.3.4") -``` - -### Creating a Client - -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 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(myUser, myApiKey); -``` - -#### Access Token -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. - -```java -import com.softlayer.api.*; -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/`. - -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(myUser, myApiKey); -``` - -### Making API Calls - -Once a client is created, it can be used to access services. There are hundreds of services to control your SoftLayer -account. A simple one is the `Account` service. Here's a call to get all of the hardware on the account: - -```java -import com.softlayer.api.service.Account; -import com.softlayer.api.service.Hardware; - -for (Hardware hardware : Account.service(client).getHardware()) { - System.out.println("Hardware: " + hardware.getFullyQualifiedDomainName()); -} -``` - -Some calls on a service require an ID to know what object to act on. This can be obtained by passing in the numeric ID -into the `service` method or by calling `asService` on an object that has an ID. Here's an example of soft-rebooting a -virtual server with "reboot-test" as the hostname: - -```java -import com.softlayer.api.service.virtual.Guest; - -for (Guest guest : Account.service(client).getVirtualGuests()) { - if ("reboot-test".equals(guest.getHostname())) { - guest.asService(client).rebootSoft(); - } -} -``` - -Some calls require sending in data. This is done by just instantiating the object and populating the data. Here's an -example of ordering a new virtual server: (Note running this can charge your account) - -```java -import com.softlayer.api.service.virtual.Guest; - -Guest guest = new Guest(); -guest.setHostname("myhostname"); -guest.setDomain("example.com"); -guest.setStartCpus(1); -guest.setMaxMemory(1024); -guest.setHourlyBillingFlag(true); -guest.setOperatingSystemReferenceCode("UBUNTU_LATEST"); -guest.setLocalDiskFlag(false); -guest.setDatacenter(new Location()); -guest.getDatacenter().setName("dal05"); -guest = Guest.service(client).createObject(guest); -System.out.println("Virtual server ordered with ID: " + guest.getId()); -``` - -### Using Object Masks - -Object masks are a great way to reduce the number of API calls to traverse the data graph of an object. For example, -here's how by just asking for an account, you can retrieve all your VLANs, their datacenter, and the firewall rules that -are on them: - -```java -import com.softlayer.api.service.Account; -import com.softlayer.api.service.network.Vlan; -import com.softlayer.api.service.network.vlan.firewall.Rule; - -Account.Service service = Account.service(client); -service.withMask().networkVlans().vlanNumber(); -service.withMask().networkVlans().primaryRouter().datacenter().longName(); -service.withMask().networkVlans().firewallRules(). - orderValue(). - sourceIpAddress(). - sourceIpCidr(); - -for (Vlan vlan : service.getObject().getNetworkVlans()) { - for (Rule rule : vlan.getFirewallRules()) { - System.out.format("Rule %d on VLAN %d in %s has some restriction on subnet %s/%d\n", - rule.getOrderValue(), vlan.getVlanNumber(), - vlan.getPrimaryRouter().getDatacenter().getLongName(), - rule.getSourceIpAddress(), rule.getSourceIpCidr()); - } -} -``` - -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](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`. - -### Asynchronous Invocation - -All services also provide an asynchronous interface. This can be obtained from a service by calling `asAsync`. Here's an -example of getting all top level billing items and listing when they were created: - -```java -import java.util.List; -import com.softlayer.api.service.ResponseHandler; -import com.softlayer.api.service.Account; -import com.softlayer.api.service.billing.Item; - -Account.service(client).asAsync().getAllTopLevelBillingItems(new ResponseHandler>() { - @Override - public void onError(Exception ex) { - ex.printStackTrace(); - } - - @Override - public void onSuccess(List items) { - for (Item item : items) { - System.out.format("Billing item %s created on %s\n", item.getDescription(), item.getCreateDate()); - } - } -}).get(); -``` - -Using the default HTTP client, this runs the call in a separate thread and calls the handler parameter upon completion. -The `get` at the end basically makes it wait forever so the application doesn't exit out from under us. With the default -HTTP client the asynchronous invocations are handled by a simple thread pool that defaults to a cached thread pool that -creates daemon threads. It can be changed: - -```java -import java.util.concurrent.Executors; -import java.util.concurrent.ExecutorService; -import com.softlayer.api.service.RestApiClient; -import com.softlayer.api.service.http.ThreadPoolHttpClientFactory; -import com.softlayer.api.service.billing.Item; - -RestApiClient client = new RestApiClient(); -ExecutorService threadPool = Executors.newFixedThreadPool(3); -((ThreadPoolHttpClientFactory) client.getHttpClientFactory()).setThreadPool(threadPool); -``` - -Unlike using the default thread pool, you will be responsible for shutting down this overridden thread pool as -necessary. Other HTTP client implementations may handle asynchrony differently and not use thread pools at all. - -In addition to the callback-style above, can also get the response as a `Future`. Here's an example of waiting 10 -seconds to get all top level billing items: - -```java -import java.util.List; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.Future; -import com.softlayer.api.service.Account; -import com.softlayer.api.service.billing.Item; - -Future> response = Account.service(client).asAsync().getAllTopLevelBillingItems(); -List items = response.get(10, TimeUnit.SECONDS); -for (Item item : items) { - System.out.format("Billing item %s created on %s\n", item.getDescription(), item.getCreateDate()); -} -``` - -### Thread Safety - -No class in this library is guaranteed to be thread-safe. Callers are expected to keep this in mind when developing -with the library and to never use the same `ApiClient` (or any other object created with it) concurrently across -threads. - -### Pagination - -Sometimes there is a need to get the responses from the SoftLayer API in a paginated way instead of all at once. This -can be done by utilizing result limits. A result limit can be passed in with the number of results requested and the -offset to start reading from. Requesting smaller amounts of data will increase the performance of the call. Here is an -example of obtaining the first 10 tickets and outputting the total: - -```java -import com.softlayer.api.ResultLimit; -import com.softlayer.api.service.Account; -import com.softlayer.api.service.Ticket; - -Account.Service service = Account.service(client); -service.setResultLimit(new ResultLimit(10)); -for (Ticket ticket : service.getTickets()) { - System.out.println("Got ticket " + ticket.getTitle()); -} -System.out.println("Total tickets on the account: " + service.getLastResponseTotalItemCount()); -``` - -The services are not guaranteed to be thread-safe on their own, so it is difficult to obtain the total with -`getLastResponseTotalItemCount` when using the service asynchronously. To assist with this when using the callback -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 -work properly and will regenerate the client. To specifically regenerate the Java service-related files, run: - - mvn generate-sources - -## Customization - -### Logging - -Logging the requests and response to stdout can be enabled by invoking `withLoggingEnabled` on the `RestApiClient`. In -order to log elsewhere, simply make your own implementation of `RestApiClient` with `logRequest` and `logResponse` -overridden. - -### HTTP Client - -The default HTTP client that is used is the JVM's native `HttpUrlConnection`. In order to create your own, alternative -implementation you must implement `com.softlayer.api.http.HttpClientFactory`. Once implemented, this can be explicitly -set on the `RestApiClient` by calling `setHttpClientFactory`. 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 -`META-INF/com.softlayer.api.http.HttpClientFactory`. - -### 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.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 -`META-INF/com.softlayer.api.json.JsonMarshallerFactory`. - -## Copyright - -This software is Copyright (c) 2021 The SoftLayer Developer Network. See the bundled LICENSE file for more information. diff --git a/examples/pom.xml b/examples/pom.xml deleted file mode 100644 index f90f564..0000000 --- a/examples/pom.xml +++ /dev/null @@ -1,65 +0,0 @@ - - 4.0.0 - - 3.3.9 - - com.softlayer.api - softlayer-api-client-examples - jar - - 0.3.4 - softlayer-api-client-examples - https://sldn.softlayer.com/ - - - The MIT License (MIT) - https://opensource.org/licenses/MIT - repo - - - - UTF-8 - 8 - - - - com.softlayer.api - softlayer-api-client - LATEST - - - - - - org.apache.maven.plugins - maven-resources-plugin - 3.1.0 - - - org.codehaus.mojo - exec-maven-plugin - 3.0.0 - - - - java - - - - - com.softlayer.api.example.Main - - - - org.apache.maven.plugins - maven-compiler-plugin - 3.8.1 - - ${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 deleted file mode 100644 index c5d21e6..0000000 --- a/examples/src/main/java/com/softlayer/api/example/AddSecurityGroupRule.java +++ /dev/null @@ -1,45 +0,0 @@ -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 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 = sgOut.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.println("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 deleted file mode 100644 index eadbb99..0000000 --- a/examples/src/main/java/com/softlayer/api/example/CreateSecurityGroup.java +++ /dev/null @@ -1,27 +0,0 @@ -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 sgOut = service.createObject(sg); - System.out.format("Created security group with name = %s\n", sgOut.getName()); - } - - public static void main(String[] args) throws Exception { - new CreateSecurityGroup().start(args); - } -} diff --git a/examples/src/main/java/com/softlayer/api/example/Example.java b/examples/src/main/java/com/softlayer/api/example/Example.java deleted file mode 100644 index 32566fc..0000000 --- a/examples/src/main/java/com/softlayer/api/example/Example.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.softlayer.api.example; - -import com.softlayer.api.ApiClient; -import com.softlayer.api.RestApiClient; - -/** Base class for all examples that parses the command-line arguments */ -public abstract class Example { - - public void start(String[] args) throws Exception { - // Args are username, api key, and (optional) base URL - if (args.length < 2 || args.length > 3) { - throw new RuntimeException("Username and api key required. Base URL can optionally be provided"); - } - String baseUrl = args.length == 3 ? args[2] : RestApiClient.BASE_URL; - // Add trailing slash if not present - if (!baseUrl.endsWith("/")) { - baseUrl += '/'; - } - - 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 */ - public abstract void run(ApiClient client) throws Exception; -} diff --git a/examples/src/main/java/com/softlayer/api/example/ListSecurityGroups.java b/examples/src/main/java/com/softlayer/api/example/ListSecurityGroups.java deleted file mode 100644 index a7f4f4e..0000000 --- a/examples/src/main/java/com/softlayer/api/example/ListSecurityGroups.java +++ /dev/null @@ -1,31 +0,0 @@ -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); - } -} diff --git a/examples/src/main/java/com/softlayer/api/example/ListServers.java b/examples/src/main/java/com/softlayer/api/example/ListServers.java deleted file mode 100644 index fd04277..0000000 --- a/examples/src/main/java/com/softlayer/api/example/ListServers.java +++ /dev/null @@ -1,51 +0,0 @@ -package com.softlayer.api.example; - -import com.softlayer.api.ApiClient; -import com.softlayer.api.service.Account; -import com.softlayer.api.service.Hardware; -import com.softlayer.api.service.virtual.Guest; - -/** List all physical and virtual servers on an account */ -public class ListServers extends Example { - - @Override - public void run(ApiClient client) throws Exception { - Account.Service service = Account.service(client); - - // To get specific information on an account (servers in this case) a mask is provided - service.withMask().hardware(). - fullyQualifiedDomainName(). - primaryIpAddress(). - primaryBackendIpAddress(); - service.withMask().hardware().operatingSystem().softwareLicense().softwareDescription(). - manufacturer(). - name(). - version(); - service.withMask().virtualGuests(). - fullyQualifiedDomainName(). - primaryIpAddress(). - primaryBackendIpAddress(); - service.withMask().virtualGuests().operatingSystem().softwareLicense().softwareDescription(). - manufacturer(). - name(). - version(); - - // Calling getObject will now use the mask - Account account = service.getObject(); - - System.out.format("\n%d physical servers\n", account.getHardware().size()); - for (Hardware hardware : account.getHardware()) { - System.out.format("Host: %s, IP: %s (%s)\n", hardware.getFullyQualifiedDomainName(), - hardware.getPrimaryIpAddress(), hardware.getPrimaryBackendIpAddress()); - } - System.out.format("\n%d virtual servers\n", account.getVirtualGuests().size()); - for (Guest guest : account.getVirtualGuests()) { - System.out.format("Host: %s, IP: %s (%s)\n", guest.getFullyQualifiedDomainName(), - guest.getPrimaryIpAddress(), guest.getPrimaryBackendIpAddress()); - } - } - - public static void main(String[] args) throws Exception { - new ListServers().start(args); - } -} diff --git a/examples/src/main/java/com/softlayer/api/example/ListServersAsync.java b/examples/src/main/java/com/softlayer/api/example/ListServersAsync.java deleted file mode 100644 index 75daa49..0000000 --- a/examples/src/main/java/com/softlayer/api/example/ListServersAsync.java +++ /dev/null @@ -1,63 +0,0 @@ -package com.softlayer.api.example; - -import com.softlayer.api.ApiClient; -import com.softlayer.api.ResponseHandler; -import com.softlayer.api.service.Account; -import com.softlayer.api.service.Hardware; -import com.softlayer.api.service.virtual.Guest; - -/** Asynchronous version of {@link ListServers} */ -public class ListServersAsync extends Example { - - @Override - public void run(ApiClient client) throws Exception { - Account.Service service = Account.service(client); - - // To get specific information on an account (servers in this case) a mask is provided - service.withMask().hardware(). - fullyQualifiedDomainName(). - primaryIpAddress(). - primaryBackendIpAddress(); - service.withMask().hardware().operatingSystem().softwareLicense().softwareDescription(). - manufacturer(). - name(). - version(); - service.withMask().virtualGuests(). - fullyQualifiedDomainName(). - primaryIpAddress(). - primaryBackendIpAddress(); - service.withMask().virtualGuests().operatingSystem().softwareLicense().softwareDescription(). - manufacturer(). - name(). - version(); - - // Calling getObject will now use the mask - // By using asAsync this runs on a separate thread pool, and get() is called on the resulting future - // to wait until completion. In most cases other processing would continue before finishing. - service.asAsync().getObject(new ResponseHandler() { - - @Override - public void onSuccess(Account account) { - System.out.format("\n%d physical servers\n", account.getHardware().size()); - for (Hardware hardware : account.getHardware()) { - System.out.format("Host: %s, IP: %s (%s)\n", hardware.getFullyQualifiedDomainName(), - hardware.getPrimaryIpAddress(), hardware.getPrimaryBackendIpAddress()); - } - System.out.format("\n%d virtual servers\n", account.getVirtualGuests().size()); - for (Guest guest : account.getVirtualGuests()) { - System.out.format("Host: %s, IP: %s (%s)\n", guest.getFullyQualifiedDomainName(), - guest.getPrimaryIpAddress(), guest.getPrimaryBackendIpAddress()); - } - } - - @Override - public void onError(Exception ex) { - ex.printStackTrace(); - } - }).get(); - } - - public static void main(String[] args) throws Exception { - new ListServersAsync().start(args); - } -} diff --git a/examples/src/main/java/com/softlayer/api/example/Main.java b/examples/src/main/java/com/softlayer/api/example/Main.java deleted file mode 100644 index ae6ab06..0000000 --- a/examples/src/main/java/com/softlayer/api/example/Main.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.softlayer.api.example; - -import java.lang.reflect.Method; -import java.util.Arrays; - -/** Alternate entry point for examples that has the class name as the first argument */ -public class Main { - public static void main(String[] args) throws Exception { - if (args.length == 0) { - throw new IllegalArgumentException("First parameter must be example name"); - } - Method main = Class.forName(Main.class.getPackage().getName() + "." + args[0]). - getDeclaredMethod("main", String[].class); - // Take the class name off the front - main.invoke(null, (Object) Arrays.copyOfRange(args, 1, args.length)); - } -} diff --git a/examples/src/main/java/com/softlayer/api/example/OrderVirtualServer.java b/examples/src/main/java/com/softlayer/api/example/OrderVirtualServer.java deleted file mode 100644 index 8ca59e0..0000000 --- a/examples/src/main/java/com/softlayer/api/example/OrderVirtualServer.java +++ /dev/null @@ -1,113 +0,0 @@ -package com.softlayer.api.example; - -import java.util.concurrent.TimeUnit; - -import com.softlayer.api.ApiClient; -import com.softlayer.api.service.Location; -import com.softlayer.api.service.container.virtual.guest.Configuration; -import com.softlayer.api.service.container.virtual.guest.configuration.Option; -import com.softlayer.api.service.software.component.Password; -import com.softlayer.api.service.virtual.Guest; -import com.softlayer.api.service.virtual.guest.block.Device; - -/** Order an hourly virtual server and wait for it to complete provisioning */ -public class OrderVirtualServer extends Example { - - @Override - public void run(ApiClient client) throws Exception { - Guest.Service service = Guest.service(client); - - // This call helps by giving possible options that can be placed on the order - Configuration orderOptions = service.getCreateObjectOptions(); - - // A guest object represents a virtual server. The code below is an order for a virtual server - // with 2 cores, billed hourly, with local disk and the latest Ubuntu distribution. - // Ref: http://sldn.softlayer.com/reference/services/SoftLayer_Virtual_Guest/createObject - Guest guest = new Guest(); - guest.setHostname("api-test"); - guest.setDomain("example.com"); - guest.setStartCpus(2L); - guest.setHourlyBillingFlag(true); - guest.setLocalDiskFlag(true); - guest.setOperatingSystemReferenceCode("UBUNTU_LATEST"); - - // By using the options we can get the smallest amount of memory without hardcoding it - for (Option option : orderOptions.getMemory()) { - if (guest.getMaxMemory() == null || guest.getMaxMemory() > option.getTemplate().getMaxMemory()) { - guest.setMaxMemory(option.getTemplate().getMaxMemory()); - } - } - - // Datacenters are represented by their name. Here the Amsterdam datacenter is used. - guest.setDatacenter(new Location()); - guest.getDatacenter().setName("ams01"); - - // Block devices are indexed starting at 0, but 1 is reserved for swap usage. Options can be used - // here also to set the smallest disk size allowed for the disk at index 0. - Device device = null; - for (Option option : orderOptions.getBlockDevices()) { - for (Device candidate : option.getTemplate().getBlockDevices()) { - if ("0".equals(candidate.getDevice()) && (device == null || - device.getDiskImage().getCapacity() > candidate.getDiskImage().getCapacity())) { - device = candidate; - } - } - } - guest.getBlockDevices().add(device); - - System.out.println("Ordering virtual server"); - guest = service.createObject(guest); - System.out.format("Order completed for virtual server with UUID: %s\n", guest.getGlobalIdentifier()); - - // Asking for the service of a result binds the identifier (the "id" in this case) to the resulting - // service. Some calls require an ID which can also be done using the services constructor or - // withId/setId. This is the equivalent of Guest.service(client, guest.getId()). - service = guest.asService(client); - - // Once guests are ordered, they take a couple of minutes to provision. This waits 15 minutes just - // to be safe. - int minutesToWait = 15; - System.out.format("Waiting for completion for a max of %d minutes\n", minutesToWait); - int timesChecked = 0; - service.withMask().status().name(); - service.withMask().provisionDate(); - do { - TimeUnit.MINUTES.sleep(1); - guest = service.getObject(); - System.out.format("Virtual server %d is not provisioned after %d minutes; Waiting one minute\n", - guest.getId(), timesChecked + 1); - } while (++timesChecked < minutesToWait && - (!"Active".equals(guest.getStatus().getName()) || guest.getProvisionDate() == null)); - - // Either the virtual server became active or we timed out - if (!"Active".equals(guest.getStatus().getName()) || guest.getProvisionDate() == null) { - System.out.format("Virtual server %d is still not provisioned after %d minutes; Quitting\n", - guest.getId(), minutesToWait); - } else { - // Using a mask, we can ask for the operating system password - service.withNewMask().primaryIpAddress().operatingSystem().passwords(); - guest = service.getObject(); - if (guest.getOperatingSystem() == null) { - System.out.println("Unable to find operating system on completed guest"); - } else { - Password root = null; - for (Password password : guest.getOperatingSystem().getPasswords()) { - if ("root".equals(password.getUsername())) { - root = password; - break; - } - } - if (root == null) { - System.out.println("Unable to find root password"); - } else { - System.out.format("Virtual server done, can now login to %s with root password %s\n", - guest.getPrimaryIpAddress(), root.getPassword()); - } - } - } - } - - public static void main(String[] args) throws Exception { - new OrderVirtualServer().start(args); - } -} diff --git a/examples/src/main/java/com/softlayer/api/example/Pagination.java b/examples/src/main/java/com/softlayer/api/example/Pagination.java deleted file mode 100644 index b9b7b09..0000000 --- a/examples/src/main/java/com/softlayer/api/example/Pagination.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.softlayer.api.example; - -import java.util.List; - -import com.softlayer.api.ApiClient; -import com.softlayer.api.ResultLimit; -import com.softlayer.api.service.product.Package; - -/** Get all product packages in paginated form */ -public class Pagination extends Example { - - private static final int PAGE_SIZE = 10; - - @Override - public void run(ApiClient client) throws Exception { - Package.Service service = Package.service(client); - - int offset = 0; - int total; - do { - // Result limits can include a limit and a 0-based offset - service.setResultLimit(new ResultLimit(offset, PAGE_SIZE)); - List packages = service.getAllObjects(); - - // The service contains the total item count that would return if a result limit wasn't present - total = service.getLastResponseTotalItemCount(); - - System.out.format("Retrieved %d-%d of %d items\n", offset + 1, offset + packages.size(), total); - for (Package pkg : packages) { - System.out.println("Package: " + pkg.getName()); - } - offset += packages.size(); - } while (offset < total); - } - - public static void main(String[] args) throws Exception { - new Pagination().start(args); - } -} diff --git a/examples/src/main/java/com/softlayer/api/example/PaginationAsyncCallback.java b/examples/src/main/java/com/softlayer/api/example/PaginationAsyncCallback.java deleted file mode 100644 index 8848d90..0000000 --- a/examples/src/main/java/com/softlayer/api/example/PaginationAsyncCallback.java +++ /dev/null @@ -1,89 +0,0 @@ -package com.softlayer.api.example; - -import java.util.List; -import java.util.NavigableSet; -import java.util.Set; -import java.util.concurrent.ConcurrentSkipListSet; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; - -import com.softlayer.api.ApiClient; -import com.softlayer.api.ResponseHandlerWithHeaders; -import com.softlayer.api.ResultLimit; -import com.softlayer.api.http.HttpClientFactory; -import com.softlayer.api.http.ThreadPooledHttpClientFactory; -import com.softlayer.api.service.product.Package; - -/** Asynchronous version of {@link Pagination} using the callback approach */ -public class PaginationAsyncCallback extends Example { - - private static final int PAGE_SIZE = 10; - - @Override - public void run(ApiClient client) throws Exception { - // Let's give the default HTTP client factory a fixed thread pool of 3 threads - ExecutorService threadPool = Executors.newFixedThreadPool(3); - ((ThreadPooledHttpClientFactory) HttpClientFactory.getDefault()).setThreadPool(threadPool); - - // A thread-safe set is needed to hold the resulting packages ordered by name - 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 - System.out.format("Retrieving first %d items\n", PAGE_SIZE); - PackageHandler first = new PackageHandler(packages, 0); - Package.Service service = Package.service(client); - service.setResultLimit(new ResultLimit(0, PAGE_SIZE)); - service.asAsync().getAllObjects(first).get(); - - // Once the total is obtained, a call for all pages after the first can happen - for (int i = PAGE_SIZE; i < first.total; i += PAGE_SIZE) { - // We create a new service each time because the result limit is specific to the service, not the call - // so it may be overwritten if we reused it - System.out.format("Retrieving %d-%d of %d items\n", i + 1, PAGE_SIZE, first.total); - service = Package.service(client); - service.setResultLimit(new ResultLimit(i, PAGE_SIZE)); - service.asAsync().getAllObjects(new PackageHandler(packages, i)); - } - - // It can take a few seconds to obtain all the information, wait 3 minutes just to be safe - threadPool.shutdown(); - threadPool.awaitTermination(3, TimeUnit.MINUTES); - - System.out.println("Packages:"); - for (Package pkg : packages) { - System.out.println(" " + pkg.getName()); - } - } - - /** Asynchronous handler for handling a subset of packages */ - static class PackageHandler extends ResponseHandlerWithHeaders> { - private final int offset; - private final Set packages; - private Integer total; - - public PackageHandler(Set packages, int offset) { - this.packages = packages; - this.offset = offset; - } - - @Override - public void onError(Exception ex) { - ex.printStackTrace(); - } - - @Override - public void onSuccess(List value) { - packages.addAll(value); - total = getLastResponseTotalItemCount(); - System.out.format("Retrieved %d-%d of %d items\n", offset + 1, offset + value.size(), total); - } - } - - public static void main(String[] args) throws Exception { - new PaginationAsyncCallback().start(args); - } -} diff --git a/examples/src/main/java/com/softlayer/api/example/PaginationAsyncPolling.java b/examples/src/main/java/com/softlayer/api/example/PaginationAsyncPolling.java deleted file mode 100644 index 7187441..0000000 --- a/examples/src/main/java/com/softlayer/api/example/PaginationAsyncPolling.java +++ /dev/null @@ -1,89 +0,0 @@ -package com.softlayer.api.example; - -import java.util.ArrayList; -import java.util.List; -import java.util.NavigableSet; -import java.util.TreeSet; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; - -import com.softlayer.api.ApiClient; -import com.softlayer.api.ResultLimit; -import com.softlayer.api.http.HttpClientFactory; -import com.softlayer.api.http.ThreadPooledHttpClientFactory; -import com.softlayer.api.service.product.Package; - -/** Asynchronous version of {@link Pagination} using the polling approach */ -public class PaginationAsyncPolling extends Example { - - private static final int PAGE_SIZE = 10; - - @Override - public void run(ApiClient client) throws Exception { - // Let's give the default HTTP client factory a fixed thread pool of 3 threads - ExecutorService threadPool = Executors.newFixedThreadPool(3); - ((ThreadPooledHttpClientFactory) HttpClientFactory.getDefault()).setThreadPool(threadPool); - - // Asynchronous responses are held so they can be waited on once all are submitted - 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 - System.out.format("Retrieving first %d items\n", PAGE_SIZE); - Package.ServiceAsync service = Package.service(client).asAsync(); - service.setResultLimit(new ResultLimit(0, PAGE_SIZE)); - PackageResponseWrapper first = new PackageResponseWrapper(0, service.getAllObjects(), service); - responses.add(first); - first.response.get(); - - // Once the total is obtained, a call for all pages after the first can happen - for (int i = PAGE_SIZE; i < first.service.getLastResponseTotalItemCount(); i += PAGE_SIZE) { - // We create a new service each time because the result limit is specific to the service, not the call - // so it may be overwritten if we reused it - System.out.format("Retrieving %d-%d of %d items\n", i + 1, i + PAGE_SIZE, - first.service.getLastResponseTotalItemCount()); - service = Package.service(client).asAsync(); - service.setResultLimit(new ResultLimit(i, PAGE_SIZE)); - responses.add(new PackageResponseWrapper(i, service.getAllObjects(), service)); - } - - // The thread pool needs to be closed so nothing more can be added to it and it terminates after last call - threadPool.shutdown(); - - // A set is needed to hold the resulting packages ordered by name - 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 - for (PackageResponseWrapper response : responses) { - packages.addAll(response.response.get()); - System.out.format("Retrieved %d-%d of %d items\n", response.offset + 1, response.offset + - response.response.get().size(), first.service.getLastResponseTotalItemCount()); - } - - System.out.println("Packages:"); - for (Package pkg : packages) { - System.out.println(" " + pkg.getName()); - } - } - - /** Simple wrapper to hold the response and the offset */ - static class PackageResponseWrapper { - public final Future> response; - public final int offset; - public final Package.ServiceAsync service; - - public PackageResponseWrapper(int offset, Future> response, Package.ServiceAsync service) { - this.offset = offset; - this.response = response; - this.service = service; - } - } - - public static void main(String[] args) throws Exception { - new PaginationAsyncPolling().start(args); - } -} diff --git a/examples/src/main/java/com/softlayer/api/example/QuickTest.java b/examples/src/main/java/com/softlayer/api/example/QuickTest.java deleted file mode 100644 index 24701da..0000000 --- a/examples/src/main/java/com/softlayer/api/example/QuickTest.java +++ /dev/null @@ -1,28 +0,0 @@ -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); - } -} diff --git a/gen/pom.xml b/gen/pom.xml deleted file mode 100644 index 768a3a5..0000000 --- a/gen/pom.xml +++ /dev/null @@ -1,80 +0,0 @@ - - 4.0.0 - - 3.3.9 - - com.softlayer.api - softlayer-api-client-gen - jar - - 0.3.4 - softlayer-api-client-gen - https://sldn.softlayer.com/ - - - The MIT License (MIT) - http://opensource.org/licenses/MIT - repo - - - - UTF-8 - 8 - com.softlayer.api.gen.Main - - - - com.squareup - javawriter - 2.5.0 - - - com.google.code.gson - gson - 2.8.9 - - - junit - junit - 4.13.2 - test - - - - - - org.codehaus.mojo - exec-maven-plugin - 3.0.0 - - - - java - - - - - com.softlayer.api.gen.Main - - - - org.apache.maven.plugins - maven-javadoc-plugin - 3.3.1 - - ${java.version} - - - - org.apache.maven.plugins - maven-compiler-plugin - 3.8.1 - - ${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 deleted file mode 100644 index 2d4ef46..0000000 --- a/gen/src/main/java/com/softlayer/api/gen/ClassWriter.java +++ /dev/null @@ -1,493 +0,0 @@ -package com.softlayer.api.gen; - -import java.io.BufferedWriter; -import java.io.File; -import java.io.FileOutputStream; -import java.io.FileWriter; -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; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import javax.lang.model.element.Modifier; - -import com.squareup.javawriter.JavaWriter; - -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"; - public static final String TYPE_API_SERVICE = "com.softlayer.api.annotation.ApiService"; - public static final String TYPE_API_TYPE = "com.softlayer.api.annotation.ApiType"; - public static final String TYPE_API_TYPES = "com.softlayer.api.annotation.ApiTypes"; - public static final String TYPE_CALLABLE = "java.util.concurrent.Callable"; - public static final String TYPE_FUTURE = "java.util.concurrent.Future"; - public static final String TYPE_MASK = "com.softlayer.api.Mask"; - public static final String TYPE_RESPONSE_HANDLER = "com.softlayer.api.ResponseHandler"; - public static final String TYPE_SERVICE = "com.softlayer.api.Service"; - public static final String TYPE_SERVICE_ASYNC = "com.softlayer.api.ServiceAsync"; - public static final String TYPE_TYPE = "com.softlayer.api.Type"; - - private static final Set PROTECTED = EnumSet.of(Modifier.PROTECTED); - private static final Set PUBLIC = EnumSet.of(Modifier.PUBLIC); - private static final Set PUBLIC_STATIC = EnumSet.of(Modifier.PUBLIC, Modifier.STATIC); - - public static void emitPackageInfo(File baseDir, List classes) throws IOException { - // Do this manually, the Java writer doesn't help us here - StringBuilder types = new StringBuilder(); - for (TypeClass type : classes) { - if (types.length() != 0) { - types.append(",\n"); - } - types.append(" ").append(type.getFullClassName()).append(".class"); - } - Writer writer = new BufferedWriter( - new FileWriter(new File(baseDir, "com/softlayer/api/service/package-info.java"))); - try { - writer.append("@ApiTypes({\n").append(types).append("\n})\npackage "). - append(Generator.BASE_PKG).append(";\nimport ").append(TYPE_API_TYPES).append(";\n"); - } finally { - try { writer.close(); } catch (Exception e) { } - } - } - - 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")), StandardCharsets.UTF_8)); - try { - new ClassWriter(writer, type, meta).emitType(); - } finally { - try { writer.close(); } catch (Exception e) { } - } - } - - public static String getClassName(String typeName) { - // Just the last name after the underscore - int lastUnderscore = typeName.lastIndexOf('_'); - if (lastUnderscore == -1) { - return typeName; - } else { - return typeName.substring(lastUnderscore + 1); - } - } - - public static String getClassName(Meta.Type type) { - return getClassName(type.name); - } - - public final TypeClass type; - private final Meta meta; - - public ClassWriter(Writer out, TypeClass type, Meta meta) { - super(out); - this.type = type; - this.meta = meta; - setIndent(" "); - } - - public ClassWriter emitAnnotationWithAttrs(String annotationType, Object... attributes) throws IOException { - int i = 0; - Map attrMap = new HashMap<>(attributes.length / 2 + 1); - while (i < attributes.length) { - String key = attributes[i++].toString(); - attrMap.put(key, attributes[i++]); - } - emitAnnotation(annotationType, attrMap); - return this; - } - - public ClassWriter emitMask() throws IOException { - - String baseMask = type.baseJavaType != null ? type.baseJavaType + ".Mask" : TYPE_MASK; - beginType("Mask", "class", PUBLIC_STATIC, baseMask).emitEmptyLine(); - - 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.meta.name), - compressType(property.nonArrayJavaType + ".Mask")). - endMethod().emitEmptyLine(); - } else { - beginMethod("Mask", property.name, PUBLIC). - emitStatement("withLocalProperty(%s)", stringLiteral(property.meta.name)). - emitStatement("return this"). - endMethod().emitEmptyLine(); - } - } - - endType().emitEmptyLine(); - return this; - } - - 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)) { - params.put("value", stringLiteral(property.meta.name)); - } - if (property.meta.form == Meta.PropertyForm.LOCAL) { - params.put("canBeNullOrNotSet", true); - } - emitAnnotation("ApiProperty", params); - emitField(property.javaType, property.name, PROTECTED).emitEmptyLine(); - - // Getter - String capitalized = Character.toUpperCase(property.name.charAt(0)) + - property.name.substring(1); - beginMethod(property.javaType, "get" + capitalized, PUBLIC); - // If it's an array, we lazily create it here and do not allow it to be set - if (property.meta.typeArray) { - beginControlFlow("if (" + property.name + " == null)"). - emitStatement(property.name + " = new " + compressType( - property.javaType.replace("List<", "ArrayList<")) + "()"). - endControlFlow().emitStatement("return " + property.name); - endMethod().emitEmptyLine(); - } else { - emitStatement("return " + property.name); - endMethod().emitEmptyLine(); - - // Setter (which may have to set specified boolean) - beginMethod("void", "set" + capitalized, PUBLIC, property.javaType, property.name); - if (property.meta.form == Meta.PropertyForm.LOCAL) { - emitStatement(property.name + "Specified = true"); - } - emitStatement("this." + property.name + " = " + property.name). - endMethod().emitEmptyLine(); - } - - // Make the XSpecified boolean property if needed - if (property.meta.form == Meta.PropertyForm.LOCAL) { - emitField("boolean", property.name + "Specified", PROTECTED).emitEmptyLine(); - beginMethod("boolean", "is" + capitalized + "Specified", PUBLIC). - emitStatement("return " + property.name + "Specified"). - endMethod().emitEmptyLine(); - beginMethod("void", "unset" + capitalized, PUBLIC). - emitStatement(property.name + " = null"). - emitStatement(property.name + "Specified = false"). - endMethod().emitEmptyLine(); - } - - return this; - } - - public ClassWriter emitJavadoc(String javadoc, Object... params) throws IOException { - //Since the base class formats, we have to double-up our percent signs - return (ClassWriter) super.emitJavadoc(javadoc.replace("%", "%%"), params); - } - - public ClassWriter emitService() throws IOException { - String javadoc = type.meta.serviceDoc; - if (javadoc == null) { - javadoc = ""; - } else { - javadoc = javadoc.replace("\n", "
\n") + "\n\n"; - } - javadoc += "@see " + type.meta.name + ""; - emitJavadoc(javadoc); - - emitAnnotation(TYPE_API_SERVICE, stringLiteral(type.meta.name)); - String base; - if (type.baseServiceJavaType != null) { - base = compressType(type.baseServiceJavaType) + ".Service"; - } else { - base = TYPE_SERVICE; - } - beginType("Service", "interface", PUBLIC_STATIC, base).emitEmptyLine(); - - // Covariant return overrides - beginMethod("ServiceAsync", "asAsync", PUBLIC).endMethod(); - beginMethod("Mask", "withNewMask", PUBLIC).endMethod(); - beginMethod("Mask", "withMask", PUBLIC).endMethod(); - beginMethod("void", "setMask", PUBLIC, "Mask", "mask").endMethod().emitEmptyLine(); - - for (TypeClass.Method method : type.methods) { - emitServiceMethod(method, false); - } - // Method for every non-local property too... - for (TypeClass.Property property : type.properties) { - // Only relational properties support getters - if (property.meta.form == Meta.PropertyForm.RELATIONAL) { - emitServiceMethod(property, false); - } - } - endType().emitEmptyLine(); - - // And the async one... - if (type.baseServiceJavaType != null) { - base = compressType(type.baseServiceJavaType) + ".ServiceAsync"; - } else { - base = TYPE_SERVICE_ASYNC; - } - beginType("ServiceAsync", "interface", PUBLIC_STATIC, base).emitEmptyLine(); - - // Covariant return overrides - beginMethod("Mask", "withNewMask", PUBLIC).endMethod(); - beginMethod("Mask", "withMask", PUBLIC).endMethod(); - beginMethod("void", "setMask", PUBLIC, "Mask", "mask").endMethod().emitEmptyLine(); - - for (TypeClass.Method method : type.methods) { - emitServiceMethod(method, true); - } - // Method for every non-local property too... - for (TypeClass.Property property : type.properties) { - // Only relational properties support getters - if (property.meta.form == Meta.PropertyForm.RELATIONAL) { - emitServiceMethod(property, true); - } - } - endType().emitEmptyLine(); - - return this; - } - - public ClassWriter emitServiceMethod(TypeClass.Method method, boolean async) throws IOException { - if (!async) { - // Only non-async get service docs and annotation - String javadoc = method.meta.doc; - if (javadoc == null) { - javadoc = ""; - } else { - javadoc = javadoc.replace("\n", "
\n") + "\n\n"; - } - 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)) { - params.put("value", stringLiteral(method.meta.name)); - } - if (!method.meta.isstatic && !"SoftLayer_Resource_Metadata".equals(type.meta.name)) { - params.put("instanceRequired", true); - } - emitAnnotation(TYPE_API_METHOD, params); - } 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]; - for (int i = 0; i < method.parameters.size(); i++) { - TypeClass.Parameter param = method.parameters.get(i); - parameters[i * 2] = param.javaType; - parameters[i * 2 + 1] = param.meta.name; - } - - String returnType = method.javaType; - if (async) { - returnType = TYPE_FUTURE + '<' + returnType + '>'; - } - beginMethod(returnType, method.name, PUBLIC, parameters).endMethod().emitEmptyLine(); - - // 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"; - beginMethod(TYPE_FUTURE + "", method.name, PUBLIC, parameters).endMethod().emitEmptyLine(); - } - - return this; - } - - public ClassWriter emitServiceMethod(TypeClass.Property property, boolean async) throws IOException { - String capitalized = Character.toUpperCase(property.name.charAt(0)) + - property.name.substring(1); - String name = "get" + capitalized; - if (type.meta.methods.containsKey(name)) { - return this; - } - - if (!async) { - // Only non-async get service docs and annotation - String javadoc = property.meta.doc; - if (javadoc == null) { - javadoc = ""; - } else { - javadoc = javadoc.replace("\n", "
\n") + "\n\n"; - } - 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)) { - emitAnnotation(TYPE_API_METHOD); - } else { - emitAnnotationWithAttrs(TYPE_API_METHOD, "instanceRequired", true); - } - } else { - // Otherwise, just a javadoc link - emitJavadoc("Async version of {@link Service#" + name + "}"); - - if (property.meta.deprecated) { - emitAnnotation("Deprecated"); - } - } - - String returnType = property.javaType; - if (async) { - returnType = TYPE_FUTURE + '<' + returnType + '>'; - } - beginMethod(returnType, name, PUBLIC).endMethod().emitEmptyLine(); - - // Async has an extra callback method - if (async) { - emitJavadoc("Async callback version of {@link Service#" + name + "}"); - beginMethod(TYPE_FUTURE + "", name, PUBLIC, TYPE_RESPONSE_HANDLER + '<' + property.javaType + '>', - "callback").endMethod().emitEmptyLine(); - } - return this; - } - - public ClassWriter emitType() throws IOException { - emitPackage(type.packageName); - - emitTypeImports(); - - emitJavadoc(getTypeJavadoc()); - - if (type.meta.deprecated) { - emitAnnotation("Deprecated"); - } - - // Each type has a type attribute - emitAnnotation("ApiType", stringLiteral(type.meta.name)); - - String baseType = type.baseJavaType == null ? TYPE_TYPE : type.baseJavaType; - beginType(type.className, "class", PUBLIC, baseType).emitEmptyLine(); - - // Write all the API properties - for (TypeClass.Property property : type.properties) { - emitProperty(property); - } - - // Now the service - if (!type.meta.noservice) { - - // 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) { - beginMethod("Service", "asService", PUBLIC, TYPE_API_CLIENT, "client"). - beginControlFlow("if (id != null)"). - emitStatement("return service(client, id)"). - nextControlFlow("else"). - emitStatement("return service(client, globalIdentifier)"). - endControlFlow().endMethod().emitEmptyLine(); - } else { - beginMethod("Service", "asService", PUBLIC, TYPE_API_CLIENT, "client"). - emitStatement("return service(client, id)").endMethod().emitEmptyLine(); - } - } - - beginMethod("Service", "service", PUBLIC_STATIC, TYPE_API_CLIENT, "client"). - emitStatement("return client.createService(Service.class, null)"). - endMethod().emitEmptyLine(); - - 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 (containsGlobalIdentifier) { - beginMethod("Service", "service", PUBLIC_STATIC, TYPE_API_CLIENT, - "client", "String", "globalIdentifier"). - emitStatement("return client.createService(Service.class, globalIdentifier)"). - endMethod().emitEmptyLine(); - } - } - - emitService(); - } - - 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); - - imports.remove("Mask"); - imports.remove(type.className); - imports.put("ApiType", TYPE_API_TYPE); - - // If we have properties or methods... - if (!type.properties.isEmpty()) { - imports.put("ApiProperty", TYPE_API_PROPERTY); - } - if (!type.methods.isEmpty()) { - imports.put("ApiMethod", TYPE_API_METHOD); - imports.put("Future", TYPE_FUTURE); - imports.put("ResponseHandler", TYPE_RESPONSE_HANDLER); - } - - // Remove Service if we have one - if (!type.meta.noservice) { - imports.remove("Service"); - imports.remove("ServiceAsync"); - imports.put("ApiClient", TYPE_API_CLIENT); - } - - emitImports(imports.values()).emitEmptyLine(); - 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 deleted file mode 100644 index 1ba77d5..0000000 --- a/gen/src/main/java/com/softlayer/api/gen/Generator.java +++ /dev/null @@ -1,109 +0,0 @@ -package com.softlayer.api.gen; - -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; - -public class Generator { - - public static final String BASE_PKG = "com.softlayer.api.service"; - - private final File dir; - 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; - this.whitelist = whitelist; - this.blacklist = blacklist; - } - - public void buildClient() throws IOException { - log("Deleting existing service files"); - recursivelyDelete(new File(dir, "com/softlayer/api/service")); - - log("Loading metadata"); - Meta meta = Meta.fromUrl(metadataUrl); - applyRestrictions(meta); - - log("Generating source code"); - 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); - classes.add(typeClass); - } - ClassWriter.emitPackageInfo(dir, classes); - log("Done"); - } - - public void applyRestrictions(Meta meta) { - if (whitelist != null) { - if (!whitelist.types.isEmpty()) { - meta.types.keySet().retainAll(whitelist.types); - } - for (Meta.Type type : meta.types.values()) { - Set properties = whitelist.properties.get(type.name); - if (properties != null) { - type.properties.keySet().retainAll(properties); - } - Set methods = whitelist.methods.get(type.name); - if (methods != null) { - type.methods.keySet().retainAll(methods); - } - } - } - if (blacklist != null) { - meta.types.keySet().removeAll(blacklist.types); - for (Meta.Type type : meta.types.values()) { - Set properties = blacklist.properties.get(type.name); - if (properties != null) { - type.properties.keySet().removeAll(properties); - } - Set methods = blacklist.methods.get(type.name); - if (methods != null) { - type.methods.keySet().removeAll(methods); - } - } - } - } - - protected void log(String contents) { - System.out.println(contents); - } - - 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; - } - - @Override - public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { - Files.delete(dir); - return FileVisitResult.CONTINUE; - } - }); - } -} diff --git a/gen/src/main/java/com/softlayer/api/gen/Main.java b/gen/src/main/java/com/softlayer/api/gen/Main.java deleted file mode 100644 index 3fa541a..0000000 --- a/gen/src/main/java/com/softlayer/api/gen/Main.java +++ /dev/null @@ -1,109 +0,0 @@ -package com.softlayer.api.gen; - -import java.io.BufferedReader; -import java.io.File; -import java.io.FileReader; -import java.io.IOException; -import java.net.URL; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -/** 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 DEFAULT_SOURCE_PATH = "../src/main/java"; - - public static final String USAGE = - "Arguments:\n\n" + - " --help - Show this.\n" + - " --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/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" + - " This is mutually exclusive with --blacklist.\n" + - " --blacklist FILENAME - Similar to, and mututally exclusive with, --whitelist. Anything included\n" + - " here will NOT be generated.\n"; - - public static void main(String[] args) throws Exception { - // Load up args - File dir; - URL url; - Restriction whitelist; - Restriction blacklist; - try { - List argList = Arrays.asList(args); - if (argList.contains("--help")) { - System.out.println(USAGE); - return; - } - String dirString = getArg("--src", argList); - 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)); - blacklist = getRestriction(getArg("--blacklist", argList)); - if (whitelist != null && blacklist != null) { - throw new IllegalArgumentException("Can't have whitelist and blacklist"); - } - } catch (Exception e) { - System.out.println(USAGE); - throw e; - } - - new Generator(dir, url, whitelist, blacklist).buildClient(); - } - - private static String getArg(String argName, List argList) { - int index = argList.indexOf(argName); - if (index == -1) { - return null; - } else if (argList.size() == index + 1) { - throw new IllegalArgumentException("No value found for argument " + argName); - } else { - argList.remove(index); - return argList.remove(index); - } - } - - private static Restriction getRestriction(String filename) throws IOException { - if (filename == null) { - return null; - } - Restriction restriction = new Restriction(); - BufferedReader reader = new BufferedReader(new FileReader(filename)); - try { - String line; - do { - line = reader.readLine(); - if (line != null) { - line = line.trim(); - if (line.contains("::")) { - String pieces[] = line.split("::", 2); - 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<>(); - restriction.methods.put(pieces[0], properties); - } - properties.add(pieces[1]); - } else if (!line.isEmpty()) { - restriction.types.add(line); - } - } - } while (line != null); - return restriction; - } finally { - try { reader.close(); } catch (Exception e) { } - } - } -} diff --git a/gen/src/main/java/com/softlayer/api/gen/Meta.java b/gen/src/main/java/com/softlayer/api/gen/Meta.java deleted file mode 100644 index 838af47..0000000 --- a/gen/src/main/java/com/softlayer/api/gen/Meta.java +++ /dev/null @@ -1,127 +0,0 @@ -package com.softlayer.api.gen; - -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.net.URL; -import java.util.Collections; -import java.util.List; -import java.util.Map; - -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.google.gson.TypeAdapter; -import com.google.gson.annotations.SerializedName; -import com.google.gson.reflect.TypeToken; -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 { - stream = url.openStream(); - Gson gson = new GsonBuilder().registerTypeAdapter(PropertyForm.class, new TypeAdapter() { - @Override - public void write(JsonWriter out, PropertyForm value) throws IOException { - out.value(value.name().toLowerCase()); - } - - @Override - public PropertyForm read(JsonReader in) throws IOException { - return PropertyForm.valueOf(in.nextString().toUpperCase()); - } - }).create(); - Map types = gson.fromJson( - new InputStreamReader(stream), - new TypeToken>(){ }.getType() - ); - return new Meta(types); - } catch (IOException e) { - throw new RuntimeException(e); - } finally { - if (stream != null) { - try { stream.close(); } catch (Exception e) { } - } - } - } - - 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; - public Map properties = Collections.emptyMap(); - public String serviceDoc; - public Map methods = Collections.emptyMap(); - public boolean noservice; - public boolean deprecated; - } - - /** - * Representation of a property in the metadata API. - */ - public static class Property { - public String name; - public String type; - public boolean typeArray; - public PropertyForm form; - public String doc; - public boolean deprecated; - } - - 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; - public boolean typeArray; - public String doc; - @SerializedName("static") - public boolean isstatic; - public boolean noauth; - public boolean limitable; - public boolean filterable; - public boolean maskable; - public List parameters = Collections.emptyList(); - public boolean deprecated; - } - - /** - * A representation of a parameter in the metadata API. - */ - public static class Parameter { - public String name; - public String type; - public boolean typeArray; - public String doc; - public Object defaultValue; - } -} diff --git a/gen/src/main/java/com/softlayer/api/gen/MetaConverter.java b/gen/src/main/java/com/softlayer/api/gen/MetaConverter.java deleted file mode 100644 index f123dae..0000000 --- a/gen/src/main/java/com/softlayer/api/gen/MetaConverter.java +++ /dev/null @@ -1,257 +0,0 @@ -package com.softlayer.api.gen; - -import java.math.BigDecimal; -import java.math.BigInteger; -import java.util.*; - -public class MetaConverter { - - protected static final Map keywordReplacements; - protected static final Set keywords; - protected static final Set invalidClassNames; - - static { - keywordReplacements = new HashMap<>(2); - keywordReplacements.put("package", "pkg"); - keywordReplacements.put("private", "priv"); - keywordReplacements.put("native", "nat"); - keywords = new HashSet<>(Arrays.asList( - "abstract", "continue", "for", "new", "switch", - "assert", "default", "goto", "package", "synchronized", - "boolean", "do", "if", "private", "this", - "break", "double", "implements", "protected", "throw", - "byte", "else", "import", "public", "throws", - "case", "enum", "instanceof", "return", "transient", - "catch", "extends", "int", "short", "try", - "char", "final", "interface", "static", "void", - "class", "finally", "long", "strictfp", "volatile", - "const", "float", "native", "super", "while" - )); - invalidClassNames = new HashSet<>(Arrays.asList( - "Service" - )); - } - - protected final Map imports = new HashMap<>(); - protected final String basePackageName; - protected final Meta meta; - protected final Meta.Type type; - protected final String className; - - public MetaConverter(String basePackageName, Meta meta, Meta.Type type) { - this.basePackageName = basePackageName; - this.meta = meta; - this.type = type; - this.className = getClassName(type.name); - } - - 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 - // for now... - String name = pieces[pieces.length - 1]; - if (invalidClassNames.contains(name)) { - name = pieces[pieces.length - 2] + name; - } - return getValidJavaIdentifier(name); - } - - public String getMethodOrPropertyName(String className, String name) { - if (keywords.contains(name)) { - // Prefixing a uncapitalized type name - name = Character.toLowerCase(className.charAt(0)) + className.substring(1) + - Character.toUpperCase(name.charAt(0)) + name.substring(1); - } - return getValidJavaIdentifier(name); - } - - public String getPackageName(String typeName) { - StringBuilder pkg = new StringBuilder(basePackageName); - if (typeName.startsWith("SoftLayer_")) { - 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)); - } - 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); - String base = null; - Meta.Type baseMeta = null; - String baseService = null; - Meta.Type baseServiceMeta = null; - 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); - baseMeta = meta.types.get(type.base); - // Sometimes, the direct parent is not a service, but the grandparent is - baseServiceMeta = baseMeta; - if (!type.noservice) { - while (baseServiceMeta != null && baseServiceMeta.noservice) { - baseServiceMeta = baseServiceMeta.base == null ? null : meta.types.get(baseServiceMeta.base); - } - } - if (baseServiceMeta != null) { - String baseServiceClassName = getClassName(baseServiceMeta.name); - baseService = getPackageName(baseServiceMeta.name) + '.' + baseServiceClassName; - imports.put(baseServiceClassName, baseService); - } - } - - return new TypeClass(type, imports, packageName, className, base, baseMeta, - baseService, baseServiceMeta, getProperties(), getMethods(baseMeta)); - } - - public List getProperties() { - List properties = new ArrayList<>(type.properties.size()); - for (Meta.Property property : type.properties.values()) { - String javaType = getJavaType(property.type, property.typeArray); - if (javaType != null) { - properties.add(new TypeClass.Property(property, getMethodOrPropertyName(className, property.name), - javaType, getJavaType(property.type, false))); - // If we are a list, we know we need the concrete impl that is used during lazily instantiation - if (property.typeArray) { - imports.put("ArrayList", "java.util.ArrayList"); - } - } - } - return properties; - } - - public List getMethods(Meta.Type baseMeta) { - 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()); - for (Meta.Parameter parameter : method.parameters) { - String paramJavaType = getJavaType(parameter.type, parameter.typeArray); - if (paramJavaType == null) { - allParametersValid = false; - break; - } - parameters.add(new TypeClass.Parameter(parameter, paramJavaType)); - } - if (allParametersValid) { - String name = method.name; - // There are some cases where one of the parent classes contains the same method with - // the same parameters but with different return type. This is usually fine with our regular - // interface that has regular return types which are covariant. However in the async interface - // the return types are generics of future/callable which are invariant. This check makes sure - // that we change the name when this happens. - Meta.Type parent = baseMeta; - while (parent != null && name.equals(method.name)) { - Meta.Method parentMethod = parent.methods.get(method.name); - if (parentMethod != null && parentMethod.parameters.size() == method.parameters.size()) { - // Check all parameter types. Note, parameter types are equal if they are just arrays. This - // is because we use List whose type parameter is invariant too. - boolean parametersEqual = true; - for (int i = 0; i < method.parameters.size(); i++) { - Meta.Parameter methodParameter = method.parameters.get(i); - Meta.Parameter parentParameter = parentMethod.parameters.get(i); - parametersEqual = (methodParameter.typeArray == parentParameter.typeArray && - methodParameter.type.equals(parentMethod.type)) || - (methodParameter.typeArray && parentParameter.typeArray); - if (!parametersEqual) { - break; - } - } - if (parametersEqual) { - // Now we know we have invariance; we have to change the Java method name. We just - // append "for" + the class name. Wed don't do this check recursively, because it - // is rare to resolve this ambiguity and the need hasn't arisen. - name += "For" + className; - } - } - parent = parent.base == null ? null : meta.types.get(parent.base); - } - methods.add(new TypeClass.Method(method, - getMethodOrPropertyName(className, name), javaType, parameters)); - } - } - } - return methods; - } - - public String getJavaType(String typeName, boolean array) { - - String javaType; - - // Attempt primitives first - 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"); - return "java.util.List<" + javaType + ">"; - } - return javaType; - } -} diff --git a/gen/src/main/java/com/softlayer/api/gen/Restriction.java b/gen/src/main/java/com/softlayer/api/gen/Restriction.java deleted file mode 100644 index 6fecd22..0000000 --- a/gen/src/main/java/com/softlayer/api/gen/Restriction.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.softlayer.api.gen; - -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; - -public class Restriction { - /** If empty, there is no restriction on any type */ - public final Set types = new HashSet<>(); - - /** If empty, there is no restriction on any property */ - public final Map> properties = new HashMap<>(); - - /** If empty, there is no restriction on any method */ - public final Map> methods = new HashMap<>(); -} diff --git a/gen/src/main/java/com/softlayer/api/gen/TypeClass.java b/gen/src/main/java/com/softlayer/api/gen/TypeClass.java deleted file mode 100644 index 73f4c08..0000000 --- a/gen/src/main/java/com/softlayer/api/gen/TypeClass.java +++ /dev/null @@ -1,75 +0,0 @@ -package com.softlayer.api.gen; - -import java.util.List; -import java.util.Map; - -public class TypeClass { - - public final Meta.Type meta; - public final Map imports; - public final String packageName; - public final String className; - public final String baseJavaType; - public final Meta.Type baseMeta; - public final String baseServiceJavaType; - public final Meta.Type baseServiceMeta; - public final List properties; - public final List methods; - - public TypeClass(Meta.Type meta, Map imports, String packageName, String className, - String baseJavaType, Meta.Type baseMeta, String baseServiceJavaType, Meta.Type baseServiceMeta, - List properties, List methods) { - this.meta = meta; - this.imports = imports; - this.packageName = packageName; - this.className = className; - this.baseJavaType = baseJavaType; - this.baseMeta = baseMeta; - this.properties = properties; - this.methods = methods; - this.baseServiceJavaType = baseServiceJavaType; - this.baseServiceMeta = baseServiceMeta; - } - - public String getFullClassName() { - return packageName + '.' + className; - } - - public static class Property { - public final Meta.Property meta; - public final String name; - public final String javaType; - public final String nonArrayJavaType; - - public Property(Meta.Property meta, String name, String javaType, String nonArrayJavaType) { - this.meta = meta; - this.name = name; - this.javaType = javaType; - this.nonArrayJavaType = nonArrayJavaType; - } - } - - public static class Method { - public final Meta.Method meta; - public final String name; - public final String javaType; - public final List parameters; - - public Method(Meta.Method meta, String name, String javaType, List parameters) { - this.meta = meta; - this.name = name; - this.javaType = javaType; - this.parameters = parameters; - } - } - - public static class Parameter { - public final Meta.Parameter meta; - public final String javaType; - - public Parameter(Meta.Parameter meta, String javaType) { - this.meta = meta; - this.javaType = javaType; - } - } -} diff --git a/index.html b/index.html new file mode 100644 index 0000000..2cdb393 --- /dev/null +++ b/index.html @@ -0,0 +1,163 @@ +

SoftLayer API Client for Java

+

Java CI Maven Central Javadocs

+

Introduction

+

This library provides a JVM client for the SoftLayer API. 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 API instead.

+

By default the HTTP client is the Java HttpUrlConnection and the JSON marshalling is done by 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 via a command:

+
mvn -q compile exec:java -Dexec.args="EXAMPLE_NAME API_USER API_KEY"
+

Where EXAMPLE_NAME is the unqualified class name of an example in the com.softlayer.api.example package (e.g. ListServers), API_USER is your API username, and API_KEY is your API key. NOTE: Some examples order virtual servers and may charge your account.

+

Using

+

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

+ +

Gradle

+
implementation 'com.softlayer.api:softlayer-api-client:0.3.4'
+

Kotlin

+ +

Creating a Client

+

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 the literal string apikey, more information about that can be found on the SLDN Authenticating to the SoftLayer 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.

+ +

Access Token

+

Information on how to get a temporary api token can be found on the SLDN Authenticating to the SoftLayer API article.

+ +

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/.

+

If you are using the classic infrastructure private network, you can communicate with the API over that network by using the service URL instead:

+ +

Making API Calls

+

Once a client is created, it can be used to access services. There are hundreds of services to control your SoftLayer account. A simple one is the Account service. Here’s a call to get all of the hardware on the account:

+ +

Some calls on a service require an ID to know what object to act on. This can be obtained by passing in the numeric ID into the service method or by calling asService on an object that has an ID. Here’s an example of soft-rebooting a virtual server with “reboot-test” as the hostname:

+ +

Some calls require sending in data. This is done by just instantiating the object and populating the data. Here’s an example of ordering a new virtual server: (Note running this can charge your account)

+ +

Using Object Masks

+

Object masks are a great way to reduce the number of API calls to traverse the data graph of an object. For example, here’s how by just asking for an account, you can retrieve all your VLANs, their datacenter, and the firewall rules that are on them:

+ +

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. 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.

+

Asynchronous Invocation

+

All services also provide an asynchronous interface. This can be obtained from a service by calling asAsync. Here’s an example of getting all top level billing items and listing when they were created:

+ +

Using the default HTTP client, this runs the call in a separate thread and calls the handler parameter upon completion. The get at the end basically makes it wait forever so the application doesn’t exit out from under us. With the default HTTP client the asynchronous invocations are handled by a simple thread pool that defaults to a cached thread pool that creates daemon threads. It can be changed:

+ +

Unlike using the default thread pool, you will be responsible for shutting down this overridden thread pool as necessary. Other HTTP client implementations may handle asynchrony differently and not use thread pools at all.

+

In addition to the callback-style above, can also get the response as a Future. Here’s an example of waiting 10 seconds to get all top level billing items:

+ +

Thread Safety

+

No class in this library is guaranteed to be thread-safe. Callers are expected to keep this in mind when developing with the library and to never use the same ApiClient (or any other object created with it) concurrently across threads.

+

Pagination

+

Sometimes there is a need to get the responses from the SoftLayer API in a paginated way instead of all at once. This can be done by utilizing result limits. A result limit can be passed in with the number of results requested and the offset to start reading from. Requesting smaller amounts of data will increase the performance of the call. Here is an example of obtaining the first 10 tickets and outputting the total:

+ +

The services are not guaranteed to be thread-safe on their own, so it is difficult to obtain the total with getLastResponseTotalItemCount when using the service asynchronously. To assist with this when using the callback 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 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 work properly and will regenerate the client. To specifically regenerate the Java service-related files, run:

+
mvn generate-sources
+

Customization

+

Logging

+

Logging the requests and response to stdout can be enabled by invoking withLoggingEnabled on the RestApiClient. In order to log elsewhere, simply make your own implementation of RestApiClient with logRequest and logResponse overridden.

+

HTTP Client

+

The default HTTP client that is used is the JVM’s native HttpUrlConnection. In order to create your own, alternative implementation you must implement com.softlayer.api.http.HttpClientFactory. Once implemented, this can be explicitly set on the RestApiClient by calling setHttpClientFactory. 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 META-INF/com.softlayer.api.http.HttpClientFactory.

+

JSON Marshalling

+

The default JSON marshaller that is used is Gson. In order to create your own, 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 META-INF/com.softlayer.api.json.JsonMarshallerFactory.

+ +

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 deleted file mode 100644 index 23bd176..0000000 --- a/pom.xml +++ /dev/null @@ -1,205 +0,0 @@ - - 4.0.0 - - 3.3.9 - - com.softlayer.api - softlayer-api-client - jar - - 0.3.4 - SoftLayer API Client for Java - API client for accessing the SoftLayer API - https://sldn.softlayer.com - - - The MIT License (MIT) - https://opensource.org/licenses/MIT - repo - - - - GitHub Issues - https://github.com/softlayer/softlayer-java/issues - - - Travis CI - https://travis-ci.org/softlayer/softlayer-java - - - - camporter - Cameron Porter - IBM - https://ibm.com/cloud - - owner - developer - - -6 - - - - 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.4 - - - UTF-8 - 8 - - - - ossrh - https://oss.sonatype.org/content/repositories/snapshots - - - ossrh - https://oss.sonatype.org/service/local/staging/deploy/maven2/ - - - - - com.google.code.gson - gson - 2.8.9 - - - junit - junit - 4.13.2 - test - - - org.mockito - mockito-core - 4.2.0 - test - - - - - - org.apache.maven.plugins - maven-compiler-plugin - 3.8.1 - - ${java.version} - ${java.version} - - - - org.apache.maven.plugins - maven-source-plugin - 3.1.0 - - - attach-sources - - jar-no-fork - - - - - - org.apache.maven.plugins - maven-javadoc-plugin - 3.3.1 - - - - none - ${java.version} - --allow-script-in-comments - - - - attach-javadocs - - jar - - - - - - org.apache.maven.plugins - maven-gpg-plugin - 3.0.1 - - - sign-artifacts - verify - - sign - - - - - - - maven-invoker-plugin - 3.2.2 - - - generate-services - generate-sources - - run - - - gen/pom.xml - - compile - exec:java - - true - - - - - - org.jacoco - jacoco-maven-plugin - 0.8.7 - - - **/*com/softlayer/api/service/**/* - - - - - - prepare-agent - - - - report - prepare-package - - report - - - - - - - - - - org.codehaus.mojo - versions-maven-plugin - 2.8.1 - - - - dependency-updates-report - plugin-updates-report - property-updates-report - - - - - - - diff --git a/src/main/java/com/softlayer/api/ApiClient.java b/src/main/java/com/softlayer/api/ApiClient.java deleted file mode 100644 index a637995..0000000 --- a/src/main/java/com/softlayer/api/ApiClient.java +++ /dev/null @@ -1,42 +0,0 @@ -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 { - - /** - * Set the username and API key credentials. This is required for most service methods. - * - * @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. - * E.g. {@link com.softlayer.api.service.Account#service(ApiClient)}. - */ - S createService(Class serviceClass, String id); -} diff --git a/src/main/java/com/softlayer/api/ApiException.java b/src/main/java/com/softlayer/api/ApiException.java deleted file mode 100644 index 275805a..0000000 --- a/src/main/java/com/softlayer/api/ApiException.java +++ /dev/null @@ -1,67 +0,0 @@ -package com.softlayer.api; - -/** Base exception for all errors that occur inside the API */ -@SuppressWarnings("serial") -public class ApiException extends RuntimeException { - - public static ApiException fromError(String message, String code, int status) { - switch (status) { - case BadRequest.STATUS: - return new BadRequest(message, code); - case Unauthorized.STATUS: - return new Unauthorized(message, code); - case NotFound.STATUS: - return new NotFound(message, code); - case Internal.STATUS: - return new Internal(message, code); - default: - return new ApiException(message, code, status); - } - } - - public final String code; - public final int status; - - public ApiException(String message, String code, int status) { - super(message); - this.code = code; - this.status = status; - } - - @Override - public String getLocalizedMessage() { - return getMessage() + "(code: " + code + ", status: " + status + ')'; - } - - public static class BadRequest extends ApiException { - public static final int STATUS = 400; - - public BadRequest(String message, String code) { - super(message, code, STATUS); - } - } - - public static class Unauthorized extends ApiException { - public static final int STATUS = 401; - - public Unauthorized(String message, String code) { - super(message, code, STATUS); - } - } - - public static class NotFound extends ApiException { - public static final int STATUS = 404; - - public NotFound(String message, String code) { - super(message, code, STATUS); - } - } - - public static class Internal extends ApiException { - public static final int STATUS = 500; - - public Internal(String message, String code) { - super(message, code, STATUS); - } - } -} diff --git a/src/main/java/com/softlayer/api/Mask.java b/src/main/java/com/softlayer/api/Mask.java deleted file mode 100644 index 9fae4d8..0000000 --- a/src/main/java/com/softlayer/api/Mask.java +++ /dev/null @@ -1,80 +0,0 @@ -package com.softlayer.api; - -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; - -/** 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<>(); - - /** Clear out all previously masked objects and local properties */ - public void clear() { - localProperties.clear(); - subMasks.clear(); - } - - private int getChildCount() { - return localProperties.size() + subMasks.size(); - } - - protected void withLocalProperty(String localProperty) { - localProperties.add(localProperty); - } - - @SuppressWarnings("unchecked") - protected T withSubMask(String name, Class maskClass) { - T subMask = (T) subMasks.get(name); - if (subMask == null) { - try { - subMask = maskClass.getDeclaredConstructor().newInstance(); - } catch (Exception e) { - throw new RuntimeException(); - } - subMasks.put(name, subMask); - } - return subMask; - } - - protected String getMask() { - return "mask[" + toString() + "]"; - } - - @Override - public String toString() { - return toString(new StringBuilder()).toString(); - } - - /** Append this mask's string representation to the given builder and return it */ - public StringBuilder toString(StringBuilder builder) { - boolean first = true; - for (String localProperty : localProperties) { - if (first) { - first = false; - } else { - builder.append(','); - } - builder.append(localProperty); - } - - for (Map.Entry entry : subMasks.entrySet()) { - if (first) { - first = false; - } else { - builder.append(','); - } - builder.append(entry.getKey()); - - // No count means add nothing, single is a dot, and multiple means brackets - int count = entry.getValue().getChildCount(); - if (count == 1) { - entry.getValue().toString(builder.append('.')); - } else if (count > 1) { - entry.getValue().toString(builder.append('[')).append(']'); - } - } - return builder; - } -} diff --git a/src/main/java/com/softlayer/api/Maskable.java b/src/main/java/com/softlayer/api/Maskable.java deleted file mode 100644 index 4a3093e..0000000 --- a/src/main/java/com/softlayer/api/Maskable.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.softlayer.api; - -/** Interface implemented by services who accept masks for some calls */ -public interface Maskable { - - /** Overwrite the existing mask on this service with a new one and return it */ - Mask withNewMask(); - - /** Use the existing mask on this service or create it if not present */ - Mask withMask(); - - /** Set the mask to the given object */ - void setMask(Mask mask); - - /** Set the mask to a string, formatted according to http://sldn.softlayer.com/article/Object-Masks */ - void setMask(String mask); - - /** Removes the mask from the service */ - void clearMask(); -} diff --git a/src/main/java/com/softlayer/api/ResponseHandler.java b/src/main/java/com/softlayer/api/ResponseHandler.java deleted file mode 100644 index a807a35..0000000 --- a/src/main/java/com/softlayer/api/ResponseHandler.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.softlayer.api; - -/** Interface for handling async method callbacks */ -public interface ResponseHandler { - - /** Called when the method errored. This is NOT called when onSuccess errors. */ - void onError(Exception ex); - - /** Called when the method succeeds. */ - void onSuccess(T value); -} diff --git a/src/main/java/com/softlayer/api/ResponseHandlerWithHeaders.java b/src/main/java/com/softlayer/api/ResponseHandlerWithHeaders.java deleted file mode 100644 index a4785ed..0000000 --- a/src/main/java/com/softlayer/api/ResponseHandlerWithHeaders.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.softlayer.api; - -/** - * A version of {@link ResponseHandler} that, if used, will have {@link #setLastResponseTotalItemCount(Integer)} - * invoked after the response to set the total items count from the header. - */ -public abstract class ResponseHandlerWithHeaders implements ResponseHandler { - private Integer lastResponseTotalItemCount; - - public Integer getLastResponseTotalItemCount() { - return lastResponseTotalItemCount; - } - - public void setLastResponseTotalItemCount(Integer lastResponseTotalItemCount) { - this.lastResponseTotalItemCount = lastResponseTotalItemCount; - } -} diff --git a/src/main/java/com/softlayer/api/RestApiClient.java b/src/main/java/com/softlayer/api/RestApiClient.java deleted file mode 100644 index 43450c8..0000000 --- a/src/main/java/com/softlayer/api/RestApiClient.java +++ /dev/null @@ -1,534 +0,0 @@ -package com.softlayer.api; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.UnsupportedEncodingException; -import java.lang.reflect.InvocationHandler; -import java.lang.reflect.Method; -import java.lang.reflect.Proxy; -import java.net.URLEncoder; -import java.util.*; -import java.util.concurrent.Callable; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; - -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; -import com.softlayer.api.json.JsonMarshallerFactory; -import com.softlayer.api.service.Entity; - -/** - * Implementation of API client for http://sldn.softlayer.com/article/REST - */ -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(); - - static final Map> HEADERS; - - 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; - private JsonMarshallerFactory jsonMarshallerFactory; - private boolean loggingEnabled = false; - private HttpCredentials credentials; - - /** - * Create a Rest client that uses the publically available API. - */ - 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("/")) { - baseUrl += '/'; - } - this.baseUrl = baseUrl; - } - - public String getBaseUrl() { - return baseUrl; - } - - public HttpClientFactory getHttpClientFactory() { - if (httpClientFactory == null) { - httpClientFactory = HttpClientFactory.getDefault(); - } - return httpClientFactory; - } - - public void setHttpClientFactory(HttpClientFactory httpClientFactory) { - this.httpClientFactory = httpClientFactory; - } - - public RestApiClient withHttpClientFactory(HttpClientFactory httpClientFactory) { - setHttpClientFactory(httpClientFactory); - return this; - } - - public boolean isLoggingEnabled() { - return loggingEnabled; - } - - public void setLoggingEnabled(boolean loggingEnabled) { - this.loggingEnabled = loggingEnabled; - } - - @Override - public RestApiClient withLoggingEnabled() { - this.loggingEnabled = true; - return this; - } - - public JsonMarshallerFactory getJsonMarshallerFactory() { - if (jsonMarshallerFactory == null) { - jsonMarshallerFactory = JsonMarshallerFactory.getDefault(); - } - return jsonMarshallerFactory; - } - - public void setJsonMarshallerFactory(JsonMarshallerFactory jsonMarshallerFactory) { - this.jsonMarshallerFactory = jsonMarshallerFactory; - } - - public RestApiClient withJsonMarshallerFactory(JsonMarshallerFactory jsonMarshallerFactory) { - setJsonMarshallerFactory(jsonMarshallerFactory); - return this; - } - - @Override - public RestApiClient withCredentials(String username, String apiKey) { - credentials = new HttpBasicAuthCredentials(username, apiKey); - return this; - } - - @Override - public RestApiClient withBearerToken(String token) { - credentials = new HttpBearerCredentials(token); - return this; - } - - @Override - public HttpCredentials getCredentials() { - return credentials; - } - - protected void writeParameterHttpBody(Object[] params, OutputStream out) { - getJsonMarshallerFactory().getJsonMarshaller().toJson( - Collections.singletonMap("parameters", params), out); - } - - protected String getHttpMethodFromMethodName(String methodName) { - switch (methodName) { - case "deleteObject": - return "DELETE"; - case "createObject": - case "createObjects": - return "POST"; - case "editObject": - case "editObjects": - return "PUT"; - default: - 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); - // ID present? add it - if (id != null) { - url.append('/').append(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 (!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); - } - if (maskString != null && !maskString.isEmpty()) { - url.append(resultLimit == null ? '?' : '&'); - try { - url.append("objectMask=").append(URLEncoder.encode(maskString, "UTF-8")); - } catch (UnsupportedEncodingException e) { - throw new RuntimeException(e); - } - } - return url.toString(); - } - - protected void logRequest(String httpMethod, String url, Object[] params) { - // Build JSON - String body = "no body"; - if (params != null && params.length > 0) { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - getJsonMarshallerFactory().getJsonMarshaller().toJson( - Collections.singletonMap("parameters", params), out); - try { - body = "body: " + out.toString("UTF-8"); - } catch (UnsupportedEncodingException e) { - throw new RuntimeException(e); - } - } - System.out.format("Running %s on %s with %s\n", httpMethod, url, body); - } - - protected void logResponse(String url, int statusCode, String body) { - System.out.format("Got %d on %s with body: %s\n", statusCode, url, body); - } - - @Override - @SuppressWarnings("unchecked") - public S createService(Class serviceClass, String id) { - return (S) Proxy.newProxyInstance(getClass().getClassLoader(), - new Class[] { serviceClass }, new ServiceProxy<>(serviceClass, id)); - } - - class ServiceProxy implements InvocationHandler { - - final Class serviceClass; - final String id; - Mask mask; - String maskString; - ResultLimit resultLimit; - Integer lastResponseTotalItemCount; - - public ServiceProxy(Class serviceClass, String id) { - this.serviceClass = serviceClass; - this.id = id; - } - - public void logRequestAndWriteBody(HttpClient client, String httpMethod, String url, Object[] args) { - if (loggingEnabled) { - logRequest(httpMethod, url, args); - } - // If there are parameters write em - if (args != null && args.length > 0) { - OutputStream outStream = client.getBodyStream(); - try { - writeParameterHttpBody(args, outStream); - } finally { - try { outStream.close(); } catch (Exception e) { } - } - } - } - - @SuppressWarnings("resource") - public Object logAndHandleResponse(HttpResponse response, String url, - java.lang.reflect.Type returnType) throws Exception { - InputStream stream = response.getInputStream(); - if (loggingEnabled && stream != null) { - InputStream newStream; - Scanner scanner = null; - try { - scanner = new Scanner(stream, "UTF-8").useDelimiter("\\A"); - String body = scanner.hasNext() ? scanner.next() : ""; - logResponse(url, response.getStatusCode(), body); - newStream = new ByteArrayInputStream(body.getBytes("UTF-8")); - } finally { - try { - if (scanner != null) { - scanner.close(); - } - } catch (Exception e) { } - try { - stream.close(); - } catch (Exception e) { } - } - stream = newStream; - } - try { - // If it's not a 200, we have a problem - if (response.getStatusCode() < 200 || response.getStatusCode() >= 300) { - if (stream == null) { - throw new ApiException("Unknown error", null, response.getStatusCode()); - } - // Extract error and throw - Map map = getJsonMarshallerFactory().getJsonMarshaller(). - fromJson(Map.class, stream); - throw ApiException.fromError(map.get("error"), map.get("code"), response.getStatusCode()); - } - // Update total items - lastResponseTotalItemCount = null; - Map> headers = response.getHeaders(); - if (headers != null) { - List totalItems = headers.get("SoftLayer-Total-Items"); - if (totalItems != null && !totalItems.isEmpty()) { - lastResponseTotalItemCount = Integer.valueOf(totalItems.get(0)); - } - } - // Just return the serialized response - return getJsonMarshallerFactory().getJsonMarshaller().fromJson(returnType, stream); - } finally { - try { - stream.close(); - } catch (Exception e) { } - } - } - - 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) { - 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()); - final HttpClient client = getHttpClientFactory().getHttpClient(credentials, httpMethod, url, HEADERS); - - // Invoke with response - HttpResponse response = client.invokeSync(() -> { - logRequestAndWriteBody(client, httpMethod, url, args); - return null; - }); - - return logAndHandleResponse(response, url, method.getGenericReturnType()); - } - - @SuppressWarnings("unchecked") - public Object invokeServiceAsync(final Method asyncMethod, final Object[] args) throws Throwable { - // If the last parameter is a callback, it is a different type of invocation - Class[] parameterTypes = asyncMethod.getParameterTypes(); - boolean lastParamCallback = parameterTypes.length > 0 && - ResponseHandler.class.isAssignableFrom(parameterTypes[parameterTypes.length - 1]); - final Object[] trimmedArgs; - if (lastParamCallback) { - parameterTypes = Arrays.copyOfRange(parameterTypes, 0, parameterTypes.length - 1); - trimmedArgs = Arrays.copyOfRange(args, 0, args.length - 1); - } else { - trimmedArgs = args; - } - - final Method method = serviceClass.getMethod(asyncMethod.getName(), parameterTypes); - 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()); - final HttpClient client = getHttpClientFactory().getHttpClient(credentials, httpMethod, url, HEADERS); - - Callable setupBody = () -> { - logRequestAndWriteBody(client, httpMethod, url, trimmedArgs); - return null; - }; - - if (lastParamCallback) { - final ResponseHandler handler = (ResponseHandler) args[args.length - 1]; - return client.invokeAsync(setupBody, new ResponseHandler() { - @Override - public void onSuccess(HttpResponse value) { - Object result; - try { - result = logAndHandleResponse(value, url, method.getGenericReturnType()); - } catch (Exception e) { - onError(e); - return; - } - if (handler != null) { - if (handler instanceof ResponseHandlerWithHeaders) { - ((ResponseHandlerWithHeaders) handler).setLastResponseTotalItemCount( - lastResponseTotalItemCount); - } - handler.onSuccess(result); - } - } - - @Override - public void onError(Exception ex) { - if (handler != null) { - handler.onError(ex); - } - } - }); - } else { - final Future future = client.invokeAsync(setupBody); - return new Future() { - private boolean responseAttempted; - private Object response; - - @Override - public boolean cancel(boolean mayInterruptIfRunning) { - return future.cancel(mayInterruptIfRunning); - } - - @Override - public synchronized Object get() throws InterruptedException, ExecutionException { - if (!responseAttempted) { - responseAttempted = true; - try { - response = logAndHandleResponse(future.get(), url, method.getGenericReturnType()); - } catch (Exception e) { - throw new ExecutionException(e); - } - } - return response; - } - - @Override - public synchronized Object get(long timeout, TimeUnit unit) - throws InterruptedException, ExecutionException, TimeoutException { - if (!responseAttempted) { - responseAttempted = true; - try { - response = logAndHandleResponse(future.get(timeout, unit), - url, method.getGenericReturnType()); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - return response; - } - - @Override - public boolean isCancelled() { - return future.isCancelled(); - } - - @Override - public boolean isDone() { - return future.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; - asyncProxy.maskString = maskString; - asyncProxy.resultLimit = resultLimit; - return Proxy.newProxyInstance(getClass().getClassLoader(), - new Class[] { method.getReturnType() }, asyncProxy); - } else if ("withNewMask".equals(method.getName()) && noParams) { - mask = (Mask) method.getReturnType().getDeclaredConstructor().newInstance(); - maskString = null; - return mask; - } else if ("withMask".equals(method.getName()) && noParams) { - if (mask == null) { - mask = (Mask) method.getReturnType().getDeclaredConstructor().newInstance(); - maskString = null; - } - return mask; - } else if ("setMask".equals(method.getName()) && args != null - && args.length == 1 && args[0] instanceof String) { - mask = null; - maskString = args[0].toString(); - return null; - } else if ("setMask".equals(method.getName()) && args != null - && args.length == 1 && args[0] instanceof Mask) { - mask = (Mask) args[0]; - maskString = null; - return null; - } else if ("setMask".equals(method.getName()) && args != null - && args.length == 1 && args[0] == null) { - throw new IllegalArgumentException("Cannot set null mask. Use clearMask to clear"); - } else if ("clearMask".equals(method.getName())) { - mask = null; - maskString = null; - return null; - } else if ("setResultLimit".equals(method.getName()) && - method.getDeclaringClass() == ResultLimitable.class) { - resultLimit = (ResultLimit) args[0]; - return null; - } else if ("getResultLimit".equals(method.getName()) && - method.getDeclaringClass() == ResultLimitable.class) { - return resultLimit; - } else if ("getLastResponseTotalItemCount".equals(method.getName()) && - method.getDeclaringClass() == ResultLimitable.class) { - return lastResponseTotalItemCount; - } else if (Service.class.isAssignableFrom(method.getDeclaringClass())) { - return invokeService(method, args); - } else if (ServiceAsync.class.isAssignableFrom(method.getDeclaringClass())) { - return invokeServiceAsync(method, args); - } else if (method.getDeclaringClass() == Object.class) { - return method.invoke(this, args); - } else { - // Should not be possible - throw new RuntimeException("Unrecognized method: " + method); - } - } - - @Override - public boolean equals(Object obj) { - return Proxy.isProxyClass(obj.getClass()) && obj.hashCode() == hashCode(); - } - - @Override - public String toString() { - if (id == null) { - return "Service: " + serviceClass.getAnnotation(ApiService.class).value(); - } - return "Service: " + serviceClass.getAnnotation(ApiService.class).value() + " with ID " + id; - } - } -} diff --git a/src/main/java/com/softlayer/api/ResultLimit.java b/src/main/java/com/softlayer/api/ResultLimit.java deleted file mode 100644 index f02b6e4..0000000 --- a/src/main/java/com/softlayer/api/ResultLimit.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.softlayer.api; - -/** Object holding pagination information for an API request */ -public class ResultLimit { - - public final int offset; - public final int limit; - - public ResultLimit(int limit) { - this(0, limit); - } - - public ResultLimit(int offset, int limit) { - this.offset = offset; - this.limit = limit; - } -} diff --git a/src/main/java/com/softlayer/api/ResultLimitable.java b/src/main/java/com/softlayer/api/ResultLimitable.java deleted file mode 100644 index 6dd8719..0000000 --- a/src/main/java/com/softlayer/api/ResultLimitable.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.softlayer.api; - -/** Interface implemented by services to support pagination */ -public interface ResultLimitable { - - ResultLimit getResultLimit(); - - ResultLimit setResultLimit(ResultLimit limit); - - /** The non-paginated total item count. This can be overwritten if a service is reused */ - Integer getLastResponseTotalItemCount(); -} diff --git a/src/main/java/com/softlayer/api/Service.java b/src/main/java/com/softlayer/api/Service.java deleted file mode 100644 index 57b30a6..0000000 --- a/src/main/java/com/softlayer/api/Service.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.softlayer.api; - -/** Interface extended by individual service interfaces on types */ -public interface Service extends Maskable, ResultLimitable { - - /** Get an async version of this service */ - ServiceAsync asAsync(); -} diff --git a/src/main/java/com/softlayer/api/ServiceAsync.java b/src/main/java/com/softlayer/api/ServiceAsync.java deleted file mode 100644 index f9c9dce..0000000 --- a/src/main/java/com/softlayer/api/ServiceAsync.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.softlayer.api; - -/** Asynchronous service interface extended by individual async service interfaces */ -public interface ServiceAsync extends Maskable, ResultLimitable { -} diff --git a/src/main/java/com/softlayer/api/Type.java b/src/main/java/com/softlayer/api/Type.java deleted file mode 100644 index 158ec49..0000000 --- a/src/main/java/com/softlayer/api/Type.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.softlayer.api; - -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; - -public abstract class Type { - - protected Map unknownProperties; - - /** - * Get all unknown properties (or an empty map). The result of mutating the resulting - * map is undefined and may result in an error. - */ - public Map getUnknownProperties() { - if (unknownProperties == null) { - return Collections.emptyMap(); - } - return unknownProperties; - } - - /** - * Set the unknown properties for this type. The values are copied to an immutable map. - * Note, these values are NOT serialized into the type. - */ - public void setUnknownProperties(Map unknownProperties) { - this.unknownProperties = Collections.unmodifiableMap( - new HashMap<>(unknownProperties)); - } -} diff --git a/src/main/java/com/softlayer/api/annotation/ApiMethod.java b/src/main/java/com/softlayer/api/annotation/ApiMethod.java deleted file mode 100644 index c8c24a8..0000000 --- a/src/main/java/com/softlayer/api/annotation/ApiMethod.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.softlayer.api.annotation; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -@Documented -@Target(ElementType.METHOD) -@Retention(RetentionPolicy.RUNTIME) -public @interface ApiMethod { - - /** If provided, this is the name of the method. Otherwise it is the name of the method it is on. */ - String value() default ""; - - /** If provided and true, this method can only be invoked on a service with an identifier */ - boolean instanceRequired() default false; -} diff --git a/src/main/java/com/softlayer/api/annotation/ApiProperty.java b/src/main/java/com/softlayer/api/annotation/ApiProperty.java deleted file mode 100644 index 9ce5767..0000000 --- a/src/main/java/com/softlayer/api/annotation/ApiProperty.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.softlayer.api.annotation; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -@Documented -@Target(ElementType.FIELD) -@Retention(RetentionPolicy.RUNTIME) -public @interface ApiProperty { - - /** If set, this is the property name. Otherwise, this defaults to the field name this annotation is on. */ - String value() default ""; - - /** - * If set and true, the value for this property can be null or unset. All properties with these ambiguous - * states must have a sibling field that is the same name suffixed with "Specified" that is a boolean. - * If the boolean is true, the property is considered set to null and if it is false the property is not - * considered set and won't be serialized. - */ - boolean canBeNullOrNotSet() default false; -} diff --git a/src/main/java/com/softlayer/api/annotation/ApiService.java b/src/main/java/com/softlayer/api/annotation/ApiService.java deleted file mode 100644 index f836ea4..0000000 --- a/src/main/java/com/softlayer/api/annotation/ApiService.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.softlayer.api.annotation; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -@Documented -@Target(ElementType.TYPE) -@Retention(RetentionPolicy.RUNTIME) -public @interface ApiService { - - /** The API service name as visible at http://sldn.softlayer.com/reference/services/ */ - String value(); -} diff --git a/src/main/java/com/softlayer/api/annotation/ApiType.java b/src/main/java/com/softlayer/api/annotation/ApiType.java deleted file mode 100644 index 6e835bc..0000000 --- a/src/main/java/com/softlayer/api/annotation/ApiType.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.softlayer.api.annotation; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -@Documented -@Target(ElementType.TYPE) -@Retention(RetentionPolicy.RUNTIME) -public @interface ApiType { - - /** The API type name as visible at http://sldn.softlayer.com/reference/datatypes/ */ - String value(); -} diff --git a/src/main/java/com/softlayer/api/annotation/ApiTypes.java b/src/main/java/com/softlayer/api/annotation/ApiTypes.java deleted file mode 100644 index 4563bb8..0000000 --- a/src/main/java/com/softlayer/api/annotation/ApiTypes.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.softlayer.api.annotation; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import com.softlayer.api.service.Entity; - -@Documented -@Target(ElementType.PACKAGE) -@Retention(RetentionPolicy.RUNTIME) -public @interface ApiTypes { - - /** Collection of every type that extends {@link Entity} no matter how deep. */ - Class[] value(); -} diff --git a/src/main/java/com/softlayer/api/http/BuiltInHttpClientFactory.java b/src/main/java/com/softlayer/api/http/BuiltInHttpClientFactory.java deleted file mode 100644 index 2d96b32..0000000 --- a/src/main/java/com/softlayer/api/http/BuiltInHttpClientFactory.java +++ /dev/null @@ -1,210 +0,0 @@ -package com.softlayer.api.http; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.UnsupportedEncodingException; -import java.net.HttpURLConnection; -import java.net.URL; -import java.util.List; -import java.util.Map; -import java.util.concurrent.Callable; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; -import java.util.concurrent.ThreadFactory; -import java.util.concurrent.locks.ReadWriteLock; -import java.util.concurrent.locks.ReentrantReadWriteLock; - -import com.softlayer.api.ResponseHandler; - -/** - * Default implementation of {@link HttpClientFactory} that only supports simple {@link HttpURLConnection}. - */ -class BuiltInHttpClientFactory extends ThreadPooledHttpClientFactory { - - // Volatile is not enough here, we have to have more control over setting and what not - ExecutorService threadPool; - boolean threadPoolUserDefined; - final ReadWriteLock threadPoolLock = new ReentrantReadWriteLock(); - - @Override - public BuiltInHttpClient getHttpClient(HttpCredentials credentials, String method, - String fullUrl, Map> headers) { - return new BuiltInHttpClient(credentials, method, fullUrl, headers); - } - - public ExecutorService getThreadPool() { - // We support lazy loading in this method and we guarantee it's thread safe, but we do not - // synchronize on it to prevent lock down on the entire class during lots of contention - // especially since the lazy-loading is expected to be rare. - threadPoolLock.readLock().lock(); - try { - if (threadPool != null) { - return threadPool; - } - } finally { - threadPoolLock.readLock().unlock(); - } - threadPoolLock.writeLock().lock(); - try { - if (threadPool == null) { - // Here, we want to use a cached thread pool by default, but we need a custom thread - // factory to make the threads daemon threads. This default can be overridden by users, - // but in general we do not want the API client to hold a process open by default. - threadPool = Executors.newCachedThreadPool(new ThreadFactory() { - final ThreadFactory defaultFactory = Executors.defaultThreadFactory(); - - @Override - public Thread newThread(Runnable r) { - Thread thread = defaultFactory.newThread(r); - thread.setDaemon(true); - return thread; - } - }); - threadPoolUserDefined = false; - } - return threadPool; - } finally { - threadPoolLock.writeLock().unlock(); - } - } - - @Override - public void setThreadPool(ExecutorService threadPool) { - threadPoolLock.writeLock().lock(); - try { - // Shutdown existing one if a new one is being given and the existing - // one is the default. Otherwise, if there was an existing one and it - // was supplied by the user, it's his responsibility to shut it down. - if (this.threadPool != null && !threadPoolUserDefined) { - this.threadPool.shutdownNow(); - } - this.threadPool = threadPool; - threadPoolUserDefined = threadPool != null; - } finally { - threadPoolLock.writeLock().unlock(); - } - } - - class BuiltInHttpClient implements HttpClient, HttpResponse { - - final HttpCredentials credentials; - final String method; - final String fullUrl; - final Map> headers; - HttpURLConnection connection; - - public BuiltInHttpClient( - HttpCredentials credentials, - String method, - String fullUrl, - Map> headers - ) { - this.credentials = credentials; - this.method = method; - this.fullUrl = fullUrl; - this.headers = headers; - } - - @Override - public OutputStream getBodyStream() { - try { - connection.setDoOutput(true); - return connection.getOutputStream(); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - void openConnection() { - try { - connection = (HttpURLConnection) new URL(fullUrl).openConnection(); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - @Override - public HttpResponse invokeSync(Callable setupBody) { - // We let HTTP URL connection do its invocation when it wants. The built-in HTTP connection - // usually starts a stream when the request method is set or when the output or response code - // is requested. It switches from send to receive when the output or response code is requested. - // Its resources are closed when the send stream (if used) and receive stream (if used) are - // closed and internally the JVM is allowed to pool connections to common hosts which makes this - // fairly fast and safe. - openConnection(); - if (credentials != null) { - connection.addRequestProperty( - "Authorization", - credentials.getHeader() - ); - } - for (Map.Entry> headerEntry : headers.entrySet()) { - for (String headerValue : headerEntry.getValue()) { - connection.addRequestProperty(headerEntry.getKey(), headerValue); - } - } - try { - connection.setRequestMethod(method); - setupBody.call(); - } catch (Exception e) { - throw new RuntimeException(e); - } - return this; - } - - @Override - public Future invokeAsync(final Callable setupBody) { - return getThreadPool().submit(() -> invokeSync(setupBody)); - } - - @Override - public Future invokeAsync(final Callable setupBody, final ResponseHandler callback) { - return getThreadPool().submit(() -> { - HttpResponse response; - try { - response = invokeSync(setupBody); - } catch (Exception e) { - callback.onError(e); - return null; - } - callback.onSuccess(response); - return null; - }); - } - - @Override - public void close() throws IOException { - // Nothing to do, callers are expected to close streams they use - } - - @Override - public int getStatusCode() { - try { - return connection.getResponseCode(); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - @Override - public Map> getHeaders() { - return connection.getHeaderFields(); - } - - @Override - public InputStream getInputStream() { - try { - // Asking for the input stream on non-success will fail - if (connection.getResponseCode() >= 200 && connection.getResponseCode() < 300) { - return connection.getInputStream(); - } else { - return connection.getErrorStream(); - } - } catch (IOException e) { - throw new RuntimeException(e); - } - } - } -} diff --git a/src/main/java/com/softlayer/api/http/HttpBasicAuthCredentials.java b/src/main/java/com/softlayer/api/http/HttpBasicAuthCredentials.java deleted file mode 100644 index 1e10ab4..0000000 --- a/src/main/java/com/softlayer/api/http/HttpBasicAuthCredentials.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.softlayer.api.http; - -import java.util.Base64; - -/** HTTP basic authorization support for username and API key */ -public class HttpBasicAuthCredentials implements HttpCredentials { - - public final String username; - public final String apiKey; - - 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 - */ - public String getHeader() { - String authPair = username + ':' + apiKey; - return "Basic " + Base64.getEncoder().encodeToString(authPair.getBytes()); - } -} diff --git a/src/main/java/com/softlayer/api/http/HttpBearerCredentials.java b/src/main/java/com/softlayer/api/http/HttpBearerCredentials.java deleted file mode 100644 index 951f4f2..0000000 --- a/src/main/java/com/softlayer/api/http/HttpBearerCredentials.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.softlayer.api.http; - -/** HTTP Bearer authorization support for IBM IAM Tokens. - * - * @see IAM Tokens - * @see Authenticating SoftLayer API - */ -public class HttpBearerCredentials implements HttpCredentials { - - protected 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/HttpClient.java b/src/main/java/com/softlayer/api/http/HttpClient.java deleted file mode 100644 index f3a2496..0000000 --- a/src/main/java/com/softlayer/api/http/HttpClient.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.softlayer.api.http; - -import java.io.Closeable; -import java.io.OutputStream; -import java.util.concurrent.Callable; -import java.util.concurrent.Future; - -import com.softlayer.api.ResponseHandler; - -/** - * This class is not thread-safe even when using invoke async. This class will only live for the - * duration of one HTTP request. - */ -public interface HttpClient extends Closeable { - - /** Stream to write body contents to (if at all). When called, callers are expected to close it. */ - OutputStream getBodyStream(); - - /** Make synchronous HTTP invocation. Throws if unable to connect. Errors from the API are returned normally. */ - HttpResponse invokeSync(Callable setupBody); - - /** Make asynchronous HTTP invocation. All errors (inability to connect or API errors) are in the future. */ - Future invokeAsync(Callable setupBody); - - /** Callback-form of {@link #invokeAsync(Callable)} */ - Future invokeAsync(Callable setupBody, ResponseHandler callback); -} diff --git a/src/main/java/com/softlayer/api/http/HttpClientFactory.java b/src/main/java/com/softlayer/api/http/HttpClientFactory.java deleted file mode 100644 index 09f69d8..0000000 --- a/src/main/java/com/softlayer/api/http/HttpClientFactory.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.softlayer.api.http; - -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.ServiceLoader; - -/** - * Base class for an HTTP client factory. By default the {@link BuiltInHttpClientFactory} is used. This - * can be overridden by a custom client factory using the {@link ServiceLoader} pattern. It is also - * cached (though not necessarily guaranteed to be a singleton or instantiated only once) upon first access. - */ -public abstract class HttpClientFactory { - - static volatile HttpClientFactory defaultFactory = null; - - public static HttpClientFactory getDefault() { - return getDefault(true); - } - - static HttpClientFactory getDefault(boolean cache) { - // We don't mind the race condition that can occur by possibly creating multiple factories. We make - // no guarantees that there is only one factory ever created even when cache is true - HttpClientFactory result = cache ? defaultFactory : null; - if (result == null) { - Iterator iterator = ServiceLoader.load(HttpClientFactory.class).iterator(); - if (!iterator.hasNext()) { - // Default to built-in version - result = new BuiltInHttpClientFactory(); - } else { - result = iterator.next(); - if (iterator.hasNext()) { - throw new RuntimeException("Ambiguous HTTP client factories: " + result.getClass() + - ", " + iterator.next().getClass() + " and possibly more"); - } - } - if (cache) { - defaultFactory = result; - } - } - return result; - } - - /** - * Get the HTTP client for the given request information. The resulting client is only used once. - */ - public abstract HttpClient getHttpClient(HttpCredentials credentials, String method, - String fullUrl, Map> headers); -} diff --git a/src/main/java/com/softlayer/api/http/HttpCredentials.java b/src/main/java/com/softlayer/api/http/HttpCredentials.java deleted file mode 100644 index a751a46..0000000 --- a/src/main/java/com/softlayer/api/http/HttpCredentials.java +++ /dev/null @@ -1,6 +0,0 @@ -package com.softlayer.api.http; - -/** Base interface for all accepted HTTP credentials */ -public interface HttpCredentials { - String getHeader(); -} diff --git a/src/main/java/com/softlayer/api/http/HttpResponse.java b/src/main/java/com/softlayer/api/http/HttpResponse.java deleted file mode 100644 index d444c46..0000000 --- a/src/main/java/com/softlayer/api/http/HttpResponse.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.softlayer.api.http; - -import java.io.InputStream; -import java.util.List; -import java.util.Map; - -/** Interface representing an HTTP response from the HTTP client */ -public interface HttpResponse { - - int getStatusCode(); - - Map> getHeaders(); - - /** When this is used by the caller, he is expected to close it */ - InputStream getInputStream(); -} diff --git a/src/main/java/com/softlayer/api/http/ThreadPooledHttpClientFactory.java b/src/main/java/com/softlayer/api/http/ThreadPooledHttpClientFactory.java deleted file mode 100644 index 6d9099f..0000000 --- a/src/main/java/com/softlayer/api/http/ThreadPooledHttpClientFactory.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.softlayer.api.http; - -import java.util.concurrent.ExecutorService; - -/** Class implemented by HTTP client factories that use a Java thread pool */ -public abstract class ThreadPooledHttpClientFactory extends HttpClientFactory { - - /** - * By default the thread pool is a cached thread pool (using daemon threads) that is shutdown immediately - * when it is overwritten by this method or the factory is finalized. Callers who supply a thread pool are - * expected to handle its lifecycle. Null can be given here to revert to the default. - * - * @param threadPool - */ - public abstract void setThreadPool(ExecutorService threadPool); -} diff --git a/src/main/java/com/softlayer/api/json/GsonJsonMarshallerFactory.java b/src/main/java/com/softlayer/api/json/GsonJsonMarshallerFactory.java deleted file mode 100644 index e47d558..0000000 --- a/src/main/java/com/softlayer/api/json/GsonJsonMarshallerFactory.java +++ /dev/null @@ -1,407 +0,0 @@ -package com.softlayer.api.json; - -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.OutputStream; -import java.io.OutputStreamWriter; -import java.io.Writer; -import java.lang.reflect.Field; -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; -import java.math.BigDecimal; -import java.math.BigInteger; -import java.text.DateFormat; -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.*; -import java.util.function.Supplier; - -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.google.gson.TypeAdapter; -import com.google.gson.TypeAdapterFactory; -import com.google.gson.reflect.TypeToken; -import com.google.gson.stream.JsonReader; -import com.google.gson.stream.JsonToken; -import com.google.gson.stream.JsonWriter; -import com.softlayer.api.annotation.ApiProperty; -import com.softlayer.api.annotation.ApiType; -import com.softlayer.api.annotation.ApiTypes; -import com.softlayer.api.service.Entity; - -class GsonJsonMarshallerFactory extends JsonMarshallerFactory implements JsonMarshaller { - - protected final static Gson gson; - final static Map> typeClasses; - - static { - gson = new GsonBuilder(). - disableHtmlEscaping(). - disableInnerClassSerialization(). - // Three types need special attention: - // Entity (all non-scalars basically), GregorianCalendar, and BigIntegers - registerTypeAdapterFactory(new EntityTypeAdapterFactory()). - registerTypeAdapter(GregorianCalendar.class, new GregorianCalendarTypeAdapter()). - registerTypeAdapter(BigInteger.class, new BigIntegerTypeAdapter()). - registerTypeAdapter(byte[].class, new ByteArrayTypeAdapter()). - // Sometimes, when a result limit is set to 1 value, REST sends it back as - // a single object instead of an array - registerTypeAdapterFactory(new ListOrSingleObjectTypeFactory()). - serializeNulls(). - create(); - - ApiTypes types = Entity.class.getPackage().getAnnotation(ApiTypes.class); - typeClasses = new HashMap<>(types.value().length); - for (Class clazz : types.value()) { - typeClasses.put(clazz.getAnnotation(ApiType.class).value(), clazz); - } - } - - @Override - public JsonMarshaller getJsonMarshaller() { - // We can just reuse the common marshaller here to remain performant - return this; - } - - @Override - public void toJson(Object object, OutputStream out) { - Writer writer = new OutputStreamWriter(out); - gson.toJson(object, object.getClass(), writer); - try { - writer.flush(); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - @Override - public T fromJson(Type type, InputStream in) { - return gson.fromJson(new InputStreamReader(in), type); - } - - static class EntityTypeAdapterFactory implements TypeAdapterFactory { - - @Override - @SuppressWarnings("unchecked") - public TypeAdapter create(Gson gson, TypeToken type) { - Class typeClass = type.getRawType(); - if (!Entity.class.isAssignableFrom(typeClass)) { - return null; - } - // Obtain all ApiProperty fields and make them accessible... - Map fields = new HashMap<>(); - loadFields(typeClass, fields); - return (TypeAdapter) new EntityTypeAdapter((Class) typeClass, fields); - } - - protected void loadFields(Class clazz, Map fields) { - for (Field field : clazz.getDeclaredFields()) { - ApiProperty property = field.getAnnotation(ApiProperty.class); - if (property != null) { - String name = property.value().isEmpty() ? field.getName() : property.value(); - if (!fields.containsKey(name)) { - field.setAccessible(true); - Field specifiedField = null; - if (property.canBeNullOrNotSet()) { - try { - specifiedField = clazz.getDeclaredField(field.getName() + "Specified"); - } catch (NoSuchFieldException e) { - throw new RuntimeException( - "Cannot find specified field for " + name + " on " + clazz, e); - } - specifiedField.setAccessible(true); - } - fields.put(name, new EntityJsonField(field, specifiedField)); - } - } - } - - if (clazz.getSuperclass() != Object.class) { - loadFields(clazz.getSuperclass(), fields); - } - } - } - - static class EntityJsonField { - public final Field field; - public final Field specifiedField; - - public EntityJsonField(Field field, Field specifiedField) { - this.field = field; - this.specifiedField = specifiedField; - } - } - - static class EntityTypeAdapter extends TypeAdapter { - - final Class typeClass; - final String typeName; - final Map fields; - - public EntityTypeAdapter(Class typeClass, Map fields) { - this.typeClass = typeClass; - this.typeName = typeClass.getAnnotation(ApiType.class).value(); - this.fields = fields; - } - - @Override - public void write(JsonWriter out, Entity value) throws IOException { - if (value == null) { - out.nullValue(); - return; - } - out.beginObject(); - // Every type will include the "complexType" field - out.name("complexType").value(typeName); - for (Map.Entry fieldEntry : fields.entrySet()) { - EntityJsonField field = fieldEntry.getValue(); - try { - Object fieldValue = field.field.get(value); - if (fieldValue != null || - (field.specifiedField != null && field.specifiedField.getBoolean(value))) { - out.name(fieldEntry.getKey()); - gson.toJson(fieldValue, field.field.getGenericType(), out); - } - } catch (IllegalAccessException e) { - throw new RuntimeException(e); - } - } - out.endObject(); - } - - @Override - public Entity read(JsonReader in) throws IOException { - // We are allowed to assume that the first property is complexType always. This allows us to maintain - // a streaming reader and is very important. - if (in.peek() == JsonToken.NULL) { - in.nextNull(); - return null; - } - in.beginObject(); - if (!"complexType".equals(in.nextName())) { - throw new RuntimeException("Expected 'complexType' as first property"); - } - String apiTypeName = in.nextString(); - // If the API type is unrecognized by us (i.e. it's a new type), we just use the type - // 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 || !typeClass.isAssignableFrom(clazz)) { - result = readForThisType(in); - } else { - result = ((EntityTypeAdapter) gson.getAdapter(clazz)).readForThisType(in); - } - in.endObject(); - return result; - } - - 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.getDeclaredConstructor().newInstance(); - } catch (ReflectiveOperationException e) { - throw new RuntimeException(e); - } - Map unknownProperties = new HashMap<>(); - while (in.hasNext()) { - String propertyName = in.nextName(); - EntityJsonField field = fields.get(propertyName); - // No field means we just add the object to the unknown set - if (field == null) { - unknownProperties.put(propertyName, gson.fromJson(in, Object.class)); - } else { - try { - field.field.set(entity, gson.fromJson(in, field.field.getGenericType())); - if (field.specifiedField != null) { - field.specifiedField.setBoolean(entity, true); - } - } catch (IllegalAccessException e) { - throw new RuntimeException(e); - } - } - } - if (!unknownProperties.isEmpty()) { - entity.setUnknownProperties(unknownProperties); - } - return entity; - } - } - - static class GregorianCalendarTypeAdapter extends TypeAdapter { - - // Although this is ISO-8601, Java 6 does not allow a colon in the timestamp. All API - // dates look like this: 1984-02-25T20:15:25-06:00. Since we can guarantee that, we - // 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 = 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 = ThreadLocal.withInitial( - () -> new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ") - ); - - @Override - public void write(JsonWriter out, GregorianCalendar value) throws IOException { - if (value == null) { - out.nullValue(); - } else { - // Always use second-level format here - String date = secondFormat.get().format(value.getTime()); - // Add the colon - out.value(date.substring(0, date.length() - 2) + ':' + date.substring(date.length() - 2)); - } - } - - @Override - public GregorianCalendar read(JsonReader in) throws IOException { - if (in.peek() == JsonToken.NULL) { - in.nextNull(); - return null; - } - String date = in.nextString(); - // Remove the colon - date = date.substring(0, date.length() - 3) + date.substring(date.length() - 2); - // Use decimal presence to determine format and trim to ms precision - DateFormat format; - int decimalIndex = date.indexOf('.'); - if (decimalIndex != -1) { - date = trimToMillisecondPrecision(date, decimalIndex); - format = subSecondFormat.get(); - } else { - format = secondFormat.get(); - } - - GregorianCalendar calendar = new GregorianCalendar(); - try { - calendar.setTime(format.parse(date)); - } catch (ParseException e) { - throw new RuntimeException(e); - } - return calendar; - } - - private String trimToMillisecondPrecision(String date, int decimalIndex) { - // If there is a decimal we have to only keep the first three numeric characters - // after it (and pad with 0's if fewer than three) - StringBuilder newDate = new StringBuilder(date); - int offset = 1; - do { - if (!Character.isDigit(newDate.charAt(decimalIndex + offset))) { - switch (offset) { - case 1: - newDate.insert(decimalIndex + offset, "000"); - break; - case 2: - newDate.insert(decimalIndex + offset, "00"); - break; - case 3: - newDate.insert(decimalIndex + offset, "0"); - break; - } - break; - } else if (offset > 3) { - newDate.deleteCharAt(decimalIndex + offset); - } else { - offset++; - } - } while (true); - return newDate.toString(); - } - } - - static class BigIntegerTypeAdapter extends TypeAdapter { - - @Override - public void write(JsonWriter out, BigInteger value) throws IOException { - // Just write out the toString - out.value(value); - } - - @Override - public BigInteger read(JsonReader in) throws IOException { - // Do the BigDecimal parsing and just convert. The regular Gson BigInteger parser doesn't support - // exponents like we need to. Basically, the BigDecimal string constructor is better than the - // BigInteger one. - BigDecimal value = gson.fromJson(in, BigDecimal.class); - return value == null ? null : value.toBigInteger(); - } - } - - static class ByteArrayTypeAdapter extends TypeAdapter { - - @Override - public void write(JsonWriter out, byte[] value) throws IOException { - if (value == null) { - out.nullValue(); - } else { - out.value(Base64.getEncoder().encodeToString(value)); - } - } - - @Override - public byte[] read(JsonReader in) throws IOException { - if (in.peek() == JsonToken.NULL) { - in.nextNull(); - return null; - } - return Base64.getDecoder().decode(in.nextString()); - } - } - - static class ListOrSingleObjectTypeFactory implements TypeAdapterFactory { - - @Override - @SuppressWarnings({ "rawtypes", "unchecked" }) - public TypeAdapter create(Gson gson, TypeToken type) { - if (!List.class.isAssignableFrom(type.getRawType()) || !(type.getType() instanceof ParameterizedType)) { - return null; - } - Type[] typeArgs = ((ParameterizedType) type.getType()).getActualTypeArguments(); - if (typeArgs.length != 1 || !(typeArgs[0] instanceof Class)) { - return null; - } - return new ListOrSingleObjectTypeAdapter(gson.getDelegateAdapter(this, type), - gson.getAdapter((Class) typeArgs[0])); - } - } - - static class ListOrSingleObjectTypeAdapter extends TypeAdapter> { - - private final TypeAdapter> listDelegate; - private final TypeAdapter instanceDelegate; - - public ListOrSingleObjectTypeAdapter(TypeAdapter> listDelegate, TypeAdapter instanceDelegate) { - this.listDelegate = listDelegate; - this.instanceDelegate = instanceDelegate; - } - - @Override - public void write(JsonWriter out, List value) throws IOException { - // Writing always delegates to list - listDelegate.write(out, value); - } - - @Override - 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); - result.add(instanceDelegate.read(in)); - return result; - } - return listDelegate.read(in); - } - } -} diff --git a/src/main/java/com/softlayer/api/json/JsonMarshaller.java b/src/main/java/com/softlayer/api/json/JsonMarshaller.java deleted file mode 100644 index 0ee3848..0000000 --- a/src/main/java/com/softlayer/api/json/JsonMarshaller.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.softlayer.api.json; - -import java.io.InputStream; -import java.io.OutputStream; -import java.lang.reflect.Type; - -/** - * Interface that must be implemented by all JSON marshallers. This instance is not reused unless - * {@link JsonMarshallerFactory#getJsonMarshaller()} returns the same instance multiple times. - */ -public interface JsonMarshaller { - - /** Convert the given object to JSON on the stream. The output stream is closed by this marshaller */ - void toJson(Object object, OutputStream out); - - /** Convert the JSON stream to the given type. The input stream is closed by this marshaller */ - T fromJson(Type type, InputStream in); -} diff --git a/src/main/java/com/softlayer/api/json/JsonMarshallerFactory.java b/src/main/java/com/softlayer/api/json/JsonMarshallerFactory.java deleted file mode 100644 index d6b54e2..0000000 --- a/src/main/java/com/softlayer/api/json/JsonMarshallerFactory.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.softlayer.api.json; - -import java.util.Iterator; -import java.util.ServiceLoader; - -/** - * Base class for an JSON marshaller factory. By default the {@link GsonJsonMarshallerFactory} is used. - * This can be overridden by a custom marshaller factory using the {@link ServiceLoader} pattern. It is - * also cached (though not necessarily guaranteed to be a singleton or instantiated only once) upon first - * access. - */ -public abstract class JsonMarshallerFactory { - - static volatile JsonMarshallerFactory defaultFactory = null; - - public static JsonMarshallerFactory getDefault() { - return getDefault(true); - } - - static JsonMarshallerFactory getDefault(boolean cache) { - // We don't mind the race condition that can occur by possibly creating multiple factories. We make - // no guarantees that there is only one factory ever created even when cache is true - JsonMarshallerFactory result = cache ? defaultFactory : null; - if (result == null) { - Iterator iterator = ServiceLoader.load(JsonMarshallerFactory.class).iterator(); - if (!iterator.hasNext()) { - // Default to Gson (which may not be present, but we just let NoClassDefFoundError throw) - result = new GsonJsonMarshallerFactory(); - } else { - result = iterator.next(); - if (iterator.hasNext()) { - throw new RuntimeException("Ambiguous JSON marshaller factories: " + result.getClass() + - ", " + iterator.next().getClass() + " and possibly more"); - } - } - if (cache) { - defaultFactory = result; - } - } - return result; - } - - /** - * Get a JSON marshaller for use for either marshalling or unmarshalling. The resulting marshaller is - * only used once, but this method may be called from multiple threads. - */ - public abstract JsonMarshaller getJsonMarshaller(); -} diff --git a/src/test/java/com/softlayer/api/MaskTest.java b/src/test/java/com/softlayer/api/MaskTest.java deleted file mode 100644 index 928b0be..0000000 --- a/src/test/java/com/softlayer/api/MaskTest.java +++ /dev/null @@ -1,173 +0,0 @@ -package com.softlayer.api; - -import static org.junit.Assert.*; - -import java.net.URLEncoder; -import java.util.Collections; - -import org.junit.Ignore; -import org.junit.Test; - -import com.softlayer.api.http.FakeHttpClientFactory; -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() { - 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()); - } - - @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() { - 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(); - - 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() { - 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, 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() { - 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().toString()); - } -} - diff --git a/src/test/java/com/softlayer/api/RestApiClientTest.java b/src/test/java/com/softlayer/api/RestApiClientTest.java deleted file mode 100644 index e44b7e5..0000000 --- a/src/test/java/com/softlayer/api/RestApiClientTest.java +++ /dev/null @@ -1,411 +0,0 @@ -package com.softlayer.api; - -import static org.junit.Assert.*; - -import java.io.ByteArrayOutputStream; -import java.io.PrintStream; -import java.lang.reflect.Proxy; -import java.util.Collections; -import java.util.GregorianCalendar; -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 RestApiClientTest { - - static { - GsonJsonMarshallerFactoryTest.addTestEntityToGson(); - } - - @Test - public void testConstructorAppendsUrlSlash() { - assertEquals("http://example.com/", new RestApiClient("http://example.com").getBaseUrl()); - } - - @Test - public void testGetHttpMethodFromMethodName() { - RestApiClient client = new RestApiClient(); - assertEquals("DELETE", client.getHttpMethodFromMethodName("deleteObject")); - assertEquals("POST", client.getHttpMethodFromMethodName("createObject")); - assertEquals("POST", client.getHttpMethodFromMethodName("createObjects")); - assertEquals("PUT", client.getHttpMethodFromMethodName("editObject")); - assertEquals("PUT", client.getHttpMethodFromMethodName("editObjects")); - assertEquals("GET", client.getHttpMethodFromMethodName("blahblahblah")); - } - - @Test - public void testGetFullUrl() { - RestApiClient client = new RestApiClient("http://example.com/"); - assertEquals("http://example.com/SomeService/someMethod.json", - client.getFullUrl("SomeService", "someMethod", null, null, null)); - assertEquals("http://example.com/SomeService/1234/someMethod.json", - client.getFullUrl("SomeService", "someMethod", "1234", null, null)); - assertEquals("http://example.com/SomeService/1234/someMethod.json?resultLimit=5,6", - client.getFullUrl("SomeService", "someMethod", "1234", new ResultLimit(5, 6), null)); - assertEquals("http://example.com/SomeService/1234/someMethod.json?resultLimit=5,6&objectMask=someMask%26%26", - client.getFullUrl("SomeService", "someMethod", "1234", new ResultLimit(5, 6), "someMask&&")); - assertEquals("http://example.com/SomeService/1234/someMethod.json?objectMask=someMask%26%26", - client.getFullUrl("SomeService", "someMethod", "1234", null, "someMask&&")); - assertEquals("http://example.com/SomeService/Something.json", - client.getFullUrl("SomeService", "getSomething", null, null, null)); - assertEquals("http://example.com/SomeService.json", - client.getFullUrl("SomeService", "getObject", null, null, null)); - assertEquals("http://example.com/SomeService.json", - 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/createObjects.json", - client.getFullUrl("SomeService", "createObjects", null, null, null)); - assertEquals("http://example.com/SomeService.json", - client.getFullUrl("SomeService", "editObject", null, null, null)); - assertEquals("http://example.com/SomeService.json", - client.getFullUrl("SomeService", "editObjects", null, null, null)); - } - - private String withOutputCaptured(Callable closure) throws Exception { - PrintStream originalOut = System.out; - try { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - System.setOut(new PrintStream(out)); - closure.call(); - return out.toString("UTF-8"); - } finally { - System.setOut(originalOut); - } - } - - @Test - public void testLogRequest() 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 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 RestApiClient().logResponse("URL", 123, "some body"); - return null; - }) - ); - } - - @Test(expected = IllegalStateException.class) - public void testFailedIfCallingNonStaticWithoutId() { - FakeHttpClientFactory http = new FakeHttpClientFactory(123, - Collections.emptyMap(), "some response"); - RestApiClient client = new RestApiClient("http://example.com/"); - client.setHttpClientFactory(http); - TestEntity.service(client).doSomethingNonStatic(new GregorianCalendar()); - } - - @Test(expected = IllegalStateException.class) - public void testFailedIfCallingNonStaticAsyncWithoutId() { - FakeHttpClientFactory http = new FakeHttpClientFactory(123, - Collections.emptyMap(), "some response"); - RestApiClient client = new RestApiClient("http://example.com/"); - client.setHttpClientFactory(http); - TestEntity.service(client).asAsync().doSomethingNonStatic(new GregorianCalendar()); - } - - @Test - public void testSuccess() 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"); - assertEquals("some response", TestEntity.service(client).doSomethingStatic(123L, entity)); - assertEquals("user", ((HttpBasicAuthCredentials) http.credentials).username); - assertEquals("key", ((HttpBasicAuthCredentials) http.credentials).apiKey); - assertEquals("GET", http.method); - assertEquals("http://example.com/SoftLayer_TestEntity/doSomethingStatic.json", http.fullUrl); - assertEquals("{\"parameters\":[123,{\"complexType\":\"SoftLayer_TestEntity\",\"bar\":\"blah\"}]}", - http.outStream.toString("UTF-8")); - assertEquals(RestApiClient.HEADERS, http.headers); - assertTrue(http.invokeSyncCalled); - } - - @Test - public void testBadRequestFailure() throws Exception { - FakeHttpClientFactory http = new FakeHttpClientFactory(ApiException.BadRequest.STATUS, - Collections.emptyMap(), - "{\"error\": \"some error\", \"code\": \"some code\"}"); - RestApiClient client = new RestApiClient("http://example.com/") - .withCredentials("user", "key"); - client.setHttpClientFactory(http); - TestEntity entity = new TestEntity(); - entity.setFoo("blah"); - try { - TestEntity.service(client).doSomethingStatic(123L, entity); - fail(); - } catch (ApiException.BadRequest e) { - assertEquals("some error", e.getMessage()); - assertEquals("some code", e.code); - } - } - - @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"); - 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); - assertEquals("GET", http.method); - assertEquals("http://example.com/SoftLayer_TestEntity/doSomethingStatic.json", http.fullUrl); - assertEquals("{\"parameters\":[123,{\"complexType\":\"SoftLayer_TestEntity\",\"bar\":\"blah\"}]}", - http.outStream.toString("UTF-8")); - assertEquals(RestApiClient.HEADERS, http.headers); - assertTrue(http.invokeAsyncFutureCalled); - } - - @Test - public void testAsyncFutureFailure() throws Exception { - FakeHttpClientFactory http = new FakeHttpClientFactory(ApiException.BadRequest.STATUS, - Collections.emptyMap(), - "{\"error\": \"some error\", \"code\": \"some code\"}"); - RestApiClient client = new RestApiClient("http://example.com/") - .withCredentials("user", "key"); - client.setHttpClientFactory(http); - - TestEntity entity = new TestEntity(); - entity.setFoo("blah"); - try { - TestEntity.service(client).asAsync().doSomethingStatic(123L, entity).get(); - fail(); - } catch (ExecutionException e) { - assertEquals("some error", ((ApiException.BadRequest) e.getCause()).getMessage()); - assertEquals("some code", ((ApiException.BadRequest) e.getCause()).code); - } - } - - @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"); - client.setHttpClientFactory(http); - - TestEntity entity = new TestEntity(); - entity.setFoo("blah"); - final AtomicBoolean successCalled = new AtomicBoolean(); - TestEntity.service(client).asAsync().doSomethingStatic(123L, entity, new ResponseHandler() { - @Override - public void onError(Exception ex) { - fail(); - } - - @Override - public void onSuccess(String value) { - assertEquals("some response", value); - successCalled.set(true); - } - }).get(); - - assertEquals("user", ((HttpBasicAuthCredentials) http.credentials).username); - assertEquals("key", ((HttpBasicAuthCredentials) http.credentials).apiKey); - assertEquals("GET", http.method); - assertEquals("http://example.com/SoftLayer_TestEntity/doSomethingStatic.json", http.fullUrl); - assertEquals("{\"parameters\":[123,{\"complexType\":\"SoftLayer_TestEntity\",\"bar\":\"blah\"}]}", - http.outStream.toString("UTF-8")); - assertEquals(RestApiClient.HEADERS, http.headers); - assertTrue(http.invokeAsyncCallbackCalled); - assertTrue(successCalled.get()); - } - - @Test - public void testAsyncCallbackFailure() throws Exception { - FakeHttpClientFactory http = new FakeHttpClientFactory(ApiException.BadRequest.STATUS, - Collections.emptyMap(), - "{\"error\": \"some error\", \"code\": \"some code\"}"); - 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(); - TestEntity.service(client).asAsync().doSomethingStatic(123L, entity, new ResponseHandler() { - @Override - public void onError(Exception ex) { - errorCalled.set(true); - assertEquals("some error", ((ApiException.BadRequest) ex).getMessage()); - assertEquals("some code", ((ApiException.BadRequest) ex).code); - } - - @Override - public void onSuccess(String value) { - fail(); - } - }).get(); - assertTrue(errorCalled.get()); - } - - @Test - public void testCallWithLog() throws Exception { - final FakeHttpClientFactory http = new FakeHttpClientFactory(200, - 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: " - + "{\"parameters\":[123,{\"complexType\":\"SoftLayer_TestEntity\",\"bar\":\"blah\"}]}\n" - + "Got 200 on http://example.com/SoftLayer_TestEntity/doSomethingStatic.json with body: " - + "\"some response\"\n", - output); - } - - @Test - public void testDifferentMethodName() throws Exception { - FakeHttpClientFactory http = new FakeHttpClientFactory(200, - 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); - assertEquals(RestApiClient.HEADERS, http.headers); - assertTrue(http.invokeSyncCalled); - } - - @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"); - 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)); - assertEquals("http://example.com/SoftLayer_TestEntity/doSomethingStatic.json" - + "?resultLimit=1,2", http.fullUrl); - assertTrue(http.invokeSyncCalled); - } - - @Test - 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"); - 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()); - } - - @Test - 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"); - 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()); - } - - @Test - 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"); - client.setHttpClientFactory(http); - TestEntity entity = new TestEntity(); - entity.setFoo("blah"); - TestEntity.ServiceAsync service = TestEntity.service(client).asAsync(); - final AtomicBoolean successCalled = new AtomicBoolean(); - service.doSomethingStatic(123L, entity, new ResponseHandlerWithHeaders() { - @Override - public void onError(Exception ex) { - fail(); - } - - @Override - public void onSuccess(String value) { - assertEquals("some response", value); - assertEquals(234, getLastResponseTotalItemCount().intValue()); - successCalled.set(true); - } - }).get(); - assertTrue(http.invokeAsyncCallbackCalled); - assertTrue(successCalled.get()); - } - - @Test - public void testNormalObjectMethodsOnService() { - RestApiClient client = new RestApiClient("http://example.com/"); - TestEntity.Service service = TestEntity.service(client); - assertEquals("Service: SoftLayer_TestEntity", service.toString()); - assertEquals("Service: SoftLayer_TestEntity with ID 5", TestEntity.service(client, 5L).toString()); - assertTrue(Proxy.isProxyClass(service.getClass())); - assertEquals(service.hashCode(), service.hashCode()); - assertTrue(service.equals(service)); - } -} diff --git a/src/test/java/com/softlayer/api/ResultLimitTest.java b/src/test/java/com/softlayer/api/ResultLimitTest.java deleted file mode 100644 index e53d921..0000000 --- a/src/test/java/com/softlayer/api/ResultLimitTest.java +++ /dev/null @@ -1,26 +0,0 @@ -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 deleted file mode 100644 index 15a355c..0000000 --- a/src/test/java/com/softlayer/api/http/BuiltInHttpClientFactoryTest.java +++ /dev/null @@ -1,170 +0,0 @@ -package com.softlayer.api.http; - -import static org.junit.Assert.*; -import static org.mockito.Mockito.*; - -import java.net.HttpURLConnection; -import java.util.Collections; -import java.util.concurrent.Callable; -import java.util.concurrent.ExecutorService; - -import org.junit.Test; - -import com.softlayer.api.ResponseHandler; -import com.softlayer.api.http.BuiltInHttpClientFactory.BuiltInHttpClient; - -public class BuiltInHttpClientFactoryTest { - - @Test - public void testGetThreadPoolDefaultsToDaemonThreads() throws Exception { - boolean daemon = new BuiltInHttpClientFactory().getThreadPool().submit( - () -> Thread.currentThread().isDaemon() - ).get(); - 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(); - ExecutorService threadPool = mock(ExecutorService.class); - factory.threadPool = threadPool; - factory.threadPoolUserDefined = false; - factory.setThreadPool(null); - verify(threadPool).shutdownNow(); - } - - @Test - public void testSetThreadPoolDoesNotShutDownUserDefined() { - BuiltInHttpClientFactory factory = new BuiltInHttpClientFactory(); - ExecutorService threadPool = mock(ExecutorService.class); - factory.threadPool = threadPool; - factory.threadPoolUserDefined = true; - factory.setThreadPool(null); - verify(threadPool, never()).shutdownNow(); - } - - @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")) - ) - ); - client.connection = mock(HttpURLConnection.class); - // Skip opening connection - doNothing().when(client).openConnection(); - // Go - Callable setupBody = mock(Callable.class); - HttpResponse response = client.invokeSync(setupBody); - assertEquals(client, response); - // Make sure credentials are base64'd properly - verify(client.connection).addRequestProperty("Authorization", "Basic c29tZSB1c2VyOnNvbWUga2V5"); - // And other headers - verify(client.connection).addRequestProperty("header", "some header value"); - // Proper request method - verify(client.connection).setRequestMethod("NOTGET"); - // And that setup body was called - verify(setupBody).call(); - } - - @Test - public void testInvokeAsyncFutureResult() throws Exception { - 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()); - verify(client).invokeSync(callable); - } - - @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() - ) - ); - Callable callable = mock(Callable.class); - doReturn(client).when(client).invokeSync(callable); - ResponseHandler handler = mock(ResponseHandler.class); - client.invokeAsync(callable, handler).get(); - verify(client).invokeSync(callable); - verify(handler).onSuccess(client); - verify(handler, never()).onError(any(Exception.class)); - } - - @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() - ) - ); - Callable callable = mock(Callable.class); - doThrow(RuntimeException.class).when(client).invokeSync(callable); - ResponseHandler handler = mock(ResponseHandler.class); - client.invokeAsync(callable, handler).get(); - verify(client).invokeSync(callable); - verify(handler).onError(any(RuntimeException.class)); - verify(handler, never()).onSuccess(any(HttpResponse.class)); - } - - @Test - public void testGetInputStreamOnSuccess() throws Exception { - 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(); - verify(client.connection).getInputStream(); - verify(client.connection, never()).getErrorStream(); - } - - @Test - public void testGetErrorStreamOnFailure() throws Exception { - 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(); - verify(client.connection).getErrorStream(); - verify(client.connection, never()).getInputStream(); - } -} diff --git a/src/test/java/com/softlayer/api/http/FakeHttpClientFactory.java b/src/test/java/com/softlayer/api/http/FakeHttpClientFactory.java deleted file mode 100644 index 4f78ac0..0000000 --- a/src/test/java/com/softlayer/api/http/FakeHttpClientFactory.java +++ /dev/null @@ -1,181 +0,0 @@ -package com.softlayer.api.http; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.UnsupportedEncodingException; -import java.util.List; -import java.util.Map; -import java.util.concurrent.Callable; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; - -import com.softlayer.api.ResponseHandler; - -public class FakeHttpClientFactory extends HttpClientFactory implements HttpClient, HttpResponse { - - public final int statusCode; - public final Map> responseHeaders; - public final String responseBody; - - public HttpCredentials credentials; - public String method; - public String fullUrl; - public Map> headers; - public boolean closeCalled; - public ByteArrayOutputStream outStream; - public boolean invokeSyncCalled; - public boolean invokeAsyncFutureCalled; - public boolean invokeAsyncCallbackCalled; - - public FakeHttpClientFactory(int statusCode, Map> responseHeaders, String responseBody) { - this.statusCode = statusCode; - this.responseHeaders = responseHeaders; - this.responseBody = responseBody; - } - - @Override - public int getStatusCode() { - return statusCode; - } - - @Override - public Map> getHeaders() { - return responseHeaders; - } - - @Override - public InputStream getInputStream() { - try { - return new ByteArrayInputStream(responseBody.getBytes("UTF-8")); - } catch (UnsupportedEncodingException e) { - throw new RuntimeException(e); - } - } - - @Override - public HttpClient getHttpClient(HttpCredentials credentials, - String method, String fullUrl, Map> headers) { - this.credentials = credentials; - this.method = method; - this.fullUrl = fullUrl; - this.headers = headers; - return this; - } - - @Override - public void close() throws IOException { - closeCalled = true; - } - - @Override - public OutputStream getBodyStream() { - outStream = new ByteArrayOutputStream(); - return outStream; - } - - @Override - public HttpResponse invokeSync(Callable setupBody) { - invokeSyncCalled = true; - try { - setupBody.call(); - } catch (Exception e) { - throw new RuntimeException(e); - } - return this; - } - - @Override - public Future invokeAsync(final Callable setupBody) { - invokeAsyncFutureCalled = true; - return new Future() { - - @Override - public boolean cancel(boolean mayInterruptIfRunning) { - throw new UnsupportedOperationException(); - } - - @Override - public boolean isCancelled() { - throw new UnsupportedOperationException(); - } - - @Override - public boolean isDone() { - throw new UnsupportedOperationException(); - } - - @Override - public HttpResponse get() throws InterruptedException, ExecutionException { - try { - setupBody.call(); - return FakeHttpClientFactory.this; - } catch (Exception e) { - throw new ExecutionException(e); - } - } - - @Override - public HttpResponse get(long timeout, TimeUnit unit) - throws InterruptedException, ExecutionException, TimeoutException { - try { - setupBody.call(); - return FakeHttpClientFactory.this; - } catch (Exception e) { - throw new ExecutionException(e); - } - } - }; - } - - @Override - public Future invokeAsync(final Callable setupBody, final ResponseHandler callback) { - invokeAsyncCallbackCalled = true; - return new Future() { - - @Override - public boolean cancel(boolean mayInterruptIfRunning) { - throw new UnsupportedOperationException(); - } - - @Override - public boolean isCancelled() { - throw new UnsupportedOperationException(); - } - - @Override - public boolean isDone() { - throw new UnsupportedOperationException(); - } - - @Override - public Void get() throws InterruptedException, ExecutionException { - try { - setupBody.call(); - } catch (Exception e) { - callback.onError(e); - return null; - } - callback.onSuccess(FakeHttpClientFactory.this); - return null; - } - - @Override - public Void get(long timeout, TimeUnit unit) - throws InterruptedException, ExecutionException, TimeoutException { - try { - setupBody.call(); - } catch (Exception e) { - callback.onError(e); - return null; - } - callback.onSuccess(FakeHttpClientFactory.this); - return null; - } - }; - } -} diff --git a/src/test/java/com/softlayer/api/http/HttpBasicAuthCredentialsTest.java b/src/test/java/com/softlayer/api/http/HttpBasicAuthCredentialsTest.java deleted file mode 100644 index 051523e..0000000 --- a/src/test/java/com/softlayer/api/http/HttpBasicAuthCredentialsTest.java +++ /dev/null @@ -1,21 +0,0 @@ -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()); - } -} diff --git a/src/test/java/com/softlayer/api/http/HttpBearerCredentialsTest.java b/src/test/java/com/softlayer/api/http/HttpBearerCredentialsTest.java deleted file mode 100644 index 0a1d67d..0000000 --- a/src/test/java/com/softlayer/api/http/HttpBearerCredentialsTest.java +++ /dev/null @@ -1,22 +0,0 @@ -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()); - } -} diff --git a/src/test/java/com/softlayer/api/json/GsonJsonMarshallerFactoryTest.java b/src/test/java/com/softlayer/api/json/GsonJsonMarshallerFactoryTest.java deleted file mode 100644 index 5233db7..0000000 --- a/src/test/java/com/softlayer/api/json/GsonJsonMarshallerFactoryTest.java +++ /dev/null @@ -1,287 +0,0 @@ -package com.softlayer.api.json; - -import static org.junit.Assert.*; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.InputStream; -import java.lang.reflect.Type; -import java.math.BigInteger; -import java.util.Arrays; -import java.util.Calendar; -import java.util.Collections; -import java.util.GregorianCalendar; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.TimeZone; - -import org.junit.Test; - -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 { - - static { - addTestEntityToGson(); - } - - public static void addTestEntityToGson() { - GsonJsonMarshallerFactory.typeClasses.put("SoftLayer_TestEntity", TestEntity.class); - } - - private T fromJson(Type type, String json) throws Exception { - return new GsonJsonMarshallerFactory().getJsonMarshaller(). - fromJson(type, new ByteArrayInputStream(json.getBytes("UTF-8"))); - } - - private T fromJson(Class clazz, String json) throws Exception { - return fromJson((Type) clazz, json); - } - - private String toJson(Object obj) throws Exception { - ByteArrayOutputStream stream = new ByteArrayOutputStream(); - new GsonJsonMarshallerFactory().getJsonMarshaller().toJson(obj, stream); - return stream.toString("UTF-8"); - } - - @Test - 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\" }" - + "]," - + "\"testThing\": {\"complexType\": \"SoftLayer_TestThing\", \"id\": 123}" - + "}"); - assertEquals(TestEntity.class, entity.getClass()); - TestEntity obj = (TestEntity) entity; - assertEquals("some string", obj.getFoo()); - assertEquals(2, obj.getUnknownProperties().size()); - assertEquals("another string", obj.getUnknownProperties().get("foo")); - assertNull(obj.getBaz()); - assertTrue(obj.isBazSpecified()); - GregorianCalendar expectedDate = new GregorianCalendar(1984, Calendar.FEBRUARY, 25, 20, 15, 25); - expectedDate.setTimeZone(TimeZone.getTimeZone("GMT-06:00")); - assertEquals(expectedDate.getTimeInMillis(), obj.getDate().getTimeInMillis()); - assertNull(obj.getNotApiProperty()); - 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()); - 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 - @SuppressWarnings("unchecked") - public void testWrite() throws Exception { - TestEntity obj = new TestEntity(); - obj.setFoo("some string"); - obj.setBaz(null); - obj.setDate(new GregorianCalendar(1984, Calendar.FEBRUARY, 25, 20, 15, 25)); - obj.setNotApiProperty("bad value"); - obj.setChild(new TestEntity()); - obj.getChild().setFoo("child string"); - obj.getMoreChildren().add(new TestEntity()); - obj.getMoreChildren().add(new TestEntity()); - obj.getMoreChildren().get(0).setFoo("child 1"); - obj.getMoreChildren().get(1).setFoo("child 2"); - - Map actual = fromJson(new TypeToken>() { }.getType(), toJson(obj)); - Map expected = new HashMap<>(6); - expected.put("complexType", "SoftLayer_TestEntity"); - expected.put("bar", "some string"); - expected.put("baz", null); - int offsetMinutes = TimeZone.getDefault().getOffset(obj.getDate().getTimeInMillis()) / 60000; - String expectedTimeZone = - (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<>(); - childMap.put("complexType", "SoftLayer_TestEntity"); - childMap.put("bar", "child string"); - expected.put("child", childMap); - Map child1Map = new HashMap<>(); - child1Map.put("complexType", "SoftLayer_TestEntity"); - child1Map.put("bar", "child 1"); - Map child2Map = new HashMap<>(); - child2Map.put("complexType", "SoftLayer_TestEntity"); - child2Map.put("bar", "child 2"); - expected.put("moreChildren", Arrays.asList(child1Map, child2Map)); - assertEquals(expected, actual); - } - - @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 - ); - expected.setTimeZone(TimeZone.getTimeZone("GMT-06:00")); - assertEquals(expected.getTimeInMillis(), - fromJson(GregorianCalendar.class, regular).getTimeInMillis()); - - String subSecondNoDigits = "\"1984-02-25T20:15:25.-06:00\""; - assertEquals(expected.getTimeInMillis(), - fromJson(GregorianCalendar.class, subSecondNoDigits).getTimeInMillis()); - - String subSecondOneDigit = "\"1984-02-25T20:15:25.1-06:00\""; - assertEquals(expected.getTimeInMillis() + 100, - fromJson(GregorianCalendar.class, subSecondOneDigit).getTimeInMillis()); - - String subSecondTwoDigits = "\"1984-02-25T20:15:25.12-06:00\""; - assertEquals(expected.getTimeInMillis() + 120, - fromJson(GregorianCalendar.class, subSecondTwoDigits).getTimeInMillis()); - - String subSecondThreeDigits = "\"1984-02-25T20:15:25.123-06:00\""; - assertEquals(expected.getTimeInMillis() + 123, - fromJson(GregorianCalendar.class, subSecondThreeDigits).getTimeInMillis()); - - String subSecondFourDigits = "\"1984-02-25T20:15:25.1239-06:00\""; - assertEquals(expected.getTimeInMillis() + 123, - fromJson(GregorianCalendar.class, subSecondFourDigits).getTimeInMillis()); - - String subSecondSixDigits = "\"1984-02-25T20:15:25.123987-06:00\""; - assertEquals(expected.getTimeInMillis() + 123, - fromJson(GregorianCalendar.class, subSecondSixDigits).getTimeInMillis()); - - String subSecondTenDigits = "\"1984-02-25T20:15:25.1239876543-06:00\""; - assertEquals(expected.getTimeInMillis() + 123, - fromJson(GregorianCalendar.class, subSecondTenDigits).getTimeInMillis()); - } - - @Test - public void testBigIntegerWithExponents() throws Exception { - assertEquals(BigInteger.valueOf(123), fromJson(BigInteger.class, "123")); - assertEquals(new BigInteger("18446744073710000000"), fromJson(BigInteger.class, "1.844674407371e+19")); - } - - @Test - public void testBase64ByteArrays() throws Exception { - // Read entire logo into byte array (sigh, have to be Java 6 compatible w/ no extra libs) - ByteArrayOutputStream out = new ByteArrayOutputStream(); - InputStream in = getClass().getResourceAsStream("sl-logo.png"); - try { - byte[] buffer = new byte[1024]; - int len = in.read(buffer); - while (len != -1) { - out.write(buffer, 0, len); - len = in.read(buffer); - } - } finally { - try { - in.close(); - } catch (Exception e) { } - } - byte[] bytes = out.toByteArray(); - // We know from testing that the base 64 encoded amount is 2232 chars long - String json = toJson(Collections.singletonMap("foo", bytes)); - @SuppressWarnings("unchecked") - Map map = fromJson(Map.class, json); - assertEquals(1, map.size()); - assertEquals(2232, ((String) map.get("foo")).length()); - Map byteMap = fromJson(new TypeToken>(){}.getType(), json); - assertEquals(1, map.size()); - assertTrue(Arrays.equals(bytes, byteMap.get("foo"))); - } - - @Test - public void testReadSingleObjectAsArray() throws Exception { - Type listType = new TypeToken>(){}.getType(); - // First normal tests - List entities = fromJson(listType, "[]"); - assertEquals(0, entities.size()); - entities = fromJson(listType, "[{\"complexType\":\"SoftLayer_TestEntity\",\"id\": 123}]"); - assertEquals(1, entities.size()); - assertEquals(123, entities.get(0).getId().intValue()); - // Now without the array on the outside - entities = fromJson(listType, "{\"complexType\":\"SoftLayer_TestEntity\",\"id\": 123}"); - assertEquals(1, entities.size()); - assertEquals(123, entities.get(0).getId().intValue()); - } -} diff --git a/src/test/java/com/softlayer/api/service/TestEntity.java b/src/test/java/com/softlayer/api/service/TestEntity.java deleted file mode 100644 index df3f7d8..0000000 --- a/src/test/java/com/softlayer/api/service/TestEntity.java +++ /dev/null @@ -1,232 +0,0 @@ -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.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; - -@ApiType("SoftLayer_TestEntity") -public class TestEntity 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("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(); - } - return moreChildren; - } - - @ApiProperty - protected List recursiveProperty; - - 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); - } - - 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_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); - - @ApiMethod("getRecursiveProperty") - public String getRecursiveProperty(); - - @ApiMethod("getObject") - public TestEntity getObject(); - - @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); - } - - 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); - } - } -} diff --git a/src/test/java/com/softlayer/api/service/TestThing.java b/src/test/java/com/softlayer/api/service/TestThing.java deleted file mode 100644 index 548b87c..0000000 --- a/src/test/java/com/softlayer/api/service/TestThing.java +++ /dev/null @@ -1,139 +0,0 @@ -package com.softlayer.api.service; - -import java.util.ArrayList; -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; - -@ApiType("SoftLayer_TestThing") -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); - } - } -} diff --git a/src/test/resources/com/softlayer/api/json/sl-logo.png b/src/test/resources/com/softlayer/api/json/sl-logo.png deleted file mode 100644 index 304f43b..0000000 Binary files a/src/test/resources/com/softlayer/api/json/sl-logo.png and /dev/null differ