| /* |
| * 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); |
| } |
| } |