Skip to content

Commit

Permalink
Add support for @JsonView
Browse files Browse the repository at this point in the history
Closes smallrye#1008

Signed-off-by: Michael Edgar <michael@xlate.io>
  • Loading branch information
MikeEdgar committed Aug 8, 2022
1 parent 32055a8 commit c21b1f0
Show file tree
Hide file tree
Showing 18 changed files with 492 additions and 65 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,12 @@ public class JacksonConstants {
.createSimple("com.fasterxml.jackson.annotation.JsonPropertyOrder");
public static final DotName JSON_UNWRAPPED = DotName
.createSimple("com.fasterxml.jackson.annotation.JsonUnwrapped");
public static final DotName JSON_NAMING = DotName
.createSimple("com.fasterxml.jackson.databind.annotation.JsonNaming");
public static final DotName JSON_VALUE = DotName
.createSimple("com.fasterxml.jackson.annotation.JsonValue");
public static final DotName JSON_VIEW = DotName
.createSimple("com.fasterxml.jackson.annotation.JsonView");
public static final DotName JSON_NAMING = DotName
.createSimple("com.fasterxml.jackson.databind.annotation.JsonNaming");

public static final String PROP_VALUE = "value";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -583,14 +583,15 @@ private static Schema introspectClassToSchema(final AnnotationScannerContext con
}
SchemaRegistry schemaRegistry = SchemaRegistry.currentInstance();

if (schemaReferenceSupported && schemaRegistry.hasSchema(ctype)) {
return schemaRegistry.lookupRef(ctype);
} else if (!schemaReferenceSupported && schemaRegistry != null && schemaRegistry.hasSchema(ctype)) {
if (schemaReferenceSupported && schemaRegistry.hasSchema(ctype, context.getJsonViews())) {
return schemaRegistry.lookupRef(ctype, context.getJsonViews());
} else if (!schemaReferenceSupported && schemaRegistry != null
&& schemaRegistry.hasSchema(ctype, context.getJsonViews())) {
// Clone the schema from the registry using mergeObjects
return MergeUtil.mergeObjects(new SchemaImpl(), schemaRegistry.lookupSchema(ctype));
return MergeUtil.mergeObjects(new SchemaImpl(), schemaRegistry.lookupSchema(ctype, context.getJsonViews()));
} else if (context.getScanStack().contains(ctype)) {
// Protect against stack overflow when the type is in the process of being scanned.
return SchemaRegistry.registerReference(ctype, null, new SchemaImpl());
return SchemaRegistry.registerReference(ctype, context.getJsonViews(), null, new SchemaImpl());
} else {
Schema schema = OpenApiDataObjectScanner.process(context, ctype);

Expand All @@ -614,9 +615,9 @@ public static Schema schemaRegistration(final AnnotationScannerContext context,
SchemaRegistry schemaRegistry = SchemaRegistry.currentInstance();

if (allowRegistration(context, schemaRegistry, type, schema)) {
schema = schemaRegistry.register(type, schema);
} else if (schemaRegistry != null && schemaRegistry.hasRef(type)) {
schema = schemaRegistry.lookupRef(type);
schema = schemaRegistry.register(type, context.getJsonViews(), schema);
} else if (schemaRegistry != null && schemaRegistry.hasRef(type, context.getJsonViews())) {
schema = schemaRegistry.lookupRef(type, context.getJsonViews());
}

return schema;
Expand All @@ -643,7 +644,7 @@ static boolean allowRegistration(final AnnotationScannerContext context, SchemaR
/*
* Only register if the type is not already registered
*/
return !registry.hasSchema(type);
return !registry.hasSchema(type, context.getJsonViews());
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ private void depthFirstGraphSearch() {

Type currentType = currentPathEntry.getClazzType();

if (SchemaRegistry.hasSchema(currentType, null)) {
if (SchemaRegistry.hasSchema(currentType, context.getJsonViews(), null)) {
// This type has already been scanned and registered, don't do it again!
continue;
}
Expand Down Expand Up @@ -292,9 +292,9 @@ private void processInheritance(DataObjectDeque.PathEntry currentPathEntry) {
this.rootSchema = enclosingSchema;
}

if (SchemaRegistry.hasSchema(currentType, null)) {
if (SchemaRegistry.hasSchema(currentType, context.getJsonViews(), null)) {
// Replace the registered schema if one is present
SchemaRegistry.currentInstance().register(currentType, enclosingSchema);
SchemaRegistry.currentInstance().register(currentType, context.getJsonViews(), enclosingSchema);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import static io.smallrye.openapi.runtime.util.TypeUtil.getSchemaAnnotation;

import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
Expand Down Expand Up @@ -106,8 +107,8 @@ public static void remove() {
* @return the same schema if not eligible for registration, or a reference
* to the schema registered for the given Type
*/
public static Schema checkRegistration(Type type, TypeResolver resolver, Schema schema) {
return register(type, resolver, schema, (registry, key) -> registry.register(key, schema, null));
public static Schema checkRegistration(Type type, Set<Type> views, TypeResolver resolver, Schema schema) {
return register(type, views, resolver, schema, (registry, key) -> registry.register(key, schema, null));
}

/**
Expand Down Expand Up @@ -140,11 +141,11 @@ public static Schema checkRegistration(Type type, TypeResolver resolver, Schema
* @return the same schema if not eligible for registration, or a reference
* to the schema registered for the given Type
*/
public static Schema registerReference(Type type, TypeResolver resolver, Schema schema) {
return register(type, resolver, schema, (registry, key) -> registry.registerReference(key));
public static Schema registerReference(Type type, Set<Type> views, TypeResolver resolver, Schema schema) {
return register(type, views, resolver, schema, (registry, key) -> registry.registerReference(key));
}

static Schema register(Type type, TypeResolver resolver, Schema schema,
static Schema register(Type type, Set<Type> views, TypeResolver resolver, Schema schema,
BiFunction<SchemaRegistry, TypeKey, Schema> registrationAction) {
Type resolvedType;

Expand All @@ -170,7 +171,7 @@ static Schema register(Type type, TypeResolver resolver, Schema schema,
return schema;
}

TypeKey key = new TypeKey(resolvedType);
TypeKey key = new TypeKey(resolvedType, views);

if (registry.hasRef(key)) {
schema = registry.lookupRef(key);
Expand All @@ -192,7 +193,7 @@ static Schema register(Type type, TypeResolver resolver, Schema schema,
* @param resolver resolver for type parameter
* @return true when schema references are enabled and the type is present in the registry, otherwise false
*/
public static boolean hasSchema(Type type, TypeResolver resolver) {
public static boolean hasSchema(Type type, Set<Type> views, TypeResolver resolver) {
SchemaRegistry registry = currentInstance();

if (registry == null) {
Expand All @@ -207,7 +208,7 @@ public static boolean hasSchema(Type type, TypeResolver resolver) {
resolvedType = type;
}

return registry.hasSchema(resolvedType);
return registry.hasSchema(resolvedType, views);
}

/**
Expand Down Expand Up @@ -267,7 +268,7 @@ private SchemaRegistry(AnnotationScannerContext context) {
}

Type type = Type.create(DotName.createSimple(className), Type.Kind.CLASS);
this.register(new TypeKey(type), schema, ((SchemaImpl) schema).getName());
this.register(new TypeKey(type, Collections.emptySet()), schema, ((SchemaImpl) schema).getName());
ScannerLogging.logger.configSchemaRegistered(className);
});
}
Expand All @@ -279,12 +280,14 @@ private SchemaRegistry(AnnotationScannerContext context) {
*
* @param entityType
* the type the {@link Schema} applies to
* @param views
*
* @param schema
* {@link Schema} to add to the registry
* @return a reference to the newly registered {@link Schema}
*/
public Schema register(Type entityType, Schema schema) {
TypeKey key = new TypeKey(entityType);
public Schema register(Type entityType, Set<Type> views, Schema schema) {
TypeKey key = new TypeKey(entityType, views);

if (hasRef(key)) {
// This is a replacement registration
Expand Down Expand Up @@ -349,7 +352,7 @@ String deriveName(TypeKey key, String schemaName) {
}

String nameBase = schemaName != null ? schemaName : key.defaultName();
String name = nameBase;
String name = nameBase + key.viewSuffix();
int idx = 1;
while (this.names.contains(name)) {
name = nameBase + idx++;
Expand All @@ -358,20 +361,20 @@ String deriveName(TypeKey key, String schemaName) {
return name;
}

public Schema lookupRef(Type instanceType) {
return lookupRef(new TypeKey(instanceType));
public Schema lookupRef(Type instanceType, Set<Type> views) {
return lookupRef(new TypeKey(instanceType, views));
}

public boolean hasRef(Type instanceType) {
return hasRef(new TypeKey(instanceType));
public boolean hasRef(Type instanceType, Set<Type> views) {
return hasRef(new TypeKey(instanceType, views));
}

public Schema lookupSchema(Type instanceType) {
return lookupSchema(new TypeKey(instanceType));
public Schema lookupSchema(Type instanceType, Set<Type> views) {
return lookupSchema(new TypeKey(instanceType, views));
}

public boolean hasSchema(Type instanceType) {
return hasSchema(new TypeKey(instanceType));
public boolean hasSchema(Type instanceType, Set<Type> views) {
return hasSchema(new TypeKey(instanceType, views));
}

public boolean isTypeRegistrationSupported(Type type, Schema schema) {
Expand Down Expand Up @@ -431,12 +434,20 @@ private void remove(TypeKey key) {
*/
static class TypeKey {
private final Type type;
private final Set<Type> views;
private int hashCode = 0;

TypeKey(Type type) {
TypeKey(Type type, Set<Type> views) {
this.type = type;
this.views = new LinkedHashSet<>(views);
}

/*
* TypeKey(Type type) {
* this(type, Collections.emptySet());
* }
*/

public String defaultName() {
StringBuilder name = new StringBuilder(type.name().local());

Expand All @@ -454,6 +465,21 @@ public String defaultName() {
return name.toString();
}

public String viewSuffix() {
if (views.isEmpty()) {
return "";
}

StringBuilder suffix = new StringBuilder();

for (Type view : views) {
suffix.append('_');
suffix.append(view.name().local());
}

return suffix.toString();
}

static void appendParameterNames(StringBuilder name, ParameterizedType type) {
for (Type param : type.asParameterizedType().arguments()) {
switch (param.kind()) {
Expand Down Expand Up @@ -519,6 +545,10 @@ public boolean equals(Object o) {
return false;
}

if (!views.equals(other.views)) {
return false;
}

if (type instanceof ParameterizedType) {
ParameterizedType paramType = (ParameterizedType) type;
ParameterizedType otherType = (ParameterizedType) other.type;
Expand Down Expand Up @@ -565,6 +595,7 @@ public int hashCode() {
}

hash = type.name().hashCode();
hash = 31 * hash + views.hashCode();

if (type instanceof ParameterizedType) {
ParameterizedType paramType = (ParameterizedType) type;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,10 +149,17 @@ Schema processField() {
if (typeSchema.getType() != SchemaType.ARRAY) {
// Only register a reference to the type schema. The full schema will be added by subsequent
// items on the stack (if not already present in the registry).
registeredTypeSchema = SchemaRegistry.registerReference(registrationType, typeResolver, typeSchema);
if (JandexUtil.isRef(schemaAnnotation)) {
registeredTypeSchema = null;
} else {
registeredTypeSchema = SchemaRegistry.registerReference(registrationType, context.getJsonViews(),
typeResolver,
typeSchema);
}
} else {
// Allow registration of arrays since we may not encounter a List<CurrentType> again.
registeredTypeSchema = SchemaRegistry.checkRegistration(registrationType, typeResolver, typeSchema);
registeredTypeSchema = SchemaRegistry.checkRegistration(registrationType, context.getJsonViews(), typeResolver,
typeSchema);
}
}

Expand Down Expand Up @@ -210,8 +217,11 @@ Schema processField() {
} else {
fieldSchema = registeredTypeSchema; // Reference to the type schema
}
} else {
// Registration did not occur, overlay anything defined by the field on the type's schema
} else if (!JandexUtil.isRef(schemaAnnotation)) {
/*
* Registration did not occur and the user did not indicate this schema is a simple reference,
* overlay anything defined by the field on the type's schema
*/
fieldSchema = MergeUtil.mergeObjects(typeSchema, fieldSchema);
}

Expand Down Expand Up @@ -311,7 +321,7 @@ private void setXmlName(Schema fieldSchema, String realName, AnnotationInstance
* @return true if the schemas are not the same (i.e. registration occurred), otherwise false
*/
private boolean registrationSuccessful(Schema typeSchema, Schema registeredTypeSchema) {
return (typeSchema != registeredTypeSchema);
return (registeredTypeSchema != null && typeSchema != registeredTypeSchema);
}

private Schema readSchemaAnnotatedField(String propertyKey, AnnotationInstance annotation, Type postProcessedField) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ public Schema getSchema() {
public Type processType() {
// If it's a terminal type.
if (isTerminalType(type)) {
SchemaRegistry.checkRegistration(type, typeResolver, schema);
SchemaRegistry.checkRegistration(type, context.getJsonViews(), typeResolver, schema);
return type;
}

Expand Down Expand Up @@ -151,7 +151,7 @@ private Type readArrayType(ArrayType arrayType, Schema arraySchema) {
pushToStack(componentType, itemSchema);
}

itemSchema = SchemaRegistry.registerReference(componentType, typeResolver, itemSchema);
itemSchema = SchemaRegistry.registerReference(componentType, context.getJsonViews(), typeResolver, itemSchema);

while (arrayType.dimensions() > 1) {
Schema parentArrSchema = new SchemaImpl();
Expand Down Expand Up @@ -250,7 +250,7 @@ private Schema resolveParameterizedType(Type valueType, Schema schema, Schema pr
Type resolved = resolveTypeVariable(propsSchema, valueType, true);
if (index.containsClass(resolved)) {
propsSchema.type(Schema.SchemaType.OBJECT);
propsSchema = SchemaRegistry.registerReference(valueType, typeResolver, propsSchema);
propsSchema = SchemaRegistry.registerReference(valueType, context.getJsonViews(), typeResolver, propsSchema);
}
} else if (index.containsClass(valueType)) {
if (isA(valueType, ENUM_TYPE)) {
Expand All @@ -262,7 +262,7 @@ private Schema resolveParameterizedType(Type valueType, Schema schema, Schema pr
pushToStack(valueType, propsSchema);
}

propsSchema = SchemaRegistry.registerReference(valueType, typeResolver, propsSchema);
propsSchema = SchemaRegistry.registerReference(valueType, context.getJsonViews(), typeResolver, propsSchema);
}

return propsSchema;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -493,11 +493,13 @@ public static Map<String, TypeResolver> getAllFields(AnnotationScannerContext co
JandexUtil.fields(context, currentClass)
.stream()
.filter(TypeResolver::acceptField)
.filter(field -> isViewable(context, field))
.forEach(field -> scanField(context, properties, field, stack, reference, descendants));

methods(context, currentClass)
.stream()
.filter(TypeResolver::acceptMethod)
.filter(method -> isViewable(context, method))
.forEach(method -> scanMethod(context, properties, method, stack, reference, descendants));

JandexUtil.interfaces(index, currentClass)
Expand All @@ -506,6 +508,7 @@ public static Map<String, TypeResolver> getAllFields(AnnotationScannerContext co
.map(index::getClass)
.filter(Objects::nonNull)
.flatMap(clazz -> methods(context, clazz).stream())
.filter(method -> isViewable(context, method))
.forEach(method -> scanMethod(context, properties, method, stack, reference, descendants));

descendants.add(currentClass);
Expand Down Expand Up @@ -552,6 +555,22 @@ private static boolean isNonPublicOrAbsent(MethodInfo method) {
return method == null || !Modifier.isPublic(method.flags());
}

private static boolean isViewable(AnnotationScannerContext context, AnnotationTarget propertySource) {
Set<Type> activeViews = context.getJsonViews();

if (activeViews.isEmpty()) {
return true;
}

Type[] applicableViews = TypeUtil.getDeclaredAnnotationValue(propertySource, JacksonConstants.JSON_VIEW);

if (applicableViews != null && applicableViews.length > 0) {
return Arrays.stream(applicableViews).anyMatch(activeViews::contains);
}

return true;
}

/**
* Determine if the target should be exposed in the API or ignored. Explicitly un-hiding a property
* via the <code>@Schema</code> annotation overrides configuration to ignore the same property
Expand Down
Loading

0 comments on commit c21b1f0

Please sign in to comment.