Pārlūkot izejas kodu

improve flexibility in mapping entities to open api schemas

tags/2.0.1
Jonathan Cobb pirms 3 gadiem
vecāks
revīzija
2048c0f1ac
8 mainītis faili ar 86 papildinājumiem un 31 dzēšanām
  1. +12
    -0
      wizard-common/src/main/java/org/cobbzilla/wizard/model/OpenApiSchema.java
  2. +14
    -2
      wizard-common/src/main/java/org/cobbzilla/wizard/model/entityconfig/EntityConfig.java
  3. +3
    -1
      wizard-common/src/main/java/org/cobbzilla/wizard/model/entityconfig/EntityConfigSource.java
  4. +2
    -2
      wizard-common/src/main/java/org/cobbzilla/wizard/model/support/SupportInfo.java
  5. +6
    -7
      wizard-server/src/main/java/org/cobbzilla/wizard/model/search/SearchResults.java
  6. +16
    -13
      wizard-server/src/main/java/org/cobbzilla/wizard/resources/AbstractEntityConfigsResource.java
  7. +29
    -3
      wizard-server/src/main/java/org/cobbzilla/wizard/resources/AbstractTimezonesResource.java
  8. +4
    -3
      wizard-server/src/main/java/org/cobbzilla/wizard/server/config/OpenApiConfiguration.java

+ 12
- 0
wizard-common/src/main/java/org/cobbzilla/wizard/model/OpenApiSchema.java Parādīt failu

@@ -0,0 +1,12 @@
package org.cobbzilla.wizard.model;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
public @interface OpenApiSchema {

String[] exclude() default "";
String[] include() default "";

}

+ 14
- 2
wizard-common/src/main/java/org/cobbzilla/wizard/model/entityconfig/EntityConfig.java Parādīt failu

@@ -8,6 +8,7 @@ import lombok.extern.slf4j.Slf4j;
import org.cobbzilla.util.reflect.ReflectionUtil;
import org.cobbzilla.util.string.HasLocale;
import org.cobbzilla.util.string.StringUtil;
import org.cobbzilla.wizard.model.OpenApiSchema;
import org.cobbzilla.wizard.model.entityconfig.annotations.*;
import org.cobbzilla.wizard.model.search.SqlViewField;
import org.cobbzilla.wizard.validation.HasValue;
@@ -190,11 +191,11 @@ public class EntityConfig {
* non-empty values!
*/
public EntityConfig updateWithAnnotations() {
return updateWithAnnotations(getClassSafe(getClassName()), false);
return updateWithAnnotations(getClassSafe(getClassName()), false, null);
}

/** Update properties with values from the class' annotation. Doesn't override existing non-empty values! */
public EntityConfig updateWithAnnotations(Class<?> clazz, boolean isRootECCall) {
public EntityConfig updateWithAnnotations(Class<?> clazz, boolean isRootECCall, OpenApiSchema schema) {
if (isRootECCall && clazz == null) throw new NullPointerException("Root class cannot be null");

final Map<String, Integer> fieldIndexes = new HashMap<>();
@@ -215,6 +216,17 @@ public class EntityConfig {
updateWithAnnotation(clazz, clazz.getAnnotation(ECTypeURIs.class));

final Set<String> entityFields = new HashSet<>(fieldNamesWithAnnotations(clazz, ECField.class, ECSearchable.class, ECForeignKey.class));
if (schema != null) {
final boolean hasIncludes = !empty(schema.include());
final boolean hasExcludes = !empty(schema.exclude());
if (!hasIncludes && !hasExcludes) {
// include all getters
entityFields.addAll(ReflectionUtil.toMap(instantiate(clazz)).keySet());
} else {
if (hasIncludes) entityFields.addAll(Arrays.asList(schema.include()));
if (hasExcludes) entityFields.removeAll(Arrays.asList(schema.exclude()));
}
}
updateECFields(clazz, entityFields, fieldIndexes);
updateWithAnnotation(clazz, clazz.getAnnotation(ECTypeChildren.class));
}


+ 3
- 1
wizard-common/src/main/java/org/cobbzilla/wizard/model/entityconfig/EntityConfigSource.java Parādīt failu

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

import org.cobbzilla.wizard.model.OpenApiSchema;

public interface EntityConfigSource {

EntityConfig getEntityConfig(Object thing);
EntityConfig getOrCreateEntityConfig(Object thing) throws Exception;
EntityConfig getOrCreateEntityConfig(Object thing, OpenApiSchema schema) throws Exception;
}

+ 2
- 2
wizard-common/src/main/java/org/cobbzilla/wizard/model/support/SupportInfo.java Parādīt failu

@@ -1,14 +1,14 @@
package org.cobbzilla.wizard.model.support;

import com.fasterxml.jackson.annotation.JsonIgnore;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
import org.cobbzilla.wizard.model.OpenApiSchema;

import java.util.HashMap;
import java.util.Map;

@Schema
@OpenApiSchema
public class SupportInfo extends BasicSupportInfo {

@JsonIgnore @Getter @Setter private Map<String, BasicSupportInfo> locale = new HashMap<>();


+ 6
- 7
wizard-server/src/main/java/org/cobbzilla/wizard/model/search/SearchResults.java Parādīt failu

@@ -2,7 +2,6 @@ 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;
@@ -10,7 +9,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.entityconfig.annotations.ECField;
import org.cobbzilla.wizard.model.OpenApiSchema;

import java.util.ArrayList;
import java.util.List;
@@ -20,7 +19,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) @Schema
@NoArgsConstructor @Accessors(chain=true) @OpenApiSchema
public class SearchResults<E> implements Scrubbable {

public static final ScrubbableField[] SCRUBBABLE_FIELDS = new ScrubbableField[]{
@@ -44,10 +43,10 @@ public class SearchResults<E> implements Scrubbable {
return type;
}

@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;
@Getter @Setter private List<E> results = new ArrayList<>();
@Getter @Setter private Integer totalCount;
@Getter @Setter private String nextPage;
@Getter @Setter private String error;

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


+ 16
- 13
wizard-server/src/main/java/org/cobbzilla/wizard/resources/AbstractEntityConfigsResource.java Parādīt failu

@@ -17,6 +17,7 @@ import org.cobbzilla.util.string.StringUtil;
import org.cobbzilla.wizard.dao.AbstractCRUDDAO;
import org.cobbzilla.wizard.dao.DAO;
import org.cobbzilla.wizard.model.Identifiable;
import org.cobbzilla.wizard.model.OpenApiSchema;
import org.cobbzilla.wizard.model.entityconfig.EntityConfig;
import org.cobbzilla.wizard.model.entityconfig.EntityConfigSource;
import org.cobbzilla.wizard.model.entityconfig.EntityFieldConfig;
@@ -75,17 +76,15 @@ public abstract class AbstractEntityConfigsResource implements EntityConfigSourc

@GET
@Operation(security=@SecurityRequirement(name=SEC_API_KEY),
tags={API_TAG_UTILITY},
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={
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) {
@@ -93,9 +92,9 @@ public abstract class AbstractEntityConfigsResource implements EntityConfigSourc
return ok(full != null && full ? getConfigs().getEntries() : getConfigs().get().keySet());
}

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

@Override public EntityConfig getEntityConfig(Object thing) {
@@ -118,13 +117,13 @@ public abstract class AbstractEntityConfigsResource implements EntityConfigSourc

@GET @Path("/{name}")
@Operation(security=@SecurityRequirement(name=SEC_API_KEY),
tags={API_TAG_UTILITY},
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.")},
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")
@ApiResponse(responseCode=SC_NOT_FOUND, description="no EntityConfig exists with the name given")
}
)
public Response getConfig (@Context ContainerRequest ctx,
@@ -212,6 +211,10 @@ public abstract class AbstractEntityConfigsResource implements EntityConfigSourc
private EntityConfig getEntityConfig(Class<?> clazz) throws Exception { return getEntityConfig(clazz, true); }

private EntityConfig getEntityConfig(Class<?> clazz, boolean root) throws Exception {
return getEntityConfig(clazz, root, null);
}

private EntityConfig getEntityConfig(Class<?> clazz, boolean root, OpenApiSchema schema) throws Exception {
EntityConfig entityConfig;
try {
final InputStream in = loadResourceAsStream(ENTITY_CONFIG_BASE + "/" + packagePath(clazz) + "/" +
@@ -225,7 +228,7 @@ public abstract class AbstractEntityConfigsResource implements EntityConfigSourc
entityConfig.setClassName(clazz.getName());

try {
final EntityConfig updated = entityConfig.updateWithAnnotations(clazz, root);
final EntityConfig updated = entityConfig.updateWithAnnotations(clazz, root, schema);

DAO dao = null;
try {


+ 29
- 3
wizard-server/src/main/java/org/cobbzilla/wizard/resources/AbstractTimezonesResource.java Parādīt failu

@@ -1,6 +1,10 @@
package org.cobbzilla.wizard.resources;

import com.fasterxml.jackson.annotation.JsonCreator;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.cobbzilla.util.collection.NameAndValue;
@@ -8,16 +12,22 @@ import org.cobbzilla.util.time.UnicodeTimezone;

import javax.ws.rs.*;
import javax.ws.rs.core.Response;
import java.util.*;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.function.Function;

import static java.util.function.Function.identity;
import static java.util.stream.Collectors.toCollection;
import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
import static org.cobbzilla.util.collection.NameAndValue.NAME_COMPARATOR;
import static org.cobbzilla.util.http.HttpStatusCodes.SC_NOT_FOUND;
import static org.cobbzilla.util.http.HttpStatusCodes.SC_OK;
import static org.cobbzilla.util.time.UnicodeTimezone.getUnicodeTimezoneMap;
import static org.cobbzilla.wizard.resources.ResourceUtil.notFound;
import static org.cobbzilla.wizard.resources.ResourceUtil.ok;
import static org.cobbzilla.wizard.server.config.OpenApiConfiguration.API_TAG_UTILITY;
import static org.cobbzilla.wizard.server.config.OpenApiConfiguration.SEC_API_KEY;

@Slf4j
@Consumes(APPLICATION_JSON)
@@ -71,8 +81,14 @@ public class AbstractTimezonesResource {
= getUnicodeTimezoneMap().keySet().stream().map(TzFormat.dll::format).collect(toCollection(TreeSet::new));

@GET
public Response findAll (@PathParam("id") String id,
@QueryParam("format") TzFormat format) {
@Operation(security=@SecurityRequirement(name=SEC_API_KEY),
tags=API_TAG_UTILITY,
summary="List all time zones",
description="List all time zones. The format parameter determines the format of the JSON, see TzFormat enum",
parameters=@Parameter(name="format", description="format of the response"),
responses=@ApiResponse(responseCode=SC_OK, description="a JSON array of timezones in the format requested")
)
public Response findAll (@QueryParam("format") TzFormat format) {
final TzFormat fmt = format != null ? format : TzFormat.raw;
switch (fmt) {
case full: return ok(all_full);
@@ -85,6 +101,16 @@ public class AbstractTimezonesResource {
}

@GET @Path("/{id: .*}")
@Operation(security=@SecurityRequirement(name=SEC_API_KEY),
tags=API_TAG_UTILITY,
summary="Get the canonical name for a time zone",
description="Get the canonical name for a time zone. Some time zones have alias names. This returns the canonical name.",
parameters=@Parameter(name="id", description="time zone name"),
responses={
@ApiResponse(responseCode=SC_OK, description="the canonical name of the time zone"),
@ApiResponse(responseCode=SC_NOT_FOUND, description="the time zone was not found")
}
)
public Response find (@PathParam("id") String id) {

UnicodeTimezone utz = UnicodeTimezone.fromString(id);


+ 4
- 3
wizard-server/src/main/java/org/cobbzilla/wizard/server/config/OpenApiConfiguration.java Parādīt failu

@@ -2,7 +2,6 @@ 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;
@@ -18,6 +17,7 @@ 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.OpenApiSchema;
import org.cobbzilla.wizard.model.entityconfig.EntityConfig;
import org.cobbzilla.wizard.model.entityconfig.EntityConfigSource;
import org.cobbzilla.wizard.util.ClasspathScanner;
@@ -111,7 +111,7 @@ public class OpenApiConfiguration {
rc.register(new OpenApiResource().openApiConfiguration(oasConfig));
}

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

protected void addEntitySchemas(OpenAPI oas, String[] packages, RestServerConfiguration configuration) throws Exception {
final EntityConfigSource entityConfigSource = configuration.getBean(EntityConfigSource.class);
@@ -123,7 +123,8 @@ public class OpenApiConfiguration {
.setFilter(SCHEMA_FILTER)
.scan());
for (Class<?> entity : apiEntities) {
final EntityConfig entityConfig = entityConfigSource.getOrCreateEntityConfig(entity);
final OpenApiSchema schema = entity.getAnnotation(OpenApiSchema.class);
final EntityConfig entityConfig = entityConfigSource.getOrCreateEntityConfig(entity, schema);
oas.schema(entityConfig.example().getClass().getSimpleName(), entityConfig.openApiSchema());
}
}


Notiek ielāde…
Atcelt
Saglabāt