Skip to content

Commit

Permalink
Merge pull request #3 from funkyFangs/SC-2-migrate-to-custom-annotati…
Browse files Browse the repository at this point in the history
…on-processor

SC-2: replace Reflections with compile time annotation processor
  • Loading branch information
funkyFangs authored May 15, 2023
2 parents 9c8eedb + 4aa52a6 commit 4a6941f
Show file tree
Hide file tree
Showing 23 changed files with 196 additions and 305 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/gradle.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ jobs:
- name: 'Set Up JDK'
uses: 'actions/setup-java@v3'
with:
java-version: 17
java-version: 19
distribution: 'zulu'
- name: 'Validate Gradle Wrapper'
uses: 'gradle/wrapper-validation-action@ccb4328a959376b642e027874838f60f8e596de3'
Expand Down
7 changes: 1 addition & 6 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,6 @@ lombok-plugin = '8.0.1'
# Mockito
mockito = '5.3.1'

# Reflections
reflections = '0.10.2'

# SLF4J
slf4j = '2.0.7'

Expand Down Expand Up @@ -56,15 +53,13 @@ junit-jupiter-params = {group = 'org.junit.jupiter', name = 'junit-jupiter-param
mockito-core = {group = 'org.mockito', name = 'mockito-core', version.ref = 'mockito'}
mockito-junit-jupiter = {group = 'org.mockito', name ='mockito-junit-jupiter', version.ref = 'mockito'}

# Reflections
reflections = {group = 'org.reflections', name = 'reflections', version.ref = 'reflections'}

# SLF4J
slf4j-api = {group = 'org.slf4j', name = 'slf4j-api', version.ref = 'slf4j'}

# Spring
spring-boot-autoconfigure = {group = 'org.springframework.boot', name = 'spring-boot-autoconfigure', version.ref = 'spring-boot'}
spring-boot-autoconfigure-processor = {group = 'org.springframework.boot', name = 'spring-boot-autoconfigure-processor', version.ref = 'spring-boot'}
spring-boot-starter-test = {group = 'org.springframework.boot', name = 'spring-boot-starter-test', version.ref = 'spring-boot'}
spring-boot-starter-validation = {group = 'org.springframework.boot', name = 'spring-boot-starter-validation', version.ref = 'spring-boot'}
spring-boot-starter-web = {group = 'org.springframework.boot', name = 'spring-boot-starter-web', version.ref = 'spring-boot'}
spring-boot-starter-webflux = {group = 'org.springframework.boot', name = 'spring-boot-starter-webflux', version.ref = 'spring-boot'}
Expand Down
2 changes: 2 additions & 0 deletions springdoc-customizer-webmvc-demo/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ dependencies {
implementation libs.springdoc.openapi.starter.webmvc.ui

// SpringDoc Customizer
annotationProcessor project(':springdoc-customizer')
implementation project(':springdoc-customizer')
testAnnotationProcessor project(':springdoc-customizer')
}

springBoot {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
package io.funky.fangs.springdoc.customizer.pets.examples;

import io.funky.fangs.springdoc.customizer.annotations.ExampleDetails;
import io.funky.fangs.springdoc.customizer.annotations.ExampleMethod;
import io.funky.fangs.springdoc.customizer.annotations.ExampleTarget;
import io.funky.fangs.springdoc.customizer.annotations.ExampleType;
import io.funky.fangs.springdoc.customizer.annotation.ExampleDetails;
import io.funky.fangs.springdoc.customizer.annotation.ExampleMethod;
import io.funky.fangs.springdoc.customizer.annotation.ExampleTarget;
import io.funky.fangs.springdoc.customizer.annotation.ExampleType;
import io.funky.fangs.springdoc.customizer.pets.controller.PetsController;
import io.funky.fangs.springdoc.customizer.pets.model.Pet;
import io.funky.fangs.springdoc.customizer.pets.model.Species;

import java.time.LocalDate;
import java.time.Month;

import static io.funky.fangs.springdoc.customizer.annotations.ExampleType.Type.REQUEST;
import static io.funky.fangs.springdoc.customizer.annotations.ExampleType.Type.RESPONSE;
import static io.funky.fangs.springdoc.customizer.annotation.ExampleType.Type.REQUEST;
import static io.funky.fangs.springdoc.customizer.annotation.ExampleType.Type.RESPONSE;
import static org.springframework.http.HttpStatus.OK;

@SuppressWarnings("unused") // Examples are targeted reflectively
Expand Down
3 changes: 0 additions & 3 deletions springdoc-customizer/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,6 @@ dependencies {
testImplementation libs.mockito.core
testImplementation libs.mockito.junit.jupiter

// Reflections
implementation libs.reflections

// SLF4J
implementation libs.slf4j.api

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package io.funky.fangs.springdoc.customizer.annotations;
package io.funky.fangs.springdoc.customizer.annotation;

import io.swagger.v3.oas.models.OpenAPI;
import org.springframework.aot.hint.annotation.Reflective;

import java.lang.annotation.*;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package io.funky.fangs.springdoc.customizer.annotations;
package io.funky.fangs.springdoc.customizer.annotation;

import org.springframework.stereotype.Controller;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package io.funky.fangs.springdoc.customizer.annotations;
package io.funky.fangs.springdoc.customizer.annotation;

import org.springframework.stereotype.Controller;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package io.funky.fangs.springdoc.customizer.annotations;
package io.funky.fangs.springdoc.customizer.annotation;

import org.springframework.http.HttpStatus;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,10 @@
package io.funky.fangs.springdoc.customizer.configuration;

import io.funky.fangs.springdoc.customizer.annotations.ExampleDetails;
import jakarta.validation.Validator;
import lombok.Getter;
import lombok.Setter;
import org.springdoc.core.properties.SpringDocConfigProperties;

import java.util.Set;

import static java.util.Collections.emptySet;

/**
* Configuration properties related the {@link ExamplesOpenApiCustomizer}.
*
Expand All @@ -23,11 +18,6 @@
public class ExamplesCustomizerConfigurationProperties {
public static final String PREFIX = SpringDocCustomizerConfigurationProperties.PREFIX + ".examples";

/**
* Packages to scan for examples annotated with {@link ExampleDetails}.
*/
private Set<String> packagesToScan = emptySet();

/**
* Determines if examples should be validated based on a {@link Validator}, if available. Invalid examples will
* not be included in the specification.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
package io.funky.fangs.springdoc.customizer.configuration;

import com.google.common.annotations.VisibleForTesting;
import io.funky.fangs.springdoc.customizer.annotations.ExampleDetails;
import io.funky.fangs.springdoc.customizer.annotations.ExampleTarget;
import io.funky.fangs.springdoc.customizer.annotations.ExampleType;
import io.funky.fangs.springdoc.customizer.utilities.RequestMappingUtilities;
import io.funky.fangs.springdoc.customizer.annotation.ExampleDetails;
import io.funky.fangs.springdoc.customizer.annotation.ExampleTarget;
import io.funky.fangs.springdoc.customizer.annotation.ExampleType;
import io.funky.fangs.springdoc.customizer.utility.ExampleUtilities;
import io.funky.fangs.springdoc.customizer.utility.RequestMappingUtilities;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.PathItem;
import io.swagger.v3.oas.models.Paths;
Expand All @@ -14,8 +15,6 @@
import jakarta.validation.Validator;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.reflections.Reflections;
import org.reflections.scanners.Scanners;
import org.springdoc.core.customizers.OpenApiCustomizer;
import org.springframework.web.bind.annotation.RequestMethod;

Expand All @@ -24,9 +23,9 @@
import java.util.Optional;
import java.util.stream.Stream;

import static io.funky.fangs.springdoc.customizer.utilities.ExampleUtilities.*;
import static io.funky.fangs.springdoc.customizer.utilities.ReflectionUtilities.*;
import static io.funky.fangs.springdoc.customizer.utilities.RequestMappingUtilities.*;
import static io.funky.fangs.springdoc.customizer.utility.ExampleUtilities.*;
import static io.funky.fangs.springdoc.customizer.utility.ReflectionUtilities.*;
import static io.funky.fangs.springdoc.customizer.utility.RequestMappingUtilities.*;
import static lombok.AccessLevel.PACKAGE;

/**
Expand All @@ -52,32 +51,26 @@ public class ExamplesOpenApiCustomizer implements OpenApiCustomizer {
@Nullable
private final Validator validator;

@Nonnull
private final Reflections reflections;

/**
* @param group the group of a grouped REST API, or null
* @param validator the {@link Validator} used to validate examples, or null
* @param packagesToScan the packages to scan for examples
*/
public ExamplesOpenApiCustomizer(@Nullable String group,
@Nullable String defaultConsumesMediaType,
@Nullable String defaultProducesMediaType,
@Nullable Validator validator,
Collection<String> packagesToScan) {
@Nullable Validator validator) {
this.group = group == null ? "" : group;
this.defaultConsumesMediaType = defaultConsumesMediaType;
this.defaultProducesMediaType = defaultProducesMediaType;
this.validator = validator;
this.reflections = new Reflections(Objects.requireNonNull(packagesToScan), Scanners.FieldsAnnotated);
}

@Override
public void customise(OpenAPI openApi) {
var paths = openApi.getPaths();

if (paths != null)
for (var field : reflections.getFieldsAnnotatedWith(ExampleDetails.class)) {
for (var field : ExampleUtilities.getExampleFields()) {
var exampleDetails = field.getAnnotation(ExampleDetails.class);
var value = getFieldValue(field);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,7 @@ public OpenApiCustomizer examplesCustomizer(SpringDocCustomizerConfigurationProp
examplesConfigurationProperties.isIncludeDefaultProducesMediaType()
? springDocConfigProperties.getDefaultProducesMediaType()
: null,
examplesConfigurationProperties.isValidateExamples() ? validator : null,
examplesConfigurationProperties.getPackagesToScan());
examplesConfigurationProperties.isValidateExamples() ? validator : null);
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package io.funky.fangs.springdoc.customizer.processor;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.Sets;

import javax.annotation.processing.*;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.tools.FileObject;
import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import static javax.tools.StandardLocation.CLASS_OUTPUT;

@SupportedAnnotationTypes("io.funky.fangs.springdoc.customizer.annotation.ExampleDetails")
@SupportedSourceVersion(SourceVersion.RELEASE_19)
public class ExamplesProcessor extends AbstractProcessor {
private static final Set<Modifier> EXAMPLE_MODIFIERS = Sets.immutableEnumSet(Modifier.STATIC, Modifier.FINAL);

public static final String EXAMPLES_FILE_NAME = "examples.json";
public static final TypeReference<Map<String, Set<String>>> EXAMPLES_MAP_TYPE_REFERENCE = new TypeReference<>() {};
public static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();

private FileObject examplesResourceFile;

@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);

try {
examplesResourceFile = processingEnv.getFiler()
.createResource(CLASS_OUTPUT, getClass().getPackageName(), EXAMPLES_FILE_NAME,
processingEnv.getElementUtils().getTypeElement(getClass().getCanonicalName()));
}
catch (IOException ioException) {
processingEnv.getMessager().printError(ioException.getMessage());
}
}

@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
// Read map from file
Map<String, Set<String>> elements;
try {
elements = OBJECT_MAPPER.readValue(examplesResourceFile.getCharContent(false).toString(),
EXAMPLES_MAP_TYPE_REFERENCE);
}
catch (IllegalStateException | IOException exception) {
// Map is likely not serialized yet, so create empty one
elements = new HashMap<>();
}

// Add fields to map
for (var annotation : annotations) {
for (var element : roundEnv.getElementsAnnotatedWith(annotation)) {
if (element.getModifiers().containsAll(EXAMPLE_MODIFIERS)
&& element instanceof VariableElement fieldElement
&& fieldElement.getEnclosingElement() instanceof TypeElement classElement) {
elements.computeIfAbsent(classElement.getQualifiedName().toString(), ignored -> new HashSet<>())
.add(fieldElement.getSimpleName().toString());
}
}
}

// Write map to file
try (var outputStream = examplesResourceFile.openOutputStream()) {
outputStream.write(OBJECT_MAPPER.writeValueAsBytes(elements));
}
catch (IOException ignored) {
}

return true;
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
package io.funky.fangs.springdoc.customizer.utilities;
package io.funky.fangs.springdoc.customizer.utility;

import com.google.common.collect.Multimap;
import io.funky.fangs.springdoc.customizer.annotations.ExampleMethod;
import io.funky.fangs.springdoc.customizer.annotations.ExampleType;
import io.funky.fangs.springdoc.customizer.annotations.ExampleType.Type;
import io.funky.fangs.springdoc.customizer.annotation.ExampleMethod;
import io.funky.fangs.springdoc.customizer.annotation.ExampleType;
import io.funky.fangs.springdoc.customizer.annotation.ExampleType.Type;
import io.funky.fangs.springdoc.customizer.processor.ExamplesProcessor;
import io.swagger.v3.oas.models.Operation;
import io.swagger.v3.oas.models.media.Content;
import io.swagger.v3.oas.models.responses.ApiResponse;
Expand All @@ -13,16 +14,16 @@
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.RequestBody;

import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Stream;
import java.util.Optional;

import static com.google.common.collect.ImmutableListMultimap.toImmutableListMultimap;
import static io.funky.fangs.springdoc.customizer.processor.ExamplesProcessor.*;
import static java.util.Collections.emptyList;
import static java.util.stream.Collectors.toMap;
import static java.util.stream.Collectors.toSet;
Expand Down Expand Up @@ -150,4 +151,35 @@ public Collection<Content> getContents(ExampleType type, Operation operation) {
}
};
}

public Collection<Field> getExampleFields() {
try {
var examplesMap = OBJECT_MAPPER.readValue(ExamplesProcessor.class.getResource(EXAMPLES_FILE_NAME),
EXAMPLES_MAP_TYPE_REFERENCE);

var result = new ArrayList<Field>();
for (var entry : examplesMap.entrySet()) {
try {
var examplesClass = Class.forName(entry.getKey());

for (var exampleFieldName : entry.getValue()) {
try {
result.add(examplesClass.getDeclaredField(exampleFieldName));
}
catch (NoSuchFieldException ignored) {
// Field could not be found, so move on
}
}
}
catch (ClassNotFoundException ignored) {
// Class could not be found, so just move on
}
}

return result;
}
catch (IOException ignored) {
return emptyList();
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package io.funky.fangs.springdoc.customizer.utilities;
package io.funky.fangs.springdoc.customizer.utility;

import jakarta.annotation.Nullable;
import lombok.experimental.UtilityClass;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package io.funky.fangs.springdoc.customizer.utilities;
package io.funky.fangs.springdoc.customizer.utility;

import lombok.experimental.UtilityClass;
import org.springframework.core.annotation.AnnotatedElementUtils;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
io.funky.fangs.springdoc.customizer.processor.ExamplesProcessor
Loading

0 comments on commit 4a6941f

Please sign in to comment.