liubao68 closed pull request #815: [SCB-708] Aggregate simple query params into object param in spring mvc style URL: https://github.com/apache/incubator-servicecomb-java-chassis/pull/815
This is a PR merged from a forked repository. As GitHub hides the original diff on merge, it is displayed below for the sake of provenance: As this is a foreign pull request (from a fork), the diff is supplied below (as it won't show otherwise due to GitHub magic): diff --git a/core/src/main/java/org/apache/servicecomb/core/definition/schema/ProducerSchemaFactory.java b/core/src/main/java/org/apache/servicecomb/core/definition/schema/ProducerSchemaFactory.java index 1334332f7..a3b0a38cf 100644 --- a/core/src/main/java/org/apache/servicecomb/core/definition/schema/ProducerSchemaFactory.java +++ b/core/src/main/java/org/apache/servicecomb/core/definition/schema/ProducerSchemaFactory.java @@ -17,6 +17,8 @@ package org.apache.servicecomb.core.definition.schema; +import java.util.LinkedHashMap; +import java.util.Map; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executor; @@ -41,6 +43,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectWriter; +import io.swagger.models.Operation; import io.swagger.models.Swagger; import io.swagger.util.Yaml; @@ -79,7 +82,8 @@ public SchemaMeta getOrCreateProducerSchema(String microserviceName, String sche SchemaMeta schemaMeta = getOrCreateSchema(context); - SwaggerProducer producer = swaggerEnv.createProducer(producerInstance, schemaMeta.getSwaggerIntf()); + SwaggerProducer producer = swaggerEnv.createProducer(producerInstance, schemaMeta.getSwaggerIntf(), + convertSwaggerOperationMap(schemaMeta)); Executor reactiveExecutor = BeanUtils.getBean(ExecutorManager.EXECUTOR_REACTIVE); for (OperationMeta operationMeta : schemaMeta.getOperations()) { SwaggerProducerOperation producerOperation = producer.findOperation(operationMeta.getOperationId()); @@ -93,6 +97,13 @@ public SchemaMeta getOrCreateProducerSchema(String microserviceName, String sche return schemaMeta; } + private Map<String, Operation> convertSwaggerOperationMap(SchemaMeta schemaMeta) { + Map<String, Operation> operationMap = new LinkedHashMap<>(schemaMeta.getOperations().size()); + schemaMeta.getOperations().forEach( + operationMeta -> operationMap.put(operationMeta.getOperationId(), operationMeta.getSwaggerOperation())); + return operationMap; + } + protected SchemaMeta createSchema(ProducerSchemaContext context) { // 尝试从规划的目录加载契约 Swagger swagger = loadSwagger(context); diff --git a/demo/demo-schema/src/main/java/org/apache/servicecomb/demo/compute/GenericParam.java b/demo/demo-schema/src/main/java/org/apache/servicecomb/demo/compute/GenericParam.java new file mode 100644 index 000000000..3ec6f17c5 --- /dev/null +++ b/demo/demo-schema/src/main/java/org/apache/servicecomb/demo/compute/GenericParam.java @@ -0,0 +1,63 @@ +/* + * 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.demo.compute; + +public class GenericParam<T> { + private String str; + + private long num; + + private T data; + + public String getStr() { + return str; + } + + public GenericParam<T> setStr(String str) { + this.str = str; + return this; + } + + public long getNum() { + return num; + } + + public GenericParam<T> setNum(long num) { + this.num = num; + return this; + } + + public T getData() { + return data; + } + + public GenericParam<T> setData(T data) { + this.data = data; + return this; + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder("GenericParam{"); + sb.append("str='").append(str).append('\''); + sb.append(", num=").append(num); + sb.append(", data=").append(data); + sb.append('}'); + return sb.toString(); + } +} diff --git a/demo/demo-schema/src/main/java/org/apache/servicecomb/demo/compute/GenericParamExtended.java b/demo/demo-schema/src/main/java/org/apache/servicecomb/demo/compute/GenericParamExtended.java new file mode 100644 index 000000000..1f97197f4 --- /dev/null +++ b/demo/demo-schema/src/main/java/org/apache/servicecomb/demo/compute/GenericParamExtended.java @@ -0,0 +1,52 @@ +/* + * 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.demo.compute; + +public class GenericParamExtended<T> extends GenericParam<T> { + private String strExtended; + + private int intExtended; + + public String getStrExtended() { + return strExtended; + } + + public GenericParamExtended<T> setStrExtended(String strExtended) { + this.strExtended = strExtended; + return this; + } + + public int getIntExtended() { + return intExtended; + } + + public GenericParamExtended<T> setIntExtended(int intExtended) { + this.intExtended = intExtended; + return this; + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder("GenericParamExtended{"); + sb.append("strExtended='").append(strExtended).append('\''); + sb.append(", intExtended=").append(intExtended); + sb.append(", super=").append(super.toString()); + sb.append('}'); + return sb.toString(); + } +} diff --git a/demo/demo-schema/src/main/java/org/apache/servicecomb/demo/compute/GenericParamWithJsonIgnore.java b/demo/demo-schema/src/main/java/org/apache/servicecomb/demo/compute/GenericParamWithJsonIgnore.java new file mode 100644 index 000000000..0a295a881 --- /dev/null +++ b/demo/demo-schema/src/main/java/org/apache/servicecomb/demo/compute/GenericParamWithJsonIgnore.java @@ -0,0 +1,66 @@ +/* + * 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.demo.compute; + +import com.fasterxml.jackson.annotation.JsonIgnore; + +public class GenericParamWithJsonIgnore<T> { + private String str; + + private long num; + + @JsonIgnore + private T data; + + public String getStr() { + return str; + } + + public GenericParamWithJsonIgnore<T> setStr(String str) { + this.str = str; + return this; + } + + public long getNum() { + return num; + } + + public GenericParamWithJsonIgnore<T> setNum(long num) { + this.num = num; + return this; + } + + public T getData() { + return data; + } + + public GenericParamWithJsonIgnore<T> setData(T data) { + this.data = data; + return this; + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder("GenericParamWithJsonIgnore{"); + sb.append("str='").append(str).append('\''); + sb.append(", num=").append(num); + sb.append(", data=").append(data); + sb.append('}'); + return sb.toString(); + } +} diff --git a/demo/demo-springmvc/springmvc-client/src/main/java/org/apache/servicecomb/demo/springmvc/client/CodeFirstRestTemplateSpringmvc.java b/demo/demo-springmvc/springmvc-client/src/main/java/org/apache/servicecomb/demo/springmvc/client/CodeFirstRestTemplateSpringmvc.java index 04f2ec6bc..2525f8757 100644 --- a/demo/demo-springmvc/springmvc-client/src/main/java/org/apache/servicecomb/demo/springmvc/client/CodeFirstRestTemplateSpringmvc.java +++ b/demo/demo-springmvc/springmvc-client/src/main/java/org/apache/servicecomb/demo/springmvc/client/CodeFirstRestTemplateSpringmvc.java @@ -88,6 +88,7 @@ protected void testOnlyRest(RestTemplate template, String cseUrlPrefix) { testResponse.runRest(); testObject.runRest(); testGeneric.runRest(); + testRestTemplate.runRest(); super.testOnlyRest(template, cseUrlPrefix); } diff --git a/demo/demo-springmvc/springmvc-client/src/main/java/org/apache/servicecomb/demo/springmvc/client/CodeFirstSpringmvcIntf.java b/demo/demo-springmvc/springmvc-client/src/main/java/org/apache/servicecomb/demo/springmvc/client/CodeFirstSpringmvcIntf.java index 6fb55e223..cb1824ba2 100644 --- a/demo/demo-springmvc/springmvc-client/src/main/java/org/apache/servicecomb/demo/springmvc/client/CodeFirstSpringmvcIntf.java +++ b/demo/demo-springmvc/springmvc-client/src/main/java/org/apache/servicecomb/demo/springmvc/client/CodeFirstSpringmvcIntf.java @@ -24,6 +24,8 @@ import org.apache.servicecomb.demo.EmptyObject; import org.apache.servicecomb.demo.Generic; +import org.apache.servicecomb.demo.compute.GenericParam; +import org.apache.servicecomb.demo.compute.Person; import org.apache.servicecomb.demo.server.User; import org.apache.servicecomb.swagger.invocation.Response; import org.springframework.http.HttpStatus; @@ -59,4 +61,11 @@ void testvoidInRPC(); Void testVoidInRPC(); + + String checkQueryObject(String name, String otherName, Person requestBody); + + String checkQueryGenericObject(GenericParam<Person> requestBody, String str, long num); + + String checkQueryGenericString(String str, GenericParam<Person> requestBody, long num, String data, + String strExtended, int intExtended); } diff --git a/demo/demo-springmvc/springmvc-client/src/main/java/org/apache/servicecomb/demo/springmvc/client/TestResponse.java b/demo/demo-springmvc/springmvc-client/src/main/java/org/apache/servicecomb/demo/springmvc/client/TestResponse.java index e7f1d4c30..e417e200c 100644 --- a/demo/demo-springmvc/springmvc-client/src/main/java/org/apache/servicecomb/demo/springmvc/client/TestResponse.java +++ b/demo/demo-springmvc/springmvc-client/src/main/java/org/apache/servicecomb/demo/springmvc/client/TestResponse.java @@ -19,6 +19,8 @@ import java.util.Date; import org.apache.servicecomb.demo.TestMgr; +import org.apache.servicecomb.demo.compute.GenericParam; +import org.apache.servicecomb.demo.compute.Person; import org.apache.servicecomb.provider.pojo.Invoker; import org.apache.servicecomb.serviceregistry.RegistryUtils; import org.apache.servicecomb.swagger.invocation.Response; @@ -32,6 +34,8 @@ public TestResponse() { } public void runRest() { + checkQueryGenericObject(); + checkQueryGenericString(); } public void runHighway() { @@ -42,6 +46,7 @@ public void runAllTransport() { testCseResponse(); testvoidResponse(); testVoidResponse(); + checkQueryObject(); } private void testCseResponse() { @@ -72,4 +77,29 @@ private void testvoidResponse() { private void testVoidResponse() { intf.testVoidInRPC(); } + + private void checkQueryObject() { + String result = intf.checkQueryObject("name1", "otherName2", new Person("bodyName")); + TestMgr.check("invocationContext_is_null=false,person=name1,otherName=otherName2,name=name1,requestBody=bodyName", + result); + } + + private void checkQueryGenericObject() { + final GenericParam<Person> requestBody = new GenericParam<>(); + requestBody.setNum(1).setStr("str1").setData(new Person("bodyPerson")); + String result = intf.checkQueryGenericObject(requestBody, "str2", 2); + TestMgr.check( + "str=str2,generic=GenericParamWithJsonIgnore{str='str2', num=2, data=null},requestBody=GenericParam{str='str1', num=1, data=bodyPerson}", + result); + } + + private void checkQueryGenericString() { + final GenericParam<Person> requestBody = new GenericParam<>(); + requestBody.setNum(1).setStr("str1").setData(new Person("bodyPerson")); + String result = intf.checkQueryGenericString("str2", requestBody, 2, "dataTest", "strInSubclass", 33); + TestMgr.check( + "str=str2,generic=GenericParamExtended{strExtended='strInSubclass', intExtended=33, super=" + + "GenericParam{str='str2', num=2, data=dataTest}},requestBody=GenericParam{str='str1', num=1, data=bodyPerson}", + result); + } } diff --git a/demo/demo-springmvc/springmvc-client/src/main/java/org/apache/servicecomb/demo/springmvc/client/TestRestTemplate.java b/demo/demo-springmvc/springmvc-client/src/main/java/org/apache/servicecomb/demo/springmvc/client/TestRestTemplate.java index 5b8c9ebb5..06c302a56 100644 --- a/demo/demo-springmvc/springmvc-client/src/main/java/org/apache/servicecomb/demo/springmvc/client/TestRestTemplate.java +++ b/demo/demo-springmvc/springmvc-client/src/main/java/org/apache/servicecomb/demo/springmvc/client/TestRestTemplate.java @@ -17,7 +17,12 @@ package org.apache.servicecomb.demo.springmvc.client; +import org.apache.servicecomb.demo.TestMgr; +import org.apache.servicecomb.demo.compute.GenericParam; +import org.apache.servicecomb.demo.compute.Person; import org.apache.servicecomb.provider.springmvc.reference.RestTemplateBuilder; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpMethod; import org.springframework.http.ResponseEntity; import org.springframework.util.Assert; import org.springframework.web.client.RestTemplate; @@ -29,6 +34,12 @@ public void runAllTest() { testvoidResponse(); testVoidResponse(); checkAllVoidTestResult(); + checkQueryObject(); + } + + public void runRest() { + checkQueryGenericObject(); + checkQueryGenericString(); } private void testvoidResponse() { @@ -48,4 +59,37 @@ private void checkAllVoidTestResult() { .getForEntity("cse://springmvc/codeFirstSpringmvc/checkVoidResult", boolean.class); Assert.isTrue(resultEntity.getBody(), "not all void test is passed"); } + + private void checkQueryObject() { + final ResponseEntity<String> responseEntity = restTemplate + .postForEntity("cse://springmvc/codeFirstSpringmvc/checkQueryObject?name={1}&otherName={2}", + new Person("bodyName"), String.class, "name1", "otherName2"); + TestMgr.check("invocationContext_is_null=false,person=name1,otherName=otherName2,name=name1,requestBody=bodyName", + responseEntity.getBody()); + } + + private void checkQueryGenericObject() { + final GenericParam<Person> requestBody = new GenericParam<>(); + requestBody.setNum(1).setStr("str1").setData(new Person("bodyPerson")); + final HttpEntity<GenericParam<Person>> requestEntity = new HttpEntity<>(requestBody); + final ResponseEntity<String> responseEntity = restTemplate + .exchange("cse://springmvc/codeFirstSpringmvc/checkQueryGenericObject?str={1}&num={2}", + HttpMethod.PUT, requestEntity, String.class, "str2", 2); + TestMgr.check( + "str=str2,generic=GenericParamWithJsonIgnore{str='str2', num=2, data=null},requestBody=GenericParam{str='str1', num=1, data=bodyPerson}", + responseEntity.getBody()); + } + + private void checkQueryGenericString() { + final GenericParam<Person> requestBody = new GenericParam<>(); + requestBody.setNum(1).setStr("str1").setData(new Person("bodyPerson")); + final ResponseEntity<String> responseEntity = restTemplate.exchange( + "cse://springmvc/codeFirstSpringmvc/checkQueryGenericString?str={1}&num={2}&data={3}&strExtended={4}&intExtended={5}", + HttpMethod.PUT, new HttpEntity<>(requestBody), String.class, "str2", 2, "dataTest", + "strInSubclass", 33); + TestMgr.check( + "str=str2,generic=GenericParamExtended{strExtended='strInSubclass', intExtended=33, super=" + + "GenericParam{str='str2', num=2, data=dataTest}},requestBody=GenericParam{str='str1', num=1, data=bodyPerson}", + responseEntity.getBody()); + } } diff --git a/demo/demo-springmvc/springmvc-server/src/main/java/org/apache/servicecomb/demo/springmvc/server/CodeFirstSpringmvc.java b/demo/demo-springmvc/springmvc-server/src/main/java/org/apache/servicecomb/demo/springmvc/server/CodeFirstSpringmvc.java index 883f0592c..6b97de5aa 100644 --- a/demo/demo-springmvc/springmvc-server/src/main/java/org/apache/servicecomb/demo/springmvc/server/CodeFirstSpringmvc.java +++ b/demo/demo-springmvc/springmvc-server/src/main/java/org/apache/servicecomb/demo/springmvc/server/CodeFirstSpringmvc.java @@ -34,6 +34,9 @@ import org.apache.servicecomb.core.Const; import org.apache.servicecomb.demo.EmptyObject; import org.apache.servicecomb.demo.Generic; +import org.apache.servicecomb.demo.compute.GenericParam; +import org.apache.servicecomb.demo.compute.GenericParamExtended; +import org.apache.servicecomb.demo.compute.GenericParamWithJsonIgnore; import org.apache.servicecomb.demo.compute.Person; import org.apache.servicecomb.demo.ignore.InputModelForTestIgnore; import org.apache.servicecomb.demo.ignore.OutputModelForTestIgnore; @@ -141,10 +144,10 @@ public String fileUpload(@RequestPart(name = "file1") MultipartFile file1, public Response cseResponse(InvocationContext c1) { Response response = Response.createSuccess(Status.ACCEPTED, new User()); Headers headers = response.getHeaders(); - headers.addHeader("h1", "h1v " + c1.getContext().get(Const.SRC_MICROSERVICE).toString()); + headers.addHeader("h1", "h1v " + c1.getContext().get(Const.SRC_MICROSERVICE)); InvocationContext c2 = ContextUtils.getInvocationContext(); - headers.addHeader("h2", "h2v " + c2.getContext().get(Const.SRC_MICROSERVICE).toString()); + headers.addHeader("h2", "h2v " + c2.getContext().get(Const.SRC_MICROSERVICE)); return response; } @@ -464,4 +467,37 @@ public boolean checkVoidResult() { return testvoidInRPCSuccess && testVoidInRPCSuccess && testvoidInRestTemplateSuccess && testVoidInRestTemplateSuccess; } + + /** + * Simple query object test, users can use it mixed with InvocationContext and plain query param, RequestBody + */ + @PostMapping(path = "/checkQueryObject") + public String checkQueryObject(Person person, @RequestParam(name = "otherName") String otherName, + InvocationContext invocationContext, @RequestParam(name = "name") String name, @RequestBody Person requestBody) { + LOGGER.info("checkQueryObject() is called!"); + return "invocationContext_is_null=" + (null == invocationContext) + ",person=" + + person + ",otherName=" + otherName + ",name=" + name + ",requestBody=" + requestBody; + } + + /** + * For the nesting object params, including the generic params whose generic field is an object, + * the inner object field is not supported. + */ + @PutMapping(path = "/checkQueryGenericObject") + public String checkQueryGenericObject(@RequestBody GenericParam<Person> requestBody, + GenericParamWithJsonIgnore<Person> generic, String str) { + LOGGER.info("checkQueryGenericObject() is called!"); + return "str=" + str + ",generic=" + generic + ",requestBody=" + requestBody; + } + + /** + * If the generic field is simple type, it's supported to be deserialized. + * The same for those simple type field inherited from the parent class. + */ + @PutMapping(path = "/checkQueryGenericString") + public String checkQueryGenericString(String str, @RequestBody GenericParam<Person> requestBody, + GenericParamExtended<String> generic) { + LOGGER.info("checkQueryGenericObject() is called!"); + return "str=" + str + ",generic=" + generic + ",requestBody=" + requestBody; + } } diff --git a/swagger/swagger-generator/generator-core/src/main/java/org/apache/servicecomb/swagger/generator/core/OperationGenerator.java b/swagger/swagger-generator/generator-core/src/main/java/org/apache/servicecomb/swagger/generator/core/OperationGenerator.java index 40edc6795..ba0b90008 100644 --- a/swagger/swagger-generator/generator-core/src/main/java/org/apache/servicecomb/swagger/generator/core/OperationGenerator.java +++ b/swagger/swagger-generator/generator-core/src/main/java/org/apache/servicecomb/swagger/generator/core/OperationGenerator.java @@ -32,6 +32,8 @@ import org.apache.servicecomb.swagger.SwaggerUtils; import org.apache.servicecomb.swagger.extend.parameter.ContextParameter; import org.apache.servicecomb.swagger.generator.core.utils.ParamUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.util.StringUtils; import io.swagger.models.HttpMethod; @@ -45,6 +47,8 @@ import io.swagger.util.ReflectionUtils; public class OperationGenerator { + private static final Logger LOGGER = LoggerFactory.getLogger(OperationGenerator.class); + protected SwaggerGenerator swaggerGenerator; protected Swagger swagger; @@ -139,7 +143,27 @@ public void addMethodAnnotationParameter(Parameter parameter) { methodAnnotationParameters.add(parameter); } + /** + * Add a parameter into {@linkplain #providerParameters}, + * duplicated name params will be ignored(excepting for {@linkplain ContextParameter}s) + */ public void addProviderParameter(Parameter parameter) { + if (ContextParameter.class.isInstance(parameter)) { + // ContextParameter has no name and is not written in schema, + // so just add it without checking + providerParameters.add(parameter); + return; + } + // check duplicated param according to param name + for (Parameter providerParameter : providerParameters) { + if (parameter.getName().equals(providerParameter.getName())) { + LOGGER.warn( + "Param name [{}] is duplicated which may cause ambiguous deserialization result. Please check you schema definition", + parameter.getName()); + return; + } + } + providerParameters.add(parameter); } @@ -294,7 +318,6 @@ protected void processByParameterAnnotation(Annotation[] paramAnnotations, int p if (parameter instanceof AbstractSerializableParameter && defaultValue != null) { ((AbstractSerializableParameter<?>) parameter).setDefaultValue(defaultValue); } - } protected void processByParameterType(Type parameterType, int paramIdx) { 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 94323f86c..db6d20130 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 @@ -27,8 +27,6 @@ import org.springframework.core.DefaultParameterNameDiscoverer; import org.springframework.core.MethodParameter; -import com.fasterxml.jackson.databind.ser.std.MapProperty; - import io.swagger.converter.ModelConverters; import io.swagger.models.Model; import io.swagger.models.Swagger; @@ -36,6 +34,7 @@ import io.swagger.models.parameters.BodyParameter; import io.swagger.models.parameters.Parameter; import io.swagger.models.properties.ArrayProperty; +import io.swagger.models.properties.MapProperty; import io.swagger.models.properties.ObjectProperty; import io.swagger.models.properties.Property; import io.swagger.models.properties.PropertyBuilder; diff --git a/swagger/swagger-generator/generator-core/src/test/java/org/apache/servicecomb/swagger/generator/core/TestClassUtils.java b/swagger/swagger-generator/generator-core/src/test/java/org/apache/servicecomb/swagger/generator/core/TestClassUtils.java index 32d825351..d1b2d3808 100644 --- a/swagger/swagger-generator/generator-core/src/test/java/org/apache/servicecomb/swagger/generator/core/TestClassUtils.java +++ b/swagger/swagger-generator/generator-core/src/test/java/org/apache/servicecomb/swagger/generator/core/TestClassUtils.java @@ -84,10 +84,10 @@ public void getOrCreateBodyClass() throws NoSuchFieldException { @Test public void testHasAnnotation() { - Assert.assertEquals(true, ClassUtils.hasAnnotation(TestClassUtils.class, SwaggerDefinition.class)); - Assert.assertEquals(true, ClassUtils.hasAnnotation(TestClassUtils.class, Test.class)); + Assert.assertTrue(ClassUtils.hasAnnotation(TestClassUtils.class, SwaggerDefinition.class)); + Assert.assertTrue(ClassUtils.hasAnnotation(TestClassUtils.class, Test.class)); - Assert.assertEquals(false, ClassUtils.hasAnnotation(TestClassUtils.class, Path.class)); + Assert.assertFalse(ClassUtils.hasAnnotation(TestClassUtils.class, Path.class)); } @Test diff --git a/swagger/swagger-generator/generator-core/src/test/java/org/apache/servicecomb/swagger/generator/core/TestOperationGenerator.java b/swagger/swagger-generator/generator-core/src/test/java/org/apache/servicecomb/swagger/generator/core/TestOperationGenerator.java index 1ecb0e605..1b520bc9d 100644 --- a/swagger/swagger-generator/generator-core/src/test/java/org/apache/servicecomb/swagger/generator/core/TestOperationGenerator.java +++ b/swagger/swagger-generator/generator-core/src/test/java/org/apache/servicecomb/swagger/generator/core/TestOperationGenerator.java @@ -26,11 +26,16 @@ import java.util.List; import org.apache.servicecomb.foundation.test.scaffolding.spring.SpringUtils; +import org.apache.servicecomb.swagger.extend.parameter.HttpRequestParameter; import org.apache.servicecomb.swagger.generator.pojo.PojoSwaggerGeneratorContext; +import org.junit.Assert; import org.junit.Test; import org.springframework.util.StringValueResolver; import io.swagger.annotations.ApiOperation; +import io.swagger.models.parameters.BodyParameter; +import io.swagger.models.parameters.Parameter; +import io.swagger.models.parameters.QueryParameter; public class TestOperationGenerator { @Test @@ -88,6 +93,34 @@ public void testConvertTagsOnMethodWithNoAnnotation() throws NoSuchMethodExcepti assertThat(tagList, contains("default0", "default1")); } + @Test + public void addProviderParameter() throws NoSuchMethodException { + Method function = TestClass.class.getMethod("functionWithNoAnnotation"); + SwaggerGenerator swaggerGenerator = new SwaggerGenerator(new PojoSwaggerGeneratorContext(), TestClass.class); + OperationGenerator operationGenerator = new OperationGenerator(swaggerGenerator, function); + + Parameter parameter = new BodyParameter(); + parameter.setName("param0"); + operationGenerator.addProviderParameter(parameter); + Assert.assertEquals(1, operationGenerator.getProviderParameters().size()); + Assert.assertSame(parameter, operationGenerator.getProviderParameters().get(0)); + + parameter = new HttpRequestParameter(); + operationGenerator.addProviderParameter(parameter); + Assert.assertSame(parameter, operationGenerator.getProviderParameters().get(1)); + + parameter = new QueryParameter(); + parameter.setName("param1"); + operationGenerator.addProviderParameter(parameter); + Assert.assertSame(parameter, operationGenerator.getProviderParameters().get(2)); + + parameter = new QueryParameter(); + parameter.setName("param0"); + operationGenerator.addProviderParameter(parameter); + Assert.assertEquals(3, operationGenerator.getProviderParameters().size()); + Assert.assertNotSame(parameter, operationGenerator.getProviderParameters().get(2)); + } + private static class TestClass { @ApiOperation(value = "value1", tags = {"tag1", "tag2"}) public void function() { 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 ce609cf1f..1343f5f7b 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 @@ -23,11 +23,18 @@ import java.util.Map; import org.apache.servicecomb.swagger.generator.core.utils.ClassUtils; +import org.apache.servicecomb.swagger.generator.core.utils.ParamUtils; import org.junit.Assert; import org.junit.Test; import org.mockito.Mockito; import io.swagger.models.parameters.Parameter; +import io.swagger.models.properties.ArrayProperty; +import io.swagger.models.properties.MapProperty; +import io.swagger.models.properties.ObjectProperty; +import io.swagger.models.properties.Property; +import io.swagger.models.properties.RefProperty; +import io.swagger.models.properties.StringProperty; public class TestParamUtils { @Test @@ -42,4 +49,21 @@ public void testGetRawJsonType() { extensions.put(SwaggerConst.EXT_RAW_JSON_TYPE, "test"); Assert.assertFalse(ClassUtils.isRawJsonType(param)); } + + @Test + public void isComplexProperty() { + Property property = new RefProperty("ref"); + Assert.assertTrue(ParamUtils.isComplexProperty(property)); + property = new ObjectProperty(); + Assert.assertTrue(ParamUtils.isComplexProperty(property)); + property = new MapProperty(); + Assert.assertTrue(ParamUtils.isComplexProperty(property)); + property = new ArrayProperty(new ObjectProperty()); + Assert.assertTrue(ParamUtils.isComplexProperty(property)); + + property = new ArrayProperty(new StringProperty()); + Assert.assertFalse(ParamUtils.isComplexProperty(property)); + property = new StringProperty(); + Assert.assertFalse(ParamUtils.isComplexProperty(property)); + } } diff --git a/swagger/swagger-generator/generator-springmvc/src/main/java/org/apache/servicecomb/swagger/generator/springmvc/processor/parameter/SpringmvcDefaultObjectParameterProcessor.java b/swagger/swagger-generator/generator-springmvc/src/main/java/org/apache/servicecomb/swagger/generator/springmvc/processor/parameter/SpringmvcDefaultObjectParameterProcessor.java new file mode 100644 index 000000000..d1b44a01f --- /dev/null +++ b/swagger/swagger-generator/generator-springmvc/src/main/java/org/apache/servicecomb/swagger/generator/springmvc/processor/parameter/SpringmvcDefaultObjectParameterProcessor.java @@ -0,0 +1,116 @@ +/* + * 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.springmvc.processor.parameter; + +import java.lang.reflect.Type; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Map.Entry; + +import org.apache.servicecomb.swagger.generator.core.DefaultParameterProcessor; +import org.apache.servicecomb.swagger.generator.core.OperationGenerator; +import org.apache.servicecomb.swagger.generator.core.utils.ParamUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.swagger.converter.ModelConverters; +import io.swagger.models.Model; +import io.swagger.models.parameters.AbstractSerializableParameter; +import io.swagger.models.parameters.QueryParameter; +import io.swagger.models.properties.Property; +import io.swagger.models.properties.RefProperty; + +/** + * Flatten a object parameter into a set of flatten simple parameters. + * <p/> + * <em>Nesting object params and generic Object params are NOT supported.</em> + * We support query object just for aggregating query params instead of transporting objects in query param. + * So we don't support a generic param whose generic type is complex, but a simple generic type param can be supported. + */ +public class SpringmvcDefaultObjectParameterProcessor implements DefaultParameterProcessor { + + private static final Logger LOGGER = LoggerFactory.getLogger(SpringmvcDefaultObjectParameterProcessor.class); + + @Override + public void process(OperationGenerator operationGenerator, int paramIndex) { + Model paramModel = getParamModel(operationGenerator, paramIndex); + + if (null == paramModel) { + throw new Error(String.format("cannot find param, provider method is [%s], paramIndex = [%d]. " + + "Please check your parameter definition.", + operationGenerator.getProviderMethod().getName(), paramIndex)); + } + + LinkedHashMap<String, AbstractSerializableParameter<?>> resultParamMap = getFlattenParams(paramModel); + + addProviderParams(operationGenerator, resultParamMap); + } + + private void addProviderParams(OperationGenerator operationGenerator, + LinkedHashMap<String, AbstractSerializableParameter<?>> resultParamMap) { + resultParamMap.forEach((paramName, param) -> operationGenerator.addProviderParameter(param)); + } + + private Model getParamModel(OperationGenerator operationGenerator, int paramIndex) { + Type paramType = ParamUtils.getGenericParameterType(operationGenerator.getProviderMethod(), paramIndex); + Property property = ModelConverters.getInstance().readAsProperty(paramType); + // ensure type + if (!RefProperty.class.isInstance(property)) { + LOGGER.error("Unsupported property type: [{}], paramIndex is [{}]", property.getClass().getName(), paramIndex); + return null; + } + + Map<String, Model> models = ModelConverters.getInstance().readAll(paramType); + + // find param root + RefProperty refProperty = (RefProperty) property; + String refTypeName = refProperty.getSimpleRef(); + Model paramRoot = null; + for (Entry<String, Model> entry : models.entrySet()) { + if (refTypeName.equals(entry.getKey())) { + paramRoot = entry.getValue(); + break; + } + } + return paramRoot; + } + + private LinkedHashMap<String, AbstractSerializableParameter<?>> getFlattenParams(Model paramModel) { + LinkedHashMap<String, AbstractSerializableParameter<?>> flattenParamMap = new LinkedHashMap<>(); + // traversal the properties of current paramModel + // create simple parameters, nesting object param is ignored + for (Entry<String, Property> propertyEntry : paramModel.getProperties().entrySet()) { + if (ParamUtils.isComplexProperty(propertyEntry.getValue())) { + throw new Error( + "A nesting complex field is found in the query object and this is not supported, field name = [" + + propertyEntry.getKey() + "]. Please remove this field or tag @JsonIgnore on it."); + } + AbstractSerializableParameter<?> newParameter = createSimpleParam(propertyEntry); + flattenParamMap.put(propertyEntry.getKey(), newParameter); + } + + return flattenParamMap; + } + + private AbstractSerializableParameter<?> createSimpleParam(Entry<String, Property> propertyEntry) { + AbstractSerializableParameter<?> newParameter = new QueryParameter(); + newParameter.setName(propertyEntry.getKey()); + newParameter.setProperty(propertyEntry.getValue()); + return newParameter; + } +} diff --git a/swagger/swagger-generator/generator-springmvc/src/main/java/org/apache/servicecomb/swagger/generator/springmvc/processor/parameter/SpringmvcDefaultParameterProcessor.java b/swagger/swagger-generator/generator-springmvc/src/main/java/org/apache/servicecomb/swagger/generator/springmvc/processor/parameter/SpringmvcDefaultParameterProcessor.java index a87ef87f6..d9b5a5d73 100644 --- a/swagger/swagger-generator/generator-springmvc/src/main/java/org/apache/servicecomb/swagger/generator/springmvc/processor/parameter/SpringmvcDefaultParameterProcessor.java +++ b/swagger/swagger-generator/generator-springmvc/src/main/java/org/apache/servicecomb/swagger/generator/springmvc/processor/parameter/SpringmvcDefaultParameterProcessor.java @@ -17,24 +17,42 @@ package org.apache.servicecomb.swagger.generator.springmvc.processor.parameter; +import java.lang.reflect.Type; + import org.apache.servicecomb.swagger.generator.core.DefaultParameterProcessor; import org.apache.servicecomb.swagger.generator.core.OperationGenerator; import org.apache.servicecomb.swagger.generator.core.utils.ParamUtils; -import io.swagger.models.parameters.QueryParameter; +import io.swagger.converter.ModelConverters; +import io.swagger.models.properties.Property; +import io.swagger.models.properties.RefProperty; public class SpringmvcDefaultParameterProcessor implements DefaultParameterProcessor { + private SpringmvcDefaultSimpleParameterProcessor simpleParameterProcessor = new SpringmvcDefaultSimpleParameterProcessor(); + + private SpringmvcDefaultObjectParameterProcessor objectParameterProcessor = new SpringmvcDefaultObjectParameterProcessor(); @Override public void process(OperationGenerator operationGenerator, int paramIdx) { - String paramName = ParamUtils.getParameterName(operationGenerator.getProviderMethod(), paramIdx); + Type paramType = ParamUtils.getGenericParameterType(operationGenerator.getProviderMethod(), paramIdx); + Property property = ModelConverters.getInstance().readAsProperty(paramType); + + if (RefProperty.class.isInstance(property)) { + objectParameterProcessor.process(operationGenerator, paramIdx); + return; + } + if (!ParamUtils.isComplexProperty(property)) { + simpleParameterProcessor.process(operationGenerator, paramIdx); + return; + } - QueryParameter queryParameter = new QueryParameter(); - queryParameter.setName(paramName); - ParamUtils.setParameterType(operationGenerator.getSwagger(), - operationGenerator.getProviderMethod(), + // unsupported param type + String msg = String.format("cannot process parameter [%s], method=%s:%s, paramIdx=%d, type=%s", + ParamUtils.getParameterName(operationGenerator.getProviderMethod(), paramIdx), + operationGenerator.getProviderMethod().getDeclaringClass().getName(), + operationGenerator.getProviderMethod().getName(), paramIdx, - queryParameter); - operationGenerator.addProviderParameter(queryParameter); + paramType.getTypeName()); + throw new Error(msg); } } diff --git a/swagger/swagger-generator/generator-springmvc/src/main/java/org/apache/servicecomb/swagger/generator/springmvc/processor/parameter/SpringmvcDefaultSimpleParameterProcessor.java b/swagger/swagger-generator/generator-springmvc/src/main/java/org/apache/servicecomb/swagger/generator/springmvc/processor/parameter/SpringmvcDefaultSimpleParameterProcessor.java new file mode 100644 index 000000000..3d9f5a115 --- /dev/null +++ b/swagger/swagger-generator/generator-springmvc/src/main/java/org/apache/servicecomb/swagger/generator/springmvc/processor/parameter/SpringmvcDefaultSimpleParameterProcessor.java @@ -0,0 +1,40 @@ +/* + * 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.springmvc.processor.parameter; + +import org.apache.servicecomb.swagger.generator.core.DefaultParameterProcessor; +import org.apache.servicecomb.swagger.generator.core.OperationGenerator; +import org.apache.servicecomb.swagger.generator.core.utils.ParamUtils; + +import io.swagger.models.parameters.QueryParameter; + +public class SpringmvcDefaultSimpleParameterProcessor implements DefaultParameterProcessor { + + @Override + public void process(OperationGenerator operationGenerator, int paramIdx) { + String paramName = ParamUtils.getParameterName(operationGenerator.getProviderMethod(), paramIdx); + + QueryParameter queryParameter = new QueryParameter(); + queryParameter.setName(paramName); + ParamUtils.setParameterType(operationGenerator.getSwagger(), + operationGenerator.getProviderMethod(), + paramIdx, + queryParameter); + operationGenerator.addProviderParameter(queryParameter); + } +} diff --git a/swagger/swagger-generator/generator-springmvc/src/test/java/org/apache/servicecomb/swagger/generator/springmvc/processor/parameter/SpringmvcDefaultObjectParameterProcessorTest.java b/swagger/swagger-generator/generator-springmvc/src/test/java/org/apache/servicecomb/swagger/generator/springmvc/processor/parameter/SpringmvcDefaultObjectParameterProcessorTest.java new file mode 100644 index 000000000..7d948d750 --- /dev/null +++ b/swagger/swagger-generator/generator-springmvc/src/test/java/org/apache/servicecomb/swagger/generator/springmvc/processor/parameter/SpringmvcDefaultObjectParameterProcessorTest.java @@ -0,0 +1,385 @@ +/* + * 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.springmvc.processor.parameter; + +import static org.junit.Assert.fail; + +import java.lang.reflect.Method; +import java.util.List; + +import org.apache.servicecomb.swagger.generator.core.OperationGenerator; +import org.apache.servicecomb.swagger.generator.core.SwaggerGenerator; +import org.apache.servicecomb.swagger.generator.core.SwaggerGeneratorContext; +import org.junit.Assert; +import org.junit.Test; +import org.mockito.Mockito; + +import com.fasterxml.jackson.annotation.JsonIgnore; + +import io.swagger.models.parameters.Parameter; +import io.swagger.models.parameters.QueryParameter; +import mockit.Deencapsulation; + +public class SpringmvcDefaultObjectParameterProcessorTest { + + @Test + public void processOnObjectParam() throws NoSuchMethodException { + final OperationGenerator operationGenerator = mockOperationGenerator("testObjectParam", "/test", TestParam.class); + + new SpringmvcDefaultObjectParameterProcessor().process(operationGenerator, 0); + + final List<Parameter> providerParameters = operationGenerator.getProviderParameters(); + Assert.assertEquals(2, providerParameters.size()); + Parameter parameter = providerParameters.get(0); + Assert.assertEquals(QueryParameter.class, parameter.getClass()); + Assert.assertEquals("name", parameter.getName()); + Assert.assertEquals("query", parameter.getIn()); + QueryParameter queryParameter = (QueryParameter) parameter; + Assert.assertEquals("string", queryParameter.getType()); + parameter = providerParameters.get(1); + Assert.assertEquals(QueryParameter.class, parameter.getClass()); + Assert.assertEquals("age", parameter.getName()); + Assert.assertEquals("query", parameter.getIn()); + queryParameter = (QueryParameter) parameter; + Assert.assertEquals("integer", queryParameter.getType()); + Assert.assertEquals("int32", queryParameter.getFormat()); + } + + @Test + public void processOnRecursiveObjectParam() throws NoSuchMethodException { + final OperationGenerator operationGenerator = mockOperationGenerator("testRecursiveParam", "/test", + RecursiveParamA.class); + + new SpringmvcDefaultObjectParameterProcessor().process(operationGenerator, 0); + + final List<Parameter> providerParameters = operationGenerator.getProviderParameters(); + Assert.assertEquals(1, providerParameters.size()); + Parameter parameter = providerParameters.get(0); + Assert.assertEquals(QueryParameter.class, parameter.getClass()); + Assert.assertEquals("name", parameter.getName()); + Assert.assertEquals("query", parameter.getIn()); + QueryParameter queryParameter = (QueryParameter) parameter; + Assert.assertEquals("string", queryParameter.getType()); + } + + @Test + public void processOnRecursiveObjectParamWithNoJsonIgnore() throws NoSuchMethodException { + final OperationGenerator operationGenerator = mockOperationGenerator("testRecursiveParamWithNoJsonIgnore", "/test", + RecursiveParamB.class); + + try { + new SpringmvcDefaultObjectParameterProcessor().process(operationGenerator, 0); + fail("an error is expected"); + } catch (Throwable e) { + Assert.assertEquals(Error.class, e.getClass()); + Assert.assertEquals( + "A nesting complex field is found in the query object and this is not supported," + + " field name = [recursiveParamA]. Please remove this field or tag @JsonIgnore on it.", + e.getMessage()); + } + } + + @Test + public void processOnGenericObjectParam() throws NoSuchMethodException { + final OperationGenerator operationGenerator = mockOperationGenerator("testGenericObjectParam", "/test", + GenericParam.class); + try { + new SpringmvcDefaultObjectParameterProcessor().process(operationGenerator, 0); + fail("an error is expected"); + } catch (Throwable e) { + Assert.assertEquals(Error.class, e.getClass()); + Assert.assertEquals( + "A nesting complex field is found in the query object and this is not supported," + + " field name = [data]. Please remove this field or tag @JsonIgnore on it.", + e.getMessage()); + } + } + + @Test + public void processOnGenericObjectParamWithJsonIgnore() throws NoSuchMethodException { + final OperationGenerator operationGenerator = mockOperationGenerator("testGenericObjectParamWithJsonIgnore", + "/test", + GenericParamWithJsonIgnore.class); + + new SpringmvcDefaultObjectParameterProcessor().process(operationGenerator, 0); + + final List<Parameter> providerParameters = operationGenerator.getProviderParameters(); + Assert.assertEquals(2, providerParameters.size()); + Parameter parameter = providerParameters.get(0); + Assert.assertEquals(QueryParameter.class, parameter.getClass()); + Assert.assertEquals("num", parameter.getName()); + Assert.assertEquals("query", parameter.getIn()); + QueryParameter queryParameter = (QueryParameter) parameter; + Assert.assertEquals("integer", queryParameter.getType()); + Assert.assertEquals("int32", queryParameter.getFormat()); + parameter = providerParameters.get(1); + Assert.assertEquals(QueryParameter.class, parameter.getClass()); + Assert.assertEquals("str", parameter.getName()); + Assert.assertEquals("query", parameter.getIn()); + queryParameter = (QueryParameter) parameter; + Assert.assertEquals("string", queryParameter.getType()); + } + + @Test + public void processOnGenericSimpleParam() throws NoSuchMethodException { + final OperationGenerator operationGenerator = mockOperationGenerator("testGenericSimpleParam", "/test", + GenericParam.class); + + new SpringmvcDefaultObjectParameterProcessor().process(operationGenerator, 0); + + final List<Parameter> providerParameters = operationGenerator.getProviderParameters(); + Assert.assertEquals(3, providerParameters.size()); + Parameter parameter = providerParameters.get(0); + Assert.assertEquals(QueryParameter.class, parameter.getClass()); + Assert.assertEquals("num", parameter.getName()); + Assert.assertEquals("query", parameter.getIn()); + QueryParameter queryParameter = (QueryParameter) parameter; + Assert.assertEquals("integer", queryParameter.getType()); + Assert.assertEquals("int32", queryParameter.getFormat()); + parameter = providerParameters.get(1); + Assert.assertEquals(QueryParameter.class, parameter.getClass()); + Assert.assertEquals("str", parameter.getName()); + Assert.assertEquals("query", parameter.getIn()); + queryParameter = (QueryParameter) parameter; + Assert.assertEquals("string", queryParameter.getType()); + parameter = providerParameters.get(2); + Assert.assertEquals(QueryParameter.class, parameter.getClass()); + Assert.assertEquals("data", parameter.getName()); + Assert.assertEquals("query", parameter.getIn()); + queryParameter = (QueryParameter) parameter; + Assert.assertEquals("string", queryParameter.getType()); + } + + private OperationGenerator mockOperationGenerator(String providerParamName, String path, Class<?>... classes) + throws NoSuchMethodException { + final SwaggerGenerator swaggerGenerator = new SwaggerGenerator(Mockito.mock(SwaggerGeneratorContext.class), + TestProvider.class); + final Method providerMethod = TestProvider.class.getDeclaredMethod(providerParamName, classes); + final OperationGenerator operationGenerator = new OperationGenerator(swaggerGenerator, providerMethod); + Deencapsulation.setField(operationGenerator, "path", path); + return operationGenerator; + } + + static class TestProvider { + public String testObjectParam(TestParam objParam) { + return objParam.toString(); + } + + public String testRecursiveParam(RecursiveParamA recursiveParamA) { + return null; + } + + public String testRecursiveParamWithNoJsonIgnore(RecursiveParamB recursiveParamB) { + return null; + } + + public String testGenericObjectParam(GenericParam<TestParam> genericParam) { + return genericParam.toString(); + } + + public String testGenericObjectParamWithJsonIgnore(GenericParamWithJsonIgnore<TestParam> genericParam) { + return genericParam.toString(); + } + + public String testGenericSimpleParam(GenericParam<String> genericParam) { + return genericParam.toString(); + } + } + + static class TestParam { + private String name; + + private int age; + + public String getName() { + return name; + } + + public TestParam setName(String name) { + this.name = name; + return this; + } + + public int getAge() { + return age; + } + + public TestParam setAge(int age) { + this.age = age; + return this; + } + } + + static class GenericParam<T> { + private int num; + + private String str; + + T data; + + public int getNum() { + return num; + } + + public void setNum(int num) { + this.num = num; + } + + public String getStr() { + return str; + } + + public void setStr(String str) { + this.str = str; + } + + public T getData() { + return data; + } + + public void setData(T data) { + this.data = data; + } + } + + static class GenericParamWithJsonIgnore<T> { + private int num; + + private String str; + + @JsonIgnore + T data; + + public int getNum() { + return num; + } + + public void setNum(int num) { + this.num = num; + } + + public String getStr() { + return str; + } + + public void setStr(String str) { + this.str = str; + } + + public T getData() { + return data; + } + + public void setData(T data) { + this.data = data; + } + } + + static class RecursiveParamA { + private String name; + + @JsonIgnore + private RecursiveParamB recursiveParamB; + + @JsonIgnore + private RecursiveParamC recursiveParamC; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public RecursiveParamB getRecursiveParamB() { + return recursiveParamB; + } + + public void setRecursiveParamB( + RecursiveParamB recursiveParamB) { + this.recursiveParamB = recursiveParamB; + } + + public RecursiveParamC getRecursiveParamC() { + return recursiveParamC; + } + + public void setRecursiveParamC( + RecursiveParamC recursiveParamC) { + this.recursiveParamC = recursiveParamC; + } + } + + static class RecursiveParamB { + private int age; + + private RecursiveParamA recursiveParamA; + + private RecursiveParamC recursiveParamC; + + public int getAge() { + return age; + } + + public void setAge(int age) { + this.age = age; + } + + public RecursiveParamA getRecursiveParamA() { + return recursiveParamA; + } + + public void setRecursiveParamA( + RecursiveParamA recursiveParamA) { + this.recursiveParamA = recursiveParamA; + } + + public RecursiveParamC getRecursiveParamC() { + return recursiveParamC; + } + + public void setRecursiveParamC( + RecursiveParamC recursiveParamC) { + this.recursiveParamC = recursiveParamC; + } + } + + static class RecursiveParamC { + private String address; + + private RecursiveParamB recursiveParamB; + + public String getAddress() { + return address; + } + + public void setAddress(String address) { + this.address = address; + } + + public RecursiveParamB getRecursiveParamB() { + return recursiveParamB; + } + + public void setRecursiveParamB( + RecursiveParamB recursiveParamB) { + this.recursiveParamB = recursiveParamB; + } + } +} \ No newline at end of file diff --git a/swagger/swagger-generator/generator-springmvc/src/test/java/org/apache/servicecomb/swagger/generator/springmvc/processor/parameter/SpringmvcDefaultParameterProcessorTest.java b/swagger/swagger-generator/generator-springmvc/src/test/java/org/apache/servicecomb/swagger/generator/springmvc/processor/parameter/SpringmvcDefaultParameterProcessorTest.java new file mode 100644 index 000000000..4d194a1bc --- /dev/null +++ b/swagger/swagger-generator/generator-springmvc/src/test/java/org/apache/servicecomb/swagger/generator/springmvc/processor/parameter/SpringmvcDefaultParameterProcessorTest.java @@ -0,0 +1,193 @@ +/* + * 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.springmvc.processor.parameter; + +import static org.junit.Assert.fail; + +import java.lang.reflect.Method; +import java.util.List; +import java.util.Map; + +import org.apache.servicecomb.swagger.generator.core.OperationGenerator; +import org.apache.servicecomb.swagger.generator.core.SwaggerGenerator; +import org.apache.servicecomb.swagger.generator.core.SwaggerGeneratorContext; +import org.junit.Assert; +import org.junit.Test; +import org.mockito.Mockito; + +import io.swagger.models.parameters.Parameter; +import io.swagger.models.parameters.QueryParameter; +import mockit.Deencapsulation; + +public class SpringmvcDefaultParameterProcessorTest { + @Test + public void processOnSimpleParam() throws NoSuchMethodException { + final SwaggerGenerator swaggerGenerator = new SwaggerGenerator(Mockito.mock(SwaggerGeneratorContext.class), + TestProvider.class); + final Method providerMethod = TestProvider.class.getDeclaredMethod("testSimpleParam", String.class); + final OperationGenerator operationGenerator = new OperationGenerator(swaggerGenerator, providerMethod); + + new SpringmvcDefaultParameterProcessor().process(operationGenerator, 0); + + final List<Parameter> providerParameters = operationGenerator.getProviderParameters(); + Assert.assertEquals(1, providerParameters.size()); + Parameter parameter = providerParameters.get(0); + Assert.assertEquals(QueryParameter.class, parameter.getClass()); + Assert.assertEquals("strParam", parameter.getName()); + Assert.assertEquals("query", parameter.getIn()); + QueryParameter queryParameter = (QueryParameter) parameter; + Assert.assertEquals("string", queryParameter.getType()); + } + + @Test + public void processOnObjectParam() throws NoSuchMethodException { + final OperationGenerator operationGenerator = mockOperationGenerator("testObjectParam", "/test", TestParam.class); + + new SpringmvcDefaultParameterProcessor().process(operationGenerator, 0); + + final List<Parameter> providerParameters = operationGenerator.getProviderParameters(); + Assert.assertEquals(2, providerParameters.size()); + Parameter parameter = providerParameters.get(0); + Assert.assertEquals(QueryParameter.class, parameter.getClass()); + Assert.assertEquals("name", parameter.getName()); + Assert.assertEquals("query", parameter.getIn()); + QueryParameter queryParameter = (QueryParameter) parameter; + Assert.assertEquals("string", queryParameter.getType()); + parameter = providerParameters.get(1); + Assert.assertEquals(QueryParameter.class, parameter.getClass()); + Assert.assertEquals("age", parameter.getName()); + Assert.assertEquals("query", parameter.getIn()); + queryParameter = (QueryParameter) parameter; + Assert.assertEquals("integer", queryParameter.getType()); + Assert.assertEquals("int32", queryParameter.getFormat()); + } + + /** + * Map and List param is not supported + */ + @Test + public void processOnUnsupportedParam() throws NoSuchMethodException { + final SwaggerGenerator swaggerGenerator = new SwaggerGenerator(Mockito.mock(SwaggerGeneratorContext.class), + TestProvider.class); + final Method providerMethod = TestProvider.class.getDeclaredMethod("testUnsupportedParamType", + int.class, List.class, Map.class); + final OperationGenerator operationGenerator = new OperationGenerator(swaggerGenerator, providerMethod); + + try { + new SpringmvcDefaultParameterProcessor().process(operationGenerator, 1); + fail("an error is expected!"); + } catch (Error e) { + Assert.assertEquals( + "cannot process parameter [integerList], method=org.apache.servicecomb.swagger.generator.springmvc" + + ".processor.parameter.SpringmvcDefaultParameterProcessorTest$TestProvider:testUnsupportedParamType, " + + "paramIdx=1, type=java.util.List<org.apache.servicecomb.swagger.generator.springmvc.processor.parameter" + + ".SpringmvcDefaultParameterProcessorTest$TestParam>", + e.getMessage()); + } + try { + new SpringmvcDefaultParameterProcessor().process(operationGenerator, 2); + fail("an error is expected!"); + } catch (Error e) { + Assert.assertEquals( + "cannot process parameter [stringMap], method=org.apache.servicecomb.swagger.generator.springmvc" + + ".processor.parameter.SpringmvcDefaultParameterProcessorTest$TestProvider:testUnsupportedParamType, " + + "paramIdx=2, type=java.util.Map<java.lang.String, java.lang.String>", + e.getMessage()); + } + } + + @Test + public void processOnMultiObjectParamsWithSameFieldName() throws NoSuchMethodException { + final OperationGenerator operationGenerator = mockOperationGenerator("testMultiObjParamsWithSameFiledName", "/test", + String.class, TestParam.class, TestParam.class, int.class); + + final SpringmvcDefaultParameterProcessor springmvcDefaultParameterProcessor = new SpringmvcDefaultParameterProcessor(); + springmvcDefaultParameterProcessor.process(operationGenerator, 0); + springmvcDefaultParameterProcessor.process(operationGenerator, 1); + springmvcDefaultParameterProcessor.process(operationGenerator, 2); + springmvcDefaultParameterProcessor.process(operationGenerator, 3); + + final List<Parameter> providerParameters = operationGenerator.getProviderParameters(); + Assert.assertEquals(2, providerParameters.size()); + Parameter parameter = providerParameters.get(0); + Assert.assertEquals(QueryParameter.class, parameter.getClass()); + Assert.assertEquals("name", parameter.getName()); + Assert.assertEquals("query", parameter.getIn()); + QueryParameter queryParameter = (QueryParameter) parameter; + Assert.assertEquals("string", queryParameter.getType()); + parameter = providerParameters.get(1); + Assert.assertEquals(QueryParameter.class, parameter.getClass()); + Assert.assertEquals("age", parameter.getName()); + Assert.assertEquals("query", parameter.getIn()); + queryParameter = (QueryParameter) parameter; + Assert.assertEquals("integer", queryParameter.getType()); + Assert.assertEquals("int32", queryParameter.getFormat()); + } + + private OperationGenerator mockOperationGenerator(String providerParamName, String path, Class<?>... classes) + throws NoSuchMethodException { + final SwaggerGenerator swaggerGenerator = new SwaggerGenerator(Mockito.mock(SwaggerGeneratorContext.class), + TestProvider.class); + final Method providerMethod = TestProvider.class.getDeclaredMethod(providerParamName, classes); + final OperationGenerator operationGenerator = new OperationGenerator(swaggerGenerator, providerMethod); + Deencapsulation.setField(operationGenerator, "path", path); + return operationGenerator; + } + + static class TestProvider { + public String testSimpleParam(String strParam) { + return strParam; + } + + public String testObjectParam(TestParam objParam) { + return objParam.toString(); + } + + public String testUnsupportedParamType(int i, List<TestParam> integerList, Map<String, String> stringMap) { + return null; + } + + public String testMultiObjParamsWithSameFiledName(String name, TestParam objParam0, TestParam objParam1, int age) { + return objParam0 + "-" + objParam1; + } + } + + static class TestParam { + private String name; + + private int age; + + public String getName() { + return name; + } + + public TestParam setName(String name) { + this.name = name; + return this; + } + + public int getAge() { + return age; + } + + public TestParam setAge(int age) { + this.age = age; + return this; + } + } +} \ No newline at end of file diff --git a/swagger/swagger-generator/generator-springmvc/src/test/java/org/apache/servicecomb/swagger/generator/springmvc/processor/parameter/SpringmvcDefaultSimpleParameterProcessorTest.java b/swagger/swagger-generator/generator-springmvc/src/test/java/org/apache/servicecomb/swagger/generator/springmvc/processor/parameter/SpringmvcDefaultSimpleParameterProcessorTest.java new file mode 100644 index 000000000..dd2992883 --- /dev/null +++ b/swagger/swagger-generator/generator-springmvc/src/test/java/org/apache/servicecomb/swagger/generator/springmvc/processor/parameter/SpringmvcDefaultSimpleParameterProcessorTest.java @@ -0,0 +1,53 @@ +/* + * 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.springmvc.processor.parameter; + +import java.lang.reflect.Method; +import java.util.List; + +import org.apache.servicecomb.swagger.generator.core.OperationGenerator; +import org.apache.servicecomb.swagger.generator.core.SwaggerGenerator; +import org.apache.servicecomb.swagger.generator.core.SwaggerGeneratorContext; +import org.apache.servicecomb.swagger.generator.springmvc.processor.parameter.SpringmvcDefaultParameterProcessorTest.TestProvider; +import org.junit.Assert; +import org.junit.Test; +import org.mockito.Mockito; + +import io.swagger.models.parameters.Parameter; +import io.swagger.models.parameters.QueryParameter; + +public class SpringmvcDefaultSimpleParameterProcessorTest { + @Test + public void process() throws NoSuchMethodException { + final SwaggerGenerator swaggerGenerator = new SwaggerGenerator(Mockito.mock(SwaggerGeneratorContext.class), + TestProvider.class); + final Method providerMethod = TestProvider.class.getDeclaredMethod("testSimpleParam", String.class); + final OperationGenerator operationGenerator = new OperationGenerator(swaggerGenerator, providerMethod); + + new SpringmvcDefaultSimpleParameterProcessor().process(operationGenerator, 0); + + final List<Parameter> providerParameters = operationGenerator.getProviderParameters(); + Assert.assertEquals(1, providerParameters.size()); + Parameter parameter = providerParameters.get(0); + Assert.assertEquals(QueryParameter.class, parameter.getClass()); + Assert.assertEquals("strParam", parameter.getName()); + Assert.assertEquals("query", parameter.getIn()); + QueryParameter queryParameter = (QueryParameter) parameter; + Assert.assertEquals("string", queryParameter.getType()); + } +} diff --git a/swagger/swagger-invocation/invocation-core/pom.xml b/swagger/swagger-invocation/invocation-core/pom.xml index 6dfa293e5..816afbbb2 100644 --- a/swagger/swagger-invocation/invocation-core/pom.xml +++ b/swagger/swagger-invocation/invocation-core/pom.xml @@ -47,5 +47,9 @@ <artifactId>log4j</artifactId> <scope>test</scope> </dependency> + <dependency> + <groupId>org.springframework</groupId> + <artifactId>spring-web</artifactId> + </dependency> </dependencies> </project> \ No newline at end of file diff --git a/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/engine/SwaggerEnvironment.java b/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/engine/SwaggerEnvironment.java index 5224d70a3..e0ba98f39 100644 --- a/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/engine/SwaggerEnvironment.java +++ b/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/engine/SwaggerEnvironment.java @@ -25,6 +25,8 @@ import org.apache.commons.lang3.StringUtils; import org.apache.servicecomb.foundation.common.utils.BeanUtils; import org.apache.servicecomb.foundation.common.utils.ReflectUtils; +import org.apache.servicecomb.swagger.generator.core.CompositeSwaggerGeneratorContext; +import org.apache.servicecomb.swagger.invocation.arguments.ArgumentsMapperConfig; import org.apache.servicecomb.swagger.invocation.arguments.consumer.ConsumerArgumentsMapper; import org.apache.servicecomb.swagger.invocation.arguments.consumer.ConsumerArgumentsMapperFactory; import org.apache.servicecomb.swagger.invocation.arguments.producer.ProducerArgumentsMapper; @@ -40,11 +42,15 @@ import org.springframework.stereotype.Component; import io.swagger.annotations.ApiOperation; +import io.swagger.models.Operation; @Component public class SwaggerEnvironment { private static final Logger LOGGER = LoggerFactory.getLogger(SwaggerEnvironment.class); + @Inject + protected CompositeSwaggerGeneratorContext compositeSwaggerGeneratorContext; + @Inject private ProducerArgumentsMapperFactory producerArgumentsFactory; @@ -63,6 +69,15 @@ public void setConverterMgr(ConverterMgr converterMgr) { producerResponseMapperFactorys.setConverterMgr(converterMgr); } + public CompositeSwaggerGeneratorContext getCompositeSwaggerGeneratorContext() { + return compositeSwaggerGeneratorContext; + } + + public void setCompositeSwaggerGeneratorContext( + CompositeSwaggerGeneratorContext compositeSwaggerGeneratorContext) { + this.compositeSwaggerGeneratorContext = compositeSwaggerGeneratorContext; + } + public ProducerArgumentsMapperFactory getProducerArgumentsFactory() { return producerArgumentsFactory; } @@ -101,8 +116,12 @@ public SwaggerConsumer createConsumer(Class<?> consumerIntf, Class<?> swaggerInt continue; } + ArgumentsMapperConfig config = new ArgumentsMapperConfig(); + config.setSwaggerMethod(swaggerMethod); + config.setProviderMethod(consumerMethod); + ConsumerArgumentsMapper argsMapper = - consumerArgumentsFactory.createArgumentsMapper(swaggerMethod, consumerMethod); + consumerArgumentsFactory.createArgumentsMapper(config); ConsumerResponseMapper responseMapper = consumerResponseMapperFactorys.createResponseMapper( swaggerMethod.getGenericReturnType(), consumerMethod.getGenericReturnType()); @@ -129,7 +148,8 @@ protected String findSwaggerMethodName(Method consumerMethod) { return apiOperationAnnotation.nickname(); } - public SwaggerProducer createProducer(Object producerInstance, Class<?> swaggerIntf) { + public SwaggerProducer createProducer(Object producerInstance, Class<?> swaggerIntf, + Map<String, Operation> swaggerOperationMap) { Class<?> producerCls = BeanUtils.getImplClassFromBean(producerInstance); Map<String, Method> visibleProducerMethods = retrieveVisibleMethods(producerCls); @@ -148,8 +168,13 @@ public SwaggerProducer createProducer(Object producerInstance, Class<?> swaggerI throw new Error(msg); } - ProducerArgumentsMapper argsMapper = producerArgumentsFactory.createArgumentsMapper(swaggerMethod, - producerMethod); + ArgumentsMapperConfig config = new ArgumentsMapperConfig(); + config.setSwaggerMethod(swaggerMethod); + config.setProviderMethod(producerMethod); + config.setSwaggerOperation(swaggerOperationMap.get(methodName)); + config.setSwaggerGeneratorContext(compositeSwaggerGeneratorContext.selectContext(producerCls)); + + ProducerArgumentsMapper argsMapper = producerArgumentsFactory.createArgumentsMapper(config); ProducerResponseMapper responseMapper = producerResponseMapperFactorys.createResponseMapper( swaggerMethod.getGenericReturnType(), producerMethod.getGenericReturnType()); diff --git a/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/engine/bootstrap/BootstrapNormal.java b/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/engine/bootstrap/BootstrapNormal.java index 83e32aa1e..9ed501a58 100644 --- a/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/engine/bootstrap/BootstrapNormal.java +++ b/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/engine/bootstrap/BootstrapNormal.java @@ -20,6 +20,7 @@ import org.apache.servicecomb.swagger.engine.SwaggerBootstrap; import org.apache.servicecomb.swagger.engine.SwaggerEnvironment; +import org.apache.servicecomb.swagger.generator.core.CompositeSwaggerGeneratorContext; import org.apache.servicecomb.swagger.invocation.arguments.consumer.ConsumerArgumentsMapperFactory; import org.apache.servicecomb.swagger.invocation.arguments.consumer.ConsumerInvocationContextMapperFactory; import org.apache.servicecomb.swagger.invocation.arguments.producer.ProducerArgumentsMapperFactory; @@ -43,6 +44,7 @@ public SwaggerEnvironment boot() { env.setConsumerArgumentsFactory(consumerArgumentsFactory); env.setConverterMgr(converterMgr); + env.setCompositeSwaggerGeneratorContext(new CompositeSwaggerGeneratorContext()); return env; } diff --git a/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/arguments/ArgumentsMapperConfig.java b/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/arguments/ArgumentsMapperConfig.java index fcb89d536..da2708308 100644 --- a/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/arguments/ArgumentsMapperConfig.java +++ b/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/arguments/ArgumentsMapperConfig.java @@ -21,12 +21,20 @@ import java.util.ArrayList; import java.util.List; +import org.apache.servicecomb.swagger.generator.core.SwaggerGeneratorContext; + +import io.swagger.models.Operation; + public class ArgumentsMapperConfig { // input private Method swaggerMethod; private Method providerMethod; + private Operation swaggerOperation; + + private SwaggerGeneratorContext swaggerGeneratorContext; + // output private List<ArgumentMapper> argumentMapperList = new ArrayList<>(); @@ -46,6 +54,23 @@ public void setProviderMethod(Method providerMethod) { this.providerMethod = providerMethod; } + public Operation getSwaggerOperation() { + return swaggerOperation; + } + + public void setSwaggerOperation(Operation swaggerOperation) { + this.swaggerOperation = swaggerOperation; + } + + public SwaggerGeneratorContext getSwaggerGeneratorContext() { + return swaggerGeneratorContext; + } + + public void setSwaggerGeneratorContext( + SwaggerGeneratorContext swaggerGeneratorContext) { + this.swaggerGeneratorContext = swaggerGeneratorContext; + } + public List<ArgumentMapper> getArgumentMapperList() { return argumentMapperList; } @@ -57,4 +82,16 @@ public void setArgumentMapperList(List<ArgumentMapper> argumentMapperList) { public void addArgumentMapper(ArgumentMapper argumentMapper) { argumentMapperList.add(argumentMapper); } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder("ArgumentsMapperConfig{"); + sb.append("swaggerMethod=").append(swaggerMethod); + sb.append(", providerMethod=").append(providerMethod); + sb.append(", swaggerOperation=").append(swaggerOperation); + sb.append(", swaggerGeneratorContext=").append(swaggerGeneratorContext); + sb.append(", argumentMapperList=").append(argumentMapperList); + sb.append('}'); + return sb.toString(); + } } diff --git a/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/arguments/ArgumentsMapperFactory.java b/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/arguments/ArgumentsMapperFactory.java index 8e4f183dc..3d86a4962 100644 --- a/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/arguments/ArgumentsMapperFactory.java +++ b/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/arguments/ArgumentsMapperFactory.java @@ -17,6 +17,7 @@ package org.apache.servicecomb.swagger.invocation.arguments; +import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Type; @@ -26,14 +27,30 @@ import java.util.Map; import javax.inject.Inject; +import javax.ws.rs.CookieParam; +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.utils.ParamUtils; import org.apache.servicecomb.swagger.invocation.InvocationType; import org.apache.servicecomb.swagger.invocation.converter.Converter; import org.apache.servicecomb.swagger.invocation.converter.ConverterMgr; import org.apache.servicecomb.swagger.invocation.converter.impl.ConverterCommon; import org.springframework.util.TypeUtils; +import org.springframework.web.bind.annotation.CookieValue; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestAttribute; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RequestPart; -public abstract class ArgumentsMapperFactory { + +/** + * @param <T> type of the generated ArgumentsMapper + */ +public abstract class ArgumentsMapperFactory<T> { @Inject protected ConverterMgr converterMgr; @@ -59,16 +76,6 @@ protected ContextArgumentMapperFactory findFactory(Type type) { return null; } - public <T> T createArgumentsMapper(Method swaggerMethod, Method providerMethod) { - ArgumentsMapperConfig config = new ArgumentsMapperConfig(); - config.setSwaggerMethod(swaggerMethod); - config.setProviderMethod(providerMethod); - - collectArgumentsMapper(config); - - return createArgumentsMapper(config); - } - protected void collectArgumentsMapper(ArgumentsMapperConfig config) { List<ProviderParameter> providerNormalParams = collectContextArgumentsMapper(config); if (providerNormalParams.isEmpty()) { @@ -128,13 +135,70 @@ protected boolean isSwaggerWrapBody(ArgumentsMapperConfig config, List<ProviderP continue; } - ProviderParameter pp = new ProviderParameter(providerIdx, parameterType); + ProviderParameter pp = new ProviderParameter(providerIdx, parameterType, + retrieveVisibleParamName(config.getProviderMethod(), providerIdx)); providerNormalParams.add(pp); } return providerNormalParams; } + /** + * Try to get the swagger param name of the corresponding producer/consumer method param + * @param method producer/consumer method + * @param paramIndex index of the producer/consumer method + * @return the param name specified by param annotations, or the param name defined in code + */ + public static String retrieveVisibleParamName(Method method, int paramIndex) { + final Annotation[] annotations = method.getParameterAnnotations()[paramIndex]; + String paramName = null; + for (Annotation annotation : annotations) { + paramName = retrieveVisibleParamName(annotation); + } + if (null == paramName) { + paramName = ParamUtils.getParameterName(method, paramIndex); + } + return paramName; + } + + public static String retrieveVisibleParamName(Annotation annotation) { + if (CookieParam.class.isInstance(annotation)) { + return ((CookieParam) annotation).value(); + } + if (CookieValue.class.isInstance(annotation)) { + return ((CookieValue) annotation).name(); + } + if (FormParam.class.isInstance(annotation)) { + return ((FormParam) annotation).value(); + } + if (HeaderParam.class.isInstance(annotation)) { + return ((HeaderParam) annotation).value(); + } + if (PathParam.class.isInstance(annotation)) { + return ((PathParam) annotation).value(); + } + if (PathVariable.class.isInstance(annotation)) { + return ((PathVariable) annotation).value(); + } + if (QueryParam.class.isInstance(annotation)) { + return ((QueryParam) annotation).value(); + } + if (RequestAttribute.class.isInstance(annotation)) { + return ((RequestAttribute) annotation).name(); + } + if (RequestHeader.class.isInstance(annotation)) { + return ((RequestHeader) annotation).name(); + } + if (RequestParam.class.isInstance(annotation)) { + return ((RequestParam) annotation).name(); + } + if (RequestPart.class.isInstance(annotation)) { + return ((RequestPart) annotation).name(); + } + + return null; + } + protected void collectSwaggerArgumentsMapper(ArgumentsMapperConfig config, List<ProviderParameter> providerNormalParams) { Method swaggerMethod = config.getSwaggerMethod(); @@ -180,7 +244,7 @@ protected void collectWrapBodyMapper(ArgumentsMapperConfig config, List<Provider config.addArgumentMapper(bodyFieldArg); } - protected abstract <T> T createArgumentsMapper(ArgumentsMapperConfig config); + public abstract T createArgumentsMapper(ArgumentsMapperConfig config); protected abstract ArgumentMapper createArgumentMapperWithConverter(int swaggerIdx, int providerIdx, Converter converter); diff --git a/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/arguments/ProviderParameter.java b/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/arguments/ProviderParameter.java index 3382a97e6..299c02f9b 100644 --- a/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/arguments/ProviderParameter.java +++ b/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/arguments/ProviderParameter.java @@ -23,24 +23,51 @@ private Type type; - public ProviderParameter(int index, Type type) { + /** + * the param name specified by param annotations(i.e. the param name in schema), or the param name defined in code + */ + private String name; + + public ProviderParameter(int index, Type type, String name) { this.index = index; this.type = type; + this.name = name; } public int getIndex() { return index; } - public void setIndex(int index) { + public ProviderParameter setIndex(int index) { this.index = index; + return this; } public Type getType() { return type; } - public void setType(Type type) { + public ProviderParameter setType(Type type) { this.type = type; + return this; + } + + public String getName() { + return name; + } + + public ProviderParameter setName(String name) { + this.name = name; + return this; + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder("ProviderParameter{"); + sb.append("index=").append(index); + sb.append(", type=").append(type); + sb.append(", name='").append(name).append('\''); + sb.append('}'); + return sb.toString(); } } diff --git a/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/arguments/consumer/ConsumerArgumentsMapperFactory.java b/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/arguments/consumer/ConsumerArgumentsMapperFactory.java index 7d77890da..54a69ca82 100644 --- a/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/arguments/consumer/ConsumerArgumentsMapperFactory.java +++ b/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/arguments/consumer/ConsumerArgumentsMapperFactory.java @@ -33,7 +33,7 @@ import org.springframework.stereotype.Component; @Component -public class ConsumerArgumentsMapperFactory extends ArgumentsMapperFactory { +public class ConsumerArgumentsMapperFactory extends ArgumentsMapperFactory<ConsumerArgumentsMapper> { public ConsumerArgumentsMapperFactory() { type = InvocationType.CONSUMER; } @@ -46,8 +46,9 @@ public void setFactoryList(List<ContextArgumentMapperFactory> factoryList) { @SuppressWarnings("unchecked") @Override - protected <T> T createArgumentsMapper(ArgumentsMapperConfig config) { - return (T) new ConsumerArgumentsMapper(config.getArgumentMapperList(), + public ConsumerArgumentsMapper createArgumentsMapper(ArgumentsMapperConfig config) { + collectArgumentsMapper(config); + return new ConsumerArgumentsMapper(config.getArgumentMapperList(), config.getSwaggerMethod().getParameterCount()); } diff --git a/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/arguments/producer/ProducerArgumentsMapperFactory.java b/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/arguments/producer/ProducerArgumentsMapperFactory.java index c3d47eab3..204dea9fa 100644 --- a/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/arguments/producer/ProducerArgumentsMapperFactory.java +++ b/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/arguments/producer/ProducerArgumentsMapperFactory.java @@ -17,23 +17,40 @@ package org.apache.servicecomb.swagger.invocation.arguments.producer; +import java.lang.reflect.Type; +import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; import javax.inject.Inject; +import org.apache.servicecomb.swagger.generator.core.utils.ParamUtils; import org.apache.servicecomb.swagger.invocation.InvocationType; import org.apache.servicecomb.swagger.invocation.arguments.ArgumentMapper; import org.apache.servicecomb.swagger.invocation.arguments.ArgumentsMapperConfig; import org.apache.servicecomb.swagger.invocation.arguments.ArgumentsMapperFactory; import org.apache.servicecomb.swagger.invocation.arguments.ContextArgumentMapperFactory; import org.apache.servicecomb.swagger.invocation.arguments.FieldInfo; +import org.apache.servicecomb.swagger.invocation.arguments.ProviderParameter; import org.apache.servicecomb.swagger.invocation.converter.Converter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Component; +import io.swagger.converter.ModelConverters; +import io.swagger.models.parameters.Parameter; +import io.swagger.models.parameters.QueryParameter; +import io.swagger.models.properties.Property; +import io.swagger.models.properties.RefProperty; + @Component -public class ProducerArgumentsMapperFactory extends ArgumentsMapperFactory { +public class ProducerArgumentsMapperFactory extends ArgumentsMapperFactory<ProducerArgumentsMapper> { + private static final Logger LOGGER = LoggerFactory.getLogger(ProducerArgumentsMapperFactory.class); + public ProducerArgumentsMapperFactory() { type = InvocationType.PRODUCER; } @@ -46,11 +63,131 @@ public void setFactoryList(List<ContextArgumentMapperFactory> factoryList) { @SuppressWarnings("unchecked") @Override - protected <T> T createArgumentsMapper(ArgumentsMapperConfig config) { - return (T) new ProducerArgumentsMapper(config.getArgumentMapperList(), + public ProducerArgumentsMapper createArgumentsMapper(ArgumentsMapperConfig config) { + collectArgumentsMapper(config); + return new ProducerArgumentsMapper(config.getArgumentMapperList(), config.getProviderMethod().getParameterCount()); } + @Override + protected void collectSwaggerArgumentsMapper(ArgumentsMapperConfig config, + List<ProviderParameter> providerNormalParams) { + if (!config.getSwaggerGeneratorContext().getClass().getCanonicalName().equals( + "org.apache.servicecomb.swagger.generator.springmvc.SpringmvcSwaggerGeneratorContext")) { + // if this is not a SpringMVC style provider operation, there is no need to consider query object param + super.collectSwaggerArgumentsMapper(config, providerNormalParams); + return; + } + + Map<String, ProviderParameter> providerParamMap = getProviderParamMap(providerNormalParams); + Map<String, ParamWrapper<Parameter>> swaggerParamMap = getSwaggerParamMap(config); + + Set<String> queryObjectNames = findSpringMvcQueryObject(providerParamMap, swaggerParamMap); + if (queryObjectNames.isEmpty()) { + // there is no query object param, run as 1-to-1 param mapping mode + super.collectSwaggerArgumentsMapper(config, providerNormalParams); + return; + } + + // There is at lease one query object param, so the param mapping mode becomes to M-to-N + // try to map params by name + generateParamMapperByName(config, providerParamMap, swaggerParamMap, queryObjectNames); + } + + private void generateParamMapperByName(ArgumentsMapperConfig config, Map<String, ProviderParameter> providerParamMap, + Map<String, ParamWrapper<Parameter>> swaggerParamMap, Set<String> queryObjectNames) { + LOGGER.info("mapping query object params: [{}]", queryObjectNames); + generateObjectQueryParamMapper(config, providerParamMap, swaggerParamMap, queryObjectNames); + generateDefaultParamMapper(config, providerParamMap, swaggerParamMap, queryObjectNames); + } + + /** + * Generate default argument mappers. One swagger argument is mapped to one producer argument. + */ + private void generateDefaultParamMapper(ArgumentsMapperConfig config, Map<String, ProviderParameter> providerParamMap, + Map<String, ParamWrapper<Parameter>> swaggerParamMap, Set<String> queryObjectNames) { + Type[] swaggerParamTypes = config.getSwaggerMethod().getGenericParameterTypes(); + for (Entry<String, ProviderParameter> providerParamEntry : providerParamMap.entrySet()) { + if (queryObjectNames.contains(providerParamEntry.getKey())) { + continue; + } + + final int swaggerIdx = swaggerParamMap.get(providerParamEntry.getKey()).getIndex(); + Converter converter = converterMgr.findConverter(type, providerParamEntry.getValue().getType(), + swaggerParamTypes[swaggerIdx]); + ArgumentMapper mapper = + createArgumentMapperWithConverter(swaggerIdx, providerParamEntry.getValue().getIndex(), converter); + config.addArgumentMapper(mapper); + } + } + + /** + * Generate argument mappers for query object params. Collect all query params as json and map them to object param. + */ + private void generateObjectQueryParamMapper(ArgumentsMapperConfig config, + Map<String, ProviderParameter> providerParamMap, Map<String, ParamWrapper<Parameter>> swaggerParamMap, + Set<String> queryObjectNames) { + // collect all query params + Map<String, Integer> querySwaggerParamsIndex = new HashMap<>(); + for (Entry<String, ParamWrapper<Parameter>> wrapperEntry : swaggerParamMap.entrySet()) { + if (wrapperEntry.getValue().getParam() instanceof QueryParameter) { + querySwaggerParamsIndex.put(wrapperEntry.getKey(), wrapperEntry.getValue().getIndex()); + } + } + // create mapper for each query objects + for (String queryObjectName : queryObjectNames) { + final ProviderParameter providerParameter = providerParamMap.get(queryObjectName); + ArgumentMapper mapper = new ProducerSpringMVCQueryObjectMapper(querySwaggerParamsIndex, + providerParameter.getIndex(), + providerParameter.getType()); + config.addArgumentMapper(mapper); + } + } + + private Map<String, ParamWrapper<Parameter>> getSwaggerParamMap(ArgumentsMapperConfig config) { + Map<String, ParamWrapper<Parameter>> swaggerParamMap = + new HashMap<>(config.getSwaggerOperation().getParameters().size()); + List<Parameter> parameters = config.getSwaggerOperation().getParameters(); + for (int i = 0; i < parameters.size(); i++) { + Parameter parameter = parameters.get(i); + swaggerParamMap.put(parameter.getName(), new ParamWrapper<>(parameter).setIndex(i)); + } + return swaggerParamMap; + } + + private Map<String, ProviderParameter> getProviderParamMap(List<ProviderParameter> providerNormalParams) { + Map<String, ProviderParameter> providerParamMap = new HashMap<>(providerNormalParams.size()); + providerNormalParams.forEach( + providerParameter -> providerParamMap.put(providerParameter.getName(), providerParameter)); + return providerParamMap; + } + + /** + * Find all query object params + * @return the names of the query object params + */ + private Set<String> findSpringMvcQueryObject(Map<String, ProviderParameter> providerParamMap, + Map<String, ParamWrapper<Parameter>> swaggerParamMap) { + // find all reference type producer params, and exclude body param + Set<String> queryObjectSet = new HashSet<>(); + + for (Entry<String, ProviderParameter> paramEntry : providerParamMap.entrySet()) { + Type paramType = paramEntry.getValue().getType(); + Property property = ModelConverters.getInstance().readAsProperty(paramType); + if (RefProperty.class.isInstance(property)) { + queryObjectSet.add(paramEntry.getKey()); + } + } + + for (Entry<String, ParamWrapper<Parameter>> paramEntry : swaggerParamMap.entrySet()) { + if (ParamUtils.isRealBodyParameter(paramEntry.getValue().getParam())) { + queryObjectSet.remove(paramEntry.getKey()); + } + } + + return queryObjectSet; + } + @Override protected ArgumentMapper createArgumentMapperWithConverter(int swaggerIdx, int producerIdx, Converter converter) { return new ProducerArgumentSame(swaggerIdx, producerIdx, converter); @@ -61,4 +198,41 @@ protected ArgumentMapper createBodyFieldArgMapper(ArgumentsMapperConfig config, Map<Integer, FieldInfo> fieldMap) { return new SwaggerArgumentToProducerBodyField(fieldMap); } + + public static class ParamWrapper<T> { + T param; + + int index; + + public ParamWrapper(T param) { + this.param = param; + } + + public T getParam() { + return param; + } + + public ParamWrapper<T> setParam(T param) { + this.param = param; + return this; + } + + public int getIndex() { + return index; + } + + public ParamWrapper<T> setIndex(int index) { + this.index = index; + return this; + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder("ParamWrapper{"); + sb.append("param=").append(param); + sb.append(", index=").append(index); + sb.append('}'); + return sb.toString(); + } + } } diff --git a/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/arguments/producer/ProducerSpringMVCQueryObjectMapper.java b/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/arguments/producer/ProducerSpringMVCQueryObjectMapper.java new file mode 100644 index 000000000..bba8b407e --- /dev/null +++ b/swagger/swagger-invocation/invocation-core/src/main/java/org/apache/servicecomb/swagger/invocation/arguments/producer/ProducerSpringMVCQueryObjectMapper.java @@ -0,0 +1,61 @@ +/* + * 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.invocation.arguments.producer; + +import java.lang.reflect.Type; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; + +import org.apache.servicecomb.swagger.invocation.SwaggerInvocation; +import org.apache.servicecomb.swagger.invocation.arguments.ArgumentMapper; +import org.apache.servicecomb.swagger.invocation.converter.Converter; +import org.apache.servicecomb.swagger.invocation.converter.impl.ConverterCommon; + +/** + * Argument mapper for object params. + * <p/> + * Collect all query swagger params as json and deserialize to object param. + */ +public class ProducerSpringMVCQueryObjectMapper implements ArgumentMapper { + private int producerIdx; + + private Map<String, Integer> swaggerParamIndexMap; + + private Converter converter; + + public ProducerSpringMVCQueryObjectMapper(Map<String, Integer> swaggerParamIndexMap, int producerIdx, + Type producerParamType) { + this.producerIdx = producerIdx; + this.swaggerParamIndexMap = new HashMap<>(); + this.swaggerParamIndexMap.putAll(swaggerParamIndexMap); + converter = new ConverterCommon(producerParamType); + } + + @Override + public void mapArgument(SwaggerInvocation invocation, Object[] producerArguments) { + Map<String, Object> jsonMap = new HashMap<>(swaggerParamIndexMap.size()); + + for (Entry<String, Integer> swaggerIndexEntry : swaggerParamIndexMap.entrySet()) { + jsonMap.put(swaggerIndexEntry.getKey(), invocation.getSwaggerArgument(swaggerIndexEntry.getValue())); + } + + final Object producerParam = converter.convert(jsonMap); + producerArguments[producerIdx] = producerParam; + } +} diff --git a/swagger/swagger-invocation/invocation-core/src/test/java/org/apache/servicecomb/engine/SwaggerEnvironmentForTest.java b/swagger/swagger-invocation/invocation-core/src/test/java/org/apache/servicecomb/engine/SwaggerEnvironmentForTest.java index 72eb40db4..b2c6d006c 100644 --- a/swagger/swagger-invocation/invocation-core/src/test/java/org/apache/servicecomb/engine/SwaggerEnvironmentForTest.java +++ b/swagger/swagger-invocation/invocation-core/src/test/java/org/apache/servicecomb/engine/SwaggerEnvironmentForTest.java @@ -16,6 +16,8 @@ */ package org.apache.servicecomb.engine; +import java.util.LinkedHashMap; + import org.apache.servicecomb.foundation.common.utils.BeanUtils; import org.apache.servicecomb.swagger.converter.SwaggerToClassGenerator; import org.apache.servicecomb.swagger.engine.SwaggerEnvironment; @@ -47,6 +49,7 @@ public SwaggerProducer createProducer(Object producerInstance) { SwaggerToClassGenerator swaggerToClassGenerator = new SwaggerToClassGenerator(classLoader, swagger, producerInstance.getClass().getPackage().getName()); - return swaggerEnvironment.createProducer(producerInstance, swaggerToClassGenerator.convert()); + return swaggerEnvironment.createProducer(producerInstance, swaggerToClassGenerator.convert(), + new LinkedHashMap<>()); } } diff --git a/swagger/swagger-invocation/invocation-core/src/test/java/org/apache/servicecomb/swagger/invocation/arguments/producer/ProducerSpringMVCQueryObjectMapperTest.java b/swagger/swagger-invocation/invocation-core/src/test/java/org/apache/servicecomb/swagger/invocation/arguments/producer/ProducerSpringMVCQueryObjectMapperTest.java new file mode 100644 index 000000000..fea80f258 --- /dev/null +++ b/swagger/swagger-invocation/invocation-core/src/test/java/org/apache/servicecomb/swagger/invocation/arguments/producer/ProducerSpringMVCQueryObjectMapperTest.java @@ -0,0 +1,183 @@ +/* + * 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.invocation.arguments.producer; + +import java.util.Date; +import java.util.HashMap; +import java.util.Objects; + +import org.apache.servicecomb.swagger.invocation.SwaggerInvocation; +import org.apache.servicecomb.swagger.invocation.arguments.ArgumentMapper; +import org.junit.Assert; +import org.junit.Test; + +public class ProducerSpringMVCQueryObjectMapperTest { + + @Test + public void mapArgument() { + final HashMap<String, Integer> swaggerParamIndexMap = new HashMap<>(); + swaggerParamIndexMap.put("name", 0); + swaggerParamIndexMap.put("age", 1); + ArgumentMapper argumentMapper = new ProducerSpringMVCQueryObjectMapper(swaggerParamIndexMap, 0, TestParam.class); + SwaggerInvocation swaggerInvocation = new SwaggerInvocation(); + swaggerInvocation.setSwaggerArguments(new Object[] {"nameTest", 22}); + + final Object[] producerArguments = new Object[1]; + argumentMapper.mapArgument(swaggerInvocation, producerArguments); + Assert.assertEquals(producerArguments[0], new TestParam().setName("nameTest").setAge(22)); + } + + @Test + public void mapArgumentOnRecursiveParam() { + final HashMap<String, Integer> swaggerParamIndexMap = new HashMap<>(); + swaggerParamIndexMap.put("num", 0); + swaggerParamIndexMap.put("str", 1); + swaggerParamIndexMap.put("date", 2); + ArgumentMapper argumentMapper = new ProducerSpringMVCQueryObjectMapper(swaggerParamIndexMap, 1, + RecursiveParam.class); + SwaggerInvocation swaggerInvocation = new SwaggerInvocation(); + final Date testDate = new Date(); + swaggerInvocation.setSwaggerArguments(new Object[] {2, "str0_0", testDate}); + + final Object[] producerArguments = new Object[2]; + argumentMapper.mapArgument(swaggerInvocation, producerArguments); + Assert.assertNull(producerArguments[0]); + Assert.assertEquals(producerArguments[1], new RecursiveParam().setNum(2).setStr("str0_0").setDate(testDate)); + } + + static class TestParam { + private String name; + + private int age; + + public String getName() { + return name; + } + + public TestParam setName(String name) { + this.name = name; + return this; + } + + public int getAge() { + return age; + } + + public TestParam setAge(int age) { + this.age = age; + return this; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + TestParam testParam = (TestParam) o; + return age == testParam.age && + Objects.equals(name, testParam.name); + } + + @Override + public int hashCode() { + return Objects.hash(name, age); + } + } + + static class RecursiveParam { + + private int num; + + private String str; + + private Date date; + + private RecursiveParam recursiveParam; + + public int getNum() { + return num; + } + + public RecursiveParam setNum(int num) { + this.num = num; + return this; + } + + public String getStr() { + return str; + } + + public RecursiveParam setStr(String str) { + this.str = str; + return this; + } + + public Date getDate() { + return date; + } + + public RecursiveParam setDate(Date date) { + this.date = date; + return this; + } + + public RecursiveParam getRecursiveParam() { + return recursiveParam; + } + + public RecursiveParam setRecursiveParam( + RecursiveParam recursiveParam) { + this.recursiveParam = recursiveParam; + return this; + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder("RecursiveParam{"); + sb.append("num=").append(num); + sb.append(", str='").append(str).append('\''); + sb.append(", date=").append(date); + sb.append(", recursiveParam=").append(recursiveParam); + sb.append('}'); + return sb.toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + RecursiveParam that = (RecursiveParam) o; + return num == that.num && + Objects.equals(str, that.str) && + Objects.equals(date, that.date) && + Objects.equals(recursiveParam, that.recursiveParam); + } + + @Override + public int hashCode() { + return Objects.hash(num, str, date, recursiveParam); + } + } +} \ No newline at end of file ---------------------------------------------------------------- This is an automated message from the Apache Git Service. To respond to the message, please log on GitHub and use the URL above to go to the specific comment. For queries about this service, please contact Infrastructure at: us...@infra.apache.org With regards, Apache Git Services