diff --git a/README.md b/README.md index 8843e9f..5295f78 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,21 @@ # Welcome to the Licensing Project! [![Build](https://github.com/bsayli/licensing/actions/workflows/build.yml/badge.svg?branch=main)](https://github.com/bsayli/licensing/actions/workflows/build.yml) -[![Release](https://img.shields.io/github/v/release/bsayli/licensing?logo=github\&label=release)](https://github.com/bsayli/licensing/releases/latest) +[![Release](https://img.shields.io/github/v/release/bsayli/licensing?logo=github&label=release)](https://github.com/bsayli/licensing/releases/latest) [![codecov](https://codecov.io/gh/bsayli/licensing/branch/main/graph/badge.svg)](https://codecov.io/gh/bsayli/licensing) + [![Java](https://img.shields.io/badge/Java-21-red?logo=openjdk)](https://openjdk.org/projects/jdk/21/) [![Spring Boot](https://img.shields.io/badge/Spring%20Boot-3.5-brightgreen?logo=springboot)](https://spring.io/projects/spring-boot) +[![OpenAPI Generics](https://img.shields.io/badge/OpenAPI%20Generics-1.0.2-blue)](https://github.com/blueprint-platform/openapi-generics) +[![OpenAPI Generator](https://img.shields.io/badge/OpenAPI%20Generator-7.22.0-blue?logo=openapiinitiative)](https://openapi-generator.tech/) + [![Keycloak](https://img.shields.io/badge/Keycloak-26.x-purple?logo=keycloak)](https://www.keycloak.org/) [![Redis](https://img.shields.io/badge/Redis-8.x-red?logo=redis)](https://redis.io/) -[![Maven](https://img.shields.io/badge/Maven-3.9-blue?logo=apachemaven)](https://maven.apache.org/) -[![Docker](https://img.shields.io/badge/Docker-Compose-blue?logo=docker)](https://www.docker.com/) [![Caffeine](https://img.shields.io/badge/Caffeine-Cache-orange)](https://github.com/ben-manes/caffeine) [![JWT](https://img.shields.io/badge/JWT-EdDSA-lightgrey?logo=jsonwebtokens)](https://jwt.io/) + +[![Maven](https://img.shields.io/badge/Maven-3.9-blue?logo=apachemaven)](https://maven.apache.org/) +[![Docker](https://img.shields.io/badge/Docker-Compose-blue?logo=docker)](https://www.docker.com/) [![License](https://img.shields.io/badge/license-MIT-green)](LICENSE)

@@ -66,6 +71,7 @@ This project provides a **complete licensing framework** for applications, combi ## Subprojects +* **licensing-api-contract**: Shared API response contract (`ApiResponse`, `ApiError`) reused by service, client, SDK, and CLI modules. * **license-generator**: Java project for license key generation, encryption, and cryptographic tooling. * **licensing-service**: Spring Boot application that issues and validates license tokens. * **licensing-service-sdk**: Spring Boot application acting as a client SDK (with caching & detached signature) for integrating licensing capabilities into external apps. @@ -136,7 +142,7 @@ licensing-service-sdk-cli | INFO Message: License is valid cd licensing/licensing-service-sdk-cli mvn clean package cd target -java -jar licensing-service-sdk-cli-1.0.1.jar -s crm -v 1.5.0 -i "crm~macbook~00:2A:8D:BE:F1:56" -k "BSAYLI." +java -jar licensing-service-sdk-cli-1.0.5.jar -s crm -v 1.5.0 -i "crm~macbook~00:2A:8D:BE:F1:56" -k "BSAYLI." ``` --- @@ -183,9 +189,10 @@ If you found this project useful, please consider giving it a star ⭐ on GitHub ## Related Modules (Quick View) -| Module | Purpose | Documentation | -| ----------------------------- | ------------------------------------------ | ---------------------------------------------------------- | -| **licensing-service** | REST API for issuing and validating tokens | [README](licensing-service/README.md) | -| **licensing-service-sdk** | Client SDK for integration | [README](licensing-service-sdk/README.md) | -| **licensing-service-sdk-cli** | CLI demo client | [README](licensing-service-sdk-cli/README.md) | -| **license-generator** | Key & signature tooling | [README](license-generator/README.md) | \ No newline at end of file +| Module | Purpose | Documentation | +|-------------------------------|-------------------------------------------------------------|-----------------------------------------------| +| **licensing-service** | REST API for issuing and validating tokens | [README](licensing-service/README.md) | +| **licensing-service-sdk** | Client SDK for integration | [README](licensing-service-sdk/README.md) | +| **licensing-service-sdk-cli** | CLI demo client | [README](licensing-service-sdk-cli/README.md) | +| **licensing-api-contract** | Shared API response contract (`ApiResponse`, `ApiError`) | [README](licensing-api-contract/README.md) | +| **license-generator** | Key and signature tooling | [README](license-generator/README.md) | \ No newline at end of file diff --git a/docker-compose/client/docker-compose.yml b/docker-compose/client/docker-compose.yml index c4c1597..61ee77f 100644 --- a/docker-compose/client/docker-compose.yml +++ b/docker-compose/client/docker-compose.yml @@ -5,7 +5,7 @@ networks: services: licensing-service-sdk-cli: build: ./../../licensing-service-sdk-cli - image: licensing-service-sdk-cli:1.0.1 + image: licensing-service-sdk-cli:1.0.5 container_name: licensing-service-sdk-cli networks: - license_network @@ -14,5 +14,5 @@ services: INSTANCE_ID: "licensing-service~demo~00:11:22:33:44:55" SERVICE_ID: "crm" SERVICE_VERSION: "1.5.0" - LICENSE_KEY: "BSAYLI~X66e_qYlfPxWiIaN2ahPb9tQFyqjMuTih06LCytzjZ0~0aT6lLTZGkO1zHHPHFDzwF7zPiZLRLWSl06HSVQO5z+NqtzzcFCUkkVFuqHTYKcAcI9037sQQQSfBQakQDUoCA==" + LICENSE_KEY: "BSAYLI.AQA3-gCQ66DQsfC0LwnAO8DTjKgad7DhPaOtCf7WG2bFUoK9pmScIhhCf2S-D0j8g4jC7nlFrLDpuM0ezEoDQc79SizxxEIUGN9YUhTNBW7iRQ" restart: "no" \ No newline at end of file diff --git a/docker-compose/server/docker-compose.yml b/docker-compose/server/docker-compose.yml index aae31b1..c930bb8 100644 --- a/docker-compose/server/docker-compose.yml +++ b/docker-compose/server/docker-compose.yml @@ -51,8 +51,9 @@ services: restart: unless-stopped licensing-service: - build: ./../../licensing-service - image: licensing-service:1.0.1 + build: + context: ./../.. + dockerfile: licensing-service/Dockerfile container_name: licensing-service networks: [ license_network ] depends_on: @@ -80,7 +81,7 @@ services: build: context: ./../.. dockerfile: licensing-service-sdk/Dockerfile - image: licensing-service-sdk:1.0.1 + image: licensing-service-sdk:1.0.5 container_name: licensing-service-sdk networks: [ license_network ] depends_on: diff --git a/license-generator/README.md b/license-generator/README.md index 70fc107..79e6a15 100644 --- a/license-generator/README.md +++ b/license-generator/README.md @@ -3,8 +3,8 @@ [![Build](https://github.com/bsayli/licensing/actions/workflows/build.yml/badge.svg?branch=main)](https://github.com/bsayli/licensing/actions/workflows/build.yml) [![Java](https://img.shields.io/badge/Java-21-red?logo=openjdk)](https://openjdk.org/projects/jdk/21/) [![JJWT](https://img.shields.io/badge/JJWT-0.12.x-orange)](https://github.com/jwtk/jjwt) -[![BouncyCastle](https://img.shields.io/badge/BouncyCastle-1.81-blue)](https://www.bouncycastle.org/) -[![Jackson](https://img.shields.io/badge/Jackson-2.19.x-lightgrey)](https://github.com/FasterXML/jackson) +[![BouncyCastle](https://img.shields.io/badge/BouncyCastle-1.84-blue)](https://www.bouncycastle.org/) +[![Jackson](https://img.shields.io/badge/Jackson-2.21.x-lightgrey)](https://github.com/FasterXML/jackson) [![License](https://img.shields.io/badge/license-MIT-green)](../LICENSE) Tools and libraries for generating cryptographic keys, constructing license strings, creating **detached Ed25519 diff --git a/license-generator/pom.xml b/license-generator/pom.xml index 2ad3b22..dff383c 100644 --- a/license-generator/pom.xml +++ b/license-generator/pom.xml @@ -6,19 +6,19 @@ io.github.bsayli license-generator - 1.0.4 + 1.0.5 21 UTF-8 UTF-8 0.12.7 - 1.81 - 2.19.2 - 1.5.18 + 1.84 + 2.21.3 + 1.5.32 2.0.17 5.13.4 - 0.8.13 + 0.8.14 1.2.1 3.14.0 3.5.4 diff --git a/licensing-api-contract/README.md b/licensing-api-contract/README.md new file mode 100644 index 0000000..6581250 --- /dev/null +++ b/licensing-api-contract/README.md @@ -0,0 +1,73 @@ +# Licensing API Contract + +Shared API contract types used by the licensing modules. + +This module contains the response envelope used by `licensing-service`, `licensing-service-client`, `licensing-service-sdk` and `licensing-service-sdk-cli` to ensure a consistent API contract across all layers. + +```java +ApiResponse +ApiError +``` + +## Purpose + +`licensing-api-contract` keeps the API response shape in one place so server and client modules use the same contract instead of redefining response wrappers independently. + +It is also used as a BYOE (Bring Your Own Envelope) example for `openapi-generics`: generated client response wrappers extend the existing `ApiResponse` type instead of generating a new envelope model. + +Example generated client wrapper: + +```java +public class ApiResponseLicenseAccessResponse + extends ApiResponse { +} +``` + +## Contract Shape + +`ApiResponse` represents the standard licensing API envelope: + +```json +{ + "status": 200, + "message": "License is valid", + "data": {}, + "errors": [] +} +``` + +Fields: + +| Field | Type | Description | +| --------- | ---------------- | ----------------------------------------------- | +| `status` | `int` | HTTP-style status code returned in the envelope | +| `message` | `String` | Human-readable response message | +| `data` | `T` | Typed response payload | +| `errors` | `List` | Structured error details | + +## BYOE Requirements + +Because generated wrappers extend `ApiResponse`, the envelope is intentionally subclass-friendly: + +* public no-argument constructor +* public getters and setters +* generic payload accessor through `getData()` / `setData(T data)` + +This keeps the envelope compatible with generated client wrappers while preserving the original API contract. + +## Module Coordinates + +```xml + + io.github.bsayli + licensing-api-contract + 1.0.5 + +``` + +## See Also + +* [`licensing-service`](../licensing-service/README.md) — REST API that returns `ApiResponse` +* [`licensing-service-client`](../licensing-service-client/README.md) — generated client using the shared envelope +* [`licensing-service-sdk`](../licensing-service-sdk/README.md) — SDK layer built on top of the generated client +* [`licensing-service-sdk-cli`](../licensing-service-sdk-cli/README.md) — command-line client demo diff --git a/licensing-api-contract/pom.xml b/licensing-api-contract/pom.xml new file mode 100644 index 0000000..8e30efd --- /dev/null +++ b/licensing-api-contract/pom.xml @@ -0,0 +1,36 @@ + + + 4.0.0 + + io.github.bsayli + licensing-api-contract + 1.0.5 + jar + + licensing-api-contract + API contract types used by the licensing service. + + + UTF-8 + UTF-8 + + 21 + 3.15.0 + + + + + + org.apache.maven.plugins + maven-compiler-plugin + ${maven-compiler-plugin.version} + + ${maven.compiler.release} + ${project.build.sourceEncoding} + + + + + \ No newline at end of file diff --git a/licensing-service/src/main/java/io/github/bsayli/licensing/common/api/ApiError.java b/licensing-api-contract/src/main/java/io/github/bsayli/licensing/contract/api/ApiError.java similarity index 55% rename from licensing-service/src/main/java/io/github/bsayli/licensing/common/api/ApiError.java rename to licensing-api-contract/src/main/java/io/github/bsayli/licensing/contract/api/ApiError.java index 3a09fd1..11e62c4 100644 --- a/licensing-service/src/main/java/io/github/bsayli/licensing/common/api/ApiError.java +++ b/licensing-api-contract/src/main/java/io/github/bsayli/licensing/contract/api/ApiError.java @@ -1,3 +1,3 @@ -package io.github.bsayli.licensing.common.api; +package io.github.bsayli.licensing.contract.api; public record ApiError(String errorCode, String message) {} diff --git a/licensing-api-contract/src/main/java/io/github/bsayli/licensing/contract/api/ApiResponse.java b/licensing-api-contract/src/main/java/io/github/bsayli/licensing/contract/api/ApiResponse.java new file mode 100644 index 0000000..1dfe301 --- /dev/null +++ b/licensing-api-contract/src/main/java/io/github/bsayli/licensing/contract/api/ApiResponse.java @@ -0,0 +1,100 @@ +package io.github.bsayli.licensing.contract.api; + +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +public class ApiResponse { + + private int status; + private String message; + private T data; + private List errors = Collections.emptyList(); + + public ApiResponse() {} + + public ApiResponse(int status, String message, T data, List errors) { + this.status = status; + this.message = message; + this.data = data; + this.errors = errors != null ? errors : Collections.emptyList(); + } + + public static ApiResponse ok(T data) { + return new ApiResponse<>(200, "OK", data, Collections.emptyList()); + } + + public static ApiResponse of(int status, String message, T data) { + return new ApiResponse<>(status, message, data, Collections.emptyList()); + } + + public static ApiResponse error(int status, String message) { + return new ApiResponse<>(status, message, null, Collections.emptyList()); + } + + public static ApiResponse error(int status, String message, List errors) { + return new ApiResponse<>(status, message, null, errors); + } + + public int getStatus() { + return status; + } + + public void setStatus(int status) { + this.status = status; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public T getData() { + return data; + } + + public void setData(T data) { + this.data = data; + } + + public List getErrors() { + return errors; + } + + public void setErrors(List errors) { + this.errors = errors != null ? errors : Collections.emptyList(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof ApiResponse that)) return false; + return status == that.status + && Objects.equals(message, that.message) + && Objects.equals(data, that.data) + && Objects.equals(errors, that.errors); + } + + @Override + public int hashCode() { + return Objects.hash(status, message, data, errors); + } + + @Override + public String toString() { + return "ApiResponse{" + + "status=" + + status + + ", message='" + + message + + '\'' + + ", data=" + + data + + ", errors=" + + errors + + '}'; + } +} diff --git a/licensing-service-client/README.md b/licensing-service-client/README.md index 1174b93..e924443 100644 --- a/licensing-service-client/README.md +++ b/licensing-service-client/README.md @@ -2,7 +2,9 @@ [![Build](https://github.com/bsayli/licensing/actions/workflows/build.yml/badge.svg?branch=main)](https://github.com/bsayli/licensing/actions/workflows/build.yml) [![Java](https://img.shields.io/badge/Java-21-red?logo=openjdk)](https://openjdk.org/projects/jdk/21/) -[![OpenAPI Generator](https://img.shields.io/badge/OpenAPI%20Generator-7.x-blue?logo=openapiinitiative)](https://openapi-generator.tech/) +[![OpenAPI Generator](https://img.shields.io/badge/OpenAPI%20Generator-7.22.0-blue?logo=openapiinitiative)](https://openapi-generator.tech/) +[![OpenAPI Generics](https://img.shields.io/badge/OpenAPI%20Generics-1.0.2-blue)](https://github.com/blueprint-platform/openapi-generics) +[![Spring Boot](https://img.shields.io/badge/Spring%20Boot-3.5-brightgreen?logo=springboot)](https://spring.io/projects/spring-boot) [![Maven](https://img.shields.io/badge/Maven-3.9-blue?logo=apachemaven)](https://maven.apache.org/) [![License](https://img.shields.io/badge/license-MIT-green)](../LICENSE) @@ -34,7 +36,7 @@ Add to your service's `pom.xml`: io.github.bsayli licensing-service-client - 0.1.0 + 1.0.5 ``` @@ -132,7 +134,7 @@ mvn clean package Produces: ``` -target/licensing-service-client-0.1.0.jar +target/licensing-service-client-1.0.5.jar ``` --- diff --git a/licensing-service-client/pom.xml b/licensing-service-client/pom.xml index a662462..b8962f0 100644 --- a/licensing-service-client/pom.xml +++ b/licensing-service-client/pom.xml @@ -4,10 +4,17 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 + + io.github.blueprint-platform + openapi-generics-java-codegen-parent + 1.0.2 + + + io.github.bsayli licensing-service-client - 1.0.4 - + 1.0.5 + licensing-service-client Generated client for licensing-service using generics-aware OpenAPI templates jar @@ -16,20 +23,31 @@ UTF-8 UTF-8 21 - 7.15.0 - 3.13.0 - 3.6.0 - 3.5.5 - 3.0.2 - 2.1.1 - 5.13.4 - 5.1.0 - 5.5 - 3.5.3 - 0.8.13 + 3.5.14 + 7.22.0 + 1.0.5 + 3.1.1 + 3.0.0 + 5.3.2 + 5.5.2 + 2.0.17 + 1.5.32 + + 3.15.0 + 3.5.5 + 3.5.5 + 0.8.14 + + + + io.github.bsayli + licensing-api-contract + ${licensing-api-contract.version} + + org.springframework.boot spring-boot-starter-web @@ -64,6 +82,19 @@ ${httpclient5.version} + + org.slf4j + slf4j-api + ${slf4j-api.version} + + + + ch.qos.logback + logback-classic + ${logback.version} + test + + org.springframework.boot spring-boot-starter-test @@ -84,67 +115,64 @@ src/main/resources - **/*.yaml openapi-templates/** + + + org.apache.maven.plugins + maven-dependency-plugin + + + resolve-mockito-agent + + properties + + + + + org.openapitools openapi-generator-maven-plugin - ${openapi.generator.version} generate-client + generate-sources generate + java-generics-contract ${project.basedir}/src/main/resources/licensing-service-api-docs.yaml - java + restclient - ${project.build.directory}/generated-sources/openapi + io.github.bsayli.licensing.client.generated.api io.github.bsayli.licensing.client.generated.dto io.github.bsayli.licensing.client.generated.invoker - ${project.basedir}/src/main/resources/openapi-templates - - true - false - false - false - false true - true jackson - java8 - true false - src/gen/java - - - - - - org.codehaus.mojo - build-helper-maven-plugin - ${build.helper.plugin.version} - - - add-generated-sources - generate-sources - - add-source - - - - ${project.build.directory}/generated-sources/openapi/src/gen/java - + + + openapi-generics.envelope=io.github.bsayli.licensing.contract.api.ApiResponse + + + + true + false + + false + false + false + false @@ -153,10 +181,10 @@ org.apache.maven.plugins maven-compiler-plugin - ${maven.compiler.plugin.version} + ${maven-compiler-plugin.version} ${java.version} - UTF-8 + ${project.build.sourceEncoding} @@ -165,10 +193,34 @@ maven-surefire-plugin ${maven-surefire-plugin.version} - false + @{argLine} -javaagent:${org.mockito:mockito-core:jar} + + **/*Test.java + + + org.apache.maven.plugins + maven-failsafe-plugin + ${maven-failsafe-plugin.version} + + + default + + integration-test + verify + + + @{argLine} -javaagent:${org.mockito:mockito-core:jar} + + **/*IT.java + + + + + + org.jacoco jacoco-maven-plugin @@ -180,6 +232,12 @@ prepare-agent + + prepare-agent-integration + + prepare-agent-integration + + report verify @@ -187,9 +245,15 @@ report + + report-integration + verify + + report-integration + + - \ No newline at end of file diff --git a/licensing-service-client/src/main/java/io/github/bsayli/licensing/client/adapter/LicensingServiceClientAdapter.java b/licensing-service-client/src/main/java/io/github/bsayli/licensing/client/adapter/LicensingServiceClientAdapter.java index bf6ecaf..1f31551 100644 --- a/licensing-service-client/src/main/java/io/github/bsayli/licensing/client/adapter/LicensingServiceClientAdapter.java +++ b/licensing-service-client/src/main/java/io/github/bsayli/licensing/client/adapter/LicensingServiceClientAdapter.java @@ -1,14 +1,14 @@ package io.github.bsayli.licensing.client.adapter; -import io.github.bsayli.licensing.client.common.contract.ApiClientResponse; import io.github.bsayli.licensing.client.generated.dto.IssueAccessRequest; import io.github.bsayli.licensing.client.generated.dto.LicenseAccessResponse; import io.github.bsayli.licensing.client.generated.dto.ValidateAccessRequest; +import io.github.bsayli.licensing.contract.api.ApiResponse; public interface LicensingServiceClientAdapter { - ApiClientResponse issueAccess(IssueAccessRequest request); + ApiResponse issueAccess(IssueAccessRequest request); - ApiClientResponse validateAccess( + ApiResponse validateAccess( String licenseToken, ValidateAccessRequest request); } diff --git a/licensing-service-client/src/main/java/io/github/bsayli/licensing/client/adapter/impl/LicensingServiceClientAdapterImpl.java b/licensing-service-client/src/main/java/io/github/bsayli/licensing/client/adapter/impl/LicensingServiceClientAdapterImpl.java index 70b891a..850acf0 100644 --- a/licensing-service-client/src/main/java/io/github/bsayli/licensing/client/adapter/impl/LicensingServiceClientAdapterImpl.java +++ b/licensing-service-client/src/main/java/io/github/bsayli/licensing/client/adapter/impl/LicensingServiceClientAdapterImpl.java @@ -1,12 +1,12 @@ package io.github.bsayli.licensing.client.adapter.impl; import io.github.bsayli.licensing.client.adapter.LicensingServiceClientAdapter; -import io.github.bsayli.licensing.client.common.contract.ApiClientResponse; import io.github.bsayli.licensing.client.common.core.ApiClientExecutor; import io.github.bsayli.licensing.client.generated.api.LicenseControllerApi; import io.github.bsayli.licensing.client.generated.dto.IssueAccessRequest; import io.github.bsayli.licensing.client.generated.dto.LicenseAccessResponse; import io.github.bsayli.licensing.client.generated.dto.ValidateAccessRequest; +import io.github.bsayli.licensing.contract.api.ApiResponse; import org.springframework.stereotype.Service; @Service @@ -21,13 +21,13 @@ public LicensingServiceClientAdapterImpl(LicenseControllerApi api, ApiClientExec } @Override - public ApiClientResponse issueAccess(IssueAccessRequest request) { + public ApiResponse issueAccess(IssueAccessRequest request) { return executor.handle( "issueAccess", LicenseAccessResponse.class, () -> api.createAccess(request)); } @Override - public ApiClientResponse validateAccess( + public ApiResponse validateAccess( String licenseToken, ValidateAccessRequest request) { return executor.handle( "validateAccess", diff --git a/licensing-service-client/src/main/java/io/github/bsayli/licensing/client/common/contract/ApiClientError.java b/licensing-service-client/src/main/java/io/github/bsayli/licensing/client/common/contract/ApiClientError.java deleted file mode 100644 index 74abb81..0000000 --- a/licensing-service-client/src/main/java/io/github/bsayli/licensing/client/common/contract/ApiClientError.java +++ /dev/null @@ -1,3 +0,0 @@ -package io.github.bsayli.licensing.client.common.contract; - -public record ApiClientError(String errorCode, String message) {} diff --git a/licensing-service-client/src/main/java/io/github/bsayli/licensing/client/common/contract/ApiClientResponse.java b/licensing-service-client/src/main/java/io/github/bsayli/licensing/client/common/contract/ApiClientResponse.java deleted file mode 100644 index bb34561..0000000 --- a/licensing-service-client/src/main/java/io/github/bsayli/licensing/client/common/contract/ApiClientResponse.java +++ /dev/null @@ -1,103 +0,0 @@ -package io.github.bsayli.licensing.client.common.contract; - -import java.util.List; -import java.util.Objects; - -public class ApiClientResponse { - - private Integer status; - private String statusText; - private String message; - private List errors; - private T data; - - public ApiClientResponse() {} - - public ApiClientResponse( - Integer status, String statusText, String message, List errors, T data) { - this.status = status; - this.statusText = statusText; - this.message = message; - this.errors = errors; - this.data = data; - } - - public static ApiClientResponse from( - Integer status, String statusText, String message, List errors, T data) { - return new ApiClientResponse<>(status, statusText, message, errors, data); - } - - public Integer getStatus() { - return status; - } - - public void setStatus(Integer status) { - this.status = status; - } - - public String getStatusText() { - return statusText; - } - - public void setStatusText(String statusText) { - this.statusText = statusText; - } - - public String getMessage() { - return message; - } - - public void setMessage(String message) { - this.message = message; - } - - public List getErrors() { - return errors; - } - - public void setErrors(List errors) { - this.errors = errors; - } - - public T getData() { - return data; - } - - public void setData(T data) { - this.data = data; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof ApiClientResponse that)) return false; - return Objects.equals(status, that.status) - && Objects.equals(statusText, that.statusText) - && Objects.equals(message, that.message) - && Objects.equals(errors, that.errors) - && Objects.equals(data, that.data); - } - - @Override - public int hashCode() { - return Objects.hash(status, statusText, message, errors, data); - } - - @Override - public String toString() { - return "ApiClientResponse{" - + "status=" - + status - + ", statusText='" - + statusText - + '\'' - + ", message='" - + message - + '\'' - + ", errors=" - + errors - + ", data=" - + data - + '}'; - } -} diff --git a/licensing-service-client/src/main/java/io/github/bsayli/licensing/client/common/core/ApiClientExecutor.java b/licensing-service-client/src/main/java/io/github/bsayli/licensing/client/common/core/ApiClientExecutor.java index b79c47a..2ec60ec 100644 --- a/licensing-service-client/src/main/java/io/github/bsayli/licensing/client/common/core/ApiClientExecutor.java +++ b/licensing-service-client/src/main/java/io/github/bsayli/licensing/client/common/core/ApiClientExecutor.java @@ -1,6 +1,6 @@ package io.github.bsayli.licensing.client.common.core; -import io.github.bsayli.licensing.client.common.contract.ApiClientResponse; +import io.github.bsayli.licensing.contract.api.ApiResponse; import java.util.function.Supplier; import org.springframework.stereotype.Component; import org.springframework.web.client.HttpStatusCodeException; @@ -14,8 +14,8 @@ public ApiClientExecutor(ResponseParser responseParser) { this.responseParser = responseParser; } - public ApiClientResponse handle( - String operation, Class clazz, Supplier> supplier) { + public ApiResponse handle( + String operation, Class clazz, Supplier> supplier) { try { return supplier.get(); } catch (HttpStatusCodeException e) { diff --git a/licensing-service-client/src/main/java/io/github/bsayli/licensing/client/common/core/ResponseParser.java b/licensing-service-client/src/main/java/io/github/bsayli/licensing/client/common/core/ResponseParser.java index 27c13cf..acbce80 100644 --- a/licensing-service-client/src/main/java/io/github/bsayli/licensing/client/common/core/ResponseParser.java +++ b/licensing-service-client/src/main/java/io/github/bsayli/licensing/client/common/core/ResponseParser.java @@ -1,8 +1,8 @@ package io.github.bsayli.licensing.client.common.core; import com.fasterxml.jackson.databind.ObjectMapper; -import io.github.bsayli.licensing.client.common.contract.ApiClientResponse; import io.github.bsayli.licensing.client.common.exception.ApiClientException; +import io.github.bsayli.licensing.contract.api.ApiResponse; import java.util.Optional; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatusCode; @@ -20,7 +20,7 @@ public ResponseParser(ObjectMapper objectMapper) { this.objectMapper = objectMapper; } - public ApiClientResponse parseOrThrow( + public ApiResponse parseOrThrow( String operation, HttpStatusCodeException e, Class targetClass) { String responseBody = e.getResponseBodyAsString(); @@ -43,7 +43,7 @@ public ApiClientResponse parseOrThrow( responseBody, objectMapper .getTypeFactory() - .constructParametricType(ApiClientResponse.class, targetClass)); + .constructParametricType(ApiResponse.class, targetClass)); } catch (Exception ex) { throw new ApiClientException( "[" + operation + "] Failed to parse JSON error response", diff --git a/licensing-service-client/src/main/resources/licensing-service-api-docs.yaml b/licensing-service-client/src/main/resources/licensing-service-api-docs.yaml index 8bbae5d..40ecadf 100644 --- a/licensing-service-client/src/main/resources/licensing-service-api-docs.yaml +++ b/licensing-service-client/src/main/resources/licensing-service-api-docs.yaml @@ -2,12 +2,12 @@ openapi: 3.1.0 info: title: Licensing Service API description: Licensing Service type-safe generic API responses with OpenAPI - version: 1.0.1 + version: 1.0.5 servers: - url: http://localhost:8081/licensing-service description: Licensing Service URL security: - - basicAuth: [ ] + - basicAuth: [] paths: /v1/licenses/access: post: @@ -96,13 +96,21 @@ components: type: string message: type: string + x-ignore-model: true ApiResponseLicenseAccessResponse: - allOf: - - $ref: "#/components/schemas/ApiResponse" - - type: object - properties: - data: - $ref: "#/components/schemas/LicenseAccessResponse" + type: object + properties: + status: + type: integer + format: int32 + message: + type: string + data: + $ref: "#/components/schemas/LicenseAccessResponse" + errors: + type: array + items: + $ref: "#/components/schemas/ApiError" x-api-wrapper: true x-api-wrapper-datatype: LicenseAccessResponse LicenseAccessResponse: @@ -144,42 +152,6 @@ components: - serviceId - serviceVersion - signature - ApiResponse: - type: object - properties: - status: - type: integer - format: int32 - message: - type: string - errors: - type: array - items: - type: object - properties: - errorCode: - type: string - message: - type: string - ApiResponseVoid: - type: object - properties: - status: - type: integer - format: int32 - message: - type: string - data: - type: object - errors: - type: array - items: - type: object - properties: - errorCode: - type: string - message: - type: string securitySchemes: basicAuth: type: http diff --git a/licensing-service-client/src/main/resources/openapi-templates/api_wrapper.mustache b/licensing-service-client/src/main/resources/openapi-templates/api_wrapper.mustache deleted file mode 100644 index f5cea39..0000000 --- a/licensing-service-client/src/main/resources/openapi-templates/api_wrapper.mustache +++ /dev/null @@ -1,3 +0,0 @@ -public class {{classname}} - extends io.github.bsayli.licensing.client.common.contract.ApiClientResponse<{{vendorExtensions.x-api-wrapper-datatype}}> { -} \ No newline at end of file diff --git a/licensing-service-client/src/main/resources/openapi-templates/model.mustache b/licensing-service-client/src/main/resources/openapi-templates/model.mustache deleted file mode 100644 index 6383fc6..0000000 --- a/licensing-service-client/src/main/resources/openapi-templates/model.mustache +++ /dev/null @@ -1,88 +0,0 @@ -{{>licenseInfo}} - -package {{package}}; - -{{#useReflectionEqualsHashCode}} -import org.apache.commons.lang3.builder.EqualsBuilder; -import org.apache.commons.lang3.builder.HashCodeBuilder; -{{/useReflectionEqualsHashCode}} -import java.util.Objects; -import java.util.Arrays; -{{#imports}} -import {{import}}; -{{/imports}} -{{#serializableModel}} -import java.io.Serializable; -{{/serializableModel}} -{{#jackson}} -import com.fasterxml.jackson.annotation.JsonPropertyOrder; -import com.fasterxml.jackson.annotation.JsonTypeName; -{{#withXml}} -import com.fasterxml.jackson.dataformat.xml.annotation.*; -{{/withXml}} -{{#vendorExtensions.x-has-readonly-properties}} -import com.fasterxml.jackson.annotation.JsonCreator; -{{/vendorExtensions.x-has-readonly-properties}} -{{/jackson}} -{{#withXml}} -import {{javaxPackage}}.xml.bind.annotation.*; -import {{javaxPackage}}.xml.bind.annotation.adapters.*; -import io.github.threetenjaxb.core.*; -{{/withXml}} -{{#jsonb}} -import java.lang.reflect.Type; -import {{javaxPackage}}.json.bind.annotation.JsonbTypeDeserializer; -import {{javaxPackage}}.json.bind.annotation.JsonbTypeSerializer; -import {{javaxPackage}}.json.bind.serializer.DeserializationContext; -import {{javaxPackage}}.json.bind.serializer.JsonbDeserializer; -import {{javaxPackage}}.json.bind.serializer.JsonbSerializer; -import {{javaxPackage}}.json.bind.serializer.SerializationContext; -import {{javaxPackage}}.json.stream.JsonGenerator; -import {{javaxPackage}}.json.stream.JsonParser; -import {{javaxPackage}}.json.bind.annotation.JsonbProperty; -{{#vendorExtensions.x-has-readonly-properties}} -import {{javaxPackage}}.json.bind.annotation.JsonbCreator; -{{/vendorExtensions.x-has-readonly-properties}} -{{/jsonb}} -{{#parcelableModel}} -import android.os.Parcelable; -import android.os.Parcel; -{{/parcelableModel}} -{{#useBeanValidation}} -import {{javaxPackage}}.validation.constraints.*; -import {{javaxPackage}}.validation.Valid; -{{/useBeanValidation}} -{{#performBeanValidation}} -import org.hibernate.validator.constraints.*; -{{/performBeanValidation}} -{{#supportUrlQuery}} -import java.io.UnsupportedEncodingException; -import java.net.URLEncoder; -import java.util.StringJoiner; -{{/supportUrlQuery}} - -{{#models}} -{{#model}} - -{{#vendorExtensions.x-api-wrapper}} - {{>api_wrapper}} -{{/vendorExtensions.x-api-wrapper}} - -{{^vendorExtensions.x-api-wrapper}} - {{#isEnum}} - {{>modelEnum}} - {{/isEnum}} - - {{^isEnum}} - {{#vendorExtensions.x-is-one-of-interface}} - {{>oneof_interface}} - {{/vendorExtensions.x-is-one-of-interface}} - - {{^vendorExtensions.x-is-one-of-interface}} - {{>pojo}} - {{/vendorExtensions.x-is-one-of-interface}} - {{/isEnum}} -{{/vendorExtensions.x-api-wrapper}} - -{{/model}} -{{/models}} \ No newline at end of file diff --git a/licensing-service-client/src/main/resources/openapi-templates/modelEnum.mustache b/licensing-service-client/src/main/resources/openapi-templates/modelEnum.mustache deleted file mode 100644 index 90cd7f8..0000000 --- a/licensing-service-client/src/main/resources/openapi-templates/modelEnum.mustache +++ /dev/null @@ -1,120 +0,0 @@ -{{#jackson}} -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonValue; -{{/jackson}} -{{#gson}} -import java.io.IOException; -import com.google.gson.TypeAdapter; -import com.google.gson.annotations.JsonAdapter; -import com.google.gson.stream.JsonReader; -import com.google.gson.stream.JsonWriter; -{{/gson}} -{{#isUri}} -import java.net.URI; -{{/isUri}} - -/** - * {{description}}{{^description}}Gets or Sets {{{name}}}{{/description}} - */ -{{#gson}} -@JsonAdapter({{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{{classname}}}{{/datatypeWithEnum}}.Adapter.class) -{{/gson}} -{{#jsonb}} -@JsonbTypeSerializer({{datatypeWithEnum}}.Serializer.class) -@JsonbTypeDeserializer({{datatypeWithEnum}}.Deserializer.class) -{{/jsonb}} -{{>additionalEnumTypeAnnotations}}public enum {{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{{classname}}}{{/datatypeWithEnum}}{{#vendorExtensions.x-implements}}{{#-first}} implements {{{.}}}{{/-first}}{{^-first}}, {{{.}}}{{/-first}}{{/vendorExtensions.x-implements}} { - {{#allowableValues}}{{#enumVars}} - {{#enumDescription}} - /** - * {{.}} - */ - {{/enumDescription}} - {{#withXml}} - @XmlEnumValue({{#isInteger}}"{{/isInteger}}{{#isDouble}}"{{/isDouble}}{{#isLong}}"{{/isLong}}{{#isFloat}}"{{/isFloat}}{{{value}}}{{#isInteger}}"{{/isInteger}}{{#isDouble}}"{{/isDouble}}{{#isLong}}"{{/isLong}}{{#isFloat}}"{{/isFloat}}) - {{/withXml}} - {{{name}}}({{{value}}}){{^-last}}, - {{/-last}}{{#-last}};{{/-last}}{{/enumVars}}{{/allowableValues}} - - private {{{dataType}}} value; - - {{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{{classname}}}{{/datatypeWithEnum}}({{{dataType}}} value) { - this.value = value; - } - -{{#jackson}} - @JsonValue -{{/jackson}} - public {{{dataType}}} getValue() { - return value; - } - - @Override - public String toString() { - return String.valueOf(value); - } - -{{#jackson}} - @JsonCreator -{{/jackson}} - public static {{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{{classname}}}{{/datatypeWithEnum}} fromValue({{{dataType}}} value) { - for ({{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{{classname}}}{{/datatypeWithEnum}} b : {{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{{classname}}}{{/datatypeWithEnum}}.values()) { - if (b.value.{{^isString}}equals{{/isString}}{{#isString}}{{#useEnumCaseInsensitive}}equalsIgnoreCase{{/useEnumCaseInsensitive}}{{^useEnumCaseInsensitive}}equals{{/useEnumCaseInsensitive}}{{/isString}}(value)) { - return b; - } - } - {{#isNullable}}return null;{{/isNullable}}{{^isNullable}}{{#enumUnknownDefaultCase}}{{#allowableValues}}{{#enumVars}}{{#-last}}return {{{name}}};{{/-last}}{{/enumVars}}{{/allowableValues}}{{/enumUnknownDefaultCase}}{{^enumUnknownDefaultCase}}throw new IllegalArgumentException("Unexpected value '" + value + "'");{{/enumUnknownDefaultCase}}{{/isNullable}} - } -{{#gson}} - - public static class Adapter extends TypeAdapter<{{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{{classname}}}{{/datatypeWithEnum}}> { - @Override - public void write(final JsonWriter jsonWriter, final {{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{{classname}}}{{/datatypeWithEnum}} enumeration) throws IOException { - jsonWriter.value(enumeration.getValue(){{#isUri}}.toASCIIString(){{/isUri}}); - } - - @Override - public {{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{{classname}}}{{/datatypeWithEnum}} read(final JsonReader jsonReader) throws IOException { - {{^isNumber}}{{{dataType}}}{{/isNumber}}{{#isNumber}}String{{/isNumber}} value = {{#isFloat}}(float){{/isFloat}}{{#isUri}}URI.create({{/isUri}}jsonReader.{{#isNumber}}nextString(){{/isNumber}}{{#isInteger}}nextInt(){{/isInteger}}{{#isUri}}nextString()){{/isUri}}{{^isNumber}}{{^isInteger}}{{^isUri}}{{#isFloat}}nextDouble{{/isFloat}}{{^isFloat}}next{{{dataType}}}{{/isFloat}}(){{/isUri}}{{/isInteger}}{{/isNumber}}; - return {{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{classname}}{{/datatypeWithEnum}}.fromValue({{#isNumber}}new BigDecimal({{/isNumber}}value{{#isNumber}}){{/isNumber}}); - } - } -{{/gson}} -{{#jsonb}} - - public static final class Deserializer implements JsonbDeserializer<{{datatypeWithEnum}}> { - @Override - public {{datatypeWithEnum}} deserialize(JsonParser parser, DeserializationContext ctx, Type rtType) { - for ({{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{{classname}}}{{/datatypeWithEnum}} b : {{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{{classname}}}{{/datatypeWithEnum}}.values()) { - if (String.valueOf(b.value).equals(parser.getString())) { - return b; - } - } - {{#useNullForUnknownEnumValue}}return null;{{/useNullForUnknownEnumValue}}{{^useNullForUnknownEnumValue}}throw new IllegalArgumentException("Unexpected value '" + parser.getString() + "'");{{/useNullForUnknownEnumValue}} - } - } - - public static final class Serializer implements JsonbSerializer<{{datatypeWithEnum}}> { - @Override - public void serialize({{datatypeWithEnum}} obj, JsonGenerator generator, SerializationContext ctx) { - generator.write(obj.value); - } - } -{{/jsonb}} -{{#supportUrlQuery}} - - /** - * Convert the instance into URL query string. - * - * @param prefix prefix of the query string - * @return URL query string - */ - public String toUrlQueryString(String prefix) { - if (prefix == null) { - prefix = ""; - } - - return String.format("%s=%s", prefix, this.toString()); - } -{{/supportUrlQuery}} -} diff --git a/licensing-service-client/src/main/resources/openapi-templates/oneof_interface.mustache b/licensing-service-client/src/main/resources/openapi-templates/oneof_interface.mustache deleted file mode 100644 index eadcb26..0000000 --- a/licensing-service-client/src/main/resources/openapi-templates/oneof_interface.mustache +++ /dev/null @@ -1,6 +0,0 @@ -{{>additionalOneOfTypeAnnotations}}{{>generatedAnnotation}}{{>typeInfoAnnotation}}{{>xmlAnnotation}} -public {{>sealed}}interface {{classname}} {{#vendorExtensions.x-implements}}{{#-first}}extends {{{.}}}{{/-first}}{{^-first}}, {{{.}}}{{/-first}}{{/vendorExtensions.x-implements}}{{>permits}}{ - {{#discriminator}} - public {{propertyType}} {{propertyGetter}}(); - {{/discriminator}} -} diff --git a/licensing-service-client/src/main/resources/openapi-templates/pojo.mustache b/licensing-service-client/src/main/resources/openapi-templates/pojo.mustache deleted file mode 100644 index 25974f2..0000000 --- a/licensing-service-client/src/main/resources/openapi-templates/pojo.mustache +++ /dev/null @@ -1,624 +0,0 @@ -/** - * {{description}}{{^description}}{{classname}}{{/description}}{{#isDeprecated}} - * @deprecated{{/isDeprecated}} - */{{#isDeprecated}} -@Deprecated{{/isDeprecated}} -{{#swagger1AnnotationLibrary}} -{{#description}} -@ApiModel(description = "{{{.}}}") -{{/description}} -{{/swagger1AnnotationLibrary}} -{{#swagger2AnnotationLibrary}} -{{#description}} -@Schema(description = "{{{.}}}") -{{/description}} -{{/swagger2AnnotationLibrary}} -{{#jackson}} -@JsonPropertyOrder({ -{{#vars}} - {{classname}}.JSON_PROPERTY_{{nameInSnakeCase}}{{^-last}},{{/-last}} -{{/vars}} -}) -{{#isClassnameSanitized}} -{{^hasDiscriminatorWithNonEmptyMapping}} -@JsonTypeName("{{name}}") -{{/hasDiscriminatorWithNonEmptyMapping}} -{{/isClassnameSanitized}} -{{/jackson}} -{{>additionalModelTypeAnnotations}}{{>generatedAnnotation}}{{#discriminator}}{{>typeInfoAnnotation}}{{/discriminator}}{{>xmlAnnotation}} -{{#vendorExtensions.x-class-extra-annotation}} -{{{vendorExtensions.x-class-extra-annotation}}} -{{/vendorExtensions.x-class-extra-annotation}} -public class {{classname}} {{#parent}}extends {{{.}}} {{/parent}}{{#vendorExtensions.x-implements}}{{#-first}}implements {{{.}}}{{/-first}}{{^-first}}, {{{.}}}{{/-first}}{{#-last}} {{/-last}}{{/vendorExtensions.x-implements}}{ -{{#serializableModel}} - private static final long serialVersionUID = 1L; - -{{/serializableModel}} - {{#vars}} - {{#isEnum}} - {{^isContainer}} -{{>modelInnerEnum}} - - {{/isContainer}} - {{#isContainer}} - {{#mostInnerItems}} -{{>modelInnerEnum}} - - {{/mostInnerItems}} - {{/isContainer}} - {{/isEnum}} - {{#gson}} - public static final String SERIALIZED_NAME_{{nameInSnakeCase}} = "{{baseName}}"; - {{/gson}} - {{#jackson}} - public static final String JSON_PROPERTY_{{nameInSnakeCase}} = "{{baseName}}"; - {{/jackson}} - {{#withXml}} - @Xml{{#isXmlAttribute}}Attribute{{/isXmlAttribute}}{{^isXmlAttribute}}Element{{/isXmlAttribute}}(name = "{{items.xmlName}}{{^items.xmlName}}{{xmlName}}{{^xmlName}}{{baseName}}{{/xmlName}}{{/items.xmlName}}"{{#xmlNamespace}}, namespace = "{{.}}"{{/xmlNamespace}}) - {{#isXmlWrapped}} - @XmlElementWrapper(name = "{{xmlName}}{{^xmlName}}{{baseName}}{{/xmlName}}"{{#xmlNamespace}}, namespace = "{{.}}"{{/xmlNamespace}}) - {{/isXmlWrapped}} - {{^isXmlAttribute}} - {{#isDateTime}} - @XmlJavaTypeAdapter(OffsetDateTimeXmlAdapter.class) - {{/isDateTime}} - {{/isXmlAttribute}} - {{/withXml}} - {{#gson}} - @SerializedName(SERIALIZED_NAME_{{nameInSnakeCase}}) - {{/gson}} - {{^isDiscriminator}} - {{>nullable_var_annotations}}{{! prevent indent}} - {{/isDiscriminator}} - {{#isDiscriminator}} - // The discriminator does not have Nullability-annotation since it is added during serialization by the @JsonTypeName annotation - {{/isDiscriminator}} - {{#vendorExtensions.x-field-extra-annotation}} - {{{vendorExtensions.x-field-extra-annotation}}} - {{/vendorExtensions.x-field-extra-annotation}} - {{#vendorExtensions.x-is-jackson-optional-nullable}} - {{#isContainer}} - {{#hasChildren}}protected{{/hasChildren}}{{^hasChildren}}private{{/hasChildren}} JsonNullable<{{{datatypeWithEnum}}}> {{name}} = JsonNullable.<{{{datatypeWithEnum}}}>undefined(); - {{/isContainer}} - {{^isContainer}} - {{#hasChildren}}protected{{/hasChildren}}{{^hasChildren}}private{{/hasChildren}} JsonNullable<{{{datatypeWithEnum}}}> {{name}} = JsonNullable.<{{{datatypeWithEnum}}}>{{#defaultValue}}of({{{.}}}){{/defaultValue}}{{^defaultValue}}undefined(){{/defaultValue}}; - {{/isContainer}} - {{/vendorExtensions.x-is-jackson-optional-nullable}} - {{^vendorExtensions.x-is-jackson-optional-nullable}} - {{#isContainer}} - {{#hasChildren}}protected{{/hasChildren}}{{^hasChildren}}private{{/hasChildren}} {{{datatypeWithEnum}}} {{name}}{{#defaultValue}} = {{{.}}}{{/defaultValue}}; - {{/isContainer}} - {{^isContainer}} - {{#hasChildren}}protected{{/hasChildren}}{{^hasChildren}}private{{/hasChildren}} {{{datatypeWithEnum}}} {{name}}{{#defaultValue}} = {{{.}}}{{/defaultValue}}; - {{/isContainer}} - {{/vendorExtensions.x-is-jackson-optional-nullable}} - - {{/vars}} - public {{classname}}() { - {{#parent}} - {{#parcelableModel}} - super();{{/parcelableModel}} - {{/parent}} - {{#gson}} - {{#discriminator}} - {{#discriminator.isEnum}} - this.{{{discriminatorName}}} = this.getClass().getSimpleName(); - {{/discriminator.isEnum}} - {{/discriminator}} - {{/gson}} - } - {{#vendorExtensions.x-has-readonly-properties}} - {{^withXml}} - /** - * Constructor with only readonly parameters{{#generateConstructorWithAllArgs}}{{^vendorExtensions.x-java-all-args-constructor}} and all parameters{{/vendorExtensions.x-java-all-args-constructor}}{{/generateConstructorWithAllArgs}} - */ - {{#jsonb}}@JsonbCreator{{/jsonb}}{{#jackson}}@JsonCreator{{/jackson}} - public {{classname}}( - {{#readOnlyVars}} - {{#jsonb}}@JsonbProperty(value = "{{baseName}}"{{^required}}, nullable = true{{/required}}){{/jsonb}}{{#jackson}}@JsonProperty(JSON_PROPERTY_{{nameInSnakeCase}}){{/jackson}} {{{datatypeWithEnum}}} {{name}}{{^-last}}, {{/-last}} - {{/readOnlyVars}} - ) { - this(); - {{#readOnlyVars}} - this.{{name}} = {{#vendorExtensions.x-is-jackson-optional-nullable}}{{name}} == null ? JsonNullable.<{{{datatypeWithEnum}}}>undefined() : JsonNullable.of({{name}}){{/vendorExtensions.x-is-jackson-optional-nullable}}{{^vendorExtensions.x-is-jackson-optional-nullable}}{{name}}{{/vendorExtensions.x-is-jackson-optional-nullable}}; - {{/readOnlyVars}} - } - {{/withXml}} - {{/vendorExtensions.x-has-readonly-properties}} -{{#vendorExtensions.x-java-all-args-constructor}} - - /** - * Constructor with all args parameters - */ - public {{classname}}({{#vendorExtensions.x-java-all-args-constructor-vars}}{{#jsonb}}@JsonbProperty(value = "{{baseName}}"{{^required}}, nullable = true{{/required}}){{/jsonb}}{{#jackson}}@JsonProperty(JSON_PROPERTY_{{nameInSnakeCase}}){{/jackson}} {{{datatypeWithEnum}}} {{name}}{{^-last}}, {{/-last}}{{/vendorExtensions.x-java-all-args-constructor-vars}}) { -{{#parent}} - super({{#parentVars}}{{name}}{{^-last}}, {{/-last}}{{/parentVars}}); -{{/parent}} - {{#vars}} - this.{{name}} = {{#vendorExtensions.x-is-jackson-optional-nullable}}{{name}} == null ? JsonNullable.<{{{datatypeWithEnum}}}>undefined() : JsonNullable.of({{name}}){{/vendorExtensions.x-is-jackson-optional-nullable}}{{^vendorExtensions.x-is-jackson-optional-nullable}}{{name}}{{/vendorExtensions.x-is-jackson-optional-nullable}}; -{{/vars}} - } -{{/vendorExtensions.x-java-all-args-constructor}} - -{{#vars}} - {{^isReadOnly}} - public {{classname}} {{name}}({{>nullable_var_annotations}} {{{datatypeWithEnum}}} {{name}}) { - {{#vendorExtensions.x-is-jackson-optional-nullable}}this.{{name}} = JsonNullable.<{{{datatypeWithEnum}}}>of({{name}});{{/vendorExtensions.x-is-jackson-optional-nullable}} - {{^vendorExtensions.x-is-jackson-optional-nullable}}this.{{name}} = {{name}};{{/vendorExtensions.x-is-jackson-optional-nullable}} - return this; - } - {{#isArray}} - - public {{classname}} add{{nameInPascalCase}}Item({{{items.datatypeWithEnum}}} {{name}}Item) { - {{#vendorExtensions.x-is-jackson-optional-nullable}} - if (this.{{name}} == null || !this.{{name}}.isPresent()) { - this.{{name}} = JsonNullable.<{{{datatypeWithEnum}}}>of({{{defaultValue}}}{{^defaultValue}}new {{#uniqueItems}}LinkedHashSet{{/uniqueItems}}{{^uniqueItems}}ArrayList{{/uniqueItems}}<>(){{/defaultValue}}); - } - try { - this.{{name}}.get().add({{name}}Item); - } catch (java.util.NoSuchElementException e) { - // this can never happen, as we make sure above that the value is present - } - return this; - {{/vendorExtensions.x-is-jackson-optional-nullable}} - {{^vendorExtensions.x-is-jackson-optional-nullable}} - if (this.{{name}} == null) { - this.{{name}} = {{{defaultValue}}}{{^defaultValue}}new {{#uniqueItems}}LinkedHashSet{{/uniqueItems}}{{^uniqueItems}}ArrayList{{/uniqueItems}}<>(){{/defaultValue}}; - } - this.{{name}}.add({{name}}Item); - return this; - {{/vendorExtensions.x-is-jackson-optional-nullable}} - } - {{/isArray}} - {{#isMap}} - - public {{classname}} put{{nameInPascalCase}}Item(String key, {{{items.datatypeWithEnum}}} {{name}}Item) { - {{#vendorExtensions.x-is-jackson-optional-nullable}} - if (this.{{name}} == null || !this.{{name}}.isPresent()) { - this.{{name}} = JsonNullable.<{{{datatypeWithEnum}}}>of({{{defaultValue}}}{{^defaultValue}}new HashMap<>(){{/defaultValue}}); - } - try { - this.{{name}}.get().put(key, {{name}}Item); - } catch (java.util.NoSuchElementException e) { - // this can never happen, as we make sure above that the value is present - } - return this; - {{/vendorExtensions.x-is-jackson-optional-nullable}} - {{^vendorExtensions.x-is-jackson-optional-nullable}} - {{^required}} - if (this.{{name}} == null) { - this.{{name}} = {{{defaultValue}}}{{^defaultValue}}new HashMap<>(){{/defaultValue}}; - } - {{/required}} - this.{{name}}.put(key, {{name}}Item); - return this; - {{/vendorExtensions.x-is-jackson-optional-nullable}} - } - {{/isMap}} - - {{/isReadOnly}} - /** - {{#description}} - * {{.}} - {{/description}} - {{^description}} - * Get {{name}} - {{/description}} - {{#minimum}} - * minimum: {{.}} - {{/minimum}} - {{#maximum}} - * maximum: {{.}} - {{/maximum}} - * @return {{name}} - {{#deprecated}} - * @deprecated - {{/deprecated}} - */ -{{#deprecated}} - @Deprecated -{{/deprecated}} - {{>nullable_var_annotations}}{{! prevent indent}} -{{#jsonb}} - @JsonbProperty("{{baseName}}") -{{/jsonb}} -{{#useBeanValidation}} -{{>beanValidation}} - -{{/useBeanValidation}} -{{#swagger1AnnotationLibrary}} - @ApiModelProperty({{#example}}example = "{{{.}}}", {{/example}}{{#required}}required = {{required}}, {{/required}}value = "{{{description}}}") -{{/swagger1AnnotationLibrary}} -{{#swagger2AnnotationLibrary}} - @Schema({{#example}}example = "{{{.}}}", {{/example}}requiredMode = {{#required}}Schema.RequiredMode.REQUIRED{{/required}}{{^required}}Schema.RequiredMode.NOT_REQUIRED{{/required}}, description = "{{{description}}}") -{{/swagger2AnnotationLibrary}} -{{#vendorExtensions.x-extra-annotation}} - {{{vendorExtensions.x-extra-annotation}}} -{{/vendorExtensions.x-extra-annotation}} -{{#vendorExtensions.x-is-jackson-optional-nullable}} - {{!unannotated, Jackson would pick this up automatically and add it *in addition* to the _JsonNullable getter field}} - @JsonIgnore -{{/vendorExtensions.x-is-jackson-optional-nullable}} -{{^vendorExtensions.x-is-jackson-optional-nullable}}{{#jackson}}{{> jackson_annotations}}{{/jackson}}{{/vendorExtensions.x-is-jackson-optional-nullable}} - public {{{datatypeWithEnum}}} {{getter}}() { - {{#vendorExtensions.x-is-jackson-optional-nullable}} - {{#isReadOnly}}{{! A readonly attribute doesn't have setter => jackson will set null directly if explicitly returned by API, so make sure we have an empty JsonNullable}} - if ({{name}} == null) { - {{name}} = JsonNullable.<{{{datatypeWithEnum}}}>{{#defaultValue}}of({{{.}}}){{/defaultValue}}{{^defaultValue}}undefined(){{/defaultValue}}; - } - {{/isReadOnly}} - return {{name}}.orElse(null); - {{/vendorExtensions.x-is-jackson-optional-nullable}} - {{^vendorExtensions.x-is-jackson-optional-nullable}} - return {{name}}; - {{/vendorExtensions.x-is-jackson-optional-nullable}} - } - - {{#vendorExtensions.x-is-jackson-optional-nullable}} -{{> jackson_annotations}} - - public JsonNullable<{{{datatypeWithEnum}}}> {{getter}}_JsonNullable() { - return {{name}}; - } - {{/vendorExtensions.x-is-jackson-optional-nullable}}{{#vendorExtensions.x-is-jackson-optional-nullable}} - @JsonProperty(JSON_PROPERTY_{{nameInSnakeCase}}) - {{#isReadOnly}}private{{/isReadOnly}}{{^isReadOnly}}public{{/isReadOnly}} void {{setter}}_JsonNullable(JsonNullable<{{{datatypeWithEnum}}}> {{name}}) { - {{! For getters/setters that have name differing from attribute name, we must include setter (albeit private) for jackson to be able to set the attribute}} - this.{{name}} = {{name}}; - } - {{/vendorExtensions.x-is-jackson-optional-nullable}} - - {{^isReadOnly}} -{{#vendorExtensions.x-setter-extra-annotation}} {{{vendorExtensions.x-setter-extra-annotation}}} -{{/vendorExtensions.x-setter-extra-annotation}}{{#jackson}}{{^vendorExtensions.x-is-jackson-optional-nullable}}{{> jackson_annotations}}{{/vendorExtensions.x-is-jackson-optional-nullable}}{{/jackson}} public void {{setter}}({{>nullable_var_annotations}} {{{datatypeWithEnum}}} {{name}}) { - {{#vendorExtensions.x-is-jackson-optional-nullable}} - this.{{name}} = JsonNullable.<{{{datatypeWithEnum}}}>of({{name}}); - {{/vendorExtensions.x-is-jackson-optional-nullable}} - {{^vendorExtensions.x-is-jackson-optional-nullable}} - this.{{name}} = {{name}}; - {{/vendorExtensions.x-is-jackson-optional-nullable}} - } - {{/isReadOnly}} - - {{/vars}} - {{#parent}} - {{#readWriteVars}} - {{#isOverridden}} - @Override - public {{classname}} {{name}}({{>nullable_var_annotations}} {{{datatypeWithEnum}}} {{name}}) { - {{#vendorExtensions.x-is-jackson-optional-nullable}} - this.{{setter}}(JsonNullable.<{{{datatypeWithEnum}}}>of({{name}})); - {{/vendorExtensions.x-is-jackson-optional-nullable}} - {{^vendorExtensions.x-is-jackson-optional-nullable}} - this.{{setter}}({{name}}); - {{/vendorExtensions.x-is-jackson-optional-nullable}} - return this; - } - - {{/isOverridden}} - {{/readWriteVars}} - {{/parent}} - @Override - public boolean equals(Object o) { - {{#useReflectionEqualsHashCode}} - return EqualsBuilder.reflectionEquals(this, o, false, null, true); - {{/useReflectionEqualsHashCode}} - {{^useReflectionEqualsHashCode}} - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - }{{#hasVars}} - {{classname}} {{classVarName}} = ({{classname}}) o; - return {{#vars}}{{#vendorExtensions.x-is-jackson-optional-nullable}}equalsNullable(this.{{name}}, {{classVarName}}.{{name}}){{/vendorExtensions.x-is-jackson-optional-nullable}}{{^vendorExtensions.x-is-jackson-optional-nullable}}{{#isByteArray}}Arrays{{/isByteArray}}{{^isByteArray}}Objects{{/isByteArray}}.equals(this.{{name}}, {{classVarName}}.{{name}}){{/vendorExtensions.x-is-jackson-optional-nullable}}{{^-last}} && - {{/-last}}{{/vars}}{{#parent}} && - super.equals(o){{/parent}};{{/hasVars}}{{^hasVars}} - return {{#parent}}super.equals(o){{/parent}}{{^parent}}true{{/parent}};{{/hasVars}} - {{/useReflectionEqualsHashCode}} - }{{#vendorExtensions.x-jackson-optional-nullable-helpers}} - - private static boolean equalsNullable(JsonNullable a, JsonNullable b) { - return a == b || (a != null && b != null && a.isPresent() && b.isPresent() && Objects.deepEquals(a.get(), b.get())); - }{{/vendorExtensions.x-jackson-optional-nullable-helpers}} - - @Override - public int hashCode() { - {{#useReflectionEqualsHashCode}} - return HashCodeBuilder.reflectionHashCode(this); - {{/useReflectionEqualsHashCode}} - {{^useReflectionEqualsHashCode}} - return Objects.hash({{#vars}}{{#vendorExtensions.x-is-jackson-optional-nullable}}hashCodeNullable({{name}}){{/vendorExtensions.x-is-jackson-optional-nullable}}{{^vendorExtensions.x-is-jackson-optional-nullable}}{{^isByteArray}}{{name}}{{/isByteArray}}{{#isByteArray}}Arrays.hashCode({{name}}){{/isByteArray}}{{/vendorExtensions.x-is-jackson-optional-nullable}}{{^-last}}, {{/-last}}{{/vars}}{{#parent}}{{#hasVars}}, {{/hasVars}}super.hashCode(){{/parent}}); - {{/useReflectionEqualsHashCode}} - }{{#vendorExtensions.x-jackson-optional-nullable-helpers}} - - private static int hashCodeNullable(JsonNullable a) { - if (a == null) { - return 1; - } - return a.isPresent() ? Arrays.deepHashCode(new Object[]{a.get()}) : 31; - }{{/vendorExtensions.x-jackson-optional-nullable-helpers}} - - @Override - public String toString() { - StringBuilder sb = new StringBuilder(); - sb.append("class {{classname}} {\n"); - {{#parent}} - sb.append(" ").append(toIndentedString(super.toString())).append("\n"); - {{/parent}} - {{#vars}} - sb.append(" {{name}}: ").append({{#isPassword}}"*"{{/isPassword}}{{^isPassword}}toIndentedString({{name}}){{/isPassword}}).append("\n"); - {{/vars}} - sb.append("}"); - return sb.toString(); - } - - /** - * Convert the given object to string with each line indented by 4 spaces - * (except the first line). - */ - private{{#jsonb}} static{{/jsonb}} String toIndentedString(Object o) { - if (o == null) { - return "null"; - } - return o.toString().replace("\n", "\n "); - } -{{#supportUrlQuery}} - - /** - * Convert the instance into URL query string. - * - * @return URL query string - */ - public String toUrlQueryString() { - return toUrlQueryString(null); - } - - /** - * Convert the instance into URL query string. - * - * @param prefix prefix of the query string - * @return URL query string - */ - public String toUrlQueryString(String prefix) { - String suffix = ""; - String containerSuffix = ""; - String containerPrefix = ""; - if (prefix == null) { - // style=form, explode=true, e.g. /pet?name=cat&type=manx - prefix = ""; - } else { - // deepObject style e.g. /pet?id[name]=cat&id[type]=manx - prefix = prefix + "["; - suffix = "]"; - containerSuffix = "]"; - containerPrefix = "["; - } - - StringJoiner joiner = new StringJoiner("&"); - - {{#allVars}} - // add `{{baseName}}` to the URL query string - {{#isArray}} - {{#items.isPrimitiveType}} - {{#uniqueItems}} - if ({{getter}}() != null) { - int i = 0; - for ({{{items.datatypeWithEnum}}} _item : {{getter}}()) { - try { - joiner.add(String.format("%s{{baseName}}%s%s=%s", prefix, suffix, - "".equals(suffix) ? "" : String.format("%s%d%s", containerPrefix, i, containerSuffix), - URLEncoder.encode(String.valueOf(_item), "UTF-8").replaceAll("\\+", "%20"))); - } catch (UnsupportedEncodingException e) { - // Should never happen, UTF-8 is always supported - throw new RuntimeException(e); - } - } - i++; - } - {{/uniqueItems}} - {{^uniqueItems}} - if ({{getter}}() != null) { - for (int i = 0; i < {{getter}}().size(); i++) { - try { - joiner.add(String.format("%s{{baseName}}%s%s=%s", prefix, suffix, - "".equals(suffix) ? "" : String.format("%s%d%s", containerPrefix, i, containerSuffix), - URLEncoder.encode(String.valueOf({{getter}}().get(i)), "UTF-8").replaceAll("\\+", "%20"))); - } catch (UnsupportedEncodingException e) { - // Should never happen, UTF-8 is always supported - throw new RuntimeException(e); - } - } - } - {{/uniqueItems}} - {{/items.isPrimitiveType}} - {{^items.isPrimitiveType}} - {{#items.isModel}} - {{#uniqueItems}} - if ({{getter}}() != null) { - int i = 0; - for ({{{items.dataType}}} _item : {{getter}}()) { - if (_item != null) { - joiner.add(_item.toUrlQueryString(String.format("%s{{baseName}}%s%s", prefix, suffix, - "".equals(suffix) ? "" : String.format("%s%d%s", containerPrefix, i, containerSuffix)))); - } - } - i++; - } - {{/uniqueItems}} - {{^uniqueItems}} - if ({{getter}}() != null) { - for (int i = 0; i < {{getter}}().size(); i++) { - if ({{getter}}().get(i) != null) { - joiner.add({{getter}}().get(i).toUrlQueryString(String.format("%s{{baseName}}%s%s", prefix, suffix, - "".equals(suffix) ? "" : String.format("%s%d%s", containerPrefix, i, containerSuffix)))); - } - } - } - {{/uniqueItems}} - {{/items.isModel}} - {{^items.isModel}} - {{#uniqueItems}} - if ({{getter}}() != null) { - int i = 0; - for ({{{items.dataType}}} _item : {{getter}}()) { - if (_item != null) { - try { - joiner.add(String.format("%s{{baseName}}%s%s=%s", prefix, suffix, - "".equals(suffix) ? "" : String.format("%s%d%s", containerPrefix, i, containerSuffix), - URLEncoder.encode(String.valueOf(_item), "UTF-8").replaceAll("\\+", "%20"))); - } catch (UnsupportedEncodingException e) { - // Should never happen, UTF-8 is always supported - throw new RuntimeException(e); - } - } - i++; - } - } - {{/uniqueItems}} - {{^uniqueItems}} - if ({{getter}}() != null) { - for (int i = 0; i < {{getter}}().size(); i++) { - if ({{getter}}().get(i) != null) { - try { - joiner.add(String.format("%s{{baseName}}%s%s=%s", prefix, suffix, - "".equals(suffix) ? "" : String.format("%s%d%s", containerPrefix, i, containerSuffix), - URLEncoder.encode(String.valueOf({{getter}}().get(i)), "UTF-8").replaceAll("\\+", "%20"))); - } catch (UnsupportedEncodingException e) { - // Should never happen, UTF-8 is always supported - throw new RuntimeException(e); - } - } - } - } - {{/uniqueItems}} - {{/items.isModel}} - {{/items.isPrimitiveType}} - {{/isArray}} - {{^isArray}} - {{#isMap}} - {{^items.isModel}} - if ({{getter}}() != null) { - for (String _key : {{getter}}().keySet()) { - try { - joiner.add(String.format("%s{{baseName}}%s%s=%s", prefix, suffix, - "".equals(suffix) ? "" : String.format("%s%d%s", containerPrefix, _key, containerSuffix), - {{getter}}().get(_key), URLEncoder.encode(String.valueOf({{getter}}().get(_key)), "UTF-8").replaceAll("\\+", "%20"))); - } catch (UnsupportedEncodingException e) { - // Should never happen, UTF-8 is always supported - throw new RuntimeException(e); - } - } - } - {{/items.isModel}} - {{#items.isModel}} - if ({{getter}}() != null) { - for (String _key : {{getter}}().keySet()) { - if ({{getter}}().get(_key) != null) { - joiner.add({{getter}}().get(_key).toUrlQueryString(String.format("%s{{baseName}}%s%s", prefix, suffix, - "".equals(suffix) ? "" : String.format("%s%d%s", containerPrefix, _key, containerSuffix)))); - } - } - } - {{/items.isModel}} - {{/isMap}} - {{^isMap}} - {{#isPrimitiveType}} - if ({{getter}}() != null) { - try { - joiner.add(String.format("%s{{{baseName}}}%s=%s", prefix, suffix, URLEncoder.encode(String.valueOf({{{getter}}}()), "UTF-8").replaceAll("\\+", "%20"))); - } catch (UnsupportedEncodingException e) { - // Should never happen, UTF-8 is always supported - throw new RuntimeException(e); - } - } - {{/isPrimitiveType}} - {{^isPrimitiveType}} - {{#isModel}} - if ({{getter}}() != null) { - joiner.add({{getter}}().toUrlQueryString(prefix + "{{{baseName}}}" + suffix)); - } - {{/isModel}} - {{^isModel}} - if ({{getter}}() != null) { - try { - joiner.add(String.format("%s{{{baseName}}}%s=%s", prefix, suffix, URLEncoder.encode(String.valueOf({{{getter}}}()), "UTF-8").replaceAll("\\+", "%20"))); - } catch (UnsupportedEncodingException e) { - // Should never happen, UTF-8 is always supported - throw new RuntimeException(e); - } - } - {{/isModel}} - {{/isPrimitiveType}} - {{/isMap}} - {{/isArray}} - - {{/allVars}} - return joiner.toString(); - } -{{/supportUrlQuery}} -{{#parcelableModel}} - - public void writeToParcel(Parcel out, int flags) { -{{#model}} -{{#isArray}} - out.writeList(this); -{{/isArray}} -{{^isArray}} -{{#parent}} - super.writeToParcel(out, flags); -{{/parent}} -{{#vars}} - out.writeValue({{name}}); -{{/vars}} -{{/isArray}} -{{/model}} - } - - {{classname}}(Parcel in) { -{{#isArray}} - in.readTypedList(this, {{arrayModelType}}.CREATOR); -{{/isArray}} -{{^isArray}} -{{#parent}} - super(in); -{{/parent}} -{{#vars}} -{{#isPrimitiveType}} - {{name}} = ({{{datatypeWithEnum}}})in.readValue(null); -{{/isPrimitiveType}} -{{^isPrimitiveType}} - {{name}} = ({{{datatypeWithEnum}}})in.readValue({{complexType}}.class.getClassLoader()); -{{/isPrimitiveType}} -{{/vars}} -{{/isArray}} - } - - public int describeContents() { - return 0; - } - - public static final Parcelable.Creator<{{classname}}> CREATOR = new Parcelable.Creator<{{classname}}>() { - public {{classname}} createFromParcel(Parcel in) { -{{#model}} -{{#isArray}} - {{classname}} result = new {{classname}}(); - result.addAll(in.readArrayList({{arrayModelType}}.class.getClassLoader())); - return result; -{{/isArray}} -{{^isArray}} - return new {{classname}}(in); -{{/isArray}} -{{/model}} - } - public {{classname}}[] newArray(int size) { - return new {{classname}}[size]; - } - }; -{{/parcelableModel}} -{{#generateBuilders}} - - {{>javaBuilder}}{{! prevent indent}} -{{/generateBuilders}} - -} diff --git a/licensing-service-client/src/main/resources/openapi-templates/sealed.mustache b/licensing-service-client/src/main/resources/openapi-templates/sealed.mustache deleted file mode 100644 index 8e5076b..0000000 --- a/licensing-service-client/src/main/resources/openapi-templates/sealed.mustache +++ /dev/null @@ -1 +0,0 @@ -{{#useSealedOneOfInterfaces}}{{#vendorExtensions.x-is-one-of-interface}}{{#permits.0}}sealed {{/permits.0}}{{/vendorExtensions.x-is-one-of-interface}}{{^permits.0}}{{#vendorExtensions.x-implements}}final {{/vendorExtensions.x-implements}}{{/permits.0}}{{/useSealedOneOfInterfaces}} \ No newline at end of file diff --git a/licensing-service-client/src/test/java/io/github/bsayli/licensing/client/adapter/LicensingServiceClientAdapterIT.java b/licensing-service-client/src/test/java/io/github/bsayli/licensing/client/adapter/LicensingServiceClientAdapterIT.java index bca201c..ea34e0f 100644 --- a/licensing-service-client/src/test/java/io/github/bsayli/licensing/client/adapter/LicensingServiceClientAdapterIT.java +++ b/licensing-service-client/src/test/java/io/github/bsayli/licensing/client/adapter/LicensingServiceClientAdapterIT.java @@ -5,12 +5,12 @@ import com.fasterxml.jackson.databind.ObjectMapper; import io.github.bsayli.licensing.client.adapter.config.LicensingServiceApiClientConfig; import io.github.bsayli.licensing.client.adapter.impl.LicensingServiceClientAdapterImpl; -import io.github.bsayli.licensing.client.common.contract.ApiClientResponse; import io.github.bsayli.licensing.client.common.core.ApiClientExecutor; import io.github.bsayli.licensing.client.common.core.ResponseParser; import io.github.bsayli.licensing.client.generated.dto.IssueAccessRequest; import io.github.bsayli.licensing.client.generated.dto.LicenseAccessResponse; import io.github.bsayli.licensing.client.generated.dto.ValidateAccessRequest; +import io.github.bsayli.licensing.contract.api.ApiResponse; import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.MockWebServer; import okhttp3.mockwebserver.RecordedRequest; @@ -76,7 +76,7 @@ void issueAccess_shouldReturn200_andMappedBody() throws Exception { .licenseKey("BSAYLI~RND~ENC") .signature("BASE64SIG"); - ApiClientResponse resp = adapter.issueAccess(req); + ApiResponse resp = adapter.issueAccess(req); assertNotNull(resp); assertEquals(200, resp.getStatus()); @@ -120,7 +120,7 @@ void validateAccess_shouldReturn200_andMappedBody_andSendHeader() throws Excepti .signature("BASE64SIG"); String jwt = "jwt-123"; - ApiClientResponse resp = adapter.validateAccess(jwt, req); + ApiResponse resp = adapter.validateAccess(jwt, req); assertNotNull(resp); assertEquals(200, resp.getStatus()); diff --git a/licensing-service-sdk-cli/Dockerfile b/licensing-service-sdk-cli/Dockerfile index 40b8a9a..c94f0f9 100644 --- a/licensing-service-sdk-cli/Dockerfile +++ b/licensing-service-sdk-cli/Dockerfile @@ -20,9 +20,9 @@ RUN apt-get update && \ LABEL org.opencontainers.image.title="licensing-service-sdk-cli" \ org.opencontainers.image.description="CLI for Licensing Service SDK" \ - org.opencontainers.image.version="1.0.1" \ + org.opencontainers.image.version="1.0.5" \ org.opencontainers.image.source="https://github.com/bsayli/licensing" \ - org.opencontainers.image.licenses="Apache-2.0" + org.opencontainers.image.licenses="MIT" COPY --from=builder --chown=appuser:appuser /app/target/*.jar /app/licensing-service-sdk-cli.jar diff --git a/licensing-service-sdk-cli/README.md b/licensing-service-sdk-cli/README.md index dd43c4d..e581d2a 100644 --- a/licensing-service-sdk-cli/README.md +++ b/licensing-service-sdk-cli/README.md @@ -64,7 +64,7 @@ Default values can also be injected from environment variables: ## Example ```bash -java -jar target/licensing-service-sdk-cli-1.0.1.jar \ +java -jar target/licensing-service-sdk-cli-1.0.5.jar \ -k 'BSAYLI.AQA3-gCQ66DQsfC0LwnAO8DTjKgad7DhPaOtCf7WG2bFUoK9pmScIhhCf2S-D0j8g4jC7nlFrLDpuM0ezEoDQc79SizxxEIUGN9YUhTNBW7iRQ' \ -s crm \ -v 1.5.0 \ diff --git a/licensing-service-sdk-cli/pom.xml b/licensing-service-sdk-cli/pom.xml index 2101679..ea74551 100644 --- a/licensing-service-sdk-cli/pom.xml +++ b/licensing-service-sdk-cli/pom.xml @@ -4,32 +4,39 @@ 4.0.0 io.github.bsayli licensing-service-sdk-cli - 1.0.4 + ${licensing-api-contract.version} licensing-service-sdk-cli Licensing Service Sdk Cli + UTF-8 + UTF-8 21 4.7.6 5.5 - 2.19.2 + 2.21.3 2.0.17 - 1.5.18 - UTF-8 - UTF-8 + 1.5.32 5.13.4 3.5.3 - 0.8.13 - 3.5.5 - 3.14.0 + 0.8.14 + 3.5.14 + 3.15.0 5.19.0 5.2.0 5.19.0 + 1.0.5 + + io.github.bsayli + licensing-api-contract + ${licensing-api-contract.version} + + info.picocli picocli diff --git a/licensing-service-sdk-cli/src/main/java/io/github/bsayli/licensing/sdk/cli/model/ApiError.java b/licensing-service-sdk-cli/src/main/java/io/github/bsayli/licensing/sdk/cli/model/ApiError.java deleted file mode 100644 index e76bdfe..0000000 --- a/licensing-service-sdk-cli/src/main/java/io/github/bsayli/licensing/sdk/cli/model/ApiError.java +++ /dev/null @@ -1,3 +0,0 @@ -package io.github.bsayli.licensing.sdk.cli.model; - -public record ApiError(String errorCode, String message) {} diff --git a/licensing-service-sdk-cli/src/main/java/io/github/bsayli/licensing/sdk/cli/model/ApiResponse.java b/licensing-service-sdk-cli/src/main/java/io/github/bsayli/licensing/sdk/cli/model/ApiResponse.java deleted file mode 100644 index e0a6666..0000000 --- a/licensing-service-sdk-cli/src/main/java/io/github/bsayli/licensing/sdk/cli/model/ApiResponse.java +++ /dev/null @@ -1,5 +0,0 @@ -package io.github.bsayli.licensing.sdk.cli.model; - -import java.util.List; - -public record ApiResponse(int status, String message, T data, List errors) {} diff --git a/licensing-service-sdk-cli/src/main/java/io/github/bsayli/licensing/sdk/cli/service/impl/LicenseSdkClientServiceImpl.java b/licensing-service-sdk-cli/src/main/java/io/github/bsayli/licensing/sdk/cli/service/impl/LicenseSdkClientServiceImpl.java index dabf251..860721a 100644 --- a/licensing-service-sdk-cli/src/main/java/io/github/bsayli/licensing/sdk/cli/service/impl/LicenseSdkClientServiceImpl.java +++ b/licensing-service-sdk-cli/src/main/java/io/github/bsayli/licensing/sdk/cli/service/impl/LicenseSdkClientServiceImpl.java @@ -1,7 +1,7 @@ package io.github.bsayli.licensing.sdk.cli.service.impl; import com.fasterxml.jackson.databind.ObjectMapper; -import io.github.bsayli.licensing.sdk.cli.model.ApiResponse; +import io.github.bsayli.licensing.contract.api.ApiResponse; import io.github.bsayli.licensing.sdk.cli.model.LicenseAccessRequest; import io.github.bsayli.licensing.sdk.cli.model.LicenseSdkClientProperties; import io.github.bsayli.licensing.sdk.cli.model.LicenseToken; @@ -121,21 +121,22 @@ private ApiResponse parseApiResponseLicenseToken(String json) { } private boolean isSuccess(ApiResponse api) { - return api.status() == 200 && api.data() != null && api.data().licenseToken() != null; + return api.getStatus() == 200 && api.getData() != null && api.getData().licenseToken() != null; } private void logSuccess(ApiResponse api) { log.info("License validated successfully."); - log.info("Token: {}", api.data().licenseToken()); - if (api.message() != null && !api.message().isBlank()) { - log.info("Message: {}", api.message()); + log.info("Token: {}", api.getData().licenseToken()); + if (api.getMessage() != null && !api.getMessage().isBlank()) { + log.info("Message: {}", api.getMessage()); } } private void logFailure(ApiResponse api) { - log.error("License validation failed. status={}, message={}", api.status(), api.message()); - if (api.errors() != null) { - api.errors() + log.error( + "License validation failed. status={}, message={}", api.getStatus(), api.getMessage()); + if (api.getErrors() != null) { + api.getErrors() .forEach(err -> log.error("errorCode={}, message={}", err.errorCode(), err.message())); } } diff --git a/licensing-service-sdk/Dockerfile b/licensing-service-sdk/Dockerfile index 1fd11c5..429245e 100644 --- a/licensing-service-sdk/Dockerfile +++ b/licensing-service-sdk/Dockerfile @@ -1,9 +1,14 @@ FROM maven:3.9.11-eclipse-temurin-21-noble AS builder WORKDIR /build +COPY licensing-api-contract/pom.xml licensing-api-contract/pom.xml COPY licensing-service-client/pom.xml licensing-service-client/pom.xml COPY licensing-service-sdk/pom.xml licensing-service-sdk/pom.xml +COPY licensing-api-contract/ licensing-api-contract/ +RUN --mount=type=cache,target=/root/.m2 \ + mvn -q -DskipTests -f licensing-api-contract/pom.xml install + RUN --mount=type=cache,target=/root/.m2 \ mvn -q -DskipTests -f licensing-service-client/pom.xml dependency:go-offline @@ -29,7 +34,7 @@ RUN apt-get update && \ LABEL org.opencontainers.image.title="licensing-service-sdk" \ org.opencontainers.image.description="Licensing Service SDK (Spring Boot)" \ - org.opencontainers.image.version="1.0.1" \ + org.opencontainers.image.version="1.0.5" \ org.opencontainers.image.source="https://github.com/bsayli/licensing" \ org.opencontainers.image.licenses="MIT" diff --git a/licensing-service-sdk/pom.xml b/licensing-service-sdk/pom.xml index 08147b0..d87998c 100644 --- a/licensing-service-sdk/pom.xml +++ b/licensing-service-sdk/pom.xml @@ -6,12 +6,12 @@ org.springframework.boot spring-boot-starter-parent - 3.5.5 + 3.5.14 io.github.bsayli licensing-service-sdk - 1.0.4 + 1.0.5 licensing-service-sdk Licensing Service SDK App @@ -20,10 +20,10 @@ UTF-8 UTF-8 21 - 2.8.13 - 3.18.0 - 1.0.4 - 0.8.13 + 2.8.17 + 3.20.0 + 1.0.5 + 0.8.14 diff --git a/licensing-service-sdk/src/main/java/io/github/bsayli/licensing/sdk/api/controller/LicenseController.java b/licensing-service-sdk/src/main/java/io/github/bsayli/licensing/sdk/api/controller/LicenseController.java index aac028a..6056740 100644 --- a/licensing-service-sdk/src/main/java/io/github/bsayli/licensing/sdk/api/controller/LicenseController.java +++ b/licensing-service-sdk/src/main/java/io/github/bsayli/licensing/sdk/api/controller/LicenseController.java @@ -1,8 +1,8 @@ package io.github.bsayli.licensing.sdk.api.controller; +import io.github.bsayli.licensing.contract.api.ApiResponse; import io.github.bsayli.licensing.sdk.api.dto.LicenseAccessRequest; import io.github.bsayli.licensing.sdk.api.dto.LicenseToken; -import io.github.bsayli.licensing.sdk.common.api.ApiResponse; import io.github.bsayli.licensing.sdk.common.i18n.LocalizedMessageResolver; import io.github.bsayli.licensing.sdk.service.LicenseOrchestrationService; import jakarta.validation.Valid; @@ -32,6 +32,7 @@ public ResponseEntity> getLicenseToken( LicenseToken token = licenseService.getLicenseToken(request); String msg = messageResolver.getMessage("license.validation.success"); - return ResponseEntity.status(HttpStatus.OK).body(ApiResponse.of(HttpStatus.OK, msg, token)); + return ResponseEntity.status(HttpStatus.OK) + .body(ApiResponse.of(HttpStatus.OK.value(), msg, token)); } } diff --git a/licensing-service-sdk/src/main/java/io/github/bsayli/licensing/sdk/api/exception/LicenseControllerAdvice.java b/licensing-service-sdk/src/main/java/io/github/bsayli/licensing/sdk/api/exception/LicenseControllerAdvice.java index dcaa4e9..64cab6b 100644 --- a/licensing-service-sdk/src/main/java/io/github/bsayli/licensing/sdk/api/exception/LicenseControllerAdvice.java +++ b/licensing-service-sdk/src/main/java/io/github/bsayli/licensing/sdk/api/exception/LicenseControllerAdvice.java @@ -1,7 +1,7 @@ package io.github.bsayli.licensing.sdk.api.exception; -import io.github.bsayli.licensing.sdk.common.api.ApiError; -import io.github.bsayli.licensing.sdk.common.api.ApiResponse; +import io.github.bsayli.licensing.contract.api.ApiError; +import io.github.bsayli.licensing.contract.api.ApiResponse; import io.github.bsayli.licensing.sdk.common.exception.LicensingSdkHttpTransportException; import io.github.bsayli.licensing.sdk.common.exception.LicensingSdkRemoteServiceException; import io.github.bsayli.licensing.sdk.common.i18n.LocalizedMessageResolver; @@ -41,7 +41,7 @@ public ResponseEntity> handleMethodArgNotValid( } String topMsg = messages.getMessage("request.validation.failed"); return ResponseEntity.status(HttpStatus.BAD_REQUEST) - .body(ApiResponse.error(HttpStatus.BAD_REQUEST, topMsg, errors)); + .body(ApiResponse.error(HttpStatus.BAD_REQUEST.value(), topMsg, errors)); } @ExceptionHandler(ConstraintViolationException.class) @@ -56,13 +56,13 @@ public ResponseEntity> handleConstraintViolation( } String topMsg = messages.getMessage("request.validation.failed"); return ResponseEntity.status(HttpStatus.BAD_REQUEST) - .body(ApiResponse.error(HttpStatus.BAD_REQUEST, topMsg, errors)); + .body(ApiResponse.error(HttpStatus.BAD_REQUEST.value(), topMsg, errors)); } @ExceptionHandler(LicensingSdkRemoteServiceException.class) public ResponseEntity> handleRemoteError( LicensingSdkRemoteServiceException ex) { - HttpStatus http = + HttpStatus httpStatus = Objects.requireNonNullElse(ex.getHttpStatus(), HttpStatus.INTERNAL_SERVER_ERROR); String top = @@ -83,26 +83,26 @@ public ResponseEntity> handleRemoteError( errs = List.of(); } - return ResponseEntity.status(http).body(ApiResponse.error(http, top, errs)); + return ResponseEntity.status(httpStatus).body(ApiResponse.error(httpStatus.value(), top, errs)); } @ExceptionHandler(LicensingSdkHttpTransportException.class) public ResponseEntity> handleTransportError( LicensingSdkHttpTransportException ex) { log.error("Transport/parse error when calling licensing-service", ex); - HttpStatus http = HttpStatus.BAD_GATEWAY; + HttpStatus httpStatus = HttpStatus.BAD_GATEWAY; String top = messages.getMessage("license.validation.error"); List errs = List.of(new ApiError("TRANSPORT_ERROR", top)); - return ResponseEntity.status(http).body(ApiResponse.error(http, top, errs)); + return ResponseEntity.status(httpStatus).body(ApiResponse.error(httpStatus.value(), top, errs)); } @ExceptionHandler(Exception.class) public ResponseEntity> handleGeneric(Exception ex) { log.error("Unexpected error in SDK API layer", ex); - HttpStatus http = HttpStatus.INTERNAL_SERVER_ERROR; + HttpStatus httpStatus = HttpStatus.INTERNAL_SERVER_ERROR; String top = messages.getMessage("license.validation.error"); List errs = List.of(new ApiError("INTERNAL_SERVER_ERROR", top)); - return ResponseEntity.status(http).body(ApiResponse.error(http, top, errs)); + return ResponseEntity.status(httpStatus).body(ApiResponse.error(httpStatus.value(), top, errs)); } private String resolveStrict(String keyOrText, Object... args) { diff --git a/licensing-service-sdk/src/main/java/io/github/bsayli/licensing/sdk/common/api/ApiError.java b/licensing-service-sdk/src/main/java/io/github/bsayli/licensing/sdk/common/api/ApiError.java deleted file mode 100644 index fa1f1d3..0000000 --- a/licensing-service-sdk/src/main/java/io/github/bsayli/licensing/sdk/common/api/ApiError.java +++ /dev/null @@ -1,3 +0,0 @@ -package io.github.bsayli.licensing.sdk.common.api; - -public record ApiError(String errorCode, String message) {} diff --git a/licensing-service-sdk/src/main/java/io/github/bsayli/licensing/sdk/common/api/ApiResponse.java b/licensing-service-sdk/src/main/java/io/github/bsayli/licensing/sdk/common/api/ApiResponse.java deleted file mode 100644 index dc4cce0..0000000 --- a/licensing-service-sdk/src/main/java/io/github/bsayli/licensing/sdk/common/api/ApiResponse.java +++ /dev/null @@ -1,24 +0,0 @@ -package io.github.bsayli.licensing.sdk.common.api; - -import java.util.Collections; -import java.util.List; -import org.springframework.http.HttpStatus; - -public record ApiResponse(int status, String message, T data, List errors) { - public static ApiResponse ok(T data) { - return new ApiResponse<>(HttpStatus.OK.value(), "OK", data, Collections.emptyList()); - } - - public static ApiResponse of(HttpStatus status, String message, T data) { - return new ApiResponse<>(status.value(), message, data, Collections.emptyList()); - } - - public static ApiResponse error(HttpStatus status, String message) { - return new ApiResponse<>(status.value(), message, null, Collections.emptyList()); - } - - public static ApiResponse error(HttpStatus status, String message, List errors) { - return new ApiResponse<>( - status.value(), message, null, errors != null ? errors : Collections.emptyList()); - } -} diff --git a/licensing-service-sdk/src/main/java/io/github/bsayli/licensing/sdk/common/openapi/ApiResponseSchemaFactory.java b/licensing-service-sdk/src/main/java/io/github/bsayli/licensing/sdk/common/openapi/ApiResponseSchemaFactory.java deleted file mode 100644 index b53204a..0000000 --- a/licensing-service-sdk/src/main/java/io/github/bsayli/licensing/sdk/common/openapi/ApiResponseSchemaFactory.java +++ /dev/null @@ -1,25 +0,0 @@ -package io.github.bsayli.licensing.sdk.common.openapi; - -import static io.github.bsayli.licensing.sdk.common.openapi.OpenApiSchemas.*; - -import io.swagger.v3.oas.models.media.ComposedSchema; -import io.swagger.v3.oas.models.media.ObjectSchema; -import io.swagger.v3.oas.models.media.Schema; -import java.util.List; - -public final class ApiResponseSchemaFactory { - private ApiResponseSchemaFactory() {} - - public static Schema createComposedWrapper(String dataRefName) { - var schema = new ComposedSchema(); - schema.setAllOf( - List.of( - new Schema<>().$ref("#/components/schemas/" + SCHEMA_API_RESPONSE), - new ObjectSchema() - .addProperty( - PROP_DATA, new Schema<>().$ref("#/components/schemas/" + dataRefName)))); - schema.addExtension(EXT_API_WRAPPER, true); - schema.addExtension(EXT_API_WRAPPER_DATATYPE, dataRefName); - return schema; - } -} diff --git a/licensing-service-sdk/src/main/java/io/github/bsayli/licensing/sdk/common/openapi/OpenApiSchemas.java b/licensing-service-sdk/src/main/java/io/github/bsayli/licensing/sdk/common/openapi/OpenApiSchemas.java deleted file mode 100644 index a542b1f..0000000 --- a/licensing-service-sdk/src/main/java/io/github/bsayli/licensing/sdk/common/openapi/OpenApiSchemas.java +++ /dev/null @@ -1,18 +0,0 @@ -package io.github.bsayli.licensing.sdk.common.openapi; - -public final class OpenApiSchemas { - // Common property keys - public static final String PROP_STATUS = "status"; - public static final String PROP_MESSAGE = "message"; - public static final String PROP_ERRORS = "errors"; - public static final String PROP_ERROR_CODE = "errorCode"; - public static final String PROP_DATA = "data"; - // Base envelopes - public static final String SCHEMA_API_RESPONSE = "ApiResponse"; - public static final String SCHEMA_API_RESPONSE_VOID = "ApiResponseVoid"; - // Vendor extensions - public static final String EXT_API_WRAPPER = "x-api-wrapper"; - public static final String EXT_API_WRAPPER_DATATYPE = "x-api-wrapper-datatype"; - - private OpenApiSchemas() {} -} diff --git a/licensing-service-sdk/src/main/java/io/github/bsayli/licensing/sdk/common/openapi/SwaggerResponseCustomizer.java b/licensing-service-sdk/src/main/java/io/github/bsayli/licensing/sdk/common/openapi/SwaggerResponseCustomizer.java deleted file mode 100644 index a287dad..0000000 --- a/licensing-service-sdk/src/main/java/io/github/bsayli/licensing/sdk/common/openapi/SwaggerResponseCustomizer.java +++ /dev/null @@ -1,55 +0,0 @@ -package io.github.bsayli.licensing.sdk.common.openapi; - -import static io.github.bsayli.licensing.sdk.common.openapi.OpenApiSchemas.*; - -import io.swagger.v3.oas.models.media.ArraySchema; -import io.swagger.v3.oas.models.media.IntegerSchema; -import io.swagger.v3.oas.models.media.ObjectSchema; -import io.swagger.v3.oas.models.media.StringSchema; -import org.springdoc.core.customizers.OpenApiCustomizer; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -@Configuration -public class SwaggerResponseCustomizer { - - @Bean - public OpenApiCustomizer responseEnvelopeSchemas() { - return openApi -> { - if (!openApi.getComponents().getSchemas().containsKey(SCHEMA_API_RESPONSE)) { - openApi - .getComponents() - .addSchemas( - SCHEMA_API_RESPONSE, - new ObjectSchema() - .addProperty(PROP_STATUS, new IntegerSchema().format("int32")) - .addProperty(PROP_MESSAGE, new StringSchema()) - .addProperty( - PROP_ERRORS, - new ArraySchema() - .items( - new ObjectSchema() - .addProperty(PROP_ERROR_CODE, new StringSchema()) - .addProperty(PROP_MESSAGE, new StringSchema())))); - } - - if (!openApi.getComponents().getSchemas().containsKey(SCHEMA_API_RESPONSE_VOID)) { - openApi - .getComponents() - .addSchemas( - SCHEMA_API_RESPONSE_VOID, - new ObjectSchema() - .addProperty(PROP_STATUS, new IntegerSchema().format("int32")) - .addProperty(PROP_MESSAGE, new StringSchema()) - .addProperty(PROP_DATA, new ObjectSchema()) - .addProperty( - PROP_ERRORS, - new ArraySchema() - .items( - new ObjectSchema() - .addProperty(PROP_ERROR_CODE, new StringSchema()) - .addProperty(PROP_MESSAGE, new StringSchema())))); - } - }; - } -} diff --git a/licensing-service-sdk/src/main/java/io/github/bsayli/licensing/sdk/service/client/LicenseServiceClient.java b/licensing-service-sdk/src/main/java/io/github/bsayli/licensing/sdk/service/client/LicenseServiceClient.java index 223c207..2707827 100644 --- a/licensing-service-sdk/src/main/java/io/github/bsayli/licensing/sdk/service/client/LicenseServiceClient.java +++ b/licensing-service-sdk/src/main/java/io/github/bsayli/licensing/sdk/service/client/LicenseServiceClient.java @@ -1,12 +1,12 @@ package io.github.bsayli.licensing.sdk.service.client; -import io.github.bsayli.licensing.client.common.contract.ApiClientResponse; import io.github.bsayli.licensing.client.generated.dto.IssueAccessRequest; import io.github.bsayli.licensing.client.generated.dto.LicenseAccessResponse; import io.github.bsayli.licensing.client.generated.dto.ValidateAccessRequest; +import io.github.bsayli.licensing.contract.api.ApiResponse; public interface LicenseServiceClient { - ApiClientResponse issueAccess(IssueAccessRequest req); + ApiResponse issueAccess(IssueAccessRequest req); - ApiClientResponse validateAccess(String token, ValidateAccessRequest req); + ApiResponse validateAccess(String token, ValidateAccessRequest req); } diff --git a/licensing-service-sdk/src/main/java/io/github/bsayli/licensing/sdk/service/client/impl/LicenseServiceClientImpl.java b/licensing-service-sdk/src/main/java/io/github/bsayli/licensing/sdk/service/client/impl/LicenseServiceClientImpl.java index 4ad00e0..df0494d 100644 --- a/licensing-service-sdk/src/main/java/io/github/bsayli/licensing/sdk/service/client/impl/LicenseServiceClientImpl.java +++ b/licensing-service-sdk/src/main/java/io/github/bsayli/licensing/sdk/service/client/impl/LicenseServiceClientImpl.java @@ -1,10 +1,10 @@ package io.github.bsayli.licensing.sdk.service.client.impl; import io.github.bsayli.licensing.client.adapter.LicensingServiceClientAdapter; -import io.github.bsayli.licensing.client.common.contract.ApiClientResponse; import io.github.bsayli.licensing.client.generated.dto.IssueAccessRequest; import io.github.bsayli.licensing.client.generated.dto.LicenseAccessResponse; import io.github.bsayli.licensing.client.generated.dto.ValidateAccessRequest; +import io.github.bsayli.licensing.contract.api.ApiResponse; import io.github.bsayli.licensing.sdk.service.client.LicenseServiceClient; import org.springframework.stereotype.Service; @@ -18,12 +18,12 @@ public LicenseServiceClientImpl(LicensingServiceClientAdapter adapter) { } @Override - public ApiClientResponse issueAccess(IssueAccessRequest req) { + public ApiResponse issueAccess(IssueAccessRequest req) { return adapter.issueAccess(req); } @Override - public ApiClientResponse validateAccess( + public ApiResponse validateAccess( String token, ValidateAccessRequest req) { return adapter.validateAccess(token, req); } diff --git a/licensing-service-sdk/src/main/java/io/github/bsayli/licensing/sdk/service/handler/LicenseResponseHandler.java b/licensing-service-sdk/src/main/java/io/github/bsayli/licensing/sdk/service/handler/LicenseResponseHandler.java index 279e114..2abf6fc 100644 --- a/licensing-service-sdk/src/main/java/io/github/bsayli/licensing/sdk/service/handler/LicenseResponseHandler.java +++ b/licensing-service-sdk/src/main/java/io/github/bsayli/licensing/sdk/service/handler/LicenseResponseHandler.java @@ -1,7 +1,7 @@ package io.github.bsayli.licensing.sdk.service.handler; -import io.github.bsayli.licensing.client.common.contract.ApiClientResponse; import io.github.bsayli.licensing.client.generated.dto.LicenseAccessResponse; +import io.github.bsayli.licensing.contract.api.ApiResponse; import io.github.bsayli.licensing.sdk.common.exception.LicensingSdkRemoteServiceException; import io.github.bsayli.licensing.sdk.common.i18n.LocalizedMessageResolver; import java.util.List; @@ -27,16 +27,16 @@ public LicenseResponseHandler(LocalizedMessageResolver messages) { this.messages = messages; } - private void requireOk(ApiClientResponse resp) { + private void requireOk(ApiResponse resp) { if (isOk(resp)) return; throw buildRemoteException(resp); } - private boolean isOk(ApiClientResponse resp) { - return resp != null && resp.getStatus() != null && resp.getStatus() == HTTP_OK; + private boolean isOk(ApiResponse resp) { + return resp != null && resp.getStatus() == HTTP_OK; } - public String extractTokenOrThrow(ApiClientResponse resp) { + public String extractTokenOrThrow(ApiResponse resp) { requireOk(resp); String token = safeToken(resp); if (token == null) { @@ -49,19 +49,19 @@ public String extractTokenOrThrow(ApiClientResponse resp) return token; } - public String extractTokenIfPresentOrThrow(ApiClientResponse resp) { + public String extractTokenIfPresentOrThrow(ApiResponse resp) { requireOk(resp); return safeToken(resp); } - private String safeToken(ApiClientResponse resp) { + private String safeToken(ApiResponse resp) { var data = resp.getData(); if (data == null) return null; String t = data.getLicenseToken(); return (t == null || t.isBlank()) ? null : t; } - private LicensingSdkRemoteServiceException buildRemoteException(ApiClientResponse resp) { + private LicensingSdkRemoteServiceException buildRemoteException(ApiResponse resp) { HttpStatus status = resolveStatus(resp); String top = resolveTopMessage(resp); String errorCode = resolveErrorCode(resp); @@ -69,18 +69,18 @@ private LicensingSdkRemoteServiceException buildRemoteException(ApiClientRes return new LicensingSdkRemoteServiceException(status, errorCode, top, details); } - private HttpStatus resolveStatus(ApiClientResponse resp) { - if (resp == null || resp.getStatus() == null) return HttpStatus.INTERNAL_SERVER_ERROR; + private HttpStatus resolveStatus(ApiResponse resp) { + if (resp == null) return HttpStatus.INTERNAL_SERVER_ERROR; return HttpStatus.valueOf(resp.getStatus()); } - private String resolveTopMessage(ApiClientResponse resp) { + private String resolveTopMessage(ApiResponse resp) { if (resp == null) return messages.getMessage(KEY_TOP_REMOTE_FAILED); String msg = resp.getMessage(); return (msg == null || msg.isBlank()) ? messages.getMessage(KEY_TOP_REMOTE_FAILED) : msg; } - private String resolveErrorCode(ApiClientResponse resp) { + private String resolveErrorCode(ApiResponse resp) { if (resp == null || resp.getErrors() == null || resp.getErrors().isEmpty()) { return CODE_REMOTE_ERROR; } @@ -89,7 +89,7 @@ private String resolveErrorCode(ApiClientResponse resp) { return (code == null || code.isBlank()) ? CODE_REMOTE_ERROR : code; } - private List resolveDetails(ApiClientResponse resp) { + private List resolveDetails(ApiResponse resp) { if (resp == null || resp.getErrors() == null || resp.getErrors().isEmpty()) { return List.of(messages.getMessage(KEY_DETAIL_NO_PAYLOAD)); } diff --git a/licensing-service-sdk/src/main/java/io/github/bsayli/licensing/sdk/service/impl/LicenseOrchestrationServiceImpl.java b/licensing-service-sdk/src/main/java/io/github/bsayli/licensing/sdk/service/impl/LicenseOrchestrationServiceImpl.java index d60674d..12a068d 100644 --- a/licensing-service-sdk/src/main/java/io/github/bsayli/licensing/sdk/service/impl/LicenseOrchestrationServiceImpl.java +++ b/licensing-service-sdk/src/main/java/io/github/bsayli/licensing/sdk/service/impl/LicenseOrchestrationServiceImpl.java @@ -1,10 +1,10 @@ package io.github.bsayli.licensing.sdk.service.impl; -import io.github.bsayli.licensing.client.common.contract.ApiClientResponse; import io.github.bsayli.licensing.client.common.exception.ApiClientException; import io.github.bsayli.licensing.client.generated.dto.IssueAccessRequest; import io.github.bsayli.licensing.client.generated.dto.LicenseAccessResponse; import io.github.bsayli.licensing.client.generated.dto.ValidateAccessRequest; +import io.github.bsayli.licensing.contract.api.ApiResponse; import io.github.bsayli.licensing.sdk.api.dto.LicenseAccessRequest; import io.github.bsayli.licensing.sdk.api.dto.LicenseToken; import io.github.bsayli.licensing.sdk.common.exception.LicensingSdkHttpTransportException; @@ -77,7 +77,7 @@ private String validateThenMaybeRefreshOrReissue( validateReq.setSignature(signatureGenerator.generateForValidate(cachedToken, validateReq)); - ApiClientResponse vResp = + ApiResponse vResp = licenseServiceClient.validateAccess(cachedToken, validateReq); try { @@ -107,7 +107,7 @@ private String issueAndCacheToken(LicenseAccessRequest request, String clientId) String signature = signatureGenerator.generateForIssue(issueReq); issueReq.setSignature(signature); - ApiClientResponse resp = licenseServiceClient.issueAccess(issueReq); + ApiResponse resp = licenseServiceClient.issueAccess(issueReq); String token = responseHandler.extractTokenOrThrow(resp); licenseTokenCacheService.put(clientId, token); diff --git a/licensing-service-sdk/src/test/java/io/github/bsayli/licensing/sdk/api/controller/LicenseControllerTest.java b/licensing-service-sdk/src/test/java/io/github/bsayli/licensing/sdk/api/controller/LicenseControllerTest.java index 71da194..e4d5305 100644 --- a/licensing-service-sdk/src/test/java/io/github/bsayli/licensing/sdk/api/controller/LicenseControllerTest.java +++ b/licensing-service-sdk/src/test/java/io/github/bsayli/licensing/sdk/api/controller/LicenseControllerTest.java @@ -3,9 +3,9 @@ import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.*; +import io.github.bsayli.licensing.contract.api.ApiResponse; import io.github.bsayli.licensing.sdk.api.dto.LicenseAccessRequest; import io.github.bsayli.licensing.sdk.api.dto.LicenseToken; -import io.github.bsayli.licensing.sdk.common.api.ApiResponse; import io.github.bsayli.licensing.sdk.common.i18n.LocalizedMessageResolver; import io.github.bsayli.licensing.sdk.service.LicenseOrchestrationService; import org.junit.jupiter.api.DisplayName; @@ -47,11 +47,11 @@ void getLicenseToken_ok() { assertEquals(HttpStatus.OK, resp.getStatusCode()); assertNotNull(resp.getBody()); - assertEquals(200, resp.getBody().status()); - assertEquals("License is valid", resp.getBody().message()); - assertNotNull(resp.getBody().data()); - assertEquals("jwt-abc.def.ghi", resp.getBody().data().licenseToken()); - assertTrue(resp.getBody().errors().isEmpty()); + assertEquals(200, resp.getBody().getStatus()); + assertEquals("License is valid", resp.getBody().getMessage()); + assertNotNull(resp.getBody().getData()); + assertEquals("jwt-abc.def.ghi", resp.getBody().getData().licenseToken()); + assertTrue(resp.getBody().getErrors().isEmpty()); verify(licenseService).getLicenseToken(req); verify(messageResolver).getMessage("license.validation.success"); diff --git a/licensing-service-sdk/src/test/java/io/github/bsayli/licensing/sdk/generator/impl/SignatureGeneratorImplTest.java b/licensing-service-sdk/src/test/java/io/github/bsayli/licensing/sdk/generator/impl/SignatureGeneratorImplTest.java index 9c0c933..d413146 100644 --- a/licensing-service-sdk/src/test/java/io/github/bsayli/licensing/sdk/generator/impl/SignatureGeneratorImplTest.java +++ b/licensing-service-sdk/src/test/java/io/github/bsayli/licensing/sdk/generator/impl/SignatureGeneratorImplTest.java @@ -102,6 +102,28 @@ void generateForValidate_verifiable() throws Exception { assertTrue(verifyWithPublic(kp, expectedJson, sigBytes)); } + @Test + @DisplayName("Generate Postman signature for validate access request") + void generatePostmanSignatureForValidate() { + String privateKey = "MC4CAQAwBQYDK2VwBCIEIMmR+qdAZ/vAWUKt0ZNlL+CCyqaRScTpAq52WtOmyg8E"; + + String licenseToken = + "eyJhbGciOiJFZERTQSJ9.eyJzdWIiOiJ2X1VfR0l6VnEzdlBxQmtTcjZXWTZWZU1TMjQ0QURwcnlXNlFpYjYyZGcwIiwibGljZW5zZVN0YXR1cyI6IkFDVElWRSIsImxpY2Vuc2VUaWVyIjoiUHJvZmVzc2lvbmFsIiwibWVzc2FnZSI6IllvdXIgbGljZW5zZSBpcyBhY3RpdmUiLCJpYXQiOjE3NzkxOTg3NzEsImV4cCI6MTc3OTIwNDI1Mn0.Hhx10GUBhuDLcrVk7f563BFir5bqcDN4Wt6vV8a37pEQiYm8eQU1K9fGpQKx_1mWCt4xfg28w_WeraVXs7ZaBA"; + + SignatureGenerator gen = new SignatureGeneratorImpl(privateKey); + + ValidateAccessRequest request = + new ValidateAccessRequest() + .serviceId("crm") + .serviceVersion("1.5.0") + .instanceId("licensing-service~demo~00:11:22:33:44:55") + .checksum("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b8a5"); + + String signature = gen.generateForValidate(licenseToken, request); + + System.out.println("signature = " + signature); + } + @Test @DisplayName("generateForValidate(null, ...) -> NullPointerException") void generateForValidate_nullToken() throws Exception { diff --git a/licensing-service-sdk/src/test/java/io/github/bsayli/licensing/sdk/service/client/impl/LicenseServiceClientImplTest.java b/licensing-service-sdk/src/test/java/io/github/bsayli/licensing/sdk/service/client/impl/LicenseServiceClientImplTest.java index be75264..d3cc4c9 100644 --- a/licensing-service-sdk/src/test/java/io/github/bsayli/licensing/sdk/service/client/impl/LicenseServiceClientImplTest.java +++ b/licensing-service-sdk/src/test/java/io/github/bsayli/licensing/sdk/service/client/impl/LicenseServiceClientImplTest.java @@ -1,13 +1,14 @@ package io.github.bsayli.licensing.sdk.service.client.impl; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertSame; import static org.mockito.Mockito.*; import io.github.bsayli.licensing.client.adapter.LicensingServiceClientAdapter; -import io.github.bsayli.licensing.client.common.contract.ApiClientResponse; import io.github.bsayli.licensing.client.generated.dto.IssueAccessRequest; import io.github.bsayli.licensing.client.generated.dto.LicenseAccessResponse; import io.github.bsayli.licensing.client.generated.dto.ValidateAccessRequest; +import io.github.bsayli.licensing.contract.api.ApiResponse; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; @@ -37,10 +38,10 @@ void issueAccess_delegates() { .checksum("chk") .licenseKey("LK_xxx"); @SuppressWarnings("unchecked") - ApiClientResponse expected = mock(ApiClientResponse.class); + ApiResponse expected = mock(ApiResponse.class); when(adapter.issueAccess(any(IssueAccessRequest.class))).thenReturn(expected); - ApiClientResponse actual = client.issueAccess(req); + ApiResponse actual = client.issueAccess(req); assertSame(expected, actual); ArgumentCaptor captor = ArgumentCaptor.forClass(IssueAccessRequest.class); @@ -49,11 +50,11 @@ void issueAccess_delegates() { verify(adapter, times(1)).issueAccess(any(IssueAccessRequest.class)); verifyNoMoreInteractions(adapter); - org.junit.jupiter.api.Assertions.assertEquals("crm", passed.getServiceId()); - org.junit.jupiter.api.Assertions.assertEquals("1.2.3", passed.getServiceVersion()); - org.junit.jupiter.api.Assertions.assertEquals("inst-1", passed.getInstanceId()); - org.junit.jupiter.api.Assertions.assertEquals("chk", passed.getChecksum()); - org.junit.jupiter.api.Assertions.assertEquals("LK_xxx", passed.getLicenseKey()); + assertEquals("crm", passed.getServiceId()); + assertEquals("1.2.3", passed.getServiceVersion()); + assertEquals("inst-1", passed.getInstanceId()); + assertEquals("chk", passed.getChecksum()); + assertEquals("LK_xxx", passed.getLicenseKey()); } @Test @@ -67,11 +68,11 @@ void validateAccess_delegates() { .instanceId("inst-1") .checksum("chk"); @SuppressWarnings("unchecked") - ApiClientResponse expected = mock(ApiClientResponse.class); + ApiResponse expected = mock(ApiResponse.class); when(adapter.validateAccess(anyString(), any(ValidateAccessRequest.class))) .thenReturn(expected); - ApiClientResponse actual = client.validateAccess(token, req); + ApiResponse actual = client.validateAccess(token, req); assertSame(expected, actual); ArgumentCaptor tokenCap = ArgumentCaptor.forClass(String.class); @@ -80,11 +81,11 @@ void validateAccess_delegates() { verify(adapter).validateAccess(tokenCap.capture(), reqCap.capture()); verify(adapter, times(1)).validateAccess(anyString(), any(ValidateAccessRequest.class)); verifyNoMoreInteractions(adapter); - org.junit.jupiter.api.Assertions.assertEquals("jwt-token", tokenCap.getValue()); + assertEquals("jwt-token", tokenCap.getValue()); ValidateAccessRequest passed = reqCap.getValue(); - org.junit.jupiter.api.Assertions.assertEquals("crm", passed.getServiceId()); - org.junit.jupiter.api.Assertions.assertEquals("1.2.3", passed.getServiceVersion()); - org.junit.jupiter.api.Assertions.assertEquals("inst-1", passed.getInstanceId()); - org.junit.jupiter.api.Assertions.assertEquals("chk", passed.getChecksum()); + assertEquals("crm", passed.getServiceId()); + assertEquals("1.2.3", passed.getServiceVersion()); + assertEquals("inst-1", passed.getInstanceId()); + assertEquals("chk", passed.getChecksum()); } } diff --git a/licensing-service-sdk/src/test/java/io/github/bsayli/licensing/sdk/service/handler/LicenseResponseHandlerTest.java b/licensing-service-sdk/src/test/java/io/github/bsayli/licensing/sdk/service/handler/LicenseResponseHandlerTest.java index 9ec6744..009f535 100644 --- a/licensing-service-sdk/src/test/java/io/github/bsayli/licensing/sdk/service/handler/LicenseResponseHandlerTest.java +++ b/licensing-service-sdk/src/test/java/io/github/bsayli/licensing/sdk/service/handler/LicenseResponseHandlerTest.java @@ -3,8 +3,8 @@ import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.*; -import io.github.bsayli.licensing.client.common.contract.ApiClientResponse; import io.github.bsayli.licensing.client.generated.dto.LicenseAccessResponse; +import io.github.bsayli.licensing.contract.api.ApiResponse; import io.github.bsayli.licensing.sdk.common.exception.LicensingSdkRemoteServiceException; import io.github.bsayli.licensing.sdk.common.i18n.LocalizedMessageResolver; import java.util.List; @@ -29,7 +29,7 @@ class LicenseResponseHandlerTest { @DisplayName("extractTokenOrThrow -> OK + token") void extractTokenOrThrow_ok_withToken() { @SuppressWarnings("unchecked") - ApiClientResponse resp = mock(ApiClientResponse.class); + ApiResponse resp = mock(ApiResponse.class); when(resp.getStatus()).thenReturn(HttpStatus.OK.value()); LicenseAccessResponse data = mock(LicenseAccessResponse.class); when(resp.getData()).thenReturn(data); @@ -47,7 +47,7 @@ void extractTokenOrThrow_ok_emptyToken_throws() { when(messages.getMessage("sdk.remote.empty.token.detail")).thenReturn("detail"); @SuppressWarnings("unchecked") - ApiClientResponse resp = mock(ApiClientResponse.class); + ApiResponse resp = mock(ApiResponse.class); when(resp.getStatus()).thenReturn(HttpStatus.OK.value()); LicenseAccessResponse data = mock(LicenseAccessResponse.class); when(resp.getData()).thenReturn(data); @@ -67,7 +67,7 @@ void extractTokenOrThrow_ok_emptyToken_throws() { @DisplayName("extractTokenIfPresentOrThrow -> OK + no data -> null") void extractTokenIfPresentOrThrow_ok_noData_returnsNull() { @SuppressWarnings("unchecked") - ApiClientResponse resp = mock(ApiClientResponse.class); + ApiResponse resp = mock(ApiResponse.class); when(resp.getStatus()).thenReturn(HttpStatus.OK.value()); when(resp.getData()).thenReturn(null); @@ -83,7 +83,7 @@ void nonOk_throwsRemoteServiceException() { when(messages.getMessage("sdk.remote.no.payload")).thenReturn("no-payload"); @SuppressWarnings("unchecked") - ApiClientResponse resp = mock(ApiClientResponse.class); + ApiResponse resp = mock(ApiResponse.class); when(resp.getStatus()).thenReturn(HttpStatus.BAD_REQUEST.value()); when(resp.getMessage()).thenReturn(null); when(resp.getErrors()).thenReturn(null); diff --git a/licensing-service-sdk/src/test/java/io/github/bsayli/licensing/sdk/service/impl/LicenseOrchestrationServiceImplTest.java b/licensing-service-sdk/src/test/java/io/github/bsayli/licensing/sdk/service/impl/LicenseOrchestrationServiceImplTest.java index ba4f16d..6a0aa3b 100644 --- a/licensing-service-sdk/src/test/java/io/github/bsayli/licensing/sdk/service/impl/LicenseOrchestrationServiceImplTest.java +++ b/licensing-service-sdk/src/test/java/io/github/bsayli/licensing/sdk/service/impl/LicenseOrchestrationServiceImplTest.java @@ -3,11 +3,11 @@ import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.*; -import io.github.bsayli.licensing.client.common.contract.ApiClientResponse; import io.github.bsayli.licensing.client.common.exception.ApiClientException; import io.github.bsayli.licensing.client.generated.dto.IssueAccessRequest; import io.github.bsayli.licensing.client.generated.dto.LicenseAccessResponse; import io.github.bsayli.licensing.client.generated.dto.ValidateAccessRequest; +import io.github.bsayli.licensing.contract.api.ApiResponse; import io.github.bsayli.licensing.sdk.api.dto.LicenseAccessRequest; import io.github.bsayli.licensing.sdk.api.dto.LicenseToken; import io.github.bsayli.licensing.sdk.common.exception.LicensingSdkHttpTransportException; @@ -61,7 +61,7 @@ void cacheMiss_issueAndCache() { when(cache.get("cid")).thenReturn(null); when(signatureGenerator.generateForIssue(any(IssueAccessRequest.class))).thenReturn("sig"); @SuppressWarnings("unchecked") - ApiClientResponse resp = mock(ApiClientResponse.class); + ApiResponse resp = mock(ApiResponse.class); when(licenseServiceClient.issueAccess(any(IssueAccessRequest.class))).thenReturn(resp); when(responseHandler.extractTokenOrThrow(resp)).thenReturn("jwt-1"); @@ -86,7 +86,7 @@ void cacheHit_validate_active_noRefresh() { when(signatureGenerator.generateForValidate(eq("jwt-old"), any(ValidateAccessRequest.class))) .thenReturn("vsig"); @SuppressWarnings("unchecked") - ApiClientResponse vResp = mock(ApiClientResponse.class); + ApiResponse vResp = mock(ApiResponse.class); when(licenseServiceClient.validateAccess(eq("jwt-old"), any(ValidateAccessRequest.class))) .thenReturn(vResp); when(responseHandler.extractTokenIfPresentOrThrow(vResp)).thenReturn(null); @@ -110,7 +110,7 @@ void cacheHit_validate_refreshed() { when(signatureGenerator.generateForValidate(eq("jwt-old"), any(ValidateAccessRequest.class))) .thenReturn("vsig"); @SuppressWarnings("unchecked") - ApiClientResponse vResp = mock(ApiClientResponse.class); + ApiResponse vResp = mock(ApiResponse.class); when(licenseServiceClient.validateAccess(eq("jwt-old"), any(ValidateAccessRequest.class))) .thenReturn(vResp); when(responseHandler.extractTokenIfPresentOrThrow(vResp)).thenReturn("jwt-new"); @@ -130,7 +130,7 @@ void cacheHit_validate_tooOld_fallbackIssue() { eq("jwt-very-old"), any(ValidateAccessRequest.class))) .thenReturn("vsig"); @SuppressWarnings("unchecked") - ApiClientResponse vResp = mock(ApiClientResponse.class); + ApiResponse vResp = mock(ApiResponse.class); when(licenseServiceClient.validateAccess(eq("jwt-very-old"), any(ValidateAccessRequest.class))) .thenReturn(vResp); LicensingSdkRemoteServiceException tooOld = @@ -139,7 +139,7 @@ void cacheHit_validate_tooOld_fallbackIssue() { when(responseHandler.extractTokenIfPresentOrThrow(vResp)).thenThrow(tooOld); @SuppressWarnings("unchecked") - ApiClientResponse iResp = mock(ApiClientResponse.class); + ApiResponse iResp = mock(ApiResponse.class); when(signatureGenerator.generateForIssue(any(IssueAccessRequest.class))).thenReturn("sig"); when(licenseServiceClient.issueAccess(any(IssueAccessRequest.class))).thenReturn(iResp); when(responseHandler.extractTokenOrThrow(iResp)).thenReturn("jwt-new"); diff --git a/licensing-service/Dockerfile b/licensing-service/Dockerfile index fbff5f4..768479e 100644 --- a/licensing-service/Dockerfile +++ b/licensing-service/Dockerfile @@ -1,9 +1,18 @@ FROM maven:3.9.11-eclipse-temurin-21-noble AS builder -WORKDIR /app -COPY pom.xml . +WORKDIR /build + +# Install shared contract module into the local Maven repository +COPY licensing-api-contract/ licensing-api-contract/ +RUN --mount=type=cache,target=/root/.m2 \ + mvn -q -DskipTests -f licensing-api-contract/pom.xml install + +# Build licensing-service +WORKDIR /build/licensing-service +COPY licensing-service/pom.xml . RUN --mount=type=cache,target=/root/.m2 \ mvn -q -DskipTests dependency:go-offline -COPY src ./src + +COPY licensing-service/src ./src RUN --mount=type=cache,target=/root/.m2 \ mvn -q -T 2C -DskipTests --no-transfer-progress clean package && \ java -Djarmode=layertools -jar target/*.jar extract @@ -23,14 +32,14 @@ RUN apt-get update && \ LABEL org.opencontainers.image.title="licensing-service" \ org.opencontainers.image.description="Licensing Service (Spring Boot, EdDSA, Caffeine, Keycloak)" \ - org.opencontainers.image.version="1.0.1" \ + org.opencontainers.image.version="1.0.5" \ org.opencontainers.image.source="https://github.com/bsayli/licensing" \ org.opencontainers.image.licenses="MIT" -COPY --from=builder --chown=appuser:appuser /app/dependencies/ ./ -COPY --from=builder --chown=appuser:appuser /app/snapshot-dependencies/ ./ -COPY --from=builder --chown=appuser:appuser /app/spring-boot-loader/ ./ -COPY --from=builder --chown=appuser:appuser /app/application/ ./ +COPY --from=builder --chown=appuser:appuser /build/licensing-service/dependencies/ ./ +COPY --from=builder --chown=appuser:appuser /build/licensing-service/snapshot-dependencies/ ./ +COPY --from=builder --chown=appuser:appuser /build/licensing-service/spring-boot-loader/ ./ +COPY --from=builder --chown=appuser:appuser /build/licensing-service/application/ ./ USER appuser diff --git a/licensing-service/README.md b/licensing-service/README.md index b235c45..2a09eef 100644 --- a/licensing-service/README.md +++ b/licensing-service/README.md @@ -6,6 +6,7 @@ [![Keycloak](https://img.shields.io/badge/Keycloak-26.x-8A2BE2?logo=keycloak)](https://www.keycloak.org/) [![Redis](https://img.shields.io/badge/Redis-8.x-red?logo=redis)](https://redis.io/) [![OpenAPI](https://img.shields.io/badge/OpenAPI-3.x-blue?logo=openapiinitiative)](https://www.openapis.org/) +[![OpenAPI Generics](https://img.shields.io/badge/OpenAPI%20Generics-1.0.2-blue)](https://github.com/blueprint-platform/openapi-generics) [![Docker](https://img.shields.io/badge/Docker-Compose-blue?logo=docker)](https://www.docker.com/) [![License](https://img.shields.io/badge/license-MIT-green)](../LICENSE) @@ -315,7 +316,7 @@ mvn clean package ```bash mvn spring-boot:run # or -java -jar target/licensing-service-1.0.1.jar +java -jar target/licensing-service-1.0.5.jar ``` ### With Docker Compose (from repo root) diff --git a/licensing-service/pom.xml b/licensing-service/pom.xml index bc6ad38..34f3d50 100644 --- a/licensing-service/pom.xml +++ b/licensing-service/pom.xml @@ -6,12 +6,12 @@ org.springframework.boot spring-boot-starter-parent - 3.5.5 + 3.5.14 io.github.bsayli licensing-service - 1.0.4 + 1.0.5 licensing-service Licensing Service App @@ -19,9 +19,11 @@ UTF-8 UTF-8 21 + 1.0.2 + 2.8.17 + 1.0.5 0.12.7 - 1.81 - 2.8.13 + 1.84 3.18.0 2.12.1 0.8.13 @@ -30,6 +32,19 @@ 6.0.0 + + + io.github.blueprint-platform + openapi-generics-server-starter + ${openapi-generics-server-starter.version} + + + + io.github.bsayli + licensing-api-contract + ${licensing-api-contract.version} + + org.springframework.boot spring-boot-starter-security diff --git a/licensing-service/src/main/java/io/github/bsayli/licensing/api/controller/LicenseController.java b/licensing-service/src/main/java/io/github/bsayli/licensing/api/controller/LicenseController.java index 18c43d3..027e94a 100644 --- a/licensing-service/src/main/java/io/github/bsayli/licensing/api/controller/LicenseController.java +++ b/licensing-service/src/main/java/io/github/bsayli/licensing/api/controller/LicenseController.java @@ -4,8 +4,8 @@ import io.github.bsayli.licensing.api.dto.LicenseAccessResponse; import io.github.bsayli.licensing.api.dto.ValidateAccessRequest; import io.github.bsayli.licensing.api.validation.annotations.ValidLicenseToken; -import io.github.bsayli.licensing.common.api.ApiResponse; import io.github.bsayli.licensing.common.i18n.LocalizedMessageResolver; +import io.github.bsayli.licensing.contract.api.ApiResponse; import io.github.bsayli.licensing.service.LicenseOrchestrationService; import jakarta.validation.Valid; import org.springframework.http.HttpStatus; @@ -32,7 +32,8 @@ public ResponseEntity> createAccess( @Valid @RequestBody IssueAccessRequest request) { var result = service.issueAccess(request); String msg = messageResolver.getMessage("license.validation.success"); - return ResponseEntity.status(HttpStatus.OK).body(ApiResponse.of(HttpStatus.OK, msg, result)); + return ResponseEntity.status(HttpStatus.OK) + .body(ApiResponse.of(HttpStatus.OK.value(), msg, result)); } @PostMapping("/access/validate") @@ -42,6 +43,7 @@ public ResponseEntity> validateAccess( var result = service.validateAccess(request, licenseToken); String msg = messageResolver.getMessage("license.validation.success"); - return ResponseEntity.status(HttpStatus.OK).body(ApiResponse.of(HttpStatus.OK, msg, result)); + return ResponseEntity.status(HttpStatus.OK) + .body(ApiResponse.of(HttpStatus.OK.value(), msg, result)); } } diff --git a/licensing-service/src/main/java/io/github/bsayli/licensing/api/exception/LicenseControllerAdvice.java b/licensing-service/src/main/java/io/github/bsayli/licensing/api/exception/LicenseControllerAdvice.java index ce42a11..4f72fd0 100644 --- a/licensing-service/src/main/java/io/github/bsayli/licensing/api/exception/LicenseControllerAdvice.java +++ b/licensing-service/src/main/java/io/github/bsayli/licensing/api/exception/LicenseControllerAdvice.java @@ -1,10 +1,10 @@ package io.github.bsayli.licensing.api.exception; -import io.github.bsayli.licensing.common.api.ApiError; -import io.github.bsayli.licensing.common.api.ApiResponse; import io.github.bsayli.licensing.common.exception.ServiceErrorCode; import io.github.bsayli.licensing.common.exception.ServiceException; import io.github.bsayli.licensing.common.i18n.LocalizedMessageResolver; +import io.github.bsayli.licensing.contract.api.ApiError; +import io.github.bsayli.licensing.contract.api.ApiResponse; import io.github.bsayli.licensing.repository.exception.RepositoryException; import io.github.bsayli.licensing.service.user.exception.UserOperationException; import jakarta.validation.ConstraintViolation; @@ -51,7 +51,7 @@ public ResponseEntity> handleMethodArgNotValid( String topMsg = messageResolver.getMessage("request.validation.failed"); return ResponseEntity.status(HttpStatus.BAD_REQUEST) - .body(ApiResponse.error(HttpStatus.BAD_REQUEST, topMsg, errors)); + .body(ApiResponse.error(HttpStatus.BAD_REQUEST.value(), topMsg, errors)); } @ExceptionHandler(ConstraintViolationException.class) @@ -74,7 +74,7 @@ public ResponseEntity> handleConstraintViolation( String topMsg = messageResolver.getMessage("request.validation.failed"); return ResponseEntity.status(HttpStatus.BAD_REQUEST) - .body(ApiResponse.error(HttpStatus.BAD_REQUEST, topMsg, errors)); + .body(ApiResponse.error(HttpStatus.BAD_REQUEST.value(), topMsg, errors)); } @ExceptionHandler(MissingServletRequestParameterException.class) @@ -88,7 +88,7 @@ public ResponseEntity> handleMissingParameter( String msg = messageResolver.getMessage("request.param.missing", ex.getParameterName()); List errors = List.of(new ApiError(ServiceErrorCode.MISSING_PARAMETER.name(), msg)); return ResponseEntity.status(HttpStatus.BAD_REQUEST) - .body(ApiResponse.error(HttpStatus.BAD_REQUEST, msg, errors)); + .body(ApiResponse.error(HttpStatus.BAD_REQUEST.value(), msg, errors)); } @ExceptionHandler(MissingRequestHeaderException.class) @@ -98,18 +98,18 @@ public ResponseEntity> handleMissingHeader(MissingRequestHeade String msg = messageResolver.getMessage("request.header.missing", ex.getHeaderName()); List errors = List.of(new ApiError(ServiceErrorCode.MISSING_PARAMETER.name(), msg)); return ResponseEntity.status(HttpStatus.BAD_REQUEST) - .body(ApiResponse.error(HttpStatus.BAD_REQUEST, msg, errors)); + .body(ApiResponse.error(HttpStatus.BAD_REQUEST.value(), msg, errors)); } @ExceptionHandler(ServiceException.class) public ResponseEntity> handleServiceException(ServiceException ex) { ServiceErrorCode code = ex.getCode(); - HttpStatus http = code.httpStatus(); + HttpStatus httpStatus = code.httpStatus(); - var logger = http.is5xxServerError() ? log.atError() : log.atWarn(); + var logger = httpStatus.is5xxServerError() ? log.atError() : log.atWarn(); logger .setCause(ex) - .addArgument(http.value()) + .addArgument(httpStatus.value()) .addArgument(code) .addArgument(ex.getMessageKey()) .log("Service exception. statusCode={}, code={}, messageKey={} "); @@ -118,23 +118,23 @@ public ResponseEntity> handleServiceException(ServiceException String top = messageResolver.getMessage("license.validation.failed"); ApiResponse body = - ApiResponse.error(http, top, List.of(new ApiError(code.name(), detail))); - return ResponseEntity.status(http).body(body); + ApiResponse.error(httpStatus.value(), top, List.of(new ApiError(code.name(), detail))); + return ResponseEntity.status(httpStatus).body(body); } @ExceptionHandler(RepositoryException.class) public ResponseEntity> handleRepositoryException(RepositoryException ex) { - HttpStatus http = + HttpStatus httpStatus = switch (ex.getErrorCode()) { case USER_NOT_FOUND -> HttpStatus.NOT_FOUND; case USER_ATTRIBUTE_MISSING, USER_ATTRIBUTE_INVALID_FORMAT -> HttpStatus.INTERNAL_SERVER_ERROR; }; - var logger = http.is5xxServerError() ? log.atError() : log.atWarn(); + var logger = httpStatus.is5xxServerError() ? log.atError() : log.atWarn(); logger .setCause(ex) - .addArgument(http.value()) + .addArgument(httpStatus.value()) .addArgument(ex.getErrorCode()) .addArgument(ex.getMessageKey()) .log("Repository exception handled. statusCode={}, errorCode={}, messageKey={} "); @@ -143,22 +143,23 @@ public ResponseEntity> handleRepositoryException(RepositoryExc String top = messageResolver.getMessage("repository.operation.failed"); ApiResponse body = - ApiResponse.error(http, top, List.of(new ApiError(ex.getErrorCode().name(), detail))); - return ResponseEntity.status(http).body(body); + ApiResponse.error( + httpStatus.value(), top, List.of(new ApiError(ex.getErrorCode().name(), detail))); + return ResponseEntity.status(httpStatus).body(body); } @ExceptionHandler(UserOperationException.class) public ResponseEntity> handleUserOperationException(UserOperationException ex) { - HttpStatus http = + HttpStatus httpStatus = switch (ex.getErrorCode()) { case ALREADY_PROCESSING -> HttpStatus.CONFLICT; case MAX_RETRY_ATTEMPTS_EXCEEDED -> HttpStatus.TOO_MANY_REQUESTS; }; - var logger = http.is5xxServerError() ? log.atError() : log.atWarn(); + var logger = httpStatus.is5xxServerError() ? log.atError() : log.atWarn(); logger .setCause(ex) - .addArgument(http.value()) + .addArgument(httpStatus.value()) .addArgument(ex.getErrorCode()) .addArgument(ex.getMessageKey()) .log("User operation exception handled. statusCode={}, errorCode={}, messageKey={} "); @@ -167,8 +168,9 @@ public ResponseEntity> handleUserOperationException(UserOperat String top = messageResolver.getMessage("user.operation.failed"); ApiResponse body = - ApiResponse.error(http, top, List.of(new ApiError(ex.getErrorCode().name(), detail))); - return ResponseEntity.status(http).body(body); + ApiResponse.error( + httpStatus.value(), top, List.of(new ApiError(ex.getErrorCode().name(), detail))); + return ResponseEntity.status(httpStatus).body(body); } @ExceptionHandler(Exception.class) @@ -179,7 +181,7 @@ public ResponseEntity> handleGenericException(Exception ex) { List errors = List.of(new ApiError(ServiceErrorCode.INTERNAL_SERVER_ERROR.name(), msg)); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) - .body(ApiResponse.error(HttpStatus.INTERNAL_SERVER_ERROR, msg, errors)); + .body(ApiResponse.error(HttpStatus.INTERNAL_SERVER_ERROR.value(), msg, errors)); } private String resolveStrict(String keyOrText, Object... args) { diff --git a/licensing-service/src/main/java/io/github/bsayli/licensing/api/openapi/SwaggerLicensingResponseCustomizer.java b/licensing-service/src/main/java/io/github/bsayli/licensing/api/openapi/SwaggerLicensingResponseCustomizer.java deleted file mode 100644 index 68cb349..0000000 --- a/licensing-service/src/main/java/io/github/bsayli/licensing/api/openapi/SwaggerLicensingResponseCustomizer.java +++ /dev/null @@ -1,30 +0,0 @@ -package io.github.bsayli.licensing.api.openapi; - -import static io.github.bsayli.licensing.common.openapi.OpenApiSchemas.SCHEMA_API_RESPONSE; - -import io.github.bsayli.licensing.api.dto.LicenseAccessResponse; -import io.github.bsayli.licensing.common.openapi.ApiResponseSchemaFactory; -import org.springdoc.core.customizers.OpenApiCustomizer; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -@Configuration -public class SwaggerLicensingResponseCustomizer { - - private static final String REF_LICENSE_VALIDATION_RESPONSE = - LicenseAccessResponse.class.getSimpleName(); - - private static String apiResponseWrapperNameFor(String ref) { - return SCHEMA_API_RESPONSE + ref; - } - - @Bean - public OpenApiCustomizer licensingWrappers() { - return openApi -> - openApi - .getComponents() - .addSchemas( - apiResponseWrapperNameFor(REF_LICENSE_VALIDATION_RESPONSE), - ApiResponseSchemaFactory.createComposedWrapper(REF_LICENSE_VALIDATION_RESPONSE)); - } -} diff --git a/licensing-service/src/main/java/io/github/bsayli/licensing/common/api/ApiResponse.java b/licensing-service/src/main/java/io/github/bsayli/licensing/common/api/ApiResponse.java deleted file mode 100644 index 46f04e7..0000000 --- a/licensing-service/src/main/java/io/github/bsayli/licensing/common/api/ApiResponse.java +++ /dev/null @@ -1,24 +0,0 @@ -package io.github.bsayli.licensing.common.api; - -import java.util.Collections; -import java.util.List; -import org.springframework.http.HttpStatus; - -public record ApiResponse(int status, String message, T data, List errors) { - public static ApiResponse ok(T data) { - return new ApiResponse<>(HttpStatus.OK.value(), "OK", data, Collections.emptyList()); - } - - public static ApiResponse of(HttpStatus status, String message, T data) { - return new ApiResponse<>(status.value(), message, data, Collections.emptyList()); - } - - public static ApiResponse error(HttpStatus status, String message) { - return new ApiResponse<>(status.value(), message, null, Collections.emptyList()); - } - - public static ApiResponse error(HttpStatus status, String message, List errors) { - return new ApiResponse<>( - status.value(), message, null, errors != null ? errors : Collections.emptyList()); - } -} diff --git a/licensing-service/src/main/java/io/github/bsayli/licensing/common/openapi/ApiResponseSchemaFactory.java b/licensing-service/src/main/java/io/github/bsayli/licensing/common/openapi/ApiResponseSchemaFactory.java deleted file mode 100644 index 7bf4d0e..0000000 --- a/licensing-service/src/main/java/io/github/bsayli/licensing/common/openapi/ApiResponseSchemaFactory.java +++ /dev/null @@ -1,25 +0,0 @@ -package io.github.bsayli.licensing.common.openapi; - -import static io.github.bsayli.licensing.common.openapi.OpenApiSchemas.*; - -import io.swagger.v3.oas.models.media.ComposedSchema; -import io.swagger.v3.oas.models.media.ObjectSchema; -import io.swagger.v3.oas.models.media.Schema; -import java.util.List; - -public final class ApiResponseSchemaFactory { - private ApiResponseSchemaFactory() {} - - public static Schema createComposedWrapper(String dataRefName) { - var schema = new ComposedSchema(); - schema.setAllOf( - List.of( - new Schema<>().$ref("#/components/schemas/" + SCHEMA_API_RESPONSE), - new ObjectSchema() - .addProperty( - PROP_DATA, new Schema<>().$ref("#/components/schemas/" + dataRefName)))); - schema.addExtension(EXT_API_WRAPPER, true); - schema.addExtension(EXT_API_WRAPPER_DATATYPE, dataRefName); - return schema; - } -} diff --git a/licensing-service/src/main/java/io/github/bsayli/licensing/common/openapi/OpenApiSchemas.java b/licensing-service/src/main/java/io/github/bsayli/licensing/common/openapi/OpenApiSchemas.java deleted file mode 100644 index 4a071e6..0000000 --- a/licensing-service/src/main/java/io/github/bsayli/licensing/common/openapi/OpenApiSchemas.java +++ /dev/null @@ -1,18 +0,0 @@ -package io.github.bsayli.licensing.common.openapi; - -public final class OpenApiSchemas { - // Common property keys - public static final String PROP_STATUS = "status"; - public static final String PROP_MESSAGE = "message"; - public static final String PROP_ERRORS = "errors"; - public static final String PROP_ERROR_CODE = "errorCode"; - public static final String PROP_DATA = "data"; - // Base envelopes - public static final String SCHEMA_API_RESPONSE = "ApiResponse"; - public static final String SCHEMA_API_RESPONSE_VOID = "ApiResponseVoid"; - // Vendor extensions - public static final String EXT_API_WRAPPER = "x-api-wrapper"; - public static final String EXT_API_WRAPPER_DATATYPE = "x-api-wrapper-datatype"; - - private OpenApiSchemas() {} -} diff --git a/licensing-service/src/main/java/io/github/bsayli/licensing/common/openapi/SwaggerResponseCustomizer.java b/licensing-service/src/main/java/io/github/bsayli/licensing/common/openapi/SwaggerResponseCustomizer.java deleted file mode 100644 index 87f2961..0000000 --- a/licensing-service/src/main/java/io/github/bsayli/licensing/common/openapi/SwaggerResponseCustomizer.java +++ /dev/null @@ -1,55 +0,0 @@ -package io.github.bsayli.licensing.common.openapi; - -import static io.github.bsayli.licensing.common.openapi.OpenApiSchemas.*; - -import io.swagger.v3.oas.models.media.ArraySchema; -import io.swagger.v3.oas.models.media.IntegerSchema; -import io.swagger.v3.oas.models.media.ObjectSchema; -import io.swagger.v3.oas.models.media.StringSchema; -import org.springdoc.core.customizers.OpenApiCustomizer; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -@Configuration -public class SwaggerResponseCustomizer { - - @Bean - public OpenApiCustomizer responseEnvelopeSchemas() { - return openApi -> { - if (!openApi.getComponents().getSchemas().containsKey(SCHEMA_API_RESPONSE)) { - openApi - .getComponents() - .addSchemas( - SCHEMA_API_RESPONSE, - new ObjectSchema() - .addProperty(PROP_STATUS, new IntegerSchema().format("int32")) - .addProperty(PROP_MESSAGE, new StringSchema()) - .addProperty( - PROP_ERRORS, - new ArraySchema() - .items( - new ObjectSchema() - .addProperty(PROP_ERROR_CODE, new StringSchema()) - .addProperty(PROP_MESSAGE, new StringSchema())))); - } - - if (!openApi.getComponents().getSchemas().containsKey(SCHEMA_API_RESPONSE_VOID)) { - openApi - .getComponents() - .addSchemas( - SCHEMA_API_RESPONSE_VOID, - new ObjectSchema() - .addProperty(PROP_STATUS, new IntegerSchema().format("int32")) - .addProperty(PROP_MESSAGE, new StringSchema()) - .addProperty(PROP_DATA, new ObjectSchema()) - .addProperty( - PROP_ERRORS, - new ArraySchema() - .items( - new ObjectSchema() - .addProperty(PROP_ERROR_CODE, new StringSchema()) - .addProperty(PROP_MESSAGE, new StringSchema())))); - } - }; - } -} diff --git a/licensing-service/src/main/resources/application.yml b/licensing-service/src/main/resources/application.yml index fa0089f..36b9ad5 100644 --- a/licensing-service/src/main/resources/application.yml +++ b/licensing-service/src/main/resources/application.yml @@ -59,6 +59,8 @@ logging: org.springframework.web: INFO org.springframework.security: INFO io.github.bsayli.licensing: DEBUG + io.github.blueprintplatform: DEBUG + licensing: api: @@ -72,6 +74,10 @@ app: version: @project.version@ base-url: "http://localhost:${server.port}${server.servlet.context-path:}" +openapi-generics: + envelope: + type: io.github.bsayli.licensing.contract.api.ApiResponse + license: jwt: private: diff --git a/licensing-service/src/test/java/io/github/bsayli/licensing/api/controller/LicenseControllerTest.java b/licensing-service/src/test/java/io/github/bsayli/licensing/api/controller/LicenseControllerTest.java index 18f7f72..edddf0e 100644 --- a/licensing-service/src/test/java/io/github/bsayli/licensing/api/controller/LicenseControllerTest.java +++ b/licensing-service/src/test/java/io/github/bsayli/licensing/api/controller/LicenseControllerTest.java @@ -6,8 +6,8 @@ import io.github.bsayli.licensing.api.dto.IssueAccessRequest; import io.github.bsayli.licensing.api.dto.LicenseAccessResponse; import io.github.bsayli.licensing.api.dto.ValidateAccessRequest; -import io.github.bsayli.licensing.common.api.ApiResponse; import io.github.bsayli.licensing.common.i18n.LocalizedMessageResolver; +import io.github.bsayli.licensing.contract.api.ApiResponse; import io.github.bsayli.licensing.service.LicenseOrchestrationService; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Tag; @@ -43,11 +43,11 @@ void createAccess() { assertEquals(HttpStatus.OK, resp.getStatusCode()); assertNotNull(resp.getBody()); - assertEquals(200, resp.getBody().status()); - assertEquals("License is valid", resp.getBody().message()); - assertNotNull(resp.getBody().data()); - assertEquals("jwt-token", resp.getBody().data().licenseToken()); - assertTrue(resp.getBody().errors().isEmpty()); + assertEquals(200, resp.getBody().getStatus()); + assertEquals("License is valid", resp.getBody().getMessage()); + assertNotNull(resp.getBody().getData()); + assertEquals("jwt-token", resp.getBody().getData().licenseToken()); + assertTrue(resp.getBody().getErrors().isEmpty()); verify(service).issueAccess(req); verify(messageResolver).getMessage("license.validation.success"); @@ -68,11 +68,11 @@ void validateToken_shouldReturnOkWithRefreshedAccess() { assertEquals(HttpStatus.OK, resp.getStatusCode()); assertNotNull(resp.getBody()); - assertEquals(200, resp.getBody().status()); - assertEquals("License is valid", resp.getBody().message()); - assertNotNull(resp.getBody().data()); - assertEquals("new-jwt", resp.getBody().data().licenseToken()); - assertTrue(resp.getBody().errors().isEmpty()); + assertEquals(200, resp.getBody().getStatus()); + assertEquals("License is valid", resp.getBody().getMessage()); + assertNotNull(resp.getBody().getData()); + assertEquals("new-jwt", resp.getBody().getData().licenseToken()); + assertTrue(resp.getBody().getErrors().isEmpty()); verify(service).validateAccess(req, token); verify(messageResolver).getMessage("license.validation.success"); @@ -93,12 +93,12 @@ void validateAccess_shouldReturnOkActive() { assertEquals(HttpStatus.OK, resp.getStatusCode()); assertNotNull(resp.getBody()); - assertEquals("License is valid", resp.getBody().message()); - assertNotNull(resp.getBody().data()); - assertNull(resp.getBody().data().licenseToken()); + assertEquals("License is valid", resp.getBody().getMessage()); + assertNotNull(resp.getBody().getData()); + assertNull(resp.getBody().getData().licenseToken()); verify(service).validateAccess(req, token); verify(messageResolver).getMessage("license.validation.success"); verifyNoMoreInteractions(service, messageResolver); } -} +} \ No newline at end of file diff --git a/pom.xml b/pom.xml index e80b277..8ef821a 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ io.github.bsayli licensing-aggregator - 1.0.4 + 1.0.5 pom licensing (aggregator) @@ -16,7 +16,7 @@ HEAD - 0.8.13 + 0.8.14 @@ -28,6 +28,7 @@ + licensing-api-contract license-generator licensing-service licensing-service-client