diff --git a/wizard-server/pom.xml b/wizard-server/pom.xml index f53546f..e18918e 100644 --- a/wizard-server/pom.xml +++ b/wizard-server/pom.xml @@ -277,6 +277,18 @@ This code is available under the Apache License, version 2: http://www.apache.or 4.1 + + + io.swagger.core.v3 + swagger-jaxrs2 + 2.1.2 + + + io.swagger.core.v3 + swagger-integration + 2.1.2 + + diff --git a/wizard-server/src/main/java/org/cobbzilla/wizard/server/RestServerBase.java b/wizard-server/src/main/java/org/cobbzilla/wizard/server/RestServerBase.java index 9353faf..6e8852c 100644 --- a/wizard-server/src/main/java/org/cobbzilla/wizard/server/RestServerBase.java +++ b/wizard-server/src/main/java/org/cobbzilla/wizard/server/RestServerBase.java @@ -58,9 +58,10 @@ import static org.cobbzilla.util.system.Sleep.sleep; @NoArgsConstructor @Slf4j public abstract class RestServerBase implements RestServer { - public RestServerBase (RestServer other) { - copy(this, other, new String[]{"httpServer", "configuration", "applicationContext"}); - } + public static final String[] COPY_FIELDS + = {"httpServer", "configuration", "applicationContext"}; + + public RestServerBase (RestServer other) { copy(this, other, COPY_FIELDS); } @Getter @Setter private volatile static ErrorApi errorApi; @@ -298,6 +299,11 @@ public abstract class RestServerBase implemen rc.register(new StreamingOutputProvider()); rc.register(MultiPartFeature.class); // rc.register(new StringProvider()); + + if (configuration.hasOpenApi()) { + configuration.getOpenApi().register(configuration, rc); + } + return rc; } diff --git a/wizard-server/src/main/java/org/cobbzilla/wizard/server/config/OpenApiConfiguration.java b/wizard-server/src/main/java/org/cobbzilla/wizard/server/config/OpenApiConfiguration.java new file mode 100644 index 0000000..c12daa9 --- /dev/null +++ b/wizard-server/src/main/java/org/cobbzilla/wizard/server/config/OpenApiConfiguration.java @@ -0,0 +1,94 @@ +package org.cobbzilla.wizard.server.config; + +import com.github.jknack.handlebars.Handlebars; +import io.swagger.v3.jaxrs2.integration.resources.OpenApiResource; +import io.swagger.v3.oas.integration.SwaggerConfiguration; +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.info.Contact; +import io.swagger.v3.oas.models.info.Info; +import io.swagger.v3.oas.models.info.License; +import io.swagger.v3.oas.models.servers.Server; +import lombok.Getter; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; +import org.cobbzilla.util.handlebars.HandlebarsUtil; +import org.cobbzilla.util.handlebars.HasHandlebars; +import org.glassfish.jersey.server.ResourceConfig; + +import java.util.*; +import java.util.stream.Collectors; + +import static org.cobbzilla.util.daemon.ZillaRuntime.die; +import static org.cobbzilla.util.daemon.ZillaRuntime.empty; + +@Slf4j +public class OpenApiConfiguration { + + // set contactEmail to this value to disable OpenAPI + public static final String OPENAPI_DISABLED = "openapi_disabled"; + + @Getter @Setter private String title; + @Getter @Setter private String description; + @Getter @Setter private String contactEmail; + @Getter @Setter private String terms; + @Getter @Setter private String licenseName; + @Getter @Setter private String licenseUrl; + + public boolean valid() { + return !empty(contactEmail) && !contactEmail.equalsIgnoreCase(OPENAPI_DISABLED) + && !empty(terms) && !empty(licenseName) && !empty(licenseUrl); + } + + public String title(RestServerConfiguration configuration) { + return empty(this.title) ? configuration.getServerName() : this.title; + } + + public void register(RestServerConfiguration configuration, ResourceConfig rc) { + if (!valid()) { + log.warn("register: config not valid, not registering OpenApiResource"); + return; + } + + final Handlebars handlebars; + final Map ctx = new HashMap<>(); + if (configuration instanceof HasHandlebars) { + handlebars = ((HasHandlebars) configuration).getHandlebars(); + ctx.put("configuration", configuration); + } else { + handlebars = null; + } + + final OpenAPI oas = new OpenAPI(); + final Info info = new Info() + .title(subst(title(configuration), handlebars, ctx, configuration)) + .description(subst((empty(description) ? title(configuration) : description), handlebars, ctx, configuration)) + .termsOfService(subst(terms, handlebars, ctx, configuration)) + .contact(new Contact() + .email(subst(contactEmail, handlebars, ctx, configuration))) + .license(new License() + .name(subst(licenseName, handlebars, ctx, configuration)) + .url(subst(licenseUrl, handlebars, ctx, configuration))) + .version((configuration.hasVersion() ? configuration.getVersion() : "(configuration.version was missing or empty)")); + + oas.info(info); + final List servers = new ArrayList<>(); + servers.add(new Server().url(configuration.getHttp().getBaseUri())); + oas.servers(servers); + final SwaggerConfiguration oasConfig = new SwaggerConfiguration() + .openAPI(oas) + .prettyPrint(true) + .resourcePackages(Arrays.stream(configuration.getJersey().getResourcePackages()).collect(Collectors.toSet())); + + rc.register(new OpenApiResource().openApiConfiguration(oasConfig)); + } + + public String subst (String value, + Handlebars handlebars, + Map ctx, + RestServerConfiguration configuration) { + if (!(value.contains("<<") && value.contains(">>"))) return value; + if (handlebars == null) return die("subst: value contained <<...>> but configuration does not support Handlebars: "+configuration.getClass().getSimpleName()); + return HandlebarsUtil.apply(handlebars, value, ctx, '<', '>'); + } + +} diff --git a/wizard-server/src/main/java/org/cobbzilla/wizard/server/config/RestServerConfiguration.java b/wizard-server/src/main/java/org/cobbzilla/wizard/server/config/RestServerConfiguration.java index 7ee8075..cd770c3 100644 --- a/wizard-server/src/main/java/org/cobbzilla/wizard/server/config/RestServerConfiguration.java +++ b/wizard-server/src/main/java/org/cobbzilla/wizard/server/config/RestServerConfiguration.java @@ -62,6 +62,9 @@ public class RestServerConfiguration { return !empty(publicUriBase) && publicUriBase.endsWith("/") ? publicUriBase.substring(0, publicUriBase.length()-1) : publicUriBase; } + @Getter @Setter private OpenApiConfiguration openApi; + public boolean hasOpenApi () { return openApi != null && openApi.valid(); } + @Getter @Setter private String springContextPath = "classpath:/spring.xml"; @Getter @Setter private String springShardContextPath = "classpath:/spring-shard.xml"; @Getter @Setter private int bcryptRounds = 12;