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 SDK for Java - Software licensing and activation
|
||||
# IronLicensing Java SDK
|
||||
|
||||
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