Currently, the internal org.springdoc.core.customizers.SpringDocCustomizers is as follows:
public class SpringDocCustomizers implements ApplicationContextAware, InitializingBean {
/**
* The Open api customisers.
*/
private final Optional<Set<OpenApiCustomizer>> openApiCustomizers;
/**
* The Operation customizers.
*/
private final Optional<Set<OperationCustomizer>> operationCustomizers;
// ... other code ...
}
And this is where it is used:
public abstract class AbstractOpenApiResource extends SpecFilter {
// ... other code ...
protected OpenAPI getOpenApi(String serverBaseUrl, Locale locale) {
this.reentrantLock.lock();
try {
final OpenAPI openAPI;
final Locale finalLocale = selectLocale(locale);
if (openAPIService.getCachedOpenAPI(finalLocale) == null || springDocConfigProperties.isCacheDisabled()) {
// ... code ...
// here
openAPIService.getContext().getBeansOfType(OpenApiLocaleCustomizer.class).values().forEach(openApiLocaleCustomizer -> openApiLocaleCustomizer.customise(openAPI, finalLocale));
springDocCustomizers.getOpenApiCustomizers().ifPresent(apiCustomizers -> apiCustomizers.forEach(openApiCustomizer -> openApiCustomizer.customise(openAPI)));
// ... code ...
}
else {
LOGGER.debug("Fetching openApi document from cache");
openAPI = openAPIService.getCachedOpenAPI(finalLocale);
openAPIService.updateServers(serverBaseUrl, openAPI);
}
return openAPI;
}
finally {
SpringDocAnnotationsUtils.clearCache(operationParser.getJavadocProvider());
this.reentrantLock.unlock();
}
}
// ... other code ...
}
This approach does not support the @Order annotation.
Describe the solution you'd like
I would like to use ObjectProvider<T> instead of Optional<Set<T>>.
import org.springframework.beans.factory.ObjectProvider;
public class SpringDocCustomizers implements ApplicationContextAware, InitializingBean {
/**
* The Open api customisers.
*/
private final ObjectProvider<OpenApiCustomizer> openApiCustomizers;
/**
* The Operation customizers.
*/
private final ObjectProvider<OperationCustomizer> operationCustomizers;
// ... other code ...
}
public abstract class AbstractOpenApiResource extends SpecFilter {
// ... other code ...
protected OpenAPI getOpenApi(String serverBaseUrl, Locale locale) {
this.reentrantLock.lock();
try {
final OpenAPI openAPI;
final Locale finalLocale = selectLocale(locale);
if (openAPIService.getCachedOpenAPI(finalLocale) == null || springDocConfigProperties.isCacheDisabled()) {
// ... code ...
// here
openAPIService.getContext().getBeanProvider(OpenApiLocaleCustomizer.class).orderedStream().forEach(customizer -> customizer.customise(openAPI, finalLocale));
springDocCustomizers.getOpenApiCustomizers().orderedStream().forEach(customizer -> customizer.customise(openAPI));
// ... code ...
}
else {
LOGGER.debug("Fetching openApi document from cache");
openAPI = openAPIService.getCachedOpenAPI(finalLocale);
openAPIService.updateServers(serverBaseUrl, openAPI);
}
return openAPI;
}
finally {
SpringDocAnnotationsUtils.clearCache(operationParser.getJavadocProvider());
this.reentrantLock.unlock();
}
}
// ... other code ...
}
This way, Spring's built-in .orderedStream() can be used to handle the ordering of @Order.
Otherwise, the execution order cannot be guaranteed, because I want to execute the following code after SpringDocSecurityConfiguration.SpringSecurityLoginEndpointConfiguration#springSecurityLoginEndpointCustomizer:
Add example values to the default login endpoint.
@Bean
// Set the lowest priority, execute last, to ensure it runs after SpringDocSecurityConfiguration.SpringSecurityLoginEndpointConfiguration#springSecurityLoginEndpointCustomizer
// Otherwise the login endpoint cannot be found
@Order
OpenApiCustomizer springSecurityLoginEndpointSchemaCustomizer(ApplicationContext applicationContext) {
FilterChainProxy filterChainProxy = applicationContext.getBean(AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME, FilterChainProxy.class);
return openAPI -> {
for (SecurityFilterChain filterChain : filterChainProxy.getFilterChains()) {
Optional<UsernamePasswordAuthenticationFilter> optionalFilter =
filterChain.getFilters().stream()
.filter(UsernamePasswordAuthenticationFilter.class::isInstance)
.map(UsernamePasswordAuthenticationFilter.class::cast)
.findAny();
if (optionalFilter.isPresent()) {
UsernamePasswordAuthenticationFilter usernamePasswordAuthenticationFilter = optionalFilter.get();
try {
RequestMatcher requestMatcher = (RequestMatcher) FieldUtils.readField(
usernamePasswordAuthenticationFilter,
"requiresAuthenticationRequestMatcher",
true
);
if (requestMatcher instanceof PathPatternRequestMatcher pathPatternRequestMatcher) {
String loginPath = getPath(pathPatternRequestMatcher);
Map.Entry<String, PathItem> loginPathEntry = openAPI.getPaths().entrySet().stream().filter(entry -> entry.getKey().equals(loginPath)).findAny().orElse(null);
if (loginPathEntry == null) {
return;
}
PathItem value = loginPathEntry.getValue();
value.readOperations().forEach(operation -> {
// Add example values to the login operation
});
}
}
catch (IllegalAccessException | ClassCastException e) {
// Exception escaped
log.trace(e.getMessage());
}
}
}
};
}
Currently, the internal
org.springdoc.core.customizers.SpringDocCustomizersis as follows:And this is where it is used:
This approach does not support the
@Orderannotation.Describe the solution you'd like
I would like to use
ObjectProvider<T>instead ofOptional<Set<T>>.This way, Spring's built-in
.orderedStream()can be used to handle the ordering of@Order.Otherwise, the execution order cannot be guaranteed, because I want to execute the following code after
SpringDocSecurityConfiguration.SpringSecurityLoginEndpointConfiguration#springSecurityLoginEndpointCustomizer:Add example values to the default login endpoint.