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/09 00:59:57 UTC

[incubator-servicecomb-java-chassis] 01/02: [SCB-800] params in BeanParam is generated by declared field order

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 33036e194f2d726e58e08450d9b3c14392d86c22
Author: yaohaishi <ya...@huawei.com>
AuthorDate: Mon Aug 6 17:07:14 2018 +0800

    [SCB-800] params in BeanParam is generated by declared field order
---
 .../annotation/BeanParamAnnotationProcessor.java   | 110 +++++++++---
 .../BeanParamAnnotationProcessorTest.java          | 185 ++++++++++++++++++---
 2 files changed, 247 insertions(+), 48 deletions(-)

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
index a3a8eae..0d7f32d 100644
--- 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
@@ -17,11 +17,17 @@
 
 package org.apache.servicecomb.swagger.generator.jaxrs.processor.annotation;
 
+import java.beans.IntrospectionException;
+import java.beans.PropertyDescriptor;
 import java.lang.annotation.Annotation;
 import java.lang.reflect.Field;
 import java.lang.reflect.Method;
 import java.lang.reflect.Type;
+import java.util.Arrays;
+import java.util.HashMap;
 import java.util.HashSet;
+import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 
 import javax.ws.rs.CookieParam;
@@ -37,7 +43,10 @@ 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 com.fasterxml.jackson.annotation.JsonIgnore;
+
 import io.swagger.models.parameters.AbstractSerializableParameter;
+import io.swagger.models.parameters.Parameter;
 
 /**
  * For {@link javax.ws.rs.BeanParam}
@@ -58,12 +67,11 @@ public class BeanParamAnnotationProcessor implements ParameterAnnotationProcesso
   @Override
   public void process(Object annotation, OperationGenerator operationGenerator, int paramIdx) {
     final Class<?> beanParamClazz = operationGenerator.getProviderMethod().getParameterTypes()[paramIdx];
+    Map<String, Parameter> swaggerParamMap = new HashMap<>();
     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) {
+      traversalParamField(operationGenerator, beanParamClazz, swaggerParamMap);
+    } catch (IllegalArgumentException | IntrospectionException e) {
       throw new Error(String.format(
           "Processing param failed, method=%s:%s, beanParamIdx=%d",
           operationGenerator.getProviderMethod().getDeclaringClass().getName(),
@@ -71,41 +79,79 @@ public class BeanParamAnnotationProcessor implements ParameterAnnotationProcesso
           paramIdx)
           , e);
     }
+
+    // set swagger params into operationGenerator, in declared field order
+    Field[] declaredProducerFields = beanParamClazz.getDeclaredFields();
+    Arrays.stream(declaredProducerFields)
+        .map(declaredProducerField -> swaggerParamMap.get(declaredProducerField.getName()))
+        .filter(Objects::nonNull)
+        .forEach(operationGenerator::addProviderParameter);
   }
 
   /**
-   * Process those setter methods tagged by JAX-RS param annotations.
+   * Traversal fields of {@code beanParamClazz},
+   * generate swagger params according to JAX-RS param annotations and set them into {@code swaggerParamMap}.
+   *
+   * @param swaggerParamMap the map contains the generated swagger param,
+   * key is the name defined by source code(the declared field name) and value is the swagger param
    */
-  private void processParamSetter(OperationGenerator operationGenerator, Class<?> beanParamClazz) {
-    for (Method method : beanParamClazz.getDeclaredMethods()) {
-      if (!method.getName().startsWith(SETTER_METHOD_PREFIX)) {
-        // only process setter methods
+  private void traversalParamField(OperationGenerator operationGenerator, Class<?> beanParamClazz,
+      Map<String, Parameter> swaggerParamMap) throws IntrospectionException {
+    for (Field beanParamField : beanParamClazz.getDeclaredFields()) {
+      // ignore synthetic member to avoid build failure
+      // see https://github.com/jacoco/jacoco/issues/168
+      if (fieldShouldIgnore(beanParamField) || beanParamField.isSynthetic()) {
         continue;
       }
-      // There should be one and only one param in a setter method
-      final Type genericParamType = method.getGenericParameterTypes()[0];
-      processBeanParamMember(operationGenerator, method.getAnnotations(), genericParamType);
+      // try to process this field directly
+      Parameter swaggerParam = generateSwaggerParam(operationGenerator, beanParamField.getAnnotations(),
+          beanParamField.getGenericType());
+      if (null == swaggerParam) {
+        // if swaggerParam is null, maybe the JAX-RS param annotation is tagged onto the write method
+        swaggerParam = processFieldSetter(operationGenerator, beanParamClazz, beanParamField);
+      }
+
+      if (null == swaggerParam) {
+        throw new IllegalArgumentException(String.format(
+            "There is a field[%s] cannot be mapped to swagger param. Maybe you should tag @JsonIgnore on it.",
+            beanParamField.getName())
+        );
+      }
+      swaggerParamMap.put(beanParamField.getName(), swaggerParam);
     }
   }
 
   /**
-   * Process those fields tagged by JAX-RS param annotations.
+   * Sometimes user may tag JAX-RS param annotations on setter method instead of fields.
+   *
+   * @param beanParamClazz class of the BeanParam
+   * @param beanParamField the field of BeanParam whose setter method is processed
+   * @return the generated swagger param, or null if the setter method is not tagged by JAX-RS param annotations
+   * @throws IntrospectionException see {@linkplain PropertyDescriptor#PropertyDescriptor(String, Class)}
    */
-  private void processParamField(OperationGenerator operationGenerator, Class<?> beanParamClazz) {
-    for (Field beanParamField : beanParamClazz.getDeclaredFields()) {
-      processBeanParamMember(operationGenerator, beanParamField.getAnnotations(), beanParamField.getGenericType());
+  private Parameter processFieldSetter(OperationGenerator operationGenerator, Class<?> beanParamClazz,
+      Field beanParamField) throws IntrospectionException {
+    Parameter swaggerParam = null;
+    PropertyDescriptor propertyDescriptor = new PropertyDescriptor(beanParamField.getName(), beanParamClazz);
+    Method writeMethod = propertyDescriptor.getWriteMethod();
+    if (null != writeMethod) {
+      swaggerParam = generateSwaggerParam(operationGenerator, writeMethod.getAnnotations(),
+          beanParamField.getGenericType());
     }
+    return swaggerParam;
   }
 
   /**
-   * 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}.
+   * Generate a swagger parameter according to {@code annotations} and {@code genericType}.
    *
    * @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
+   * @return a swagger param, or null if there is no JAX-RS annotation in {@code annotations}
    */
-  private void processBeanParamMember(OperationGenerator operationGenerator, Annotation[] annotations,
+  private Parameter generateSwaggerParam(
+      OperationGenerator operationGenerator,
+      Annotation[] annotations,
       Type genericType) {
     String defaultValue = null;
     for (Annotation fieldAnnotation : annotations) {
@@ -116,24 +162,26 @@ public class BeanParamAnnotationProcessor implements ParameterAnnotationProcesso
         continue;
       }
 
-      setUpParameter(operationGenerator, fieldAnnotation, genericType, defaultValue);
+      return setUpParameter(operationGenerator, fieldAnnotation, genericType, defaultValue);
     }
+    return null;
   }
 
   /**
-   * Generate swagger parameter, set default value, and add it into {@code operationGenerator}.
+   * Generate swagger parameter, set default value, and return it.
    *
    * @param operationGenerator operationGenerator
    * @param fieldAnnotation JAX-RS param annotation
    * @param genericParamType type of the parameter
    * @param defaultValue default value, can be null
+   * @return the generated swagger Parameter
    */
-  private void setUpParameter(
+  private Parameter setUpParameter(
       OperationGenerator operationGenerator,
       Annotation fieldAnnotation,
       Type genericParamType,
       String defaultValue) {
-    AbstractSerializableParameter<?> parameter = generateParameter(
+    AbstractSerializableParameter<?> parameter = createParameter(
         operationGenerator.getContext(),
         fieldAnnotation,
         genericParamType);
@@ -141,7 +189,7 @@ public class BeanParamAnnotationProcessor implements ParameterAnnotationProcesso
     if (null != defaultValue) {
       parameter.setDefaultValue(defaultValue);
     }
-    operationGenerator.addProviderParameter(parameter);
+    return parameter;
   }
 
   /**
@@ -152,7 +200,7 @@ public class BeanParamAnnotationProcessor implements ParameterAnnotationProcesso
    * @param genericParamType default value, can be null
    * @return the generated swagger parameter
    */
-  private AbstractSerializableParameter<?> generateParameter(
+  private AbstractSerializableParameter<?> createParameter(
       SwaggerGeneratorContext swaggerGeneratorContext,
       Annotation fieldAnnotation,
       Type genericParamType) {
@@ -166,4 +214,16 @@ public class BeanParamAnnotationProcessor implements ParameterAnnotationProcesso
     ParamUtils.setParameterType(genericParamType, parameter);
     return parameter;
   }
+
+  /**
+   * Those fields tagged by @JsonIgnore should be ignored.
+   */
+  private boolean fieldShouldIgnore(Field beanParamField) {
+    for (Annotation annotation : beanParamField.getAnnotations()) {
+      if (annotation instanceof JsonIgnore) {
+        return true;
+      }
+    }
+    return false;
+  }
 }
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
index f81e2dd..bc22169 100644
--- 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
@@ -18,10 +18,12 @@
 package org.apache.servicecomb.swagger.generator.jaxrs.processor.annotation;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
 
 import java.lang.reflect.Method;
 import java.util.List;
 
+import javax.servlet.http.Part;
 import javax.ws.rs.BeanParam;
 import javax.ws.rs.CookieParam;
 import javax.ws.rs.DefaultValue;
@@ -35,6 +37,8 @@ import org.apache.servicecomb.swagger.generator.core.SwaggerGenerator;
 import org.apache.servicecomb.swagger.generator.jaxrs.JaxrsSwaggerGeneratorContext;
 import org.junit.Test;
 
+import com.fasterxml.jackson.annotation.JsonIgnore;
+
 import io.swagger.models.parameters.AbstractSerializableParameter;
 import io.swagger.models.parameters.Parameter;
 
@@ -49,7 +53,8 @@ public class BeanParamAnnotationProcessorTest {
 
     final List<Parameter> providerParameters = operationGenerator.getProviderParameters();
     assertEquals(5, providerParameters.size());
-    AbstractSerializableParameter<?> parameter = (AbstractSerializableParameter<?>) providerParameters.get(0);
+    AbstractSerializableParameter<?> parameter;
+    parameter = (AbstractSerializableParameter<?>) providerParameters.get(0);
     assertEquals("path", parameter.getIn());
     assertEquals("path0", parameter.getName());
     assertEquals("pa", parameter.getDefault());
@@ -60,20 +65,45 @@ public class BeanParamAnnotationProcessorTest {
     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);
+    parameter = (AbstractSerializableParameter<?>) providerParameters.get(3);
     assertEquals("cookie", parameter.getIn());
     assertEquals("cookie4", parameter.getName());
     assertEquals("integer", parameter.getType());
     assertEquals("int64", parameter.getFormat());
+    parameter = (AbstractSerializableParameter<?>) providerParameters.get(4);
+    assertEquals("header", parameter.getIn());
+    assertEquals("header2", parameter.getName());
+    assertEquals("string", parameter.getType());
+  }
+
+  @Test
+  public void processOnParamWithPart() throws NoSuchMethodException {
+    BeanParamAnnotationProcessor processor = new BeanParamAnnotationProcessor();
+    final OperationGenerator operationGenerator = mockOperationGenerator("testBeanParamWithPart",
+        BeanParamWithPart.class);
+
+    processor.process(null, operationGenerator, 0);
+
+    final List<Parameter> providerParameters = operationGenerator.getProviderParameters();
+    assertEquals(3, providerParameters.size());
+    AbstractSerializableParameter<?> parameter;
+    parameter = (AbstractSerializableParameter<?>) providerParameters.get(0);
+    assertEquals("queryStr", parameter.getName());
+    assertEquals("query", parameter.getIn());
+    assertEquals("boolean", parameter.getType());
+    parameter = (AbstractSerializableParameter<?>) providerParameters.get(1);
+    assertEquals("up0", parameter.getName());
+    assertEquals("formData", parameter.getIn());
+    assertEquals("file", parameter.getType());
+    parameter = (AbstractSerializableParameter<?>) providerParameters.get(2);
+    assertEquals("up1", parameter.getName());
+    assertEquals("formData", parameter.getIn());
+    assertEquals("file", parameter.getType());
   }
 
   @Test
@@ -84,6 +114,7 @@ public class BeanParamAnnotationProcessorTest {
 
     try {
       processor.process(null, operationGenerator, 0);
+      fail("A error is expected!");
     } catch (Error e) {
       assertEquals("Processing param failed, method=org.apache.servicecomb.swagger.generator.jaxrs.processor"
               + ".annotation.BeanParamAnnotationProcessorTest$TestProvider:testBeanParamComplexField, beanParamIdx=0",
@@ -102,6 +133,7 @@ public class BeanParamAnnotationProcessorTest {
 
     try {
       processor.process(null, operationGenerator, 0);
+      fail("A error is expected!");
     } catch (Error e) {
       assertEquals("Processing param failed, method=org.apache.servicecomb.swagger.generator.jaxrs.processor"
               + ".annotation.BeanParamAnnotationProcessorTest$TestProvider:testBeanParamComplexSetter, beanParamIdx=0",
@@ -112,6 +144,38 @@ public class BeanParamAnnotationProcessorTest {
     }
   }
 
+  @Test
+  public void processOnParamFieldUntagged() throws NoSuchMethodException {
+    BeanParamAnnotationProcessor processor = new BeanParamAnnotationProcessor();
+    OperationGenerator operationGenerator = mockOperationGenerator("testBeanParamWithUntaggedField",
+        BeanParamWithUntaggedField.class);
+
+    try {
+      processor.process(null, operationGenerator, 0);
+      fail("A error is expected!");
+    } catch (Error e) {
+      assertEquals("There is a field[name] cannot be mapped to swagger param. "
+              + "Maybe you should tag @JsonIgnore on it.",
+          e.getCause().getMessage());
+    }
+  }
+
+  @Test
+  public void processOnParamFieldWithJsonIgnore() throws NoSuchMethodException {
+    BeanParamAnnotationProcessor processor = new BeanParamAnnotationProcessor();
+    OperationGenerator operationGenerator = mockOperationGenerator("testBeanParamWithJsonIgnore",
+        BeanParamWithJsonIgnoredTagged.class);
+
+    processor.process(null, operationGenerator, 0);
+
+    final List<Parameter> providerParameters = operationGenerator.getProviderParameters();
+    assertEquals(1, providerParameters.size());
+    AbstractSerializableParameter<?> parameter = (AbstractSerializableParameter<?>) providerParameters.get(0);
+    assertEquals("name", parameter.getName());
+    assertEquals("query", parameter.getIn());
+    assertEquals("string", parameter.getType());
+  }
+
   private OperationGenerator mockOperationGenerator(String methodName, Class<?>... paramTypes)
       throws NoSuchMethodException {
     final Method providerMethod = TestProvider.class.getDeclaredMethod(methodName, paramTypes);
@@ -125,6 +189,10 @@ public class BeanParamAnnotationProcessorTest {
       return aggregatedParam.toString();
     }
 
+    public String testBeanParamWithPart(@BeanParam BeanParamWithPart aggregatedParam) {
+      return aggregatedParam.toString();
+    }
+
     public String testBeanParamComplexField(@BeanParam BeanParamComplexField param) {
       return param.toString();
     }
@@ -132,6 +200,14 @@ public class BeanParamAnnotationProcessorTest {
     public String testBeanParamComplexSetter(@BeanParam BeanParamComplexSetter param) {
       return param.toString();
     }
+
+    public String testBeanParamWithUntaggedField(@BeanParam BeanParamWithUntaggedField param) {
+      return param.toString();
+    }
+
+    public String testBeanParamWithJsonIgnore(@BeanParam BeanParamWithJsonIgnoredTagged param) {
+      return param.toString();
+    }
   }
 
   static class AggregatedParam {
@@ -153,18 +229,16 @@ public class BeanParamAnnotationProcessorTest {
       return strVal;
     }
 
-    public AggregatedParam setStrVal(String strVal) {
+    public void setStrVal(String strVal) {
       this.strVal = strVal;
-      return this;
     }
 
     public int getIntVal() {
       return intVal;
     }
 
-    public AggregatedParam setIntVal(int intVal) {
+    public void setIntVal(int intVal) {
       this.intVal = intVal;
-      return this;
     }
 
     public long getLongVal() {
@@ -173,9 +247,8 @@ public class BeanParamAnnotationProcessorTest {
 
     @DefaultValue("12")
     @FormParam("form3")
-    public AggregatedParam setLongVal(long longVal) {
+    public void setLongVal(long longVal) {
       this.longVal = longVal;
-      return this;
     }
 
     public long getCookieVal() {
@@ -183,18 +256,51 @@ public class BeanParamAnnotationProcessorTest {
     }
 
     @CookieParam("cookie4")
-    public AggregatedParam setCookieVal(long cookieVal) {
+    public void setCookieVal(long cookieVal) {
       this.cookieVal = cookieVal;
-      return this;
     }
 
     public String getHeaderVal() {
       return headerVal;
     }
 
-    public AggregatedParam setHeaderVal(String headerVal) {
+    public void setHeaderVal(String headerVal) {
       this.headerVal = headerVal;
-      return this;
+    }
+  }
+
+  static class BeanParamWithPart {
+    @QueryParam("queryStr")
+    private boolean queryStr;
+
+    @FormParam("up0")
+    private Part up0;
+
+    private Part up1;
+
+    public boolean isQueryStr() {
+      return queryStr;
+    }
+
+    public void setQueryStr(boolean queryStr) {
+      this.queryStr = queryStr;
+    }
+
+    public Part getUp0() {
+      return up0;
+    }
+
+    public void setUp0(Part up0) {
+      this.up0 = up0;
+    }
+
+    public Part getUp1() {
+      return up1;
+    }
+
+    @FormParam("up1")
+    public void setUp1(Part up1) {
+      this.up1 = up1;
     }
   }
 
@@ -206,10 +312,8 @@ public class BeanParamAnnotationProcessorTest {
       return complex;
     }
 
-    public BeanParamComplexField setComplex(
-        AggregatedParam complex) {
+    public void setComplex(AggregatedParam complex) {
       this.complex = complex;
-      return this;
     }
   }
 
@@ -221,10 +325,45 @@ public class BeanParamAnnotationProcessorTest {
     }
 
     @HeaderParam("h")
-    public BeanParamComplexSetter setComplex(
-        AggregatedParam complex) {
+    public void setComplex(AggregatedParam complex) {
       this.complex = complex;
-      return this;
     }
   }
-}
\ No newline at end of file
+
+  static class BeanParamWithUntaggedField {
+    private String name;
+
+    public String getName() {
+      return name;
+    }
+
+    public void setName(String name) {
+      this.name = name;
+    }
+  }
+
+  static class BeanParamWithJsonIgnoredTagged {
+    @QueryParam("name")
+    private String name;
+
+    @JsonIgnore
+    private AggregatedParam ignored;
+
+    public String getName() {
+      return name;
+    }
+
+    public void setName(String name) {
+      this.name = name;
+    }
+
+    public AggregatedParam getIgnored() {
+      return ignored;
+    }
+
+    public void setIgnored(
+        AggregatedParam ignored) {
+      this.ignored = ignored;
+    }
+  }
+}