Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for @JsonView #1209

Merged
merged 1 commit into from
Aug 8, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -595,16 +595,16 @@ private static Schema introspectClassToSchema(final AnnotationScannerContext con
}
SchemaRegistry schemaRegistry = SchemaRegistry.currentInstance();

if (schemaRegistry != null && schemaRegistry.hasSchema(ctype)) {
if (schemaRegistry != null && schemaRegistry.hasSchema(ctype, context.getJsonViews())) {
if (schemaReferenceSupported) {
return schemaRegistry.lookupRef(ctype);
return schemaRegistry.lookupRef(ctype, context.getJsonViews());
} else {
// 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 @@ -628,9 +628,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 @@ -657,7 +657,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 @@ -212,8 +219,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 @@ -313,7 +323,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