io.agrest.jaxrs3.openapi.modelconverter.AgEntityAwareModelConverter Maven / Gradle / Ivy
package io.agrest.jaxrs3.openapi.modelconverter;
import io.agrest.PathConstants;
import io.agrest.meta.AgAttribute;
import io.agrest.meta.AgEntity;
import io.agrest.meta.AgIdPart;
import io.agrest.meta.AgRelationship;
import io.swagger.v3.core.converter.AnnotatedType;
import io.swagger.v3.core.converter.ModelConverterContext;
import io.swagger.v3.core.util.PrimitiveType;
import io.swagger.v3.core.util.RefUtils;
import io.swagger.v3.oas.models.media.ArraySchema;
import io.swagger.v3.oas.models.media.ObjectSchema;
import io.swagger.v3.oas.models.media.Schema;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* @since 5.0
*/
public abstract class AgEntityAwareModelConverter extends AgModelConverter {
protected Schema doResolveValue(Class> type, ModelConverterContext context) {
Schema primitive = PrimitiveType.createProperty(type);
return primitive != null
? primitive
: context.resolve(new AnnotatedType().type(type));
}
protected Schema doResolveId(AgEntity> entity, ModelConverterContext context, PropertyAccessChecker accessChecker) {
switch (entity.getIdParts().size()) {
case 0:
return null;
case 1:
return doResolveSingleId(entity, context, accessChecker);
default:
return doResolveIdMap(entity, context, accessChecker);
}
}
protected Schema doResolveSingleId(AgEntity> entity, ModelConverterContext context, PropertyAccessChecker accessChecker) {
AgIdPart idPart = entity.getIdParts().iterator().next();
return accessChecker.allow(idPart) ? doResolveValue(idPart.getType(), context) : null;
}
protected Schema doResolveIdMap(AgEntity> entity, ModelConverterContext context, PropertyAccessChecker accessChecker) {
Map properties = new LinkedHashMap<>();
List sortedIds = new ArrayList<>(entity.getIdParts());
sortedIds.sort(Comparator.comparing(AgIdPart::getName));
for (AgIdPart idPart : sortedIds) {
if (accessChecker.allow(idPart)) {
properties.put(idPart.getName(), doResolveValue(idPart.getType(), context));
}
}
return properties.isEmpty() ? null : new ObjectSchema().properties(properties);
}
protected Schema doResolveAttribute(AgAttribute attribute, ModelConverterContext context, PropertyAccessChecker accessChecker) {
return accessChecker.allow(attribute) ? doResolveValue(attribute.getType(), context) : null;
}
protected Schema doResolveRelationship(AgRelationship r, ModelConverterContext context, PropertyAccessChecker accessChecker) {
if (!accessChecker.allow(r)) {
return null;
}
AgEntity> targetEntity = r.getTargetEntity();
// ensure related entity and any other entities reachable from it are resolved
context.resolve(new AnnotatedType().type(targetEntity.getType()));
// link to resolved entity
Schema relatedSchemaRef = new Schema().$ref(RefUtils.constructRef(targetEntity.getName()));
return r.isToMany()
? new ArraySchema().items(relatedSchemaRef)
: relatedSchemaRef;
}
protected Schema doResolveRelationshipRef(AgRelationship r, ModelConverterContext context, PropertyAccessChecker accessChecker) {
if (!accessChecker.allow(r)) {
return null;
}
// relationships in updates are resolved as refs to IDs.. The permission here is for writing a relationship,
// not the ID, so since we just checked it above, always allow to proceed
Schema idSchema = doResolveId(r.getTargetEntity(), context, PropertyAccessChecker.allowAll());
if (idSchema != null) {
return r.isToMany() ? new ArraySchema().items(idSchema) : idSchema;
}
return null;
}
protected Map doCollectProperties(
Schema idSchema,
List> attributesAndRelationships) {
// property sorting should be stable and match that of DataResponse: "id" goes first, then a mix of
// attributes and relationships in alphabetic order
Map properties = new LinkedHashMap<>();
if (idSchema != null) {
properties.put(PathConstants.ID_PK_ATTRIBUTE, idSchema);
}
attributesAndRelationships.stream()
.sorted(Map.Entry.comparingByKey())
.forEach(e -> properties.put(e.getKey(), e.getValue()));
return properties;
}
}