You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@servicecomb.apache.org by li...@apache.org on 2018/08/01 09:53:38 UTC

[incubator-servicecomb-java-chassis] 02/06: [SCB-777] Add BeanParamAnnotationProcessor

This is an automated email from the ASF dual-hosted git repository.

liubao pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-servicecomb-java-chassis.git

commit a6853602f784dc382b12795397ced64f71276d93
Author: yaohaishi <ya...@huawei.com>
AuthorDate: Sat Jul 28 17:35:29 2018 +0800

    [SCB-777] Add BeanParamAnnotationProcessor
---
 .../swagger/generator/core/utils/ParamUtils.java   |  23 ++-
 .../swagger/generator/core/TestParamUtils.java     |  29 +++
 .../jaxrs/JaxrsSwaggerGeneratorContext.java        |   3 +
 .../annotation/BeanParamAnnotationProcessor.java   | 169 +++++++++++++++
 .../BeanParamAnnotationProcessorTest.java          | 230 +++++++++++++++++++++
 5 files changed, 453 insertions(+), 1 deletion(-)

diff --git a/swagger/swagger-generator/generator-core/src/main/java/org/apache/servicecomb/swagger/generator/core/utils/ParamUtils.java b/swagger/swagger-generator/generator-core/src/main/java/org/apache/servicecomb/swagger/generator/core/utils/ParamUtils.java
index db6d201..924bc65 100644
--- a/swagger/swagger-generator/generator-core/src/main/java/org/apache/servicecomb/swagger/generator/core/utils/ParamUtils.java
+++ b/swagger/swagger-generator/generator-core/src/main/java/org/apache/servicecomb/swagger/generator/core/utils/ParamUtils.java
@@ -113,7 +113,7 @@ public final class ParamUtils {
     Property property = ModelConverters.getInstance().readAsProperty(paramType);
 
     if (isComplexProperty(property)) {
-      // 简单参数不可以是复杂类型
+      // cannot set a simple parameter(header, query, etc.) as complex type
       String msg = String.format("not allow complex type for %s parameter, method=%s:%s, paramIdx=%d, type=%s",
           parameter.getIn(),
           method.getDeclaringClass().getName(),
@@ -125,6 +125,27 @@ public final class ParamUtils {
     parameter.setProperty(property);
   }
 
+  /**
+   * Set param type info. For {@linkplain javax.ws.rs.BeanParam BeanParam} scenario.
+   *
+   * @param paramType type of the swagger parameter
+   * @param parameter swagger parameter
+   */
+  public static void setParameterType(Type paramType, AbstractSerializableParameter<?> parameter) {
+    Property property = ModelConverters.getInstance().readAsProperty(paramType);
+
+    if (isComplexProperty(property)) {
+      // cannot set a simple parameter(header, query, etc.) as complex type
+      throw new IllegalArgumentException(
+          String.format(
+              "not allow such type of param:[%s], param name is [%s]",
+              property.getClass(),
+              parameter.getName())
+      );
+    }
+    parameter.setProperty(property);
+  }
+
   public static boolean isComplexProperty(Property property) {
     if (RefProperty.class.isInstance(property) || ObjectProperty.class.isInstance(property)
         || MapProperty.class.isInstance(property)) {
diff --git a/swagger/swagger-generator/generator-core/src/test/java/org/apache/servicecomb/swagger/generator/core/TestParamUtils.java b/swagger/swagger-generator/generator-core/src/test/java/org/apache/servicecomb/swagger/generator/core/TestParamUtils.java
index 1343f5f..b116174 100644
--- a/swagger/swagger-generator/generator-core/src/test/java/org/apache/servicecomb/swagger/generator/core/TestParamUtils.java
+++ b/swagger/swagger-generator/generator-core/src/test/java/org/apache/servicecomb/swagger/generator/core/TestParamUtils.java
@@ -19,6 +19,7 @@ package org.apache.servicecomb.swagger.generator.core;
 
 import static org.mockito.Mockito.when;
 
+import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.Map;
 
@@ -28,7 +29,10 @@ import org.junit.Assert;
 import org.junit.Test;
 import org.mockito.Mockito;
 
+import io.swagger.models.parameters.AbstractSerializableParameter;
+import io.swagger.models.parameters.HeaderParameter;
 import io.swagger.models.parameters.Parameter;
+import io.swagger.models.parameters.QueryParameter;
 import io.swagger.models.properties.ArrayProperty;
 import io.swagger.models.properties.MapProperty;
 import io.swagger.models.properties.ObjectProperty;
@@ -66,4 +70,29 @@ public class TestParamUtils {
     property = new StringProperty();
     Assert.assertFalse(ParamUtils.isComplexProperty(property));
   }
+
+  @Test
+  public void setParameterTypeByTypeNormal() {
+    AbstractSerializableParameter<?> parameter = new QueryParameter();
+    ParamUtils.setParameterType(String.class, parameter);
+    Assert.assertEquals("string", parameter.getType());
+
+    parameter = new HeaderParameter();
+    ParamUtils.setParameterType(long.class, parameter);
+    Assert.assertEquals("integer", parameter.getType());
+    Assert.assertEquals("int64", parameter.getFormat());
+  }
+
+  @Test
+  public void setParameterTypeByTypeOnComplexType() {
+    AbstractSerializableParameter<?> parameter = new QueryParameter();
+    parameter.setName("testName");
+    try {
+      ParamUtils.setParameterType(ArrayList.class, parameter);
+      Assert.fail("an exception is expected!");
+    } catch (IllegalArgumentException e) {
+      Assert.assertEquals("not allow such type of param:[class io.swagger.models.properties.ArrayProperty], "
+          + "param name is [testName]", e.getMessage());
+    }
+  }
 }
diff --git a/swagger/swagger-generator/generator-jaxrs/src/main/java/org/apache/servicecomb/swagger/generator/jaxrs/JaxrsSwaggerGeneratorContext.java b/swagger/swagger-generator/generator-jaxrs/src/main/java/org/apache/servicecomb/swagger/generator/jaxrs/JaxrsSwaggerGeneratorContext.java
index 1c707cf..7acabe0 100644
--- a/swagger/swagger-generator/generator-jaxrs/src/main/java/org/apache/servicecomb/swagger/generator/jaxrs/JaxrsSwaggerGeneratorContext.java
+++ b/swagger/swagger-generator/generator-jaxrs/src/main/java/org/apache/servicecomb/swagger/generator/jaxrs/JaxrsSwaggerGeneratorContext.java
@@ -20,6 +20,7 @@ package org.apache.servicecomb.swagger.generator.jaxrs;
 import java.lang.annotation.Annotation;
 import java.lang.reflect.Method;
 
+import javax.ws.rs.BeanParam;
 import javax.ws.rs.Consumes;
 import javax.ws.rs.CookieParam;
 import javax.ws.rs.DELETE;
@@ -35,6 +36,7 @@ import javax.ws.rs.Produces;
 import javax.ws.rs.QueryParam;
 
 import org.apache.servicecomb.swagger.generator.core.utils.ClassUtils;
+import org.apache.servicecomb.swagger.generator.jaxrs.processor.annotation.BeanParamAnnotationProcessor;
 import org.apache.servicecomb.swagger.generator.jaxrs.processor.annotation.ConsumesAnnotationProcessor;
 import org.apache.servicecomb.swagger.generator.jaxrs.processor.annotation.CookieParamAnnotationProcessor;
 import org.apache.servicecomb.swagger.generator.jaxrs.processor.annotation.FormParamAnnotationProcessor;
@@ -110,5 +112,6 @@ public class JaxrsSwaggerGeneratorContext extends RestSwaggerGeneratorContext {
 
     parameterAnnotationMgr.register(HeaderParam.class, new HeaderParamAnnotationProcessor());
     parameterAnnotationMgr.register(QueryParam.class, new QueryParamAnnotationProcessor());
+    parameterAnnotationMgr.register(BeanParam.class, new BeanParamAnnotationProcessor());
   }
 }
diff --git a/swagger/swagger-generator/generator-jaxrs/src/main/java/org/apache/servicecomb/swagger/generator/jaxrs/processor/annotation/BeanParamAnnotationProcessor.java b/swagger/swagger-generator/generator-jaxrs/src/main/java/org/apache/servicecomb/swagger/generator/jaxrs/processor/annotation/BeanParamAnnotationProcessor.java
new file mode 100644
index 0000000..a3a8eae
--- /dev/null
+++ b/swagger/swagger-generator/generator-jaxrs/src/main/java/org/apache/servicecomb/swagger/generator/jaxrs/processor/annotation/BeanParamAnnotationProcessor.java
@@ -0,0 +1,169 @@
+/*
+ * 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.jaxrs.processor.annotation;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.Type;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.ws.rs.CookieParam;
+import javax.ws.rs.DefaultValue;
+import javax.ws.rs.FormParam;
+import javax.ws.rs.HeaderParam;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.QueryParam;
+
+import org.apache.servicecomb.swagger.generator.core.OperationGenerator;
+import org.apache.servicecomb.swagger.generator.core.ParameterAnnotationProcessor;
+import org.apache.servicecomb.swagger.generator.core.SwaggerGeneratorContext;
+import org.apache.servicecomb.swagger.generator.core.processor.parameter.AbstractParameterProcessor;
+import org.apache.servicecomb.swagger.generator.core.utils.ParamUtils;
+
+import io.swagger.models.parameters.AbstractSerializableParameter;
+
+/**
+ * For {@link javax.ws.rs.BeanParam}
+ */
+public class BeanParamAnnotationProcessor implements ParameterAnnotationProcessor {
+  public static final Set<Class<?>> SUPPORTED_PARAM_ANNOTATIONS = new HashSet<>();
+
+  public static final String SETTER_METHOD_PREFIX = "set";
+
+  static {
+    SUPPORTED_PARAM_ANNOTATIONS.add(PathParam.class);
+    SUPPORTED_PARAM_ANNOTATIONS.add(QueryParam.class);
+    SUPPORTED_PARAM_ANNOTATIONS.add(HeaderParam.class);
+    SUPPORTED_PARAM_ANNOTATIONS.add(CookieParam.class);
+    SUPPORTED_PARAM_ANNOTATIONS.add(FormParam.class);
+  }
+
+  @Override
+  public void process(Object annotation, OperationGenerator operationGenerator, int paramIdx) {
+    final Class<?> beanParamClazz = operationGenerator.getProviderMethod().getParameterTypes()[paramIdx];
+    try {
+      // traversal fields, get those JAX-RS params
+      processParamField(operationGenerator, beanParamClazz);
+      // traversal setter methods, some setter method may also be tagged with param annotations
+      processParamSetter(operationGenerator, beanParamClazz);
+    } catch (IllegalArgumentException e) {
+      throw new Error(String.format(
+          "Processing param failed, method=%s:%s, beanParamIdx=%d",
+          operationGenerator.getProviderMethod().getDeclaringClass().getName(),
+          operationGenerator.getProviderMethod().getName(),
+          paramIdx)
+          , e);
+    }
+  }
+
+  /**
+   * Process those setter methods tagged by JAX-RS param annotations.
+   */
+  private void processParamSetter(OperationGenerator operationGenerator, Class<?> beanParamClazz) {
+    for (Method method : beanParamClazz.getDeclaredMethods()) {
+      if (!method.getName().startsWith(SETTER_METHOD_PREFIX)) {
+        // only process setter methods
+        continue;
+      }
+      // There should be one and only one param in a setter method
+      final Type genericParamType = method.getGenericParameterTypes()[0];
+      processBeanParamMember(operationGenerator, method.getAnnotations(), genericParamType);
+    }
+  }
+
+  /**
+   * Process those fields tagged by JAX-RS param annotations.
+   */
+  private void processParamField(OperationGenerator operationGenerator, Class<?> beanParamClazz) {
+    for (Field beanParamField : beanParamClazz.getDeclaredFields()) {
+      processBeanParamMember(operationGenerator, beanParamField.getAnnotations(), beanParamField.getGenericType());
+    }
+  }
+
+  /**
+   * Process a swagger parameter defined by field or setter method in this BeanParam.
+   * After processing, a swagger parameter is generated and set into {@code operationGenerator}.
+   *
+   * @param operationGenerator operationGenerator
+   * @param annotations annotations on fields or setter methods
+   * @param genericType type of the fields, or the param type of the setter methods
+   */
+  private void processBeanParamMember(OperationGenerator operationGenerator, Annotation[] annotations,
+      Type genericType) {
+    String defaultValue = null;
+    for (Annotation fieldAnnotation : annotations) {
+      if (!SUPPORTED_PARAM_ANNOTATIONS.contains(fieldAnnotation.annotationType())) {
+        if (fieldAnnotation instanceof DefaultValue) {
+          defaultValue = ((DefaultValue) fieldAnnotation).value();
+        }
+        continue;
+      }
+
+      setUpParameter(operationGenerator, fieldAnnotation, genericType, defaultValue);
+    }
+  }
+
+  /**
+   * Generate swagger parameter, set default value, and add it into {@code operationGenerator}.
+   *
+   * @param operationGenerator operationGenerator
+   * @param fieldAnnotation JAX-RS param annotation
+   * @param genericParamType type of the parameter
+   * @param defaultValue default value, can be null
+   */
+  private void setUpParameter(
+      OperationGenerator operationGenerator,
+      Annotation fieldAnnotation,
+      Type genericParamType,
+      String defaultValue) {
+    AbstractSerializableParameter<?> parameter = generateParameter(
+        operationGenerator.getContext(),
+        fieldAnnotation,
+        genericParamType);
+
+    if (null != defaultValue) {
+      parameter.setDefaultValue(defaultValue);
+    }
+    operationGenerator.addProviderParameter(parameter);
+  }
+
+  /**
+   * Generate a swagger parameter, set up name and type info.
+   *
+   * @param swaggerGeneratorContext context data carried by {@linkplain OperationGenerator}
+   * @param fieldAnnotation JAX-RS param annotation
+   * @param genericParamType default value, can be null
+   * @return the generated swagger parameter
+   */
+  private AbstractSerializableParameter<?> generateParameter(
+      SwaggerGeneratorContext swaggerGeneratorContext,
+      Annotation fieldAnnotation,
+      Type genericParamType) {
+    // find the corresponding ParameterProcessor and process the parameter
+    final AbstractParameterProcessor<?> parameterAnnotationProcessor =
+        (AbstractParameterProcessor<?>) swaggerGeneratorContext
+            .findParameterAnnotationProcessor(fieldAnnotation.annotationType());
+    AbstractSerializableParameter<?> parameter = parameterAnnotationProcessor.createParameter();
+    String paramName = parameterAnnotationProcessor.getAnnotationParameterName(fieldAnnotation);
+    parameter.setName(paramName);
+    ParamUtils.setParameterType(genericParamType, parameter);
+    return parameter;
+  }
+}
diff --git a/swagger/swagger-generator/generator-jaxrs/src/test/java/org/apache/servicecomb/swagger/generator/jaxrs/processor/annotation/BeanParamAnnotationProcessorTest.java b/swagger/swagger-generator/generator-jaxrs/src/test/java/org/apache/servicecomb/swagger/generator/jaxrs/processor/annotation/BeanParamAnnotationProcessorTest.java
new file mode 100644
index 0000000..f81e2dd
--- /dev/null
+++ b/swagger/swagger-generator/generator-jaxrs/src/test/java/org/apache/servicecomb/swagger/generator/jaxrs/processor/annotation/BeanParamAnnotationProcessorTest.java
@@ -0,0 +1,230 @@
+/*
+ * 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.jaxrs.processor.annotation;
+
+import static org.junit.Assert.assertEquals;
+
+import java.lang.reflect.Method;
+import java.util.List;
+
+import javax.ws.rs.BeanParam;
+import javax.ws.rs.CookieParam;
+import javax.ws.rs.DefaultValue;
+import javax.ws.rs.FormParam;
+import javax.ws.rs.HeaderParam;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.QueryParam;
+
+import org.apache.servicecomb.swagger.generator.core.OperationGenerator;
+import org.apache.servicecomb.swagger.generator.core.SwaggerGenerator;
+import org.apache.servicecomb.swagger.generator.jaxrs.JaxrsSwaggerGeneratorContext;
+import org.junit.Test;
+
+import io.swagger.models.parameters.AbstractSerializableParameter;
+import io.swagger.models.parameters.Parameter;
+
+public class BeanParamAnnotationProcessorTest {
+
+  @Test
+  public void processSuccess() throws NoSuchMethodException {
+    BeanParamAnnotationProcessor processor = new BeanParamAnnotationProcessor();
+    final OperationGenerator operationGenerator = mockOperationGenerator("testBeanParam", AggregatedParam.class);
+
+    processor.process(null, operationGenerator, 0);
+
+    final List<Parameter> providerParameters = operationGenerator.getProviderParameters();
+    assertEquals(5, providerParameters.size());
+    AbstractSerializableParameter<?> parameter = (AbstractSerializableParameter<?>) providerParameters.get(0);
+    assertEquals("path", parameter.getIn());
+    assertEquals("path0", parameter.getName());
+    assertEquals("pa", parameter.getDefault());
+    assertEquals("string", parameter.getType());
+    parameter = (AbstractSerializableParameter<?>) providerParameters.get(1);
+    assertEquals("query", parameter.getIn());
+    assertEquals("query1", parameter.getName());
+    assertEquals("integer", parameter.getType());
+    assertEquals("int32", parameter.getFormat());
+    parameter = (AbstractSerializableParameter<?>) providerParameters.get(2);
+    assertEquals("header", parameter.getIn());
+    assertEquals("header2", parameter.getName());
+    assertEquals("string", parameter.getType());
+    parameter = (AbstractSerializableParameter<?>) providerParameters.get(3);
+    assertEquals("formData", parameter.getIn());
+    assertEquals("form3", parameter.getName());
+    assertEquals(12L, parameter.getDefault());
+    assertEquals("integer", parameter.getType());
+    assertEquals("int64", parameter.getFormat());
+    parameter = (AbstractSerializableParameter<?>) providerParameters.get(4);
+    assertEquals("cookie", parameter.getIn());
+    assertEquals("cookie4", parameter.getName());
+    assertEquals("integer", parameter.getType());
+    assertEquals("int64", parameter.getFormat());
+  }
+
+  @Test
+  public void processOnComplexBeanParamField() throws NoSuchMethodException {
+    BeanParamAnnotationProcessor processor = new BeanParamAnnotationProcessor();
+    final OperationGenerator operationGenerator = mockOperationGenerator("testBeanParamComplexField",
+        BeanParamComplexField.class);
+
+    try {
+      processor.process(null, operationGenerator, 0);
+    } catch (Error e) {
+      assertEquals("Processing param failed, method=org.apache.servicecomb.swagger.generator.jaxrs.processor"
+              + ".annotation.BeanParamAnnotationProcessorTest$TestProvider:testBeanParamComplexField, beanParamIdx=0",
+          e.getMessage());
+      assertEquals("not allow such type of param:[class io.swagger.models.properties.RefProperty], "
+              + "param name is [q]",
+          e.getCause().getMessage());
+    }
+  }
+
+  @Test
+  public void processOnComplexBeanParamSetter() throws NoSuchMethodException {
+    BeanParamAnnotationProcessor processor = new BeanParamAnnotationProcessor();
+    final OperationGenerator operationGenerator = mockOperationGenerator("testBeanParamComplexSetter",
+        BeanParamComplexSetter.class);
+
+    try {
+      processor.process(null, operationGenerator, 0);
+    } catch (Error e) {
+      assertEquals("Processing param failed, method=org.apache.servicecomb.swagger.generator.jaxrs.processor"
+              + ".annotation.BeanParamAnnotationProcessorTest$TestProvider:testBeanParamComplexSetter, beanParamIdx=0",
+          e.getMessage());
+      assertEquals("not allow such type of param:[class io.swagger.models.properties.RefProperty], "
+              + "param name is [h]",
+          e.getCause().getMessage());
+    }
+  }
+
+  private OperationGenerator mockOperationGenerator(String methodName, Class<?>... paramTypes)
+      throws NoSuchMethodException {
+    final Method providerMethod = TestProvider.class.getDeclaredMethod(methodName, paramTypes);
+    final SwaggerGenerator swaggerGenerator = new SwaggerGenerator(new JaxrsSwaggerGeneratorContext(),
+        TestProvider.class);
+    return new OperationGenerator(swaggerGenerator, providerMethod);
+  }
+
+  static class TestProvider {
+    public String testBeanParam(@BeanParam AggregatedParam aggregatedParam) {
+      return aggregatedParam.toString();
+    }
+
+    public String testBeanParamComplexField(@BeanParam BeanParamComplexField param) {
+      return param.toString();
+    }
+
+    public String testBeanParamComplexSetter(@BeanParam BeanParamComplexSetter param) {
+      return param.toString();
+    }
+  }
+
+  static class AggregatedParam {
+    @DefaultValue("pa")
+    @PathParam("path0")
+    private String strVal;
+
+    @QueryParam("query1")
+    private int intVal;
+
+    private long longVal;
+
+    private long cookieVal;
+
+    @HeaderParam("header2")
+    private String headerVal;
+
+    public String getStrVal() {
+      return strVal;
+    }
+
+    public AggregatedParam setStrVal(String strVal) {
+      this.strVal = strVal;
+      return this;
+    }
+
+    public int getIntVal() {
+      return intVal;
+    }
+
+    public AggregatedParam setIntVal(int intVal) {
+      this.intVal = intVal;
+      return this;
+    }
+
+    public long getLongVal() {
+      return longVal;
+    }
+
+    @DefaultValue("12")
+    @FormParam("form3")
+    public AggregatedParam setLongVal(long longVal) {
+      this.longVal = longVal;
+      return this;
+    }
+
+    public long getCookieVal() {
+      return cookieVal;
+    }
+
+    @CookieParam("cookie4")
+    public AggregatedParam setCookieVal(long cookieVal) {
+      this.cookieVal = cookieVal;
+      return this;
+    }
+
+    public String getHeaderVal() {
+      return headerVal;
+    }
+
+    public AggregatedParam setHeaderVal(String headerVal) {
+      this.headerVal = headerVal;
+      return this;
+    }
+  }
+
+  static class BeanParamComplexField {
+    @QueryParam("q")
+    private AggregatedParam complex;
+
+    public AggregatedParam getComplex() {
+      return complex;
+    }
+
+    public BeanParamComplexField setComplex(
+        AggregatedParam complex) {
+      this.complex = complex;
+      return this;
+    }
+  }
+
+  static class BeanParamComplexSetter {
+    private AggregatedParam complex;
+
+    public AggregatedParam getComplex() {
+      return complex;
+    }
+
+    @HeaderParam("h")
+    public BeanParamComplexSetter setComplex(
+        AggregatedParam complex) {
+      this.complex = complex;
+      return this;
+    }
+  }
+}
\ No newline at end of file