diff --git a/README.md b/README.md
index 3c70e08..04e2533 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,104 @@
# HTTP RESTful API for SPACE
[](https://sonarcloud.io/summary/new_code?id=pgmarc_space-java-client)
-[](https://sonarcloud.io/summary/new_code?id=pgmarc_space-java-client)
\ No newline at end of file
+[](https://sonarcloud.io/summary/new_code?id=pgmarc_space-java-client)
+[](https://sonarcloud.io/summary/new_code?id=pgmarc_space-java-client)
+[](https://sonarcloud.io/summary/new_code?id=pgmarc_space-java-client)
+
+HTTP REST client implemented in Java for Subscription and Pricing Access Control Engine (SPACE).
+
+## Features
+
+- Read, create and update user's subscriptions
+- Verify a user's feature availability based on their subscription
+- Configure SPACE client with:
+ - Host (required)
+ - Port (optional, by default SPACE listens on port `5403`)
+ - SPACE URL path prefix (optional, by default space is under `api/v1`, i.e., `http://example.com/api/v1`)
+ - Read and write timeout of HTTP client (optional, uses OkHTTP client under the hood)
+
+SPACE client implements the following operations of [SPACE OAS](space-oas):
+
+- `POST /contracts`
+- `GET /contracts/{userId}`
+- `PUT /contracts/{userId}`
+- `POST /features/{userId}/{featureId}`
+- `POST /features/{userId}/pricing-token`
+
+
+## Installation
+
+### Requirements
+
+- Java 11 or later
+- Apache Maven 3.6.3 or later (excluding `4.X`)
+
+### Maven
+
+```xml
+
+ io.github.pgmarc.space
+ space-client
+ 0.0.1-SNAPSHOT
+
+```
+
+To build and install the jar locally execute the following:
+
+```bash
+cd space-client
+mvn clean install
+```
+
+### Usage
+
+Before using `SpaceClient` you will need to do the following:
+1. Start a SPACE instance in your machine
+2. Get the corresponding api key for the required role
+3. As `MANAGER` or `ADMIN` add a service in SPACE by uploading a pricing in `Pricing2Yaml` format specification
+
+See [Pricing4SaaS docs](pricing4saas-docs) for more information.
+
+> [!WARNING]
+> API Keys are secrets and should be kept safe end encrypted.
+
+Configure `SpaceClient` using the builder:
+```java
+SpaceClient client = SpaceClient.builder("example.com", apiKey)
+ .port(8080)
+ .path("example/path")
+ .build();
+```
+
+Create subscriptions:
+```java
+UserContact contact = UserContact
+ .builder("3f5f934c-951b-4a2d-ad10-b1679ac9b7ba", "example-user").build();
+
+SubscriptionRequest createSubscriptionRequest = SubscriptionRequest.builder(contact)
+ .startService("Petclinic", "2024")
+ .plan("Enterprise")
+ .addOn("petLover", 1)
+ .endService()
+ .build();
+
+Subscription subscription = client.contracts().addContract(createSubscriptionRequest);
+```
+
+Evaluate subscription features at runtime:
+
+```java
+String userId = "3f5f934c-951b-4a2d-ad10-b1679ac9b7ba";
+FeatureEvaluationResult result = client
+ .features()
+ .evaluate(userid, "Petclinic", "pets");
+```
+
+These are just some examples, but you can find more in `examples` directory.
+
+### Documentation
+
+You read more documentation about SPACE in [Pricing4SaaS docs](pricing4saas-docs).
+
+[pricing4saas-docs]: https://pricing4saas-docs.vercel.app/
+[space-oas]: https://github.com/Alex-GF/space/blob/docs/oas/api/docs/space-api-docs.yaml
diff --git a/examples/src/main/java/io/github/pgmarc/space/examples/FeatureEvaluation.java b/examples/src/main/java/io/github/pgmarc/space/examples/FeatureEvaluation.java
new file mode 100644
index 0000000..9e9c808
--- /dev/null
+++ b/examples/src/main/java/io/github/pgmarc/space/examples/FeatureEvaluation.java
@@ -0,0 +1,68 @@
+package io.github.pgmarc.space.examples;
+
+import io.github.pgmarc.space.SpaceClient;
+import io.github.pgmarc.space.features.FeatureEvaluationResult;
+import io.github.pgmarc.space.features.Revert;
+import io.github.pgmarc.space.features.UsageLimitConsumption;
+
+import java.io.IOException;
+import java.util.Objects;
+
+final class FeatureEvaluation {
+
+
+ public static void main(String[] args) throws IOException {
+
+ String apiKey = System.getenv("SPACE_API_KEY");
+ Objects.requireNonNull(apiKey, "you must set SPACE_API_KEY env variable");
+ SpaceClient client = SpaceClient.builder("localhost", apiKey).build();
+
+ String userId = "4427d118-073d-4da2-a145-f77a75b52595";
+ String service = "WireMock";
+ String feature = "mockAPI";
+ evaluateFeature(client, userId, service, feature);
+ evaluateWithConsumption(client, userId, service, feature);
+ revertFeatureOptimisticEvaluation(client, userId, service, feature);
+ getPricingToken(client, userId);
+ }
+
+ private static void evaluateFeature(SpaceClient client, String userId, String service, String feature) throws IOException {
+ FeatureEvaluationResult eval = client.features().evaluate(userId, service, feature);
+
+ if (eval.isAvailable()) {
+ System.out.println("User with id '" + userId + "' is able to use " + service + " " + feature);
+ } else {
+ System.out.println("User with id '" + userId + "' is not able to use " + service + " " + feature);
+ }
+ }
+
+ private static void evaluateWithConsumption(SpaceClient client, String userId, String service, String feature) throws IOException {
+ String usageLimit = "mockAPICallsLimit";
+ UsageLimitConsumption consumption = UsageLimitConsumption.builder(service)
+ .addInt(usageLimit, 1)
+ .build();
+ FeatureEvaluationResult eval = client.features().evaluateOptimistically(userId, service, feature, consumption);
+
+ if (eval.isAvailable()) {
+ System.out.println("User with id '" + userId + "' is able to use " + service + " " + feature);
+ Number consumed = eval.getConsumed(usageLimit).orElse(null);
+ Number limit = eval.getLimit(usageLimit).orElse(null);
+ if (consumed != null) {
+ System.out.println("Consumed " + consumed + " API calls out of " + limit);
+ }
+ } else {
+ System.out.println("User with id '" + userId + "' is not able to use " + service + " " + feature);
+ }
+ }
+
+ private static void revertFeatureOptimisticEvaluation(SpaceClient client, String userId, String service, String feature) throws IOException {
+ boolean success = client.features().revert(userId, service, feature, Revert.NEWEST_VALUE);
+ System.out.println("Revert operation has " + (success ? "succeeded" : "failed"));
+ }
+
+ private static void getPricingToken(SpaceClient client, String userId) throws IOException {
+
+ String pricingJwtToken = client.features().generatePricingTokenForUser(userId);
+ System.out.println(pricingJwtToken);
+ }
+}
diff --git a/examples/src/main/java/io/github/pgmarc/space/examples/SingleService.java b/examples/src/main/java/io/github/pgmarc/space/examples/SingleService.java
index 2eee104..086f433 100644
--- a/examples/src/main/java/io/github/pgmarc/space/examples/SingleService.java
+++ b/examples/src/main/java/io/github/pgmarc/space/examples/SingleService.java
@@ -1,14 +1,13 @@
package io.github.pgmarc.space.examples;
-import java.io.IOException;
-import java.util.Objects;
-
import io.github.pgmarc.space.SpaceClient;
import io.github.pgmarc.space.contracts.Subscription;
import io.github.pgmarc.space.contracts.SubscriptionRequest;
import io.github.pgmarc.space.contracts.SubscriptionUpdateRequest;
import io.github.pgmarc.space.contracts.UserContact;
-import okhttp3.*;
+
+import java.io.IOException;
+import java.util.Objects;
/**
* In this example you will be subscribing to a service with only a single
@@ -20,7 +19,7 @@
* src/main/resources/single folder.
*
*/
-public class SingleService {
+final class SingleService {
/**
* Before executing the code snippets you have to do the following:
@@ -28,7 +27,7 @@ public class SingleService {
* Start a SPACE instance
* Get a MANAGER or ADMIN api key
*
- *
+ *
* You are free to experiment with the API and adjust the code to your needs.
* Happy Hacking :)
*
@@ -38,14 +37,99 @@ public class SingleService {
public static void main(String[] args) throws IOException {
String apiKey = Objects.requireNonNull(System.getenv("SPACE_API_KEY"),
- "You need to set SPACE_API_KEY env variable") ;
+ "You need to set SPACE_API_KEY env variable");
SpaceClient client = SpaceClient.builder("localhost", apiKey).build();
- // A subscription at least requires a user id and a username
String userId = "4427d118-073d-4da2-a145-f77a75b52595";
+
+ addContract(client, userId);
+ getUserContract(client, userId);
+ //updateContract(client, userId);
+
+ }
+
+ /**
+ * To create a SubscriptionRequest, i.e, create a Subscription in SPACE
+ * API you need the following parameters:
+ *
+ * - An
UserContact
+ * - A
Service
+ *
+ *
+ *
+ * An UserContact needs al least the following parameters:
+ *
+ * userId: is the id of the user of YOUR application
+ * username: alias that identifies the user of YOUR application
+ *
+ *
+ * You can pass optional parameters using the UserContact.Builder methods
+ *
+ *
+ *
+ * UserContact.builder("you_user_id", "janedoe")
+ * .firstName("Jane")
+ * .lastName("Doe")
+ * .email("janedoe@example.com")
+ * .phone("280-689-4646")
+ * .build();
+ *
+ *
+ *
+ *
+ * A subscription al least contains one contracted service. A service
+ * can optionally have a plan or multiple add-ons, but at least you
+ * have to be subscribed to one of them. For example the following combinations
+ * are possible:
+ *
+ *
+ *
+ *
+ * | Plan |
+ * Add-on |
+ * Posible |
+ *
+ *
+ *
+ *
+ * | 0 |
+ * 0 |
+ * No |
+ *
+ *
+ * | 0 |
+ * 1 |
+ * Yes |
+ *
+ *
+ * | 0 |
+ * N |
+ * Yes |
+ *
+ *
+ * | 1 |
+ * 0 |
+ * Yes |
+ *
+ *
+ * | 1 |
+ * 1 |
+ * Yes |
+ *
+ *
+ * | 1 |
+ * N |
+ * Yes |
+ *
+ *
+ *
+ *
+ */
+ private static void addContract(SpaceClient client, String userId) throws IOException {
+ // A subscription at least requires a user id and a username
String username = "alex";
- UserContact contact = UserContact.builder(userId, username).build();
+ UserContact contact1 = UserContact.builder(userId, username).build();
// But, you can provide more user contact information if you want
UserContact contact2 = UserContact
@@ -55,27 +139,33 @@ public static void main(String[] args) throws IOException {
.email("janedoe@example.com")
.phone("280-689-4646").build();
- SubscriptionRequest subReq = SubscriptionRequest.builder(contact)
+ SubscriptionRequest subReq = SubscriptionRequest.builder(contact1)
.startService("WireMock", "2024")
- .plan("Enterprise")
+ .plan("Enterprise")
.endService()
.build();
Subscription newSubscription = client.contracts().addContract(subReq);
System.out.println(newSubscription);
+ }
+ private static void getUserContract(SpaceClient client, String userId) throws IOException {
Subscription subscription = client.contracts().getContractByUserId(userId);
System.out.println(subscription);
+ }
- // Updating (novating) the contract to version of 2025
+ /**
+ * Updating (in legal terms, novating) the subscription of WireMock
+ * version 2024 to WireMock version 2025
+ */
+ private static void updateContract(SpaceClient client, String userId) throws IOException {
SubscriptionUpdateRequest upReq = SubscriptionUpdateRequest.builder()
.startService("WireMock", "2025")
- .plan("Enterprise")
+ .plan("Enterprise")
.endService()
.build();
Subscription updatedSubscription = client.contracts().updateContractByUserId(userId, upReq);
System.out.println(updatedSubscription);
}
-
}