diff --git a/.github/workflows/maven-publish.yml b/.github/workflows/maven-publish.yml index 5f0fc09a..1fafc176 100644 --- a/.github/workflows/maven-publish.yml +++ b/.github/workflows/maven-publish.yml @@ -16,7 +16,7 @@ jobs: if: ${{ github.ref == 'refs/heads/develop' }} environment: dev permissions: - contents: read + contents: write packages: write steps: @@ -39,7 +39,7 @@ jobs: # Install gpg secret key cat <(echo -e "${{ secrets.MAVEN_GPG_PRIVATE_KEY }}") | gpg --batch --import # Verify gpg secret key - gpg --list-secret-keys --keyid-format LONG + gpg --list-secret-keys --keyid-format=long || echo "❌ No secret keys found" - id: ssh-setup name: Set SSH key @@ -81,13 +81,17 @@ jobs: if: ${{ github.ref == 'refs/heads/main' }} environment: dev permissions: - contents: read + contents: write packages: write steps: - id: checkout name: Checkout uses: actions/checkout@v4 + with: + ref: main + fetch-depth: 0 # Required for Maven Release Plugin + persist-credentials: false - id: gpg-install name: Install gpg secret key @@ -95,7 +99,8 @@ jobs: # Install gpg secret key cat <(echo -e "${{ secrets.MAVEN_GPG_PRIVATE_KEY }}") | gpg --batch --import # Verify gpg secret key - gpg --list-secret-keys --keyid-format LONG + gpg --list-secret-keys --keyid-format=long || echo "❌ No secret keys found" + echo "Installed gpg secret key" - id: ssh-setup name: Set SSH key @@ -131,7 +136,8 @@ jobs: - id: release name: Release to production - run: mvn clean release:clean release:prepare release:perform -B -Darguments="-Ddevelop.api.key=${{ secrets.DEVELOP_SERVER_API_KEY }} -Dprod.eu1.api.key=${{ secrets.PROD_EU1_SERVER_API_KEY }} -Dprod.na1.api.key=${{ secrets.PROD_NA1_SERVER_API_KEY }}" -DpreparationGoals=install + run: mvn clean release:clean release:prepare release:perform -B -DskipTests=true -DpreparationGoals=install + # run: mvn clean release:clean release:prepare release:perform -B -Darguments="-Ddevelop.api.key=${{ secrets.DEVELOP_SERVER_API_KEY }} -Dprod.eu1.api.key=${{ secrets.PROD_EU1_SERVER_API_KEY }} -Dprod.na1.api.key=${{ secrets.PROD_NA1_SERVER_API_KEY }}" -DskipTests=true -X -DpreparationGoals=install - id: rollback name: Rollback diff --git a/CHANGELOG.md b/CHANGELOG.md index b903e2ba..64ee4ccc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,25 @@ # Changelog +## +## [0.15.5] - 2025-06-04 + +### Added + +- `classFqn` helper to compute fully-qualified names +- import-filter to drop any illegal import strings +- `typeRef` helper to pick simple name or FQN and avoid duplicates +- `lastSegment` helper to filter out a context’s own class from imports + +### Changed + +- Updated `ResolvedContext.hbs` so that all generated-type references go through `typeRef` (simple names only when safe) +- Updated constructor loops in `ResolvedContext.hbs` to call proxy methods with `classFqn` (true FQNs) + +### Fixed + +- “already defined in this compilation unit” compile errors—SDK now builds without name collisions + +## ## [0.15.4] - 2024-11-15 ### Added diff --git a/README.md b/README.md index 662f2fba..88cbc32a 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Java Client Library -* Latest released version 0.15.3 -* Latest snapshot version 0.15.4-SNAPSHOT +* Latest released version 0.15.5 +* Latest snapshot version 0.15.6-SNAPSHOT ## Introduction This is the PolyAPI Java client GitHub page. If you are here, then it means you're familiar with what we do at Poly. If you aren't, you can always check [here](https://github.com/polyapi/poly-alpha). diff --git a/commons/pom.xml b/commons/pom.xml index 539fcfd0..141d4256 100644 --- a/commons/pom.xml +++ b/commons/pom.xml @@ -4,7 +4,7 @@ io.polyapi parent-pom - 0.15.4-SNAPSHOT + 0.15.6-SNAPSHOT ../parent-pom @@ -154,6 +154,12 @@ + + + --pinentry-mode + loopback + + org.sonatype.central diff --git a/library/pom.xml b/library/pom.xml index eca445a0..17a60586 100644 --- a/library/pom.xml +++ b/library/pom.xml @@ -4,7 +4,7 @@ io.polyapi parent-pom - 0.15.4-SNAPSHOT + 0.15.6-SNAPSHOT ../parent-pom library @@ -104,6 +104,12 @@ + + + --pinentry-mode + loopback + + org.sonatype.central diff --git a/parent-pom/pom.xml b/parent-pom/pom.xml index 32862ca7..b7e6bc64 100644 --- a/parent-pom/pom.xml +++ b/parent-pom/pom.xml @@ -1,11 +1,9 @@ - + 4.0.0 io.polyapi parent-pom - 0.15.4-SNAPSHOT + 0.15.6-SNAPSHOT pom PolyAPI Java parent POM https://polyapi.io @@ -130,6 +128,12 @@ + + + --pinentry-mode + loopback + + org.sonatype.central diff --git a/polyapi-maven-plugin/pom.xml b/polyapi-maven-plugin/pom.xml index 8ee5cb6a..449bc724 100644 --- a/polyapi-maven-plugin/pom.xml +++ b/polyapi-maven-plugin/pom.xml @@ -1,11 +1,10 @@ - + 4.0.0 io.polyapi parent-pom - 0.15.4-SNAPSHOT + 0.15.6-SNAPSHOT ../parent-pom polyapi-maven-plugin @@ -127,9 +126,6 @@ false - - MAVEN_GPG_KEY - @@ -145,6 +141,12 @@ + + + --pinentry-mode + loopback + + org.sonatype.central diff --git a/polyapi-maven-plugin/src/main/java/io/polyapi/plugin/service/generation/PolyObjectResolverService.java b/polyapi-maven-plugin/src/main/java/io/polyapi/plugin/service/generation/PolyObjectResolverService.java index fc5190ad..7fb74f16 100644 --- a/polyapi-maven-plugin/src/main/java/io/polyapi/plugin/service/generation/PolyObjectResolverService.java +++ b/polyapi-maven-plugin/src/main/java/io/polyapi/plugin/service/generation/PolyObjectResolverService.java @@ -5,6 +5,7 @@ import java.util.ArrayList; import java.util.HashSet; import java.util.List; +import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.function.Function; @@ -43,6 +44,8 @@ @Slf4j public class PolyObjectResolverService { private final JsonSchemaParser jsonSchemaParser; + private static final Pattern VALID_IMPORT = + Pattern.compile("^[a-zA-Z_][a-zA-Z0-9_]*(\\.[a-zA-Z_][a-zA-Z0-9_]*)+$"); public PolyObjectResolverService(JsonSchemaParser jsonSchemaParser) { this.jsonSchemaParser = jsonSchemaParser; @@ -92,12 +95,21 @@ public ResolvedServerVariableSpecification resolve(ServerVariableSpecification s public ResolvedContext resolve(Context context) { Set imports = new HashSet<>(); - context.getSubcontexts().stream().map(subcontext -> format("%s.%s", subcontext.getPackageName(), subcontext.getClassName())).forEach(imports::add); + context.getSubcontexts().stream() + .map(subcontext -> format("%s.%s", subcontext.getPackageName(), subcontext.getClassName())) + .filter(s -> !s.isBlank()) + .forEach(imports::add); context.getSpecifications().forEach(specification -> { ImportsCollectorVisitor importsCollectorVisitor = new ImportsCollectorVisitor(specification.getPackageName(), specification.getClassName(), jsonSchemaParser); importsCollectorVisitor.doVisit(specification); - imports.addAll(importsCollectorVisitor.getImports()); + importsCollectorVisitor.getImports().stream() + .filter(Objects::nonNull) + .filter(s -> !s.isBlank()) + .filter(s -> VALID_IMPORT.matcher(s).matches()) + .filter(s -> !s.substring(s.lastIndexOf('.') + 1).equals(context.getClassName())) + .forEach(imports::add); }); + return new ResolvedContext(context.getName(), context.getPackageName(), imports, context.getClassName(), context.getSubcontexts().stream().map(this::resolve).toList(), context.getSpecifications().stream().map(specification -> { PolyObjectResolverVisitor visitor = new PolyObjectResolverVisitor(this); visitor.doVisit(specification); diff --git a/polyapi-maven-plugin/src/main/java/io/polyapi/plugin/service/template/PolyHandlebars.java b/polyapi-maven-plugin/src/main/java/io/polyapi/plugin/service/template/PolyHandlebars.java index 5d812e8a..5781e0ba 100644 --- a/polyapi-maven-plugin/src/main/java/io/polyapi/plugin/service/template/PolyHandlebars.java +++ b/polyapi-maven-plugin/src/main/java/io/polyapi/plugin/service/template/PolyHandlebars.java @@ -7,6 +7,7 @@ import java.util.function.BiPredicate; import java.util.function.Function; +import java.lang.reflect.Method; public class PolyHandlebars extends Handlebars { @@ -15,6 +16,50 @@ public PolyHandlebars() { registerSimpleHelper("toCamelCase", StringUtils::toCamelCase); registerSimpleHelper("toPascalCase", StringUtils::toCamelCase); registerConditionalHelper("ifIsType", (object, options) -> object.getClass().getSimpleName().equals(options.param(0))); + registerSimpleHelper("lastSegment", (Object fqn) -> { + if (fqn == null) { + return ""; + } + String s = fqn.toString(); + int idx = s.lastIndexOf('.'); + return idx == -1 ? s : s.substring(idx + 1); + }); + registerConditionalHelper("eq", + (obj, opts) -> { + Object other = opts.param(0, ""); + return obj != null && obj.toString().equals(other == null ? "" : other.toString()); + }); + registerHelper("typeRef", (Object ctx, Options opts) -> { + if (ctx == null) { + return ""; + } + String fqn = ctx.toString(); + String parentSimple = opts.param(0, "").toString(); + String simple = fqn.substring(fqn.lastIndexOf('.') + 1); + return simple.equals(parentSimple) ? fqn : simple; + }); + registerHelper("classFqn", (Object ctx, Options o) -> { + if (ctx == null) return ""; + + for (String m : new String[]{"getFullClassName", "getFullName"}) { + try { + Method mm = ctx.getClass().getMethod(m); + Object val = mm.invoke(ctx); + if (val != null) return val.toString(); + } catch (ReflectiveOperationException ignored) {} + } + + try { + Method pm = ctx.getClass().getMethod("getPackageName"); + Method cm = ctx.getClass().getMethod("getClassName"); + Object pkg = pm.invoke(ctx); + Object cls = cm.invoke(ctx); + if (pkg != null && cls != null) return pkg + "." + cls; + if (cls != null) return cls.toString(); + } catch (ReflectiveOperationException ignored) {} + + return ""; + }); } private void registerSimpleHelper(String name, Function helper) { diff --git a/polyapi-maven-plugin/src/main/resources/templates/ResolvedContext.hbs b/polyapi-maven-plugin/src/main/resources/templates/ResolvedContext.hbs index 9765b8fd..92eff32b 100644 --- a/polyapi-maven-plugin/src/main/resources/templates/ResolvedContext.hbs +++ b/polyapi-maven-plugin/src/main/resources/templates/ResolvedContext.hbs @@ -8,68 +8,74 @@ import io.polyapi.client.api.model.PolyEntity; import io.polyapi.client.api.AuthTokenOptions; import io.polyapi.commons.api.model.PolyGeneratedClass; {{~#each this.imports}} +{{~#unless (eq (lastSegment this) ../className)}} import {{{this}}}; +{{~/unless}} {{~/each}} @PolyGeneratedClass public class {{className}} extends PolyContext { {{~#each functionSpecifications}} - private final {{this.className}} {{this.name}}; + private final {{typeRef (classFqn this) ../className}} {{this.name}}; {{~/each}} {{~#each standardAuthFunctionSpecifications}} - private final {{this.className}} {{this.name}}; + private final {{typeRef (classFqn this) ../className}} {{this.name}}; {{~/each}} {{~#each subresourceAuthFunctionSpecifications}} - private final {{this.className}} {{this.name}}; + private final {{typeRef (classFqn this) ../className}} {{this.name}}; {{~/each}} {{~#each serverVariableSpecifications}} - public final {{this.className}} {{this.name}}; + private final {{typeRef (classFqn this) ../className}} {{this.name}}; {{~/each}} {{~#each webhookHandlerSpecifications}} - private final {{this.className}} {{this.name}}; + private final {{typeRef (classFqn this) ../className}} {{this.name}}; {{~/each}} {{#each subcontexts}} - public final {{this.className}} {{this.name}}; + private final {{typeRef (classFqn this) ../className}} {{this.name}}; {{~/each}} - public {{className}}(PolyProxyFactory proxyFactory, WebSocketClient webSocketClient) { - super(proxyFactory, webSocketClient); +public {{className}}(PolyProxyFactory proxyFactory, WebSocketClient webSocketClient) { +super(proxyFactory, webSocketClient); {{~#each serverFunctionSpecifications}} - this.{{this.name}} = createServerFunctionProxy({{this.className}}.class); + this.{{this.name}} = + createServerFunctionProxy({{classFqn this}}.class); {{~/each}} {{~#each customFunctionSpecifications}} - this.{{this.name}} = createCustomFunctionProxy({{this.className}}.class); + this.{{this.name}} = + createCustomFunctionProxy({{classFqn this}}.class); {{~/each}} {{~#each apiFunctionSpecifications}} - this.{{this.name}} = createApiFunctionProxy({{this.className}}.class); + this.{{this.name}} = + createApiFunctionProxy({{classFqn this}}.class); {{~/each}} {{~#each subresourceAuthFunctionSpecifications}} - this.{{this.name}} = createSubresourceAuthFunction({{this.className}}.class); + this.{{this.name}} = + createSubresourceAuthFunction({{classFqn this}}.class); {{~/each}} {{~#each standardAuthFunctionSpecifications}} - this.{{this.name}} = create{{#if audienceRequired}}Audience{{/if}}TokenAuthFunction({{this.className}}.class); + this.{{this.name}} = + create{{#if audienceRequired}}Audience{{/if}}TokenAuthFunction({{classFqn this}}.class); {{~/each}} {{~#each serverVariableSpecifications}} - this.{{this.name}} = createServerVariableHandler({{this.className}}.class); + this.{{this.name}} = + createServerVariableHandler({{classFqn this}}.class); {{~/each}} {{~#each webhookHandlerSpecifications}} - this.{{this.name}} = createPolyTriggerProxy({{this.className}}.class); + this.{{this.name}} = + createPolyTriggerProxy({{classFqn this}}.class); {{~/each}} {{#each subcontexts}} - this.{{this.name}} = new {{this.className}}(proxyFactory, webSocketClient); + this.{{this.name}} = new {{typeRef (classFqn this) ../className}}(proxyFactory, webSocketClient); {{~/each}} } {{~#each functionSpecifications}} public {{{this.returnType}}} {{{this.methodSignature}}} { - {{~#if this.returnsValue}} - return - {{~else}} - {{~/if}} this.{{this.name}}.{{this.name}}({{this.paramVariableNames}}); + {{#if this.returnsValue}}return {{/if}}this.{{this.name}}.{{this.name}}({{this.paramVariableNames}}); } - public {{{this.className}}} get{{{this.className}}}Function() { - return this.{{{this.name}}}; + public {{typeRef (classFqn this) ../className}} get{{this.className}}Function() { + return this.{{this.name}}; } {{~/each}} @@ -110,6 +116,9 @@ public class {{className}} extends PolyContext { {{~#each specifications}} {{~#ifIsType this "AuthFunctionSpecification"}} + public {{typeRef (classFqn this) ../className}} get{{this.className}}AuthFunction() { + return this.{{this.name}}; + } {{~#if subResource}} public void {{name}}(String token) { this.{{name}}.{{name}}(token); @@ -127,8 +136,8 @@ public class {{className}} extends PolyContext { } {{~/if}} - public {{{this.className}}} get{{{this.className}}}AuthFunction() { - return this.{{{this.name}}}; + public {{typeRef (classFqn this) ../className}} get{{this.className}}AuthFunction() { + return this.{{this.name}}; } {{~/ifIsType}} {{~/each}} diff --git a/pom.xml b/pom.xml index f69e2965..60c23271 100644 --- a/pom.xml +++ b/pom.xml @@ -1,12 +1,10 @@ - + 4.0.0 io.polyapi polyapi-java - 0.15.4-SNAPSHOT + 0.15.6-SNAPSHOT pom parent-pom @@ -193,6 +191,12 @@ + + + --pinentry-mode + loopback + + org.sonatype.central