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;