ソースを参照

improve openapi support

tags/2.0.1
Jonathan Cobb 3年前
コミット
c40f0704e1
22個のファイルの変更320行の追加44行の削除
  1. +17
    -0
      wizard-common/pom.xml
  2. +46
    -0
      wizard-common/src/main/java/org/cobbzilla/wizard/model/entityconfig/EntityConfig.java
  3. +1
    -1
      wizard-common/src/main/java/org/cobbzilla/wizard/model/entityconfig/EntityConfigSource.java
  4. +72
    -0
      wizard-common/src/main/java/org/cobbzilla/wizard/model/entityconfig/EntityFieldConfig.java
  5. +6
    -0
      wizard-common/src/main/java/org/cobbzilla/wizard/model/entityconfig/EntityFieldType.java
  6. +6
    -5
      wizard-common/src/main/java/org/cobbzilla/wizard/model/entityconfig/EntityReferences.java
  7. +36
    -0
      wizard-common/src/main/java/org/cobbzilla/wizard/model/entityconfig/validation/EntityConfigFieldValidator_json.java
  8. +10
    -0
      wizard-common/src/main/java/org/cobbzilla/wizard/model/entityconfig/validation/EntityConfigFieldValidator_json_array.java
  9. +0
    -18
      wizard-server/pom.xml
  10. +1
    -0
      wizard-server/src/main/java/org/cobbzilla/wizard/dao/AbstractDAO.java
  11. +1
    -0
      wizard-server/src/main/java/org/cobbzilla/wizard/dao/AbstractElasticSearchDAO.java
  12. +1
    -0
      wizard-server/src/main/java/org/cobbzilla/wizard/dao/AbstractLdapDAO.java
  13. +1
    -0
      wizard-server/src/main/java/org/cobbzilla/wizard/dao/AbstractRedisDAO.java
  14. +1
    -0
      wizard-server/src/main/java/org/cobbzilla/wizard/dao/DAO.java
  15. +1
    -0
      wizard-server/src/main/java/org/cobbzilla/wizard/dao/SqlViewSearchHelper.java
  16. +1
    -1
      wizard-server/src/main/java/org/cobbzilla/wizard/dao/shard/AbstractShardedDAO.java
  17. +1
    -1
      wizard-server/src/main/java/org/cobbzilla/wizard/docstore/mongo/MongoDocStoreDAOBase.java
  18. +1
    -1
      wizard-server/src/main/java/org/cobbzilla/wizard/filters/EntityTypeHeaderResponseFilter.java
  19. +9
    -8
      wizard-server/src/main/java/org/cobbzilla/wizard/model/search/SearchResults.java
  20. +53
    -2
      wizard-server/src/main/java/org/cobbzilla/wizard/resources/AbstractEntityConfigsResource.java
  21. +51
    -4
      wizard-server/src/main/java/org/cobbzilla/wizard/server/config/OpenApiConfiguration.java
  22. +4
    -3
      wizard-server/src/main/java/org/cobbzilla/wizard/server/listener/BrowserLauncherListener.java

+ 17
- 0
wizard-common/pom.xml ファイルの表示

@@ -98,6 +98,23 @@ This code is available under the Apache License, version 2: http://www.apache.or
<version>2.3.0</version>
</dependency>

<!-- API docs -->
<dependency>
<groupId>io.swagger.core.v3</groupId>
<artifactId>swagger-jaxrs2</artifactId>
<version>${swagger.version}</version>
</dependency>
<dependency>
<groupId>io.swagger.core.v3</groupId>
<artifactId>swagger-integration</artifactId>
<version>${swagger.version}</version>
</dependency>
<dependency>
<groupId>io.swagger.core.v3</groupId>
<artifactId>swagger-annotations</artifactId>
<version>${swagger.version}</version>
</dependency>

</dependencies>

</project>

+ 46
- 0
wizard-common/src/main/java/org/cobbzilla/wizard/model/entityconfig/EntityConfig.java ファイルの表示

@@ -1,5 +1,6 @@
package org.cobbzilla.wizard.model.entityconfig;

import io.swagger.v3.oas.models.media.Schema;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@@ -24,7 +25,11 @@ import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;

import static lombok.AccessLevel.PRIVATE;
import static org.cobbzilla.util.daemon.ZillaRuntime.empty;
import static org.cobbzilla.util.daemon.ZillaRuntime.shortError;
import static org.cobbzilla.util.json.JsonUtil.NOTNULL_MAPPER;
import static org.cobbzilla.util.json.JsonUtil.json;
import static org.cobbzilla.util.reflect.ReflectionUtil.*;
import static org.cobbzilla.util.string.StringUtil.*;

@@ -698,4 +703,45 @@ public class EntityConfig {
}
return validation;
}

// do not expose a getter for the example, we don't want this appearing in JSON
@Getter(lazy=true, value=PRIVATE) private final Object example = initExample();
private Object initExample() {
final Object o = instantiate(className);
for (String field : fieldNames) {
try {
ReflectionUtil.set(o, field, fields.get(field).example());
} catch (Exception e) {
log.info("initExample: error setting "+field+" on "+o.getClass().getName()+": "+shortError(e));
}
}
return o;
}

public <T> T example() { return (T) getExample(); }

public <T> Schema<T> openApiSchema() {
final T defaultObj = instantiate(className);
final T example = example();
final Schema<T> s = new Schema<>();

final String simpleName = defaultObj.getClass().getSimpleName();
s.name(simpleName)
.title(camelCaseToString(simpleName))
.example(json(example, NOTNULL_MAPPER));
s.setDefault(defaultObj);
final Map<String, Schema> props = new HashMap<>();
final List<String> required = new ArrayList<>();
for (String field : fieldNames) {
final Schema<Object> fieldSchema = fieldToOpenApiSchema(this.getFields().get(field), required);
props.put(field, fieldSchema);
}
s.required(required).properties(props);
return s;
}

private <T> Schema<T> fieldToOpenApiSchema(EntityFieldConfig fieldConfig, List<String> required) {
if (fieldConfig.required()) required.add(fieldConfig.getDisplayName());
return fieldConfig.openApiType();
}
}

+ 1
- 1
wizard-common/src/main/java/org/cobbzilla/wizard/model/entityconfig/EntityConfigSource.java ファイルの表示

@@ -3,5 +3,5 @@ package org.cobbzilla.wizard.model.entityconfig;
public interface EntityConfigSource {

EntityConfig getEntityConfig(Object thing);
EntityConfig getOrCreateEntityConfig(Object thing) throws Exception;
}

+ 72
- 0
wizard-common/src/main/java/org/cobbzilla/wizard/model/entityconfig/EntityFieldConfig.java ファイルの表示

@@ -1,6 +1,7 @@
package org.cobbzilla.wizard.model.entityconfig;

import com.fasterxml.jackson.annotation.JsonIgnore;
import io.swagger.v3.oas.models.media.Schema;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@@ -9,15 +10,19 @@ import lombok.extern.slf4j.Slf4j;
import org.cobbzilla.wizard.model.Identifiable;
import org.cobbzilla.wizard.validation.ValidationResult;
import org.cobbzilla.wizard.validation.Validator;
import org.joda.time.DateTime;

import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Map;

import static java.util.concurrent.TimeUnit.DAYS;
import static java.util.concurrent.TimeUnit.MINUTES;
import static org.cobbzilla.util.daemon.ZillaRuntime.*;
import static org.cobbzilla.util.json.JsonUtil.json;
import static org.cobbzilla.util.reflect.ReflectionUtil.copy;
import static org.cobbzilla.util.reflect.ReflectionUtil.instantiate;
import static org.cobbzilla.util.string.StringUtil.camelCaseToString;

/**
@@ -198,4 +203,71 @@ public class EntityFieldConfig implements VerifyLogAware<EntityFieldConfig> {
return die("displayValueFor("+answer+"): unsupported control type: "+getControl());
}
}

public Object example() {
final DateTime today = new DateTime();
switch (getTypeOrDefault()) {
case flag: return true;
case date_future: return now()+DAYS.toMillis(1);
case date_past: return now()-DAYS.toMillis(1);
case epoch_time: case date: return now();
case age: return ""+42;
case currency: return "USD";
case decimal: return 10.5;
case email: return "someone@example.com";
case embedded: return instantiate(this.objectType);
case error: return "An error occurred";
case expiration_time: case time_duration:
return MINUTES.toMillis(1);
case hostname: case fqdn: return "test.example.com";
case http_url: return "https://example.com/";
case integer: return 42;
case ip4: return "10.0.1.42";
case ip6: return "fd00::42";
case json: return "{}";
case json_array: return "[]";
case locale: return "en_US";
case money_decimal: return "10.42";
case money_integer: return "1042";
case us_state: return "ND";
case us_zip: return "90210";
case us_phone: return "+18885551212";
case time_zone: return "America/New York";
case year: return ""+ today.year().get();
case year_future: return ""+(today.year().get()+1);
case year_past: return ""+(today.year().get()-1);
case year_and_month: return ""+ today.year().get() + "-"+today.monthOfYear().get();
case year_and_month_future: return ""+ (today.year().get()+1) + "-"+today.monthOfYear().get();
case year_and_month_past: return ""+ (today.year().get()-1) + "-"+today.monthOfYear().get();
case string:
default: return "foo";
}
}

public Schema openApiType() {
return openApiBaseType().name(getName())
.title(getDisplayName())
.example(example())
.readOnly(readOnly());
}

private Schema openApiBaseType() {
switch (getTypeOrDefault()) {
case flag: return new Schema<Boolean>().type("boolean");

case date_future: case date_past: case date:
case epoch_time: case expiration_time: case time_duration:
case money_integer: return new Schema<Long>().type("integer").format("int64");

case age: case integer: case year: case year_future: case year_past:
case year_and_month: case year_and_month_future: case year_and_month_past:
return new Schema<Integer>().type("integer").format("int32");

case decimal: return new Schema<Double>().type("number").format("double");

case embedded: return null; // todo: better handling of embedded types

default: return new Schema<String>().type("string");
}
}
}

+ 6
- 0
wizard-common/src/main/java/org/cobbzilla/wizard/model/entityconfig/EntityFieldType.java ファイルの表示

@@ -32,6 +32,12 @@ public enum EntityFieldType {
/** a string of characters */
string (new EntityConfigFieldValidator_string()),

/** valid JSON object (as a string) */
json (new EntityConfigFieldValidator_json()),

/** valid JSON array (as a string) */
json_array (new EntityConfigFieldValidator_json_array()),

/** a string of characters where comparisons like lt/le/gt/ge are not useful */
opaque_string (new EntityConfigFieldValidator_string()),



+ 6
- 5
wizard-common/src/main/java/org/cobbzilla/wizard/model/entityconfig/EntityReferences.java ファイルの表示

@@ -23,6 +23,7 @@ import static org.cobbzilla.util.collection.ArrayUtil.arrayToString;
import static org.cobbzilla.util.daemon.ZillaRuntime.die;
import static org.cobbzilla.util.daemon.ZillaRuntime.empty;
import static org.cobbzilla.util.string.StringUtil.camelCaseToSnakeCase;
import static org.cobbzilla.wizard.model.entityconfig.EntityConfig.ENTITY_FILTER;

@NoArgsConstructor @Accessors(chain=true) @Slf4j
public class EntityReferences {
@@ -47,7 +48,7 @@ public class EntityReferences {
// find entity classes
final List<Class<? extends Identifiable>> classes = new ClasspathScanner<Identifiable>()
.setPackages(packages)
.setFilter(EntityConfig.ENTITY_FILTER)
.setFilter(ENTITY_FILTER)
.scan();
final Topology<Class<? extends Identifiable>> topology = new Topology<>();
classes.forEach(c -> topology.addNode(c, getReferencedEntities(c)));
@@ -57,7 +58,7 @@ public class EntityReferences {
public static boolean hasForeignKey(Class candidate, Class<? extends Identifiable> entityClass) {
if (candidate.equals(Object.class)) return false;
if (Arrays.stream(candidate.getDeclaredFields())
.filter(EntityReferences.FIELD_HAS_FK)
.filter(FIELD_HAS_FK)
.anyMatch(f -> f.getAnnotation(ECForeignKey.class).entity().equals(entityClass))) {
return true;
}
@@ -96,7 +97,7 @@ public class EntityReferences {
final List<String> constraints = new ArrayList<>();
new ClasspathScanner<>()
.setPackages(packages)
.setFilter(EntityConfig.ENTITY_FILTER)
.setFilter(ENTITY_FILTER)
.scan()
.forEach(c -> constraints.addAll(constraintsForClass((Class<? extends Identifiable>) c, includeIndexes)));
return constraints;
@@ -106,8 +107,8 @@ public class EntityReferences {
final List<Class<? extends Identifiable>> refs = new ArrayList<>();
while (!clazz.getName().equals(Object.class.getName())) {
refs.addAll(Arrays.stream(clazz.getDeclaredFields())
.filter(EntityReferences.FIELD_HAS_CASCADING_FK)
.map(EntityReferences.FIELD_TO_FK_CLASS)
.filter(FIELD_HAS_CASCADING_FK)
.map(FIELD_TO_FK_CLASS)
.collect(Collectors.toList()));
clazz = clazz.getSuperclass();
}


+ 36
- 0
wizard-common/src/main/java/org/cobbzilla/wizard/model/entityconfig/validation/EntityConfigFieldValidator_json.java ファイルの表示

@@ -0,0 +1,36 @@
package org.cobbzilla.wizard.model.entityconfig.validation;

import com.fasterxml.jackson.databind.JsonNode;
import org.cobbzilla.wizard.model.entityconfig.EntityFieldConfig;
import org.cobbzilla.wizard.validation.ValidationResult;
import org.cobbzilla.wizard.validation.Validator;

import java.util.Locale;

import static org.cobbzilla.util.daemon.ZillaRuntime.empty;
import static org.cobbzilla.util.daemon.ZillaRuntime.shortError;
import static org.cobbzilla.util.json.JsonUtil.json;

public class EntityConfigFieldValidator_json extends EntityConfigFieldValidator_string {

@Override public ValidationResult validate(Locale locale, Validator validator, EntityFieldConfig fieldConfig,
Object value) {
ValidationResult validation = super.validate(locale, validator, fieldConfig, value);
if (validation.isInvalid()) return validation;
final String val = empty(value) ? "" : value.toString().trim();
if (empty(val)) return fieldConfig.required() ? new ValidationResult("err."+fieldConfig.getName()+".required") : null;
try {
json(value.toString(), getJsonClass());
} catch (Exception e) {
return new ValidationResult("err."+fieldConfig.getName()+".invalid", "Error converting to JSON: "+shortError(e), ""+value);
}
return validation;
}

public Class<? extends JsonNode> getJsonClass() { return JsonNode.class; }

@Override public Object toObject(Locale locale, String value) {
return empty(value) ? "" : value.toString().trim();
}

}

+ 10
- 0
wizard-common/src/main/java/org/cobbzilla/wizard/model/entityconfig/validation/EntityConfigFieldValidator_json_array.java ファイルの表示

@@ -0,0 +1,10 @@
package org.cobbzilla.wizard.model.entityconfig.validation;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;

public class EntityConfigFieldValidator_json_array extends EntityConfigFieldValidator_json {

@Override public Class<? extends JsonNode> getJsonClass() { return ArrayNode.class; }

}

+ 0
- 18
wizard-server/pom.xml ファイルの表示

@@ -276,24 +276,6 @@ This code is available under the Apache License, version 2: http://www.apache.or
<artifactId>opencsv</artifactId>
<version>4.1</version>
</dependency>

<!-- API docs -->
<dependency>
<groupId>io.swagger.core.v3</groupId>
<artifactId>swagger-jaxrs2</artifactId>
<version>${swagger.version}</version>
</dependency>
<dependency>
<groupId>io.swagger.core.v3</groupId>
<artifactId>swagger-integration</artifactId>
<version>${swagger.version}</version>
</dependency>
<dependency>
<groupId>io.swagger.core.v3</groupId>
<artifactId>swagger-annotations</artifactId>
<version>${swagger.version}</version>
</dependency>

</dependencies>

</project>

+ 1
- 0
wizard-server/src/main/java/org/cobbzilla/wizard/dao/AbstractDAO.java ファイルの表示

@@ -12,6 +12,7 @@ import org.cobbzilla.util.collection.NameAndValue;
import org.cobbzilla.util.string.StringUtil;
import org.cobbzilla.wizard.model.Identifiable;
import org.cobbzilla.wizard.model.IdentifiableBase;
import org.cobbzilla.wizard.model.search.SearchResults;
import org.cobbzilla.wizard.model.search.SearchQuery;
import org.cobbzilla.wizard.model.search.SqlViewField;
import org.cobbzilla.wizard.server.config.PgRestServerConfiguration;


+ 1
- 0
wizard-server/src/main/java/org/cobbzilla/wizard/dao/AbstractElasticSearchDAO.java ファイルの表示

@@ -13,6 +13,7 @@ import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.cobbzilla.util.cache.AutoRefreshingReference;
import org.cobbzilla.util.http.URIUtil;
import org.cobbzilla.wizard.model.Identifiable;
import org.cobbzilla.wizard.model.search.SearchResults;
import org.cobbzilla.wizard.server.config.ElasticSearchConfig;
import org.elasticsearch.action.admin.indices.create.CreateIndexRequest;
import org.elasticsearch.action.admin.indices.exists.indices.IndicesExistsRequest;


+ 1
- 0
wizard-server/src/main/java/org/cobbzilla/wizard/dao/AbstractLdapDAO.java ファイルの表示

@@ -6,6 +6,7 @@ import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.Transformer;
import org.cobbzilla.util.collection.NameAndValue;
import org.cobbzilla.wizard.ldap.LdapService;
import org.cobbzilla.wizard.model.search.SearchResults;
import org.cobbzilla.wizard.model.search.SearchQuery;
import org.cobbzilla.wizard.model.ldap.LdapEntity;
import org.cobbzilla.wizard.server.config.LdapConfiguration;


+ 1
- 0
wizard-server/src/main/java/org/cobbzilla/wizard/dao/AbstractRedisDAO.java ファイルの表示

@@ -3,6 +3,7 @@ package org.cobbzilla.wizard.dao;
import lombok.Getter;
import org.cobbzilla.wizard.cache.redis.RedisService;
import org.cobbzilla.wizard.model.ExpirableBase;
import org.cobbzilla.wizard.model.search.SearchResults;
import org.cobbzilla.wizard.model.search.SearchQuery;
import org.springframework.beans.factory.annotation.Autowired;



+ 1
- 0
wizard-server/src/main/java/org/cobbzilla/wizard/dao/DAO.java ファイルの表示

@@ -2,6 +2,7 @@ package org.cobbzilla.wizard.dao;

import lombok.NonNull;
import org.cobbzilla.wizard.model.Identifiable;
import org.cobbzilla.wizard.model.search.SearchResults;
import org.cobbzilla.wizard.model.search.SearchQuery;
import org.hibernate.criterion.Order;



+ 1
- 0
wizard-server/src/main/java/org/cobbzilla/wizard/dao/SqlViewSearchHelper.java ファイルの表示

@@ -6,6 +6,7 @@ import org.cobbzilla.util.jdbc.ResultSetBean;
import org.cobbzilla.util.reflect.ReflectionUtil;
import org.cobbzilla.util.string.StringUtil;
import org.cobbzilla.wizard.model.Identifiable;
import org.cobbzilla.wizard.model.search.SearchResults;
import org.cobbzilla.wizard.model.search.SearchQuery;
import org.cobbzilla.wizard.model.search.SearchSort;
import org.cobbzilla.wizard.model.search.SqlViewField;


+ 1
- 1
wizard-server/src/main/java/org/cobbzilla/wizard/dao/shard/AbstractShardedDAO.java ファイルの表示

@@ -8,7 +8,7 @@ import org.cobbzilla.util.reflect.ReflectionUtil;
import org.cobbzilla.wizard.cache.redis.HasRedisConfiguration;
import org.cobbzilla.wizard.cache.redis.RedisService;
import org.cobbzilla.wizard.dao.DAO;
import org.cobbzilla.wizard.dao.SearchResults;
import org.cobbzilla.wizard.model.search.SearchResults;
import org.cobbzilla.wizard.dao.shard.cache.ShardCacheableFindByUnique2FieldFinder;
import org.cobbzilla.wizard.dao.shard.cache.ShardCacheableFindByUnique3FieldFinder;
import org.cobbzilla.wizard.dao.shard.cache.ShardCacheableIdentityFinder;


+ 1
- 1
wizard-server/src/main/java/org/cobbzilla/wizard/docstore/mongo/MongoDocStoreDAOBase.java ファイルの表示

@@ -2,7 +2,7 @@ package org.cobbzilla.wizard.docstore.mongo;

import lombok.Getter;
import org.cobbzilla.wizard.dao.DAO;
import org.cobbzilla.wizard.dao.SearchResults;
import org.cobbzilla.wizard.model.search.SearchResults;
import org.cobbzilla.wizard.model.search.SearchQuery;

import javax.validation.Valid;


+ 1
- 1
wizard-server/src/main/java/org/cobbzilla/wizard/filters/EntityTypeHeaderResponseFilter.java ファイルの表示

@@ -1,7 +1,7 @@
package org.cobbzilla.wizard.filters;

import lombok.extern.slf4j.Slf4j;
import org.cobbzilla.wizard.dao.SearchResults;
import org.cobbzilla.wizard.model.search.SearchResults;
import org.cobbzilla.wizard.model.Identifiable;

import javax.ws.rs.container.ContainerRequestContext;


wizard-server/src/main/java/org/cobbzilla/wizard/dao/SearchResults.java → wizard-server/src/main/java/org/cobbzilla/wizard/model/search/SearchResults.java ファイルの表示

@@ -1,7 +1,8 @@
package org.cobbzilla.wizard.dao;
package org.cobbzilla.wizard.model.search;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.databind.JavaType;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@@ -9,7 +10,7 @@ import lombok.experimental.Accessors;
import org.cobbzilla.util.json.JsonUtil;
import org.cobbzilla.wizard.filters.Scrubbable;
import org.cobbzilla.wizard.filters.ScrubbableField;
import org.cobbzilla.wizard.model.search.SearchQuery;
import org.cobbzilla.wizard.model.entityconfig.annotations.ECField;

import java.util.ArrayList;
import java.util.List;
@@ -19,7 +20,7 @@ import java.util.concurrent.ConcurrentHashMap;
import static org.cobbzilla.util.daemon.ZillaRuntime.die;
import static org.cobbzilla.util.daemon.ZillaRuntime.empty;

@NoArgsConstructor @Accessors(chain=true)
@NoArgsConstructor @Accessors(chain=true) @Schema
public class SearchResults<E> implements Scrubbable {

public static final ScrubbableField[] SCRUBBABLE_FIELDS = new ScrubbableField[]{
@@ -33,7 +34,7 @@ public class SearchResults<E> implements Scrubbable {

@Override public ScrubbableField[] fieldsToScrub() { return SCRUBBABLE_FIELDS; }

private static Map<Class, JavaType> jsonTypeCache = new ConcurrentHashMap<>();
private static final Map<Class, JavaType> jsonTypeCache = new ConcurrentHashMap<>();
public static JavaType jsonType(Class klazz) {
JavaType type = jsonTypeCache.get(klazz);
if (type == null) {
@@ -43,10 +44,10 @@ public class SearchResults<E> implements Scrubbable {
return type;
}

@Getter @Setter private List<E> results = new ArrayList<>();
@Getter @Setter private Integer totalCount;
@Getter @Setter private String nextPage;
@Getter @Setter private String error;
@ECField @Getter @Setter private List<E> results = new ArrayList<>();
@ECField @Getter @Setter private Integer totalCount;
@ECField @Getter @Setter private String nextPage;
@ECField @Getter @Setter private String error;

public String getResultType() { return empty(results) ? null : results.get(0).getClass().getName(); }
public void setResultType (String val) {} // noop

+ 53
- 2
wizard-server/src/main/java/org/cobbzilla/wizard/resources/AbstractEntityConfigsResource.java ファイルの表示

@@ -1,5 +1,11 @@
package org.cobbzilla.wizard.resources;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.ExampleObject;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
@@ -32,12 +38,18 @@ import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;

import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
import static org.cobbzilla.util.daemon.ZillaRuntime.empty;
import static org.cobbzilla.util.daemon.ZillaRuntime.shortError;
import static org.cobbzilla.util.http.HttpStatusCodes.SC_NOT_FOUND;
import static org.cobbzilla.util.http.HttpStatusCodes.SC_OK;
import static org.cobbzilla.util.io.StreamUtil.loadResourceAsStream;
import static org.cobbzilla.util.json.JsonUtil.FULL_MAPPER_ALLOW_COMMENTS;
import static org.cobbzilla.util.json.JsonUtil.fromJson;
import static org.cobbzilla.util.reflect.ReflectionUtil.forName;
import static org.cobbzilla.util.string.StringUtil.packagePath;
import static org.cobbzilla.wizard.resources.ResourceUtil.*;
import static org.cobbzilla.wizard.server.config.OpenApiConfiguration.API_TAG_UTILITY;
import static org.cobbzilla.wizard.server.config.OpenApiConfiguration.SEC_API_KEY;

@Consumes(APPLICATION_JSON)
@Produces(APPLICATION_JSON)
@@ -62,17 +74,35 @@ public abstract class AbstractEntityConfigsResource implements EntityConfigSourc
}

@GET
@Operation(security=@SecurityRequirement(name=SEC_API_KEY),
tags={API_TAG_UTILITY},
summary="Read entity configs. Returns an array of Strings, each an entity type. When param 'full' is true, response is a Map of entity type names to the full EntityConfig object for each type.",
description="Read entity configs. Returns an array of Strings, each an entity type. When param 'full' is true, response is a Map of entity type names to the full EntityConfig object for each type.",
parameters={@Parameter(name="full", description="return all configs")},
responses={@ApiResponse(responseCode=SC_OK, description="the name of the entity types, or a map of all configs",
content={@Content(mediaType=APPLICATION_JSON, examples={
@ExampleObject(name="an array of entity type names", value="[\"SomeEntity\", \"AnotherEntity\"]"),
@ExampleObject(name="when 'full' param is passed, returns map of name->config", value="{\"SomeEntity\": {\"entity-config-fields\": \"would-be-here\"}, \"AnotherEntity\": {\"entity-config-fields\": \"would-be-here\"}}")
}
)})
}
)
public Response getConfigs(@Context ContainerRequest ctx,
@QueryParam("full") Boolean full) {
if (!authorized(ctx)) return forbidden();
return ok(full != null && full ? getConfigs().getEntries() : getConfigs().get().keySet());
}

@Override public EntityConfig getOrCreateEntityConfig(Object thing) throws Exception {
final EntityConfig entityConfig = getEntityConfig(thing);
return entityConfig != null ? entityConfig : getEntityConfig(toClass(thing), false);
}

@Override public EntityConfig getEntityConfig(Object thing) {
final AutoRefreshingReference<Map<String, EntityConfig>> configs = getConfigs();
final Map<String, EntityConfig> configMap = configs.get();
synchronized (configMap) {
Class<?> clazz = thing instanceof Class ? (Class<?>) thing : thing.getClass();
Class<?> clazz = toClass(thing);
do {
final EntityConfig entityConfig = configMap.get(clazz.getName().toLowerCase());
if (entityConfig != null) return entityConfig;
@@ -82,7 +112,21 @@ public abstract class AbstractEntityConfigsResource implements EntityConfigSourc
return null;
}

public Class<?> toClass(Object thing) {
return thing instanceof Class ? (Class<?>) thing : thing.getClass();
}

@GET @Path("/{name}")
@Operation(security=@SecurityRequirement(name=SEC_API_KEY),
tags={API_TAG_UTILITY},
summary="Read the entity config for an entity type. Type names are case-insensitive.",
description="Read the entity config for an entity type. Type names are case-insensitive.",
parameters={@Parameter(name="name", description="name of the entity type. names are case-insensitive.")},
responses={
@ApiResponse(responseCode=SC_OK, description="the EntityConfig object for the type"),
@ApiResponse(responseCode=SC_NOT_FOUND, description = "no EntityConfig exists with the name given")
}
)
public Response getConfig (@Context ContainerRequest ctx,
@PathParam("name") String name,
@QueryParam("debug") boolean debug,
@@ -181,14 +225,21 @@ public abstract class AbstractEntityConfigsResource implements EntityConfigSourc
entityConfig.setClassName(clazz.getName());

try {
final DAO dao = getConfiguration().getDaoForEntityClass(clazz);
final EntityConfig updated = entityConfig.updateWithAnnotations(clazz, root);

DAO dao = null;
try {
dao = getConfiguration().getDaoForEntityClass(clazz);
} catch (Exception e) {
log.info("getEntityConfig: creating EntityConfig for class without a DAO: "+clazz.getName()+": "+shortError(e));
}

// add SQL search fields, if the entity supports them
if (SqlViewSearchResult.class.isAssignableFrom(clazz) && dao instanceof AbstractCRUDDAO) {
updated.setSqlViewFields(((AbstractCRUDDAO) dao).getSearchFields());
}

if (empty(updated.getName())) updated.setName(clazz.getSimpleName());
return updated;

} catch (Exception e) {


+ 51
- 4
wizard-server/src/main/java/org/cobbzilla/wizard/server/config/OpenApiConfiguration.java ファイルの表示

@@ -2,6 +2,7 @@ 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.annotations.media.Schema;
import io.swagger.v3.oas.integration.SwaggerConfiguration;
import io.swagger.v3.oas.models.Components;
import io.swagger.v3.oas.models.OpenAPI;
@@ -17,13 +18,15 @@ import org.apache.commons.collections4.map.SingletonMap;
import org.cobbzilla.util.handlebars.HandlebarsUtil;
import org.cobbzilla.util.handlebars.HasHandlebars;
import org.cobbzilla.wizard.filters.auth.AuthFilter;
import org.cobbzilla.wizard.model.entityconfig.EntityConfig;
import org.cobbzilla.wizard.model.entityconfig.EntityConfigSource;
import org.cobbzilla.wizard.util.ClasspathScanner;
import org.glassfish.jersey.server.ResourceConfig;
import org.springframework.core.type.filter.AnnotationTypeFilter;

import java.util.*;
import java.util.stream.Collectors;

import static org.cobbzilla.util.daemon.ZillaRuntime.die;
import static org.cobbzilla.util.daemon.ZillaRuntime.empty;
import static org.cobbzilla.util.daemon.ZillaRuntime.*;

@Slf4j
public class OpenApiConfiguration {
@@ -31,6 +34,7 @@ public class OpenApiConfiguration {
// set contactEmail to this value to disable OpenAPI
public static final String OPENAPI_DISABLED = "openapi_disabled";
public static final String SEC_API_KEY = "apiKey";
public static final String API_TAG_UTILITY = "utility";

@Getter @Setter private String title;
@Getter @Setter private String description;
@@ -38,6 +42,7 @@ public class OpenApiConfiguration {
@Getter @Setter private String terms;
@Getter @Setter private String licenseName;
@Getter @Setter private String licenseUrl;
@Getter @Setter private String[] additionalPackages;

public boolean valid() {
return !empty(contactEmail) && !contactEmail.equalsIgnoreCase(OPENAPI_DISABLED)
@@ -89,14 +94,56 @@ public class OpenApiConfiguration {
.info(info)
.servers(servers);

final Set<String> packages = getPackages(configuration);
if (configuration instanceof HasDatabaseConfiguration) {
try {
addEntitySchemas(oas, packages.toArray(String[]::new), configuration);
} catch (Exception e) {
log.warn("register: error reading entity configs or converting to OpenApi schemas: "+shortError(e));
}
}

final SwaggerConfiguration oasConfig = new SwaggerConfiguration()
.openAPI(oas)
.prettyPrint(true)
.resourcePackages(Arrays.stream(configuration.getJersey().getResourcePackages()).collect(Collectors.toSet()));
.resourcePackages(packages);

rc.register(new OpenApiResource().openApiConfiguration(oasConfig));
}

public static final AnnotationTypeFilter SCHEMA_FILTER = new AnnotationTypeFilter(Schema.class);

protected void addEntitySchemas(OpenAPI oas, String[] packages, RestServerConfiguration configuration) throws Exception {
final EntityConfigSource entityConfigSource = configuration.getBean(EntityConfigSource.class);
final Set<Class<?>> apiEntities = new HashSet<>();
final PgRestServerConfiguration pgConfig = (PgRestServerConfiguration) configuration;
apiEntities.addAll(pgConfig.getEntityClassesReverse());
apiEntities.addAll(new ClasspathScanner<>()
.setPackages(packages)
.setFilter(SCHEMA_FILTER)
.scan());
for (Class<?> entity : apiEntities) {
final EntityConfig entityConfig = entityConfigSource.getOrCreateEntityConfig(entity);
oas.schema(entityConfig.example().getClass().getSimpleName(), entityConfig.openApiSchema());
}
}

protected Set<String> getPackages(RestServerConfiguration configuration) {
// always add jersey resources
final Set<String> packages
= new HashSet<>(Arrays.asList(configuration.getJersey().getResourcePackages()));

// add entities if we have them
if (configuration instanceof HasDatabaseConfiguration) {
final DatabaseConfiguration db = ((HasDatabaseConfiguration) configuration).getDatabase();
packages.addAll(Arrays.asList(db.getHibernate().getEntityPackages()));
}
if (!empty(additionalPackages)) {
packages.addAll(Arrays.asList(additionalPackages));
}
return packages;
}

public String subst (String value,
Handlebars handlebars,
Map<String, Object> ctx,


+ 4
- 3
wizard-server/src/main/java/org/cobbzilla/wizard/server/listener/BrowserLauncherListener.java ファイルの表示

@@ -27,8 +27,12 @@ public class BrowserLauncherListener extends RestServerLifecycleListenerBase {
final Thread appThread = new Thread(() -> {
final boolean allowLaunch = configAllowsBrowserLaunch(config);
final Desktop desktop = allowLaunch && isDesktopSupported() ? Desktop.getDesktop() : null;
final String versionInfo = server.getConfiguration().hasVersion()
? "\nVersion: " + server.getConfiguration().getVersion()
: "";
if (desktop != null && desktop.isSupported(Desktop.Action.BROWSE)) {
try {
log.info("\n\n"+server.getConfiguration().getServerName()+" Successfully Started"+versionInfo+"\n\nLaunching browser to: "+baseUri+"\n\nSet env var "+getDisableBrowserAutoLaunchEnvVarName()+"=true to disable browser launching\n\nHit Control-C to stop the server\n");
desktop.browse(URIUtil.toUri(baseUri));
} catch (Exception e) {
final String msg = "onStart: error launching default browser with url '" + baseUri + "': " + e;
@@ -37,9 +41,6 @@ public class BrowserLauncherListener extends RestServerLifecycleListenerBase {
}
} else {
// no browser. tell the user where the server is listening via log statement
final String versionInfo = server.getConfiguration().hasVersion()
? "\nVersion: " + server.getConfiguration().getVersion()
: "";
log.info("\n\n"+server.getConfiguration().getServerName()+" Successfully Started"+versionInfo+"\n\nNot launching browser: System lacks a browser and/or desktop window manager.\n\nWeb UI is: "+baseUri+"\nAPI is: "+baseUri+"/api\nHit Control-C to stop the server\n");
}
});


読み込み中…
キャンセル
保存