blob: 61bd96fb474d565fec4de516fb9d72e6c385eb4b [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.servicecomb.swagger.generator;
import java.lang.annotation.Annotation;
import java.lang.reflect.Executable;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
import org.apache.servicecomb.foundation.common.utils.SPIServiceUtils;
import org.apache.servicecomb.swagger.generator.core.AbstractOperationGenerator;
import org.apache.servicecomb.swagger.generator.core.AbstractSwaggerGenerator;
import org.apache.servicecomb.swagger.generator.core.model.HttpParameterType;
import org.apache.servicecomb.swagger.generator.core.processor.response.DefaultResponseTypeProcessor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.Module;
import com.fasterxml.jackson.databind.introspect.BeanPropertyDefinition;
import com.fasterxml.jackson.databind.type.TypeFactory;
import io.swagger.models.parameters.Parameter;
import io.swagger.util.Json;
public final class SwaggerGeneratorUtils {
private static final Logger LOGGER = LoggerFactory.getLogger(SwaggerGeneratorUtils.class);
// all static fields load from SPI and stateless
private static final Set<JavaType> contextTypes = SPIServiceUtils.getOrLoadSortedService(SwaggerContextRegister.class)
.stream()
.map(swaggerContextRegister -> TypeFactory.defaultInstance()
.constructType(swaggerContextRegister.getContextType()))
.collect(Collectors.toSet());
private static final Map<Type, ClassAnnotationProcessor<?>> classAnnotationProcessors = new HashMap<>();
private static final Map<Type, MethodAnnotationProcessor<?>> methodAnnotationProcessors = new HashMap<>();
private static final Map<JavaType, ParameterProcessor<?, ?>> parameterProcessors = new HashMap<>();
private static final Map<Type, ResponseTypeProcessor> responseTypeProcessors = new HashMap<>();
private static final DefaultResponseTypeProcessor defaultResponseTypeProcessor = new DefaultResponseTypeProcessor();
private static final List<OperationPostProcessor> operationPostProcessors = SPIServiceUtils
.getOrLoadSortedService(OperationPostProcessor.class);
static {
// low order value has high priority
for (ClassAnnotationProcessor<?> processor : SPIServiceUtils
.getOrLoadSortedService(ClassAnnotationProcessor.class)) {
if (classAnnotationProcessors.putIfAbsent(processor.getProcessType(), processor) != null) {
LOGGER.info("ignore duplicated ClassAnnotationProcessor, type={}, processor={}.",
processor.getProcessType().getTypeName(), processor.getClass().getName());
}
}
for (MethodAnnotationProcessor<?> processor : SPIServiceUtils
.getOrLoadSortedService(MethodAnnotationProcessor.class)) {
if (methodAnnotationProcessors.putIfAbsent(processor.getProcessType(), processor) != null) {
LOGGER.info("ignore duplicated MethodAnnotationProcessor, type={}, processor={}.",
processor.getProcessType().getTypeName(), processor.getClass().getName());
}
}
for (ParameterProcessor<?, ?> processor : SPIServiceUtils.getOrLoadSortedService(ParameterProcessor.class)) {
JavaType javaType = processor.getProcessJavaType();
if (parameterProcessors.putIfAbsent(javaType, processor) != null) {
LOGGER.info("ignore duplicated ParameterProcessor, type={}, processor={}.",
javaType.toCanonical(), processor.getClass().getName());
}
}
for (ResponseTypeProcessor processor : SPIServiceUtils.getOrLoadSortedService(ResponseTypeProcessor.class)) {
if (responseTypeProcessors.putIfAbsent(processor.getProcessType(), processor) != null) {
LOGGER.info("ignore duplicated ResponseTypeProcessor, type={}, processor={}.",
processor.getProcessType().getTypeName(), processor.getClass().getName());
}
}
List<Module> modules = SPIServiceUtils.getOrLoadSortedService(Module.class);
Json.mapper().registerModules(modules.toArray(new Module[0]));
}
private SwaggerGeneratorUtils() {
}
public static void postProcessOperation(AbstractSwaggerGenerator swaggerGenerator,
AbstractOperationGenerator operationGenerator) {
for (OperationPostProcessor processor : operationPostProcessors) {
if (processor.shouldProcess(swaggerGenerator, operationGenerator)) {
processor.process(swaggerGenerator, operationGenerator);
}
}
}
@SuppressWarnings("unchecked")
public static <ANNOTATION> ClassAnnotationProcessor<ANNOTATION> findClassAnnotationProcessor(Type type) {
return (ClassAnnotationProcessor<ANNOTATION>) classAnnotationProcessors.get(type);
}
@SuppressWarnings("unchecked")
public static <ANNOTATION> MethodAnnotationProcessor<ANNOTATION> findMethodAnnotationProcessor(Type type) {
return (MethodAnnotationProcessor<ANNOTATION>) methodAnnotationProcessors.get(type);
}
@SuppressWarnings("unchecked")
public static <SWAGGER_PARAMETER, ANNOTATION> ParameterProcessor<SWAGGER_PARAMETER, ANNOTATION> findParameterProcessors(
Type type) {
type = TypeFactory.defaultInstance().constructType(type);
return (ParameterProcessor<SWAGGER_PARAMETER, ANNOTATION>) parameterProcessors.get(type);
}
public static ResponseTypeProcessor findResponseTypeProcessor(Type type) {
ResponseTypeProcessor processor = responseTypeProcessors.get(type);
if (processor != null) {
return processor;
}
if (type instanceof ParameterizedType) {
return responseTypeProcessors.getOrDefault(((ParameterizedType) type).getRawType(), defaultResponseTypeProcessor);
}
return defaultResponseTypeProcessor;
}
public static boolean isContextParameter(JavaType type) {
return contextTypes.contains(type);
}
public static Annotation[] collectAnnotations(BeanPropertyDefinition propertyDefinition) {
List<Annotation> annotations = new ArrayList<>();
if (propertyDefinition.getField() != null) {
Collections.addAll(annotations, propertyDefinition.getField().getAnnotated().getAnnotations());
}
if (propertyDefinition.getGetter() != null) {
Collections.addAll(annotations, propertyDefinition.getGetter().getAnnotated().getAnnotations());
}
if (propertyDefinition.getSetter() != null) {
Collections.addAll(annotations, propertyDefinition.getSetter().getAnnotated().getAnnotations());
}
return annotations.toArray(new Annotation[0]);
}
public static String collectParameterName(java.lang.reflect.Parameter methodParameter) {
return collectParameterName(methodParameter.getDeclaringExecutable(), methodParameter.getAnnotations(),
methodParameter.isNamePresent() ? methodParameter.getName() : null);
}
public static String collectParameterName(Method method, BeanPropertyDefinition propertyDefinition) {
Annotation[] annotations = collectAnnotations(propertyDefinition);
return collectParameterName(method, annotations, propertyDefinition.getName());
}
public static String collectParameterName(Executable executable, Annotation[] annotations, String defaultName) {
// 1.annotations
// it's ambiguous to use different name in different annotation
// so we only read the first available name
for (Annotation annotation : annotations) {
ParameterProcessor<Parameter, Annotation> processor = findParameterProcessors(annotation.annotationType());
if (processor == null) {
continue;
}
String name = processor.getParameterName(annotation);
if (StringUtils.isNotEmpty(name)) {
return name;
}
}
// 2.use signature name
// ensure present parameter name
if (StringUtils.isNotEmpty(defaultName)) {
return defaultName;
}
String msg = String.format("parameter name is not present, method=%s:%s\n"
+ "solution:\n"
+ " change pom.xml, add compiler argument: -parameters, for example:\n"
+ " <plugin>\n"
+ " <groupId>org.apache.maven.plugins</groupId>\n"
+ " <artifactId>maven-compiler-plugin</artifactId>\n"
+ " <configuration>\n"
+ " <compilerArgument>-parameters</compilerArgument>\n"
+ " </configuration>\n"
+ " </plugin>",
executable.getDeclaringClass().getName(), executable.getName());
throw new IllegalStateException(msg);
}
public static Type collectGenericType(List<Annotation> annotations, Type defaultType) {
Type genericType = null;
for (Annotation annotation : annotations) {
ParameterProcessor<Parameter, Annotation> processor = findParameterProcessors(annotation.annotationType());
if (processor == null) {
continue;
}
Type type = processor.getGenericType(annotation);
if (type != null) {
genericType = type;
}
}
return genericType != null ? genericType : defaultType;
}
public static List<Annotation> collectParameterAnnotations(Annotation[] parameterAnnotations,
Map<String, List<Annotation>> methodAnnotationMap, String parameterName) {
List<Annotation> methodAnnotations = methodAnnotationMap.remove(parameterName);
if (methodAnnotations == null) {
methodAnnotations = Collections.emptyList();
}
List<Annotation> annotations = new ArrayList<>();
Collections.addAll(annotations, parameterAnnotations);
annotations.addAll(methodAnnotations);
return annotations;
}
public static HttpParameterType collectHttpParameterType(List<Annotation> annotations, Type genericType) {
// use the last available type
for (int idx = annotations.size() - 1; idx >= 0; idx--) {
Annotation annotation = annotations.get(idx);
HttpParameterType httpParameterType = collectHttpParameterType(annotation, annotation.annotationType());
if (httpParameterType != null) {
return httpParameterType;
}
}
// try by parameter type
return collectHttpParameterType((Annotation) null, genericType);
}
private static HttpParameterType collectHttpParameterType(Annotation parameterAnnotation, Type type) {
ParameterProcessor<Parameter, Annotation> processor = findParameterProcessors(type);
if (processor == null) {
return null;
}
return processor.getHttpParameterType(parameterAnnotation);
}
}