Implement IronLicensing Java SDK
- Add LicenseClient with validation, activation, deactivation - Add feature checking with hasFeature/requireFeature pattern - Add trial management and in-app purchase support - Add CompletableFuture for async operations - Add thread-safe operations with ReadWriteLock - Add IronLicensing static facade for global usage - Add machine ID persistence for activation tracking Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
d30fd605bc
commit
c88671da39
|
|
@ -0,0 +1,60 @@
|
||||||
|
# Compiled class file
|
||||||
|
*.class
|
||||||
|
|
||||||
|
# Log file
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# BlueJ files
|
||||||
|
*.ctxt
|
||||||
|
|
||||||
|
# Mobile Tools for Java (J2ME)
|
||||||
|
.mtj.tmp/
|
||||||
|
|
||||||
|
# Package Files
|
||||||
|
*.jar
|
||||||
|
*.war
|
||||||
|
*.nar
|
||||||
|
*.ear
|
||||||
|
*.zip
|
||||||
|
*.tar.gz
|
||||||
|
*.rar
|
||||||
|
|
||||||
|
# virtual machine crash logs
|
||||||
|
hs_err_pid*
|
||||||
|
replay_pid*
|
||||||
|
|
||||||
|
# Maven
|
||||||
|
target/
|
||||||
|
pom.xml.tag
|
||||||
|
pom.xml.releaseBackup
|
||||||
|
pom.xml.versionsBackup
|
||||||
|
pom.xml.next
|
||||||
|
release.properties
|
||||||
|
dependency-reduced-pom.xml
|
||||||
|
buildNumber.properties
|
||||||
|
.mvn/timing.properties
|
||||||
|
.mvn/wrapper/maven-wrapper.jar
|
||||||
|
|
||||||
|
# Gradle
|
||||||
|
.gradle/
|
||||||
|
build/
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.idea/
|
||||||
|
*.iml
|
||||||
|
*.ipr
|
||||||
|
*.iws
|
||||||
|
.project
|
||||||
|
.classpath
|
||||||
|
.settings/
|
||||||
|
.vscode/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
.factorypath
|
||||||
|
|
||||||
|
# OS
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# IronLicensing cache
|
||||||
|
.ironlicensing/
|
||||||
360
README.md
360
README.md
|
|
@ -1,2 +1,358 @@
|
||||||
# ironlicensing-java
|
# IronLicensing Java SDK
|
||||||
IronLicensing SDK for Java - Software licensing and activation
|
|
||||||
|
Official Java SDK for [IronLicensing](https://ironlicensing.com) - Software licensing and activation for your applications.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
### Maven
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.ironservices</groupId>
|
||||||
|
<artifactId>licensing</artifactId>
|
||||||
|
<version>1.0.0</version>
|
||||||
|
</dependency>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Gradle
|
||||||
|
|
||||||
|
```groovy
|
||||||
|
implementation 'com.ironservices:licensing:1.0.0'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
### Using Static API
|
||||||
|
|
||||||
|
```java
|
||||||
|
import com.ironservices.licensing.*;
|
||||||
|
|
||||||
|
public class Main {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
// Initialize the SDK
|
||||||
|
IronLicensing.init("pk_live_your_public_key", "your-product-slug");
|
||||||
|
|
||||||
|
// Validate a license
|
||||||
|
LicenseResult result = IronLicensing.validate("IRON-XXXX-XXXX-XXXX-XXXX");
|
||||||
|
if (result.isValid()) {
|
||||||
|
System.out.println("License is valid!");
|
||||||
|
System.out.println("Status: " + result.getLicense().getStatus());
|
||||||
|
} else {
|
||||||
|
System.out.println("Validation failed: " + result.getError());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for features
|
||||||
|
if (IronLicensing.hasFeature("premium")) {
|
||||||
|
System.out.println("Premium features enabled!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Using Client Instance
|
||||||
|
|
||||||
|
```java
|
||||||
|
import com.ironservices.licensing.*;
|
||||||
|
|
||||||
|
public class Main {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
LicenseOptions options = LicenseOptions.builder("pk_live_your_public_key", "your-product-slug")
|
||||||
|
.debug(true)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
LicenseClient client = new LicenseClient(options);
|
||||||
|
|
||||||
|
// Activate license
|
||||||
|
LicenseResult result = client.activate("IRON-XXXX-XXXX-XXXX-XXXX", "My Machine");
|
||||||
|
|
||||||
|
if (result.isValid()) {
|
||||||
|
System.out.println("Activated! License type: " + result.getLicense().getType());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
### Builder Pattern
|
||||||
|
|
||||||
|
```java
|
||||||
|
LicenseOptions options = LicenseOptions.builder("pk_live_xxx", "your-product")
|
||||||
|
.apiBaseUrl("https://api.ironlicensing.com") // Custom API URL
|
||||||
|
.debug(true) // Enable debug logging
|
||||||
|
.enableOfflineCache(true) // Cache for offline use
|
||||||
|
.cacheValidationMinutes(60) // Cache duration
|
||||||
|
.offlineGraceDays(7) // Offline grace period
|
||||||
|
.httpTimeout(Duration.ofSeconds(30)) // Request timeout
|
||||||
|
.build();
|
||||||
|
```
|
||||||
|
|
||||||
|
### Fluent Setters
|
||||||
|
|
||||||
|
```java
|
||||||
|
LicenseOptions options = new LicenseOptions("pk_live_xxx", "your-product")
|
||||||
|
.setDebug(true)
|
||||||
|
.setEnableOfflineCache(true);
|
||||||
|
```
|
||||||
|
|
||||||
|
## License Validation
|
||||||
|
|
||||||
|
```java
|
||||||
|
// Synchronous validation
|
||||||
|
LicenseResult result = client.validate("IRON-XXXX-XXXX-XXXX-XXXX");
|
||||||
|
|
||||||
|
// Asynchronous validation
|
||||||
|
client.validateAsync("IRON-XXXX-XXXX-XXXX-XXXX")
|
||||||
|
.thenAccept(r -> {
|
||||||
|
if (r.isValid()) {
|
||||||
|
License license = r.getLicense();
|
||||||
|
System.out.println("License: " + license.getKey());
|
||||||
|
System.out.println("Status: " + license.getStatus());
|
||||||
|
System.out.println("Type: " + license.getType());
|
||||||
|
System.out.printf("Activations: %d/%d%n",
|
||||||
|
license.getCurrentActivations(),
|
||||||
|
license.getMaxActivations());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## License Activation
|
||||||
|
|
||||||
|
```java
|
||||||
|
// Simple activation (uses hostname as machine name)
|
||||||
|
LicenseResult result = client.activate("IRON-XXXX-XXXX-XXXX-XXXX");
|
||||||
|
|
||||||
|
// With custom machine name
|
||||||
|
LicenseResult result = client.activate("IRON-XXXX-XXXX-XXXX-XXXX", "Production Server");
|
||||||
|
|
||||||
|
if (result.isValid()) {
|
||||||
|
System.out.println("License activated successfully!");
|
||||||
|
|
||||||
|
// View activations
|
||||||
|
for (Activation activation : result.getActivations()) {
|
||||||
|
System.out.printf("- %s (%s)%n", activation.getMachineName(), activation.getPlatform());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Async activation
|
||||||
|
client.activateAsync(licenseKey, "My Machine")
|
||||||
|
.thenAccept(r -> System.out.println("Activated: " + r.isValid()));
|
||||||
|
```
|
||||||
|
|
||||||
|
## License Deactivation
|
||||||
|
|
||||||
|
```java
|
||||||
|
// Synchronous
|
||||||
|
if (client.deactivate()) {
|
||||||
|
System.out.println("License deactivated from this machine");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Asynchronous
|
||||||
|
client.deactivateAsync()
|
||||||
|
.thenAccept(success -> {
|
||||||
|
if (success) {
|
||||||
|
System.out.println("Deactivated successfully");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## Feature Checking
|
||||||
|
|
||||||
|
```java
|
||||||
|
// Check if feature is available
|
||||||
|
if (client.hasFeature("advanced-analytics")) {
|
||||||
|
// Enable advanced analytics
|
||||||
|
}
|
||||||
|
|
||||||
|
// Require feature (throws LicenseRequiredException if not available)
|
||||||
|
try {
|
||||||
|
client.requireFeature("export-pdf");
|
||||||
|
// Feature is available, continue with export
|
||||||
|
} catch (LicenseRequiredException e) {
|
||||||
|
System.out.println("Feature not available: " + e.getFeature());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get feature details
|
||||||
|
Feature feature = client.getFeature("max-users");
|
||||||
|
if (feature != null) {
|
||||||
|
System.out.printf("Feature: %s - %s%n", feature.getName(), feature.getDescription());
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Trial Management
|
||||||
|
|
||||||
|
```java
|
||||||
|
LicenseResult result = client.startTrial("user@example.com");
|
||||||
|
|
||||||
|
if (result.isValid()) {
|
||||||
|
System.out.println("Trial started!");
|
||||||
|
System.out.println("Trial key: " + result.getLicense().getKey());
|
||||||
|
|
||||||
|
String expiresAt = result.getLicense().getExpiresAt();
|
||||||
|
if (expiresAt != null) {
|
||||||
|
System.out.println("Expires: " + expiresAt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## In-App Purchase
|
||||||
|
|
||||||
|
```java
|
||||||
|
// Get available tiers
|
||||||
|
List<ProductTier> tiers = client.getTiers();
|
||||||
|
for (ProductTier tier : tiers) {
|
||||||
|
System.out.printf("%s - $%.2f %s%n", tier.getName(), tier.getPrice(), tier.getCurrency());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start checkout
|
||||||
|
CheckoutResult checkout = client.startPurchase("tier-id", "user@example.com");
|
||||||
|
if (checkout.isSuccess()) {
|
||||||
|
System.out.println("Checkout URL: " + checkout.getCheckoutUrl());
|
||||||
|
// Open URL in browser for user to complete purchase
|
||||||
|
}
|
||||||
|
|
||||||
|
// Async purchase
|
||||||
|
client.startPurchaseAsync("tier-id", "user@example.com")
|
||||||
|
.thenAccept(c -> {
|
||||||
|
if (c.isSuccess()) {
|
||||||
|
// Handle success
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## License Status
|
||||||
|
|
||||||
|
```java
|
||||||
|
// Get current license
|
||||||
|
License license = client.getLicense();
|
||||||
|
if (license != null) {
|
||||||
|
System.out.println("Licensed to: " + license.getEmail());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check status
|
||||||
|
LicenseStatus status = client.getStatus();
|
||||||
|
switch (status) {
|
||||||
|
case VALID:
|
||||||
|
System.out.println("License is valid");
|
||||||
|
break;
|
||||||
|
case EXPIRED:
|
||||||
|
System.out.println("License has expired");
|
||||||
|
break;
|
||||||
|
case TRIAL:
|
||||||
|
System.out.println("Running in trial mode");
|
||||||
|
break;
|
||||||
|
case NOT_ACTIVATED:
|
||||||
|
System.out.println("No license activated");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
System.out.println("Status: " + status);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Quick checks
|
||||||
|
if (client.isLicensed()) {
|
||||||
|
System.out.println("Application is licensed");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (client.isTrial()) {
|
||||||
|
System.out.println("Running in trial mode");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## License Change Listener
|
||||||
|
|
||||||
|
```java
|
||||||
|
client.setOnLicenseChanged(license -> {
|
||||||
|
if (license != null) {
|
||||||
|
System.out.println("License updated: " + license.getStatus());
|
||||||
|
} else {
|
||||||
|
System.out.println("License removed");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## License Types
|
||||||
|
|
||||||
|
| Type | Description |
|
||||||
|
|------|-------------|
|
||||||
|
| `PERPETUAL` | One-time purchase, never expires |
|
||||||
|
| `SUBSCRIPTION` | Recurring payment, expires if not renewed |
|
||||||
|
| `TRIAL` | Time-limited trial license |
|
||||||
|
|
||||||
|
## License Statuses
|
||||||
|
|
||||||
|
| Status | Description |
|
||||||
|
|--------|-------------|
|
||||||
|
| `VALID` | License is valid and active |
|
||||||
|
| `EXPIRED` | License has expired |
|
||||||
|
| `SUSPENDED` | License temporarily suspended |
|
||||||
|
| `REVOKED` | License permanently revoked |
|
||||||
|
| `TRIAL` | Active trial license |
|
||||||
|
| `TRIAL_EXPIRED` | Trial period ended |
|
||||||
|
| `NOT_ACTIVATED` | No license activated |
|
||||||
|
|
||||||
|
## Thread Safety
|
||||||
|
|
||||||
|
The client is thread-safe and can be used concurrently:
|
||||||
|
|
||||||
|
```java
|
||||||
|
ExecutorService executor = Executors.newFixedThreadPool(10);
|
||||||
|
for (int i = 0; i < 10; i++) {
|
||||||
|
executor.submit(() -> {
|
||||||
|
if (client.hasFeature("concurrent-feature")) {
|
||||||
|
// Safe to call from multiple threads
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
executor.shutdown();
|
||||||
|
```
|
||||||
|
|
||||||
|
## Error Handling
|
||||||
|
|
||||||
|
```java
|
||||||
|
// Validation errors
|
||||||
|
LicenseResult result = client.validate(licenseKey);
|
||||||
|
if (!result.isValid()) {
|
||||||
|
String error = result.getError();
|
||||||
|
switch (error) {
|
||||||
|
case "license_not_found":
|
||||||
|
System.out.println("Invalid license key");
|
||||||
|
break;
|
||||||
|
case "license_expired":
|
||||||
|
System.out.println("Your license has expired");
|
||||||
|
break;
|
||||||
|
case "max_activations_reached":
|
||||||
|
System.out.println("No more activations available");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
System.out.println("Error: " + error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Feature requirement errors
|
||||||
|
try {
|
||||||
|
client.requireFeature("premium");
|
||||||
|
} catch (LicenseRequiredException e) {
|
||||||
|
System.out.printf("Feature '%s' requires a valid license%n", e.getFeature());
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Machine ID
|
||||||
|
|
||||||
|
The SDK automatically generates and persists a unique machine ID at `~/.ironlicensing/machine_id`. This ID is used for:
|
||||||
|
- Tracking activations per machine
|
||||||
|
- Preventing license sharing
|
||||||
|
- Offline validation
|
||||||
|
|
||||||
|
```java
|
||||||
|
String machineId = client.getMachineId();
|
||||||
|
```
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
- Java 11 or later
|
||||||
|
- OkHttp 4.x
|
||||||
|
- Gson 2.x
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT License - see LICENSE file for details.
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,98 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<groupId>com.ironservices</groupId>
|
||||||
|
<artifactId>licensing</artifactId>
|
||||||
|
<version>1.0.0</version>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
|
<name>IronLicensing Java SDK</name>
|
||||||
|
<description>Official Java SDK for IronLicensing - Software licensing and activation</description>
|
||||||
|
<url>https://github.com/IronServices/ironlicensing-java</url>
|
||||||
|
|
||||||
|
<licenses>
|
||||||
|
<license>
|
||||||
|
<name>MIT License</name>
|
||||||
|
<url>https://opensource.org/licenses/MIT</url>
|
||||||
|
</license>
|
||||||
|
</licenses>
|
||||||
|
|
||||||
|
<developers>
|
||||||
|
<developer>
|
||||||
|
<name>IronServices</name>
|
||||||
|
<email>support@ironservices.com</email>
|
||||||
|
<organization>IronServices</organization>
|
||||||
|
<organizationUrl>https://ironservices.com</organizationUrl>
|
||||||
|
</developer>
|
||||||
|
</developers>
|
||||||
|
|
||||||
|
<scm>
|
||||||
|
<connection>scm:git:git://github.com/IronServices/ironlicensing-java.git</connection>
|
||||||
|
<developerConnection>scm:git:ssh://github.com:IronServices/ironlicensing-java.git</developerConnection>
|
||||||
|
<url>https://github.com/IronServices/ironlicensing-java/tree/main</url>
|
||||||
|
</scm>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<maven.compiler.source>11</maven.compiler.source>
|
||||||
|
<maven.compiler.target>11</maven.compiler.target>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.squareup.okhttp3</groupId>
|
||||||
|
<artifactId>okhttp</artifactId>
|
||||||
|
<version>4.12.0</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.google.code.gson</groupId>
|
||||||
|
<artifactId>gson</artifactId>
|
||||||
|
<version>2.10.1</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.junit.jupiter</groupId>
|
||||||
|
<artifactId>junit-jupiter</artifactId>
|
||||||
|
<version>5.10.0</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<version>3.11.0</version>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-source-plugin</artifactId>
|
||||||
|
<version>3.3.0</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>attach-sources</id>
|
||||||
|
<goals>
|
||||||
|
<goal>jar-no-fork</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-javadoc-plugin</artifactId>
|
||||||
|
<version>3.6.0</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>attach-javadocs</id>
|
||||||
|
<goals>
|
||||||
|
<goal>jar</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</project>
|
||||||
|
|
@ -0,0 +1,81 @@
|
||||||
|
package com.ironservices.licensing;
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents an activation of a license on a machine.
|
||||||
|
*/
|
||||||
|
public class Activation {
|
||||||
|
@SerializedName("id")
|
||||||
|
private String id;
|
||||||
|
|
||||||
|
@SerializedName("machineId")
|
||||||
|
private String machineId;
|
||||||
|
|
||||||
|
@SerializedName("machineName")
|
||||||
|
private String machineName;
|
||||||
|
|
||||||
|
@SerializedName("platform")
|
||||||
|
private String platform;
|
||||||
|
|
||||||
|
@SerializedName("activatedAt")
|
||||||
|
private String activatedAt;
|
||||||
|
|
||||||
|
@SerializedName("lastSeenAt")
|
||||||
|
private String lastSeenAt;
|
||||||
|
|
||||||
|
public Activation() {}
|
||||||
|
|
||||||
|
public String getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(String id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMachineId() {
|
||||||
|
return machineId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMachineId(String machineId) {
|
||||||
|
this.machineId = machineId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMachineName() {
|
||||||
|
return machineName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMachineName(String machineName) {
|
||||||
|
this.machineName = machineName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPlatform() {
|
||||||
|
return platform;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPlatform(String platform) {
|
||||||
|
this.platform = platform;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getActivatedAt() {
|
||||||
|
return activatedAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setActivatedAt(String activatedAt) {
|
||||||
|
this.activatedAt = activatedAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLastSeenAt() {
|
||||||
|
return lastSeenAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLastSeenAt(String lastSeenAt) {
|
||||||
|
this.lastSeenAt = lastSeenAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "Activation{id='" + id + "', machineName='" + machineName + "', platform='" + platform + "'}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,76 @@
|
||||||
|
package com.ironservices.licensing;
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the result of starting a checkout.
|
||||||
|
*/
|
||||||
|
public class CheckoutResult {
|
||||||
|
@SerializedName("success")
|
||||||
|
private boolean success;
|
||||||
|
|
||||||
|
@SerializedName("checkoutUrl")
|
||||||
|
private String checkoutUrl;
|
||||||
|
|
||||||
|
@SerializedName("sessionId")
|
||||||
|
private String sessionId;
|
||||||
|
|
||||||
|
@SerializedName("error")
|
||||||
|
private String error;
|
||||||
|
|
||||||
|
public CheckoutResult() {}
|
||||||
|
|
||||||
|
public CheckoutResult(boolean success, String error) {
|
||||||
|
this.success = success;
|
||||||
|
this.error = error;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static CheckoutResult success(String checkoutUrl, String sessionId) {
|
||||||
|
CheckoutResult result = new CheckoutResult();
|
||||||
|
result.success = true;
|
||||||
|
result.checkoutUrl = checkoutUrl;
|
||||||
|
result.sessionId = sessionId;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static CheckoutResult failure(String error) {
|
||||||
|
return new CheckoutResult(false, error);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isSuccess() {
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSuccess(boolean success) {
|
||||||
|
this.success = success;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCheckoutUrl() {
|
||||||
|
return checkoutUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCheckoutUrl(String checkoutUrl) {
|
||||||
|
this.checkoutUrl = checkoutUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSessionId() {
|
||||||
|
return sessionId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSessionId(String sessionId) {
|
||||||
|
this.sessionId = sessionId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getError() {
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setError(String error) {
|
||||||
|
this.error = error;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "CheckoutResult{success=" + success + ", checkoutUrl='" + checkoutUrl + "'}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,77 @@
|
||||||
|
package com.ironservices.licensing;
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a feature in a license.
|
||||||
|
*/
|
||||||
|
public class Feature {
|
||||||
|
@SerializedName("key")
|
||||||
|
private String key;
|
||||||
|
|
||||||
|
@SerializedName("name")
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
@SerializedName("enabled")
|
||||||
|
private boolean enabled;
|
||||||
|
|
||||||
|
@SerializedName("description")
|
||||||
|
private String description;
|
||||||
|
|
||||||
|
@SerializedName("metadata")
|
||||||
|
private Map<String, Object> metadata;
|
||||||
|
|
||||||
|
public Feature() {}
|
||||||
|
|
||||||
|
public Feature(String key, String name, boolean enabled) {
|
||||||
|
this.key = key;
|
||||||
|
this.name = name;
|
||||||
|
this.enabled = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getKey() {
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setKey(String key) {
|
||||||
|
this.key = key;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEnabled() {
|
||||||
|
return enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEnabled(boolean enabled) {
|
||||||
|
this.enabled = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDescription() {
|
||||||
|
return description;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDescription(String description) {
|
||||||
|
this.description = description;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, Object> getMetadata() {
|
||||||
|
return metadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMetadata(Map<String, Object> metadata) {
|
||||||
|
this.metadata = metadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "Feature{key='" + key + "', name='" + name + "', enabled=" + enabled + "}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,209 @@
|
||||||
|
package com.ironservices.licensing;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Static facade for the IronLicensing SDK.
|
||||||
|
* Provides a simple global API for license operations.
|
||||||
|
*/
|
||||||
|
public final class IronLicensing {
|
||||||
|
private static volatile LicenseClient client;
|
||||||
|
private static final Object lock = new Object();
|
||||||
|
|
||||||
|
private IronLicensing() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the global IronLicensing client.
|
||||||
|
*
|
||||||
|
* @param publicKey The public key for your product
|
||||||
|
* @param productSlug The product slug
|
||||||
|
*/
|
||||||
|
public static void init(String publicKey, String productSlug) {
|
||||||
|
init(new LicenseOptions(publicKey, productSlug));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the global IronLicensing client with options.
|
||||||
|
*
|
||||||
|
* @param options Configuration options
|
||||||
|
*/
|
||||||
|
public static void init(LicenseOptions options) {
|
||||||
|
synchronized (lock) {
|
||||||
|
client = new LicenseClient(options);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the global client instance.
|
||||||
|
*
|
||||||
|
* @return The client, or null if not initialized
|
||||||
|
*/
|
||||||
|
public static LicenseClient getClient() {
|
||||||
|
return client;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static LicenseClient requireClient() {
|
||||||
|
LicenseClient c = client;
|
||||||
|
if (c == null) {
|
||||||
|
throw new IllegalStateException("IronLicensing not initialized. Call IronLicensing.init() first.");
|
||||||
|
}
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates a license key.
|
||||||
|
*
|
||||||
|
* @param licenseKey The license key to validate
|
||||||
|
* @return The validation result
|
||||||
|
*/
|
||||||
|
public static LicenseResult validate(String licenseKey) {
|
||||||
|
return requireClient().validate(licenseKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates a license key asynchronously.
|
||||||
|
*
|
||||||
|
* @param licenseKey The license key to validate
|
||||||
|
* @return A CompletableFuture with the validation result
|
||||||
|
*/
|
||||||
|
public static CompletableFuture<LicenseResult> validateAsync(String licenseKey) {
|
||||||
|
return requireClient().validateAsync(licenseKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Activates a license key on this machine.
|
||||||
|
*
|
||||||
|
* @param licenseKey The license key to activate
|
||||||
|
* @return The activation result
|
||||||
|
*/
|
||||||
|
public static LicenseResult activate(String licenseKey) {
|
||||||
|
return requireClient().activate(licenseKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Activates a license key with a custom machine name.
|
||||||
|
*
|
||||||
|
* @param licenseKey The license key to activate
|
||||||
|
* @param machineName The machine name
|
||||||
|
* @return The activation result
|
||||||
|
*/
|
||||||
|
public static LicenseResult activate(String licenseKey, String machineName) {
|
||||||
|
return requireClient().activate(licenseKey, machineName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deactivates the current license from this machine.
|
||||||
|
*
|
||||||
|
* @return true if deactivation was successful
|
||||||
|
*/
|
||||||
|
public static boolean deactivate() {
|
||||||
|
return requireClient().deactivate();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts a trial for the given email.
|
||||||
|
*
|
||||||
|
* @param email The email address for the trial
|
||||||
|
* @return The trial result
|
||||||
|
*/
|
||||||
|
public static LicenseResult startTrial(String email) {
|
||||||
|
return requireClient().startTrial(email);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if a feature is available.
|
||||||
|
*
|
||||||
|
* @param featureKey The feature key to check
|
||||||
|
* @return true if the feature is enabled
|
||||||
|
*/
|
||||||
|
public static boolean hasFeature(String featureKey) {
|
||||||
|
return requireClient().hasFeature(featureKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Requires a feature to be available.
|
||||||
|
*
|
||||||
|
* @param featureKey The feature key to require
|
||||||
|
* @throws LicenseRequiredException if the feature is not available
|
||||||
|
*/
|
||||||
|
public static void requireFeature(String featureKey) throws LicenseRequiredException {
|
||||||
|
requireClient().requireFeature(featureKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a feature from the current license.
|
||||||
|
*
|
||||||
|
* @param featureKey The feature key
|
||||||
|
* @return The feature, or null if not found
|
||||||
|
*/
|
||||||
|
public static Feature getFeature(String featureKey) {
|
||||||
|
return requireClient().getFeature(featureKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the current license.
|
||||||
|
*
|
||||||
|
* @return The current license, or null if not licensed
|
||||||
|
*/
|
||||||
|
public static License getLicense() {
|
||||||
|
return requireClient().getLicense();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the current license status.
|
||||||
|
*
|
||||||
|
* @return The license status
|
||||||
|
*/
|
||||||
|
public static LicenseStatus getStatus() {
|
||||||
|
return requireClient().getStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the application is licensed.
|
||||||
|
*
|
||||||
|
* @return true if licensed
|
||||||
|
*/
|
||||||
|
public static boolean isLicensed() {
|
||||||
|
return requireClient().isLicensed();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if running in trial mode.
|
||||||
|
*
|
||||||
|
* @return true if in trial mode
|
||||||
|
*/
|
||||||
|
public static boolean isTrial() {
|
||||||
|
return requireClient().isTrial();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets available product tiers.
|
||||||
|
*
|
||||||
|
* @return List of product tiers
|
||||||
|
*/
|
||||||
|
public static List<ProductTier> getTiers() {
|
||||||
|
return requireClient().getTiers();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts a checkout session.
|
||||||
|
*
|
||||||
|
* @param tierId The tier ID to purchase
|
||||||
|
* @param email The customer's email
|
||||||
|
* @return The checkout result
|
||||||
|
*/
|
||||||
|
public static CheckoutResult startPurchase(String tierId, String email) {
|
||||||
|
return requireClient().startPurchase(tierId, email);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets a listener for license changes.
|
||||||
|
*
|
||||||
|
* @param listener The listener to call when license changes
|
||||||
|
*/
|
||||||
|
public static void setOnLicenseChanged(Consumer<License> listener) {
|
||||||
|
requireClient().setOnLicenseChanged(listener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,186 @@
|
||||||
|
package com.ironservices.licensing;
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents license information.
|
||||||
|
*/
|
||||||
|
public class License {
|
||||||
|
@SerializedName("id")
|
||||||
|
private String id;
|
||||||
|
|
||||||
|
@SerializedName("key")
|
||||||
|
private String key;
|
||||||
|
|
||||||
|
@SerializedName("status")
|
||||||
|
private LicenseStatus status;
|
||||||
|
|
||||||
|
@SerializedName("type")
|
||||||
|
private LicenseType type;
|
||||||
|
|
||||||
|
@SerializedName("email")
|
||||||
|
private String email;
|
||||||
|
|
||||||
|
@SerializedName("name")
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
@SerializedName("company")
|
||||||
|
private String company;
|
||||||
|
|
||||||
|
@SerializedName("features")
|
||||||
|
private List<Feature> features;
|
||||||
|
|
||||||
|
@SerializedName("maxActivations")
|
||||||
|
private int maxActivations;
|
||||||
|
|
||||||
|
@SerializedName("currentActivations")
|
||||||
|
private int currentActivations;
|
||||||
|
|
||||||
|
@SerializedName("expiresAt")
|
||||||
|
private String expiresAt;
|
||||||
|
|
||||||
|
@SerializedName("createdAt")
|
||||||
|
private String createdAt;
|
||||||
|
|
||||||
|
@SerializedName("lastValidatedAt")
|
||||||
|
private String lastValidatedAt;
|
||||||
|
|
||||||
|
@SerializedName("metadata")
|
||||||
|
private Map<String, Object> metadata;
|
||||||
|
|
||||||
|
public License() {}
|
||||||
|
|
||||||
|
public String getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(String id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getKey() {
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setKey(String key) {
|
||||||
|
this.key = key;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LicenseStatus getStatus() {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStatus(LicenseStatus status) {
|
||||||
|
this.status = status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LicenseType getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setType(LicenseType type) {
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getEmail() {
|
||||||
|
return email;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEmail(String email) {
|
||||||
|
this.email = email;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCompany() {
|
||||||
|
return company;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCompany(String company) {
|
||||||
|
this.company = company;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Feature> getFeatures() {
|
||||||
|
return features;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFeatures(List<Feature> features) {
|
||||||
|
this.features = features;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMaxActivations() {
|
||||||
|
return maxActivations;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMaxActivations(int maxActivations) {
|
||||||
|
this.maxActivations = maxActivations;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCurrentActivations() {
|
||||||
|
return currentActivations;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCurrentActivations(int currentActivations) {
|
||||||
|
this.currentActivations = currentActivations;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getExpiresAt() {
|
||||||
|
return expiresAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setExpiresAt(String expiresAt) {
|
||||||
|
this.expiresAt = expiresAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCreatedAt() {
|
||||||
|
return createdAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCreatedAt(String createdAt) {
|
||||||
|
this.createdAt = createdAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLastValidatedAt() {
|
||||||
|
return lastValidatedAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLastValidatedAt(String lastValidatedAt) {
|
||||||
|
this.lastValidatedAt = lastValidatedAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, Object> getMetadata() {
|
||||||
|
return metadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMetadata(Map<String, Object> metadata) {
|
||||||
|
this.metadata = metadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasFeature(String featureKey) {
|
||||||
|
if (features == null) return false;
|
||||||
|
return features.stream()
|
||||||
|
.anyMatch(f -> f.getKey().equals(featureKey) && f.isEnabled());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Feature getFeature(String featureKey) {
|
||||||
|
if (features == null) return null;
|
||||||
|
return features.stream()
|
||||||
|
.filter(f -> f.getKey().equals(featureKey))
|
||||||
|
.findFirst()
|
||||||
|
.orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "License{id='" + id + "', key='" + key + "', status=" + status + ", type=" + type + "}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,362 @@
|
||||||
|
package com.ironservices.licensing;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.concurrent.locks.ReadWriteLock;
|
||||||
|
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main client for the IronLicensing SDK.
|
||||||
|
* Thread-safe and can be used concurrently.
|
||||||
|
*/
|
||||||
|
public class LicenseClient {
|
||||||
|
private final LicenseOptions options;
|
||||||
|
private final Transport transport;
|
||||||
|
private final ReadWriteLock lock = new ReentrantReadWriteLock();
|
||||||
|
|
||||||
|
private License currentLicense;
|
||||||
|
private String licenseKey;
|
||||||
|
private Consumer<License> onLicenseChanged;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new LicenseClient with the given options.
|
||||||
|
*
|
||||||
|
* @param options Configuration options
|
||||||
|
*/
|
||||||
|
public LicenseClient(LicenseOptions options) {
|
||||||
|
this.options = options;
|
||||||
|
this.transport = new Transport(options);
|
||||||
|
if (options.isDebug()) {
|
||||||
|
log("Client initialized");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new LicenseClient with public key and product slug.
|
||||||
|
*
|
||||||
|
* @param publicKey The public key for your product
|
||||||
|
* @param productSlug The product slug
|
||||||
|
*/
|
||||||
|
public LicenseClient(String publicKey, String productSlug) {
|
||||||
|
this(new LicenseOptions(publicKey, productSlug));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void log(String message) {
|
||||||
|
if (options.isDebug()) {
|
||||||
|
System.out.println("[IronLicensing] " + message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets a listener for license changes.
|
||||||
|
*
|
||||||
|
* @param listener The listener to call when license changes
|
||||||
|
*/
|
||||||
|
public void setOnLicenseChanged(Consumer<License> listener) {
|
||||||
|
this.onLicenseChanged = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates a license key.
|
||||||
|
*
|
||||||
|
* @param licenseKey The license key to validate
|
||||||
|
* @return The validation result
|
||||||
|
*/
|
||||||
|
public LicenseResult validate(String licenseKey) {
|
||||||
|
LicenseResult result = transport.validate(licenseKey);
|
||||||
|
if (result.isValid() && result.getLicense() != null) {
|
||||||
|
updateLicense(licenseKey, result.getLicense());
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates a license key asynchronously.
|
||||||
|
*
|
||||||
|
* @param licenseKey The license key to validate
|
||||||
|
* @return A CompletableFuture with the validation result
|
||||||
|
*/
|
||||||
|
public CompletableFuture<LicenseResult> validateAsync(String licenseKey) {
|
||||||
|
return CompletableFuture.supplyAsync(() -> validate(licenseKey));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Activates a license key on this machine.
|
||||||
|
*
|
||||||
|
* @param licenseKey The license key to activate
|
||||||
|
* @return The activation result
|
||||||
|
*/
|
||||||
|
public LicenseResult activate(String licenseKey) {
|
||||||
|
return activate(licenseKey, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Activates a license key on this machine with a custom machine name.
|
||||||
|
*
|
||||||
|
* @param licenseKey The license key to activate
|
||||||
|
* @param machineName Optional machine name
|
||||||
|
* @return The activation result
|
||||||
|
*/
|
||||||
|
public LicenseResult activate(String licenseKey, String machineName) {
|
||||||
|
LicenseResult result = transport.activate(licenseKey, machineName);
|
||||||
|
if (result.isValid() && result.getLicense() != null) {
|
||||||
|
updateLicense(licenseKey, result.getLicense());
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Activates a license key asynchronously.
|
||||||
|
*
|
||||||
|
* @param licenseKey The license key to activate
|
||||||
|
* @param machineName Optional machine name
|
||||||
|
* @return A CompletableFuture with the activation result
|
||||||
|
*/
|
||||||
|
public CompletableFuture<LicenseResult> activateAsync(String licenseKey, String machineName) {
|
||||||
|
return CompletableFuture.supplyAsync(() -> activate(licenseKey, machineName));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deactivates the current license from this machine.
|
||||||
|
*
|
||||||
|
* @return true if deactivation was successful
|
||||||
|
*/
|
||||||
|
public boolean deactivate() {
|
||||||
|
lock.readLock().lock();
|
||||||
|
String key;
|
||||||
|
try {
|
||||||
|
key = this.licenseKey;
|
||||||
|
} finally {
|
||||||
|
lock.readLock().unlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (key == null || key.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (transport.deactivate(key)) {
|
||||||
|
lock.writeLock().lock();
|
||||||
|
try {
|
||||||
|
this.currentLicense = null;
|
||||||
|
this.licenseKey = null;
|
||||||
|
} finally {
|
||||||
|
lock.writeLock().unlock();
|
||||||
|
}
|
||||||
|
notifyLicenseChanged(null);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deactivates the current license asynchronously.
|
||||||
|
*
|
||||||
|
* @return A CompletableFuture with the deactivation result
|
||||||
|
*/
|
||||||
|
public CompletableFuture<Boolean> deactivateAsync() {
|
||||||
|
return CompletableFuture.supplyAsync(this::deactivate);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts a trial for the given email.
|
||||||
|
*
|
||||||
|
* @param email The email address for the trial
|
||||||
|
* @return The trial result
|
||||||
|
*/
|
||||||
|
public LicenseResult startTrial(String email) {
|
||||||
|
LicenseResult result = transport.startTrial(email);
|
||||||
|
if (result.isValid() && result.getLicense() != null) {
|
||||||
|
updateLicense(result.getLicense().getKey(), result.getLicense());
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts a trial asynchronously.
|
||||||
|
*
|
||||||
|
* @param email The email address for the trial
|
||||||
|
* @return A CompletableFuture with the trial result
|
||||||
|
*/
|
||||||
|
public CompletableFuture<LicenseResult> startTrialAsync(String email) {
|
||||||
|
return CompletableFuture.supplyAsync(() -> startTrial(email));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if a feature is available in the current license.
|
||||||
|
*
|
||||||
|
* @param featureKey The feature key to check
|
||||||
|
* @return true if the feature is enabled
|
||||||
|
*/
|
||||||
|
public boolean hasFeature(String featureKey) {
|
||||||
|
lock.readLock().lock();
|
||||||
|
try {
|
||||||
|
return currentLicense != null && currentLicense.hasFeature(featureKey);
|
||||||
|
} finally {
|
||||||
|
lock.readLock().unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Requires a feature to be available, throws if not.
|
||||||
|
*
|
||||||
|
* @param featureKey The feature key to require
|
||||||
|
* @throws LicenseRequiredException if the feature is not available
|
||||||
|
*/
|
||||||
|
public void requireFeature(String featureKey) throws LicenseRequiredException {
|
||||||
|
if (!hasFeature(featureKey)) {
|
||||||
|
throw new LicenseRequiredException(featureKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a feature from the current license.
|
||||||
|
*
|
||||||
|
* @param featureKey The feature key
|
||||||
|
* @return The feature, or null if not found
|
||||||
|
*/
|
||||||
|
public Feature getFeature(String featureKey) {
|
||||||
|
lock.readLock().lock();
|
||||||
|
try {
|
||||||
|
if (currentLicense != null) {
|
||||||
|
return currentLicense.getFeature(featureKey);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
} finally {
|
||||||
|
lock.readLock().unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the current license.
|
||||||
|
*
|
||||||
|
* @return The current license, or null if not licensed
|
||||||
|
*/
|
||||||
|
public License getLicense() {
|
||||||
|
lock.readLock().lock();
|
||||||
|
try {
|
||||||
|
return currentLicense;
|
||||||
|
} finally {
|
||||||
|
lock.readLock().unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the current license status.
|
||||||
|
*
|
||||||
|
* @return The license status
|
||||||
|
*/
|
||||||
|
public LicenseStatus getStatus() {
|
||||||
|
lock.readLock().lock();
|
||||||
|
try {
|
||||||
|
if (currentLicense != null) {
|
||||||
|
return currentLicense.getStatus();
|
||||||
|
}
|
||||||
|
return LicenseStatus.NOT_ACTIVATED;
|
||||||
|
} finally {
|
||||||
|
lock.readLock().unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the application is licensed.
|
||||||
|
*
|
||||||
|
* @return true if licensed (valid or trial)
|
||||||
|
*/
|
||||||
|
public boolean isLicensed() {
|
||||||
|
lock.readLock().lock();
|
||||||
|
try {
|
||||||
|
if (currentLicense == null) return false;
|
||||||
|
LicenseStatus status = currentLicense.getStatus();
|
||||||
|
return status == LicenseStatus.VALID || status == LicenseStatus.TRIAL;
|
||||||
|
} finally {
|
||||||
|
lock.readLock().unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if running in trial mode.
|
||||||
|
*
|
||||||
|
* @return true if in trial mode
|
||||||
|
*/
|
||||||
|
public boolean isTrial() {
|
||||||
|
lock.readLock().lock();
|
||||||
|
try {
|
||||||
|
if (currentLicense == null) return false;
|
||||||
|
return currentLicense.getStatus() == LicenseStatus.TRIAL ||
|
||||||
|
currentLicense.getType() == LicenseType.TRIAL;
|
||||||
|
} finally {
|
||||||
|
lock.readLock().unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets available product tiers for purchase.
|
||||||
|
*
|
||||||
|
* @return List of product tiers
|
||||||
|
*/
|
||||||
|
public List<ProductTier> getTiers() {
|
||||||
|
return transport.getTiers();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets available product tiers asynchronously.
|
||||||
|
*
|
||||||
|
* @return A CompletableFuture with the list of tiers
|
||||||
|
*/
|
||||||
|
public CompletableFuture<List<ProductTier>> getTiersAsync() {
|
||||||
|
return CompletableFuture.supplyAsync(this::getTiers);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts a checkout session for the specified tier.
|
||||||
|
*
|
||||||
|
* @param tierId The tier ID to purchase
|
||||||
|
* @param email The customer's email
|
||||||
|
* @return The checkout result with URL
|
||||||
|
*/
|
||||||
|
public CheckoutResult startPurchase(String tierId, String email) {
|
||||||
|
return transport.startCheckout(tierId, email);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts a checkout session asynchronously.
|
||||||
|
*
|
||||||
|
* @param tierId The tier ID to purchase
|
||||||
|
* @param email The customer's email
|
||||||
|
* @return A CompletableFuture with the checkout result
|
||||||
|
*/
|
||||||
|
public CompletableFuture<CheckoutResult> startPurchaseAsync(String tierId, String email) {
|
||||||
|
return CompletableFuture.supplyAsync(() -> startPurchase(tierId, email));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the machine ID used for activations.
|
||||||
|
*
|
||||||
|
* @return The machine ID
|
||||||
|
*/
|
||||||
|
public String getMachineId() {
|
||||||
|
return transport.getMachineId();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateLicense(String key, License license) {
|
||||||
|
lock.writeLock().lock();
|
||||||
|
try {
|
||||||
|
this.licenseKey = key;
|
||||||
|
this.currentLicense = license;
|
||||||
|
} finally {
|
||||||
|
lock.writeLock().unlock();
|
||||||
|
}
|
||||||
|
notifyLicenseChanged(license);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void notifyLicenseChanged(License license) {
|
||||||
|
if (onLicenseChanged != null) {
|
||||||
|
try {
|
||||||
|
onLicenseChanged.accept(license);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log("License change listener error: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,153 @@
|
||||||
|
package com.ironservices.licensing;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configuration options for the LicenseClient.
|
||||||
|
*/
|
||||||
|
public class LicenseOptions {
|
||||||
|
private static final String DEFAULT_API_BASE_URL = "https://api.ironlicensing.com";
|
||||||
|
private static final Duration DEFAULT_HTTP_TIMEOUT = Duration.ofSeconds(30);
|
||||||
|
private static final int DEFAULT_CACHE_VALIDATION_MINUTES = 60;
|
||||||
|
private static final int DEFAULT_OFFLINE_GRACE_DAYS = 7;
|
||||||
|
|
||||||
|
private String publicKey;
|
||||||
|
private String productSlug;
|
||||||
|
private String apiBaseUrl = DEFAULT_API_BASE_URL;
|
||||||
|
private boolean debug = false;
|
||||||
|
private boolean enableOfflineCache = true;
|
||||||
|
private int cacheValidationMinutes = DEFAULT_CACHE_VALIDATION_MINUTES;
|
||||||
|
private int offlineGraceDays = DEFAULT_OFFLINE_GRACE_DAYS;
|
||||||
|
private Duration httpTimeout = DEFAULT_HTTP_TIMEOUT;
|
||||||
|
|
||||||
|
public LicenseOptions() {}
|
||||||
|
|
||||||
|
public LicenseOptions(String publicKey, String productSlug) {
|
||||||
|
this.publicKey = publicKey;
|
||||||
|
this.productSlug = productSlug;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Builder builder(String publicKey, String productSlug) {
|
||||||
|
return new Builder(publicKey, productSlug);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPublicKey() {
|
||||||
|
return publicKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LicenseOptions setPublicKey(String publicKey) {
|
||||||
|
this.publicKey = publicKey;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getProductSlug() {
|
||||||
|
return productSlug;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LicenseOptions setProductSlug(String productSlug) {
|
||||||
|
this.productSlug = productSlug;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getApiBaseUrl() {
|
||||||
|
return apiBaseUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LicenseOptions setApiBaseUrl(String apiBaseUrl) {
|
||||||
|
this.apiBaseUrl = apiBaseUrl;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isDebug() {
|
||||||
|
return debug;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LicenseOptions setDebug(boolean debug) {
|
||||||
|
this.debug = debug;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEnableOfflineCache() {
|
||||||
|
return enableOfflineCache;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LicenseOptions setEnableOfflineCache(boolean enableOfflineCache) {
|
||||||
|
this.enableOfflineCache = enableOfflineCache;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCacheValidationMinutes() {
|
||||||
|
return cacheValidationMinutes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LicenseOptions setCacheValidationMinutes(int cacheValidationMinutes) {
|
||||||
|
this.cacheValidationMinutes = cacheValidationMinutes;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getOfflineGraceDays() {
|
||||||
|
return offlineGraceDays;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LicenseOptions setOfflineGraceDays(int offlineGraceDays) {
|
||||||
|
this.offlineGraceDays = offlineGraceDays;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Duration getHttpTimeout() {
|
||||||
|
return httpTimeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LicenseOptions setHttpTimeout(Duration httpTimeout) {
|
||||||
|
this.httpTimeout = httpTimeout;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Builder {
|
||||||
|
private final LicenseOptions options;
|
||||||
|
|
||||||
|
public Builder(String publicKey, String productSlug) {
|
||||||
|
this.options = new LicenseOptions(publicKey, productSlug);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder apiBaseUrl(String apiBaseUrl) {
|
||||||
|
options.setApiBaseUrl(apiBaseUrl);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder debug(boolean debug) {
|
||||||
|
options.setDebug(debug);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder enableOfflineCache(boolean enable) {
|
||||||
|
options.setEnableOfflineCache(enable);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder cacheValidationMinutes(int minutes) {
|
||||||
|
options.setCacheValidationMinutes(minutes);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder offlineGraceDays(int days) {
|
||||||
|
options.setOfflineGraceDays(days);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder httpTimeout(Duration timeout) {
|
||||||
|
options.setHttpTimeout(timeout);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LicenseOptions build() {
|
||||||
|
if (options.getPublicKey() == null || options.getPublicKey().isEmpty()) {
|
||||||
|
throw new IllegalArgumentException("Public key is required");
|
||||||
|
}
|
||||||
|
if (options.getProductSlug() == null || options.getProductSlug().isEmpty()) {
|
||||||
|
throw new IllegalArgumentException("Product slug is required");
|
||||||
|
}
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
package com.ironservices.licensing;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exception thrown when a required feature is not available in the current license.
|
||||||
|
*/
|
||||||
|
public class LicenseRequiredException extends RuntimeException {
|
||||||
|
private final String feature;
|
||||||
|
|
||||||
|
public LicenseRequiredException(String feature) {
|
||||||
|
super("Feature '" + feature + "' requires a valid license");
|
||||||
|
this.feature = feature;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFeature() {
|
||||||
|
return feature;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,87 @@
|
||||||
|
package com.ironservices.licensing;
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the result of a license validation or activation.
|
||||||
|
*/
|
||||||
|
public class LicenseResult {
|
||||||
|
@SerializedName("valid")
|
||||||
|
private boolean valid;
|
||||||
|
|
||||||
|
@SerializedName("license")
|
||||||
|
private License license;
|
||||||
|
|
||||||
|
@SerializedName("activations")
|
||||||
|
private List<Activation> activations;
|
||||||
|
|
||||||
|
@SerializedName("error")
|
||||||
|
private String error;
|
||||||
|
|
||||||
|
@SerializedName("cached")
|
||||||
|
private boolean cached;
|
||||||
|
|
||||||
|
public LicenseResult() {}
|
||||||
|
|
||||||
|
public LicenseResult(boolean valid, String error) {
|
||||||
|
this.valid = valid;
|
||||||
|
this.error = error;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static LicenseResult success(License license) {
|
||||||
|
LicenseResult result = new LicenseResult();
|
||||||
|
result.valid = true;
|
||||||
|
result.license = license;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static LicenseResult failure(String error) {
|
||||||
|
return new LicenseResult(false, error);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isValid() {
|
||||||
|
return valid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setValid(boolean valid) {
|
||||||
|
this.valid = valid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public License getLicense() {
|
||||||
|
return license;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLicense(License license) {
|
||||||
|
this.license = license;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Activation> getActivations() {
|
||||||
|
return activations;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setActivations(List<Activation> activations) {
|
||||||
|
this.activations = activations;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getError() {
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setError(String error) {
|
||||||
|
this.error = error;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isCached() {
|
||||||
|
return cached;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCached(boolean cached) {
|
||||||
|
this.cached = cached;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "LicenseResult{valid=" + valid + ", error='" + error + "'}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,54 @@
|
||||||
|
package com.ironservices.licensing;
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the status of a license.
|
||||||
|
*/
|
||||||
|
public enum LicenseStatus {
|
||||||
|
@SerializedName("valid")
|
||||||
|
VALID("valid"),
|
||||||
|
|
||||||
|
@SerializedName("expired")
|
||||||
|
EXPIRED("expired"),
|
||||||
|
|
||||||
|
@SerializedName("suspended")
|
||||||
|
SUSPENDED("suspended"),
|
||||||
|
|
||||||
|
@SerializedName("revoked")
|
||||||
|
REVOKED("revoked"),
|
||||||
|
|
||||||
|
@SerializedName("invalid")
|
||||||
|
INVALID("invalid"),
|
||||||
|
|
||||||
|
@SerializedName("trial")
|
||||||
|
TRIAL("trial"),
|
||||||
|
|
||||||
|
@SerializedName("trial_expired")
|
||||||
|
TRIAL_EXPIRED("trial_expired"),
|
||||||
|
|
||||||
|
@SerializedName("not_activated")
|
||||||
|
NOT_ACTIVATED("not_activated"),
|
||||||
|
|
||||||
|
@SerializedName("unknown")
|
||||||
|
UNKNOWN("unknown");
|
||||||
|
|
||||||
|
private final String value;
|
||||||
|
|
||||||
|
LicenseStatus(String value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static LicenseStatus fromValue(String value) {
|
||||||
|
for (LicenseStatus status : values()) {
|
||||||
|
if (status.value.equals(value)) {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return UNKNOWN;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
package com.ironservices.licensing;
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the type of a license.
|
||||||
|
*/
|
||||||
|
public enum LicenseType {
|
||||||
|
@SerializedName("perpetual")
|
||||||
|
PERPETUAL("perpetual"),
|
||||||
|
|
||||||
|
@SerializedName("subscription")
|
||||||
|
SUBSCRIPTION("subscription"),
|
||||||
|
|
||||||
|
@SerializedName("trial")
|
||||||
|
TRIAL("trial");
|
||||||
|
|
||||||
|
private final String value;
|
||||||
|
|
||||||
|
LicenseType(String value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static LicenseType fromValue(String value) {
|
||||||
|
for (LicenseType type : values()) {
|
||||||
|
if (type.value.equals(value)) {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return PERPETUAL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,104 @@
|
||||||
|
package com.ironservices.licensing;
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a product tier available for purchase.
|
||||||
|
*/
|
||||||
|
public class ProductTier {
|
||||||
|
@SerializedName("id")
|
||||||
|
private String id;
|
||||||
|
|
||||||
|
@SerializedName("slug")
|
||||||
|
private String slug;
|
||||||
|
|
||||||
|
@SerializedName("name")
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
@SerializedName("description")
|
||||||
|
private String description;
|
||||||
|
|
||||||
|
@SerializedName("price")
|
||||||
|
private double price;
|
||||||
|
|
||||||
|
@SerializedName("currency")
|
||||||
|
private String currency;
|
||||||
|
|
||||||
|
@SerializedName("billingPeriod")
|
||||||
|
private String billingPeriod;
|
||||||
|
|
||||||
|
@SerializedName("features")
|
||||||
|
private List<Feature> features;
|
||||||
|
|
||||||
|
public ProductTier() {}
|
||||||
|
|
||||||
|
public String getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(String id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSlug() {
|
||||||
|
return slug;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSlug(String slug) {
|
||||||
|
this.slug = slug;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDescription() {
|
||||||
|
return description;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDescription(String description) {
|
||||||
|
this.description = description;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getPrice() {
|
||||||
|
return price;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPrice(double price) {
|
||||||
|
this.price = price;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCurrency() {
|
||||||
|
return currency;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCurrency(String currency) {
|
||||||
|
this.currency = currency;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getBillingPeriod() {
|
||||||
|
return billingPeriod;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBillingPeriod(String billingPeriod) {
|
||||||
|
this.billingPeriod = billingPeriod;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Feature> getFeatures() {
|
||||||
|
return features;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFeatures(List<Feature> features) {
|
||||||
|
this.features = features;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "ProductTier{id='" + id + "', name='" + name + "', price=" + price + " " + currency + "}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,220 @@
|
||||||
|
package com.ironservices.licensing;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.GsonBuilder;
|
||||||
|
import com.google.gson.reflect.TypeToken;
|
||||||
|
import okhttp3.*;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.lang.reflect.Type;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.nio.file.*;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HTTP transport layer for IronLicensing API.
|
||||||
|
*/
|
||||||
|
class Transport {
|
||||||
|
private static final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
|
||||||
|
|
||||||
|
private final String baseUrl;
|
||||||
|
private final String publicKey;
|
||||||
|
private final String productSlug;
|
||||||
|
private final boolean debug;
|
||||||
|
private final OkHttpClient httpClient;
|
||||||
|
private final Gson gson;
|
||||||
|
private final String machineId;
|
||||||
|
|
||||||
|
Transport(LicenseOptions options) {
|
||||||
|
this.baseUrl = options.getApiBaseUrl();
|
||||||
|
this.publicKey = options.getPublicKey();
|
||||||
|
this.productSlug = options.getProductSlug();
|
||||||
|
this.debug = options.isDebug();
|
||||||
|
this.httpClient = new OkHttpClient.Builder()
|
||||||
|
.connectTimeout(options.getHttpTimeout().toMillis(), TimeUnit.MILLISECONDS)
|
||||||
|
.readTimeout(options.getHttpTimeout().toMillis(), TimeUnit.MILLISECONDS)
|
||||||
|
.writeTimeout(options.getHttpTimeout().toMillis(), TimeUnit.MILLISECONDS)
|
||||||
|
.build();
|
||||||
|
this.gson = new GsonBuilder().create();
|
||||||
|
this.machineId = getOrCreateMachineId();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void log(String message) {
|
||||||
|
if (debug) {
|
||||||
|
System.out.println("[IronLicensing] " + message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getOrCreateMachineId() {
|
||||||
|
try {
|
||||||
|
Path idPath = Paths.get(System.getProperty("user.home"), ".ironlicensing", "machine_id");
|
||||||
|
if (Files.exists(idPath)) {
|
||||||
|
return new String(Files.readAllBytes(idPath)).trim();
|
||||||
|
}
|
||||||
|
String id = UUID.randomUUID().toString();
|
||||||
|
Files.createDirectories(idPath.getParent());
|
||||||
|
Files.write(idPath, id.getBytes());
|
||||||
|
return id;
|
||||||
|
} catch (IOException e) {
|
||||||
|
return UUID.randomUUID().toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String getMachineId() {
|
||||||
|
return machineId;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getHostname() {
|
||||||
|
try {
|
||||||
|
return InetAddress.getLocalHost().getHostName();
|
||||||
|
} catch (Exception e) {
|
||||||
|
return "unknown";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getPlatform() {
|
||||||
|
String os = System.getProperty("os.name", "").toLowerCase();
|
||||||
|
if (os.contains("win")) return "windows";
|
||||||
|
if (os.contains("mac")) return "macos";
|
||||||
|
if (os.contains("nix") || os.contains("nux")) return "linux";
|
||||||
|
return os;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Request.Builder createRequest(String path) {
|
||||||
|
return new Request.Builder()
|
||||||
|
.url(baseUrl + path)
|
||||||
|
.addHeader("Content-Type", "application/json")
|
||||||
|
.addHeader("X-Public-Key", publicKey)
|
||||||
|
.addHeader("X-Product-Slug", productSlug);
|
||||||
|
}
|
||||||
|
|
||||||
|
LicenseResult validate(String licenseKey) {
|
||||||
|
log("Validating: " + licenseKey.substring(0, Math.min(10, licenseKey.length())) + "...");
|
||||||
|
|
||||||
|
Map<String, String> body = new HashMap<>();
|
||||||
|
body.put("licenseKey", licenseKey);
|
||||||
|
body.put("machineId", machineId);
|
||||||
|
|
||||||
|
Request request = createRequest("/api/v1/validate")
|
||||||
|
.post(RequestBody.create(gson.toJson(body), JSON))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
return executeRequest(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
LicenseResult activate(String licenseKey, String machineName) {
|
||||||
|
log("Activating: " + licenseKey.substring(0, Math.min(10, licenseKey.length())) + "...");
|
||||||
|
|
||||||
|
if (machineName == null || machineName.isEmpty()) {
|
||||||
|
machineName = getHostname();
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, String> body = new HashMap<>();
|
||||||
|
body.put("licenseKey", licenseKey);
|
||||||
|
body.put("machineId", machineId);
|
||||||
|
body.put("machineName", machineName);
|
||||||
|
body.put("platform", getPlatform());
|
||||||
|
|
||||||
|
Request request = createRequest("/api/v1/activate")
|
||||||
|
.post(RequestBody.create(gson.toJson(body), JSON))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
return executeRequest(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean deactivate(String licenseKey) {
|
||||||
|
log("Deactivating license");
|
||||||
|
|
||||||
|
Map<String, String> body = new HashMap<>();
|
||||||
|
body.put("licenseKey", licenseKey);
|
||||||
|
body.put("machineId", machineId);
|
||||||
|
|
||||||
|
Request request = createRequest("/api/v1/deactivate")
|
||||||
|
.post(RequestBody.create(gson.toJson(body), JSON))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
try (Response response = httpClient.newCall(request).execute()) {
|
||||||
|
return response.isSuccessful();
|
||||||
|
} catch (IOException e) {
|
||||||
|
log("Deactivation failed: " + e.getMessage());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LicenseResult startTrial(String email) {
|
||||||
|
log("Starting trial for: " + email);
|
||||||
|
|
||||||
|
Map<String, String> body = new HashMap<>();
|
||||||
|
body.put("email", email);
|
||||||
|
body.put("machineId", machineId);
|
||||||
|
|
||||||
|
Request request = createRequest("/api/v1/trial")
|
||||||
|
.post(RequestBody.create(gson.toJson(body), JSON))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
return executeRequest(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<ProductTier> getTiers() {
|
||||||
|
log("Fetching product tiers");
|
||||||
|
|
||||||
|
Request request = createRequest("/api/v1/tiers")
|
||||||
|
.get()
|
||||||
|
.build();
|
||||||
|
|
||||||
|
try (Response response = httpClient.newCall(request).execute()) {
|
||||||
|
if (response.isSuccessful() && response.body() != null) {
|
||||||
|
String json = response.body().string();
|
||||||
|
Type type = new TypeToken<Map<String, List<ProductTier>>>(){}.getType();
|
||||||
|
Map<String, List<ProductTier>> result = gson.fromJson(json, type);
|
||||||
|
return result.getOrDefault("tiers", Collections.emptyList());
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
log("Failed to fetch tiers: " + e.getMessage());
|
||||||
|
}
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
CheckoutResult startCheckout(String tierId, String email) {
|
||||||
|
log("Starting checkout for tier: " + tierId);
|
||||||
|
|
||||||
|
Map<String, String> body = new HashMap<>();
|
||||||
|
body.put("tierId", tierId);
|
||||||
|
body.put("email", email);
|
||||||
|
|
||||||
|
Request request = createRequest("/api/v1/checkout")
|
||||||
|
.post(RequestBody.create(gson.toJson(body), JSON))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
try (Response response = httpClient.newCall(request).execute()) {
|
||||||
|
String json = response.body() != null ? response.body().string() : "{}";
|
||||||
|
if (response.isSuccessful()) {
|
||||||
|
CheckoutResult result = gson.fromJson(json, CheckoutResult.class);
|
||||||
|
result.setSuccess(true);
|
||||||
|
return result;
|
||||||
|
} else {
|
||||||
|
Map<String, String> errorResponse = gson.fromJson(json,
|
||||||
|
new TypeToken<Map<String, String>>(){}.getType());
|
||||||
|
return CheckoutResult.failure(errorResponse.getOrDefault("error", "Checkout failed"));
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
return CheckoutResult.failure(e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private LicenseResult executeRequest(Request request) {
|
||||||
|
try (Response response = httpClient.newCall(request).execute()) {
|
||||||
|
String json = response.body() != null ? response.body().string() : "{}";
|
||||||
|
if (response.isSuccessful()) {
|
||||||
|
return gson.fromJson(json, LicenseResult.class);
|
||||||
|
} else {
|
||||||
|
Map<String, String> errorResponse = gson.fromJson(json,
|
||||||
|
new TypeToken<Map<String, String>>(){}.getType());
|
||||||
|
return LicenseResult.failure(errorResponse.getOrDefault("error", "Request failed"));
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
return LicenseResult.failure(e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue