|
| 1 | +package com.benjaminsproule.swagger.gradleplugin.reader.resolver |
| 2 | + |
| 3 | +import com.benjaminsproule.swagger.gradleplugin.classpath.ClassFinder |
| 4 | +import com.benjaminsproule.swagger.gradleplugin.exceptions.GenerateException |
| 5 | +import com.fasterxml.jackson.databind.JavaType |
| 6 | +import com.fasterxml.jackson.databind.ObjectMapper |
| 7 | +import io.swagger.annotations.ApiModelProperty |
| 8 | +import io.swagger.converter.ModelConverter |
| 9 | +import io.swagger.converter.ModelConverterContext |
| 10 | +import io.swagger.jackson.ModelResolver |
| 11 | +import io.swagger.models.Model |
| 12 | +import io.swagger.models.properties.Property |
| 13 | +import org.apache.commons.lang3.reflect.FieldUtils |
| 14 | +import org.slf4j.Logger |
| 15 | +import org.slf4j.LoggerFactory |
| 16 | +import org.springframework.core.annotation.AnnotationUtils |
| 17 | + |
| 18 | +import java.lang.annotation.Annotation |
| 19 | +import java.lang.reflect.Field |
| 20 | +import java.lang.reflect.Method |
| 21 | +import java.lang.reflect.Type |
| 22 | + |
| 23 | +/** |
| 24 | + * Monkey-patched ModelModifier to use JavaType in resolve() call instead of Type. |
| 25 | + * In groovy 3, type is automatically converted, but in groovy 4 (from gradle 9.0+) it is not. |
| 26 | + * TODO: Remove this when https://github.com/gigaSproule/swagger-gradle-plugin/issues/186 is fixed. |
| 27 | + */ |
| 28 | +class ModelModifier extends ModelResolver { |
| 29 | + private Map<String, JavaType> modelSubtitutes = [:] |
| 30 | + List<String> apiModelPropertyAccessExclusions = [] |
| 31 | + private ClassFinder classFinder |
| 32 | + |
| 33 | + private static Logger LOG = LoggerFactory.getLogger(ModelModifier) |
| 34 | + |
| 35 | + ModelModifier(ObjectMapper mapper, ClassFinder classFinder) { |
| 36 | + super(mapper) |
| 37 | + this.classFinder = classFinder |
| 38 | + } |
| 39 | + |
| 40 | + void addModelSubstitute(String fromClass, String toClass) throws GenerateException { |
| 41 | + try { |
| 42 | + JavaType toType = _mapper.constructType(classFinder.loadClass(toClass)) |
| 43 | + modelSubtitutes.put(fromClass, toType) |
| 44 | + } catch (ClassNotFoundException ignored) { |
| 45 | + LOG.warn("Problem with loading class: ${toClass}. Mapping from: ${fromClass} to: ${toClass} will be ignored.") |
| 46 | + } |
| 47 | + } |
| 48 | + |
| 49 | + @Override |
| 50 | + Property resolveProperty(Type type, ModelConverterContext context, Annotation[] annotations, Iterator<ModelConverter> chain) { |
| 51 | + // for method parameter types we get here Type but we need JavaType |
| 52 | + JavaType javaType = toJavaType(type) |
| 53 | + String typeName = javaType.getRawClass().getCanonicalName() |
| 54 | + |
| 55 | + if (modelSubtitutes.containsKey(typeName)) { |
| 56 | + return super.resolveProperty(modelSubtitutes.get(typeName), context, annotations, chain) |
| 57 | + } else if (chain.hasNext()) { |
| 58 | + return chain.next().resolveProperty(type, context, annotations, chain) |
| 59 | + } else { |
| 60 | + return super.resolveProperty(type, context, annotations, chain) |
| 61 | + } |
| 62 | + |
| 63 | + } |
| 64 | + |
| 65 | + @Override |
| 66 | + Model resolve(Type type, ModelConverterContext context, Iterator<ModelConverter> chain) { |
| 67 | + // for method parameter types we get here Type but we need JavaType |
| 68 | + JavaType javaType = toJavaType(type) |
| 69 | + String typeName = javaType.getRawClass().getCanonicalName() |
| 70 | + def model |
| 71 | + if (modelSubtitutes.containsKey(typeName)) { |
| 72 | + model = super.resolve(modelSubtitutes.get(typeName), context, chain) |
| 73 | + } else { |
| 74 | + // --- FIX START --- |
| 75 | + // WAS: model = super.resolve(type, context, chain) |
| 76 | + // CHANGE: Pass 'javaType' to explicitly target resolve(JavaType, ...) |
| 77 | + model = super.resolve(javaType, context, chain) |
| 78 | + // --- FIX END --- |
| 79 | + } |
| 80 | + |
| 81 | + // If there are no @ApiModelPropety exclusions configured, return the untouched model |
| 82 | + if (apiModelPropertyAccessExclusions == null || apiModelPropertyAccessExclusions.isEmpty()) { |
| 83 | + return model |
| 84 | + } |
| 85 | + |
| 86 | + Class<?> cls = javaType.getRawClass() |
| 87 | + |
| 88 | + for (Method method : cls.getDeclaredMethods()) { |
| 89 | + ApiModelProperty apiModelPropertyAnnotation = AnnotationUtils.findAnnotation(method, ApiModelProperty) |
| 90 | + |
| 91 | + processProperty(apiModelPropertyAnnotation, model) |
| 92 | + } |
| 93 | + |
| 94 | + for (Field field : FieldUtils.getAllFields(cls)) { |
| 95 | + ApiModelProperty apiModelPropertyAnnotation = AnnotationUtils.getAnnotation(field, ApiModelProperty) |
| 96 | + |
| 97 | + processProperty(apiModelPropertyAnnotation, model) |
| 98 | + } |
| 99 | + |
| 100 | + return model |
| 101 | + } |
| 102 | + |
| 103 | + /** |
| 104 | + * Remove property from {@link Model} for provided {@link ApiModelProperty}. |
| 105 | + * @param apiModelPropertyAnnotation annotation |
| 106 | + * @param model model with properties |
| 107 | + */ |
| 108 | + private void processProperty(ApiModelProperty apiModelPropertyAnnotation, Model model) { |
| 109 | + if (apiModelPropertyAnnotation == null) { |
| 110 | + return |
| 111 | + } |
| 112 | + |
| 113 | + String apiModelPropertyAccess = apiModelPropertyAnnotation.access() |
| 114 | + String apiModelPropertyName = apiModelPropertyAnnotation.name() |
| 115 | + |
| 116 | + // If the @ApiModelProperty is not populated with both #name and #access, skip it |
| 117 | + if (apiModelPropertyAccess.isEmpty() || apiModelPropertyName.isEmpty()) { |
| 118 | + return |
| 119 | + } |
| 120 | + |
| 121 | + // Check to see if the value of @ApiModelProperty#access is one to exclude. |
| 122 | + // If so, remove it from the previously-calculated model. |
| 123 | + if (apiModelPropertyAccessExclusions.contains(apiModelPropertyAccess)) { |
| 124 | + model.getProperties().remove(apiModelPropertyName) |
| 125 | + } |
| 126 | + } |
| 127 | + |
| 128 | + /** |
| 129 | + * Converts {@link Type} to {@link JavaType}. |
| 130 | + * @param type object to convert |
| 131 | + * @return object converted to {@link JavaType} |
| 132 | + */ |
| 133 | + private JavaType toJavaType(Type type) { |
| 134 | + JavaType typeToFind |
| 135 | + if (type instanceof JavaType) { |
| 136 | + typeToFind = (JavaType) type |
| 137 | + } else { |
| 138 | + typeToFind = _mapper.constructType(type) |
| 139 | + } |
| 140 | + return typeToFind |
| 141 | + } |
| 142 | +} |
0 commit comments