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

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


The following commit(s) were added to refs/heads/master by this push:
     new e3ede5a5d [SCB-2803]fix proto-buffer nested list and map problems 
(#3915)
e3ede5a5d is described below

commit e3ede5a5dd1aea05480ceb826a8b98f226062045
Author: liubao68 <[email protected]>
AuthorDate: Fri Aug 25 08:50:02 2023 +0800

    [SCB-2803]fix proto-buffer nested list and map problems (#3915)
---
 .../protobuf/schema/SchemaToProtoGenerator.java    | 258 ++++++++++++++++-----
 .../utils/ScopedProtobufSchemaManager.java         |   4 +-
 .../schema/TestSchemaToProtoGenerator.java         |  99 ++++++++
 .../codec/protobuf/schema/model/CyclicInfo.java    |  38 +++
 .../codec/protobuf/schema/model/DeptInfo.java      |  38 +++
 .../codec/protobuf/schema/model/SchemaService.java |  38 +++
 .../codec/protobuf/schema/model/UserInfo.java      |  52 +++++
 .../rest/codec/param/BodyProcessorCreator.java     |  14 +-
 .../codec/produce/ProduceProtoBufferProcessor.java |   2 +-
 .../apache/servicecomb/swagger/SwaggerUtils.java   |   5 +-
 10 files changed, 480 insertions(+), 68 deletions(-)

diff --git 
a/common/common-protobuf/src/main/java/org/apache/servicecomb/codec/protobuf/schema/SchemaToProtoGenerator.java
 
b/common/common-protobuf/src/main/java/org/apache/servicecomb/codec/protobuf/schema/SchemaToProtoGenerator.java
index 0e0c4bb6a..f100a5c14 100644
--- 
a/common/common-protobuf/src/main/java/org/apache/servicecomb/codec/protobuf/schema/SchemaToProtoGenerator.java
+++ 
b/common/common-protobuf/src/main/java/org/apache/servicecomb/codec/protobuf/schema/SchemaToProtoGenerator.java
@@ -32,11 +32,10 @@ import java.util.Set;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.servicecomb.foundation.protobuf.internal.ProtoConst;
 import org.apache.servicecomb.foundation.protobuf.internal.parser.ProtoParser;
-import org.springframework.util.CollectionUtils;
+import org.apache.servicecomb.swagger.SwaggerUtils;
 
 import com.google.common.hash.Hashing;
 
-import io.protostuff.compiler.model.Message;
 import io.protostuff.compiler.model.Proto;
 import io.swagger.v3.oas.models.Components;
 import io.swagger.v3.oas.models.OpenAPI;
@@ -44,6 +43,32 @@ import io.swagger.v3.oas.models.media.Schema;
 
 @SuppressWarnings({"rawtypes", "unchecked"})
 public class SchemaToProtoGenerator {
+  record IdentifierRunnable(Schema<?> identifier, Runnable target)
+      implements Runnable {
+
+    @Override
+    public void run() {
+      this.target.run();
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (this == o) {
+        return true;
+      }
+      if (o == null || getClass() != o.getClass()) {
+        return false;
+      }
+      IdentifierRunnable that = (IdentifierRunnable) o;
+      return SwaggerUtils.schemaEquals(identifier, that.identifier);
+    }
+
+    @Override
+    public int hashCode() {
+      return SwaggerUtils.schemaHashCode(identifier);
+    }
+  }
+
   private final String protoPackage;
 
   private final OpenAPI openAPI;
@@ -52,8 +77,6 @@ public class SchemaToProtoGenerator {
 
   private final String rootName;
 
-  private final Set<String> imports = new HashSet<>();
-
   private final Set<String> messages = new HashSet<>();
 
   private final StringBuilder msgStringBuilder = new StringBuilder();
@@ -68,19 +91,28 @@ public class SchemaToProtoGenerator {
   }
 
   public Proto convert() {
-    convertSwaggerType(this.rootSchema);
-
-    Map<String, Schema> wrap = new HashMap<>(1);
-    wrap.put("value", this.rootSchema);
-    createMessage(rootName, wrap, ProtoConst.ANNOTATION_WRAP_PROPERTY);
+    createMessage(this.rootSchema);
 
+    int iteration = 0;
     do {
       List<Runnable> oldPending = pending;
       pending = new ArrayList<>();
       for (Runnable runnable : oldPending) {
         runnable.run();
       }
-    } while (!pending.isEmpty());
+      if (pending.size() >= oldPending.size()) {
+        iteration++;
+      }
+    } while (!pending.isEmpty() && iteration < 1000);
+
+    if (iteration == 1000) {
+      throw new IllegalArgumentException(
+          String.format("Failed to create schema %s. May be cyclic object.", 
this.rootName));
+    }
+
+    Map<String, Schema> wrap = new HashMap<>(1);
+    wrap.put("value", this.rootSchema);
+    createMessage(rootName, wrap, ProtoConst.ANNOTATION_WRAP_PROPERTY);
 
     return createProto();
   }
@@ -88,9 +120,6 @@ public class SchemaToProtoGenerator {
   protected Proto createProto() {
     StringBuilder sb = new StringBuilder();
     appendLine(sb, "syntax = \"proto3\";");
-    for (String importMsg : imports) {
-      appendLine(sb, "import \"%s\";", importMsg);
-    }
     if (StringUtils.isNotEmpty(protoPackage)) {
       sb.append("package ").append(protoPackage).append(";\n");
     }
@@ -99,52 +128,58 @@ public class SchemaToProtoGenerator {
     return protoParser.parseFromContent(sb.toString());
   }
 
-  private String convertSwaggerType(Schema<?> swaggerType) {
+  private String findSchemaType(Schema<?> schema) {
     @SuppressWarnings("unchecked")
-    String type = tryFindEnumType((List<String>) swaggerType.getEnum());
+    String type = tryFindEnumType((List<String>) schema.getEnum());
     if (type != null) {
       return type;
     }
 
-    type = findBaseType(swaggerType.getType(), swaggerType.getFormat());
+    type = findBaseType(schema.getType(), schema.getFormat());
     if (type != null) {
       return type;
     }
 
-    Schema<?> itemProperty = swaggerType.getItems();
+    Schema<?> itemProperty = schema.getItems();
     if (itemProperty != null) {
-      return "repeated " + convertArrayOrMapItem(itemProperty);
+      String containerType = findArrayOrMapItemType(itemProperty);
+      if (containerType != null) {
+        return "repeated " + containerType;
+      }
+      return null;
     }
 
-    itemProperty = swaggerType.getAdditionalItems();
+    itemProperty = (Schema<?>) schema.getAdditionalProperties();
     if (itemProperty != null) {
-      return String.format("map<string, %s>", 
convertArrayOrMapItem(itemProperty));
+      String containerType = findArrayOrMapItemType(itemProperty);
+      if (containerType != null) {
+        return String.format("map<string, %s>", containerType);
+      }
+      return null;
     }
 
-    type = swaggerType.get$ref();
+    type = schema.get$ref();
     if (type != null) {
-      Schema<?> refSchema = openAPI.getComponents().getSchemas().get(
-          type.substring(Components.COMPONENTS_SCHEMAS_REF.length()));
+      String typeName = 
type.substring(Components.COMPONENTS_SCHEMAS_REF.length());
+      Schema<?> refSchema = openAPI.getComponents().getSchemas().get(typeName);
       if (refSchema == null) {
         throw new IllegalArgumentException("not found ref in components " + 
type);
       }
-      return convertSwaggerType(refSchema);
+      if (StringUtils.isEmpty(refSchema.getName())) {
+        refSchema.setName(typeName);
+      }
+      return findSchemaType(refSchema);
     }
 
-    Map<String, Schema> properties = swaggerType.getProperties();
-    if (CollectionUtils.isEmpty(properties)) {
-      addImports(ProtoConst.ANY_PROTO);
-      return ProtoConst.ANY.getCanonicalName();
-    }
-    createMessage(swaggerType.getName(), properties);
-    return swaggerType.getName();
+    return findObjectType(schema);
   }
 
-  private void addImports(Proto proto) {
-    imports.add(proto.getFilename());
-    for (Message message : proto.getMessages()) {
-      messages.add(message.getCanonicalName());
+  private String findObjectType(Schema<?> schema) {
+    String name = schema.getName();
+    if (messages.contains(name)) {
+      return name;
     }
+    return null;
   }
 
   private void createEnum(String enumName, List<String> enums) {
@@ -195,38 +230,40 @@ public class SchemaToProtoGenerator {
     };
   }
 
-  private String convertArrayOrMapItem(Schema<?> itemProperty) {
+  private String findArrayOrMapItemType(Schema<?> itemProperty) {
     // List<List<>>, need to wrap
     if (itemProperty.getItems() != null) {
-      String protoName = generateWrapPropertyName(List.class.getSimpleName(), 
itemProperty.getItems());
-      pending.add(() -> wrapPropertyToMessage(protoName, itemProperty));
-      return protoName;
+      return findWrapPropertyType(List.class.getSimpleName(), 
itemProperty.getItems());
     }
 
     // List<Map<>>, need to wrap
-    if (itemProperty.getAdditionalItems() != null) {
-      String protoName = generateWrapPropertyName(Map.class.getSimpleName(), 
itemProperty.getAdditionalItems());
-      pending.add(() -> wrapPropertyToMessage(protoName, itemProperty));
-      return protoName;
+    if (itemProperty.getAdditionalProperties() != null) {
+      return findWrapPropertyType(Map.class.getSimpleName(), (Schema<?>) 
itemProperty.getAdditionalProperties());
     }
 
-    return convertSwaggerType(itemProperty);
+    return findSchemaType(itemProperty);
   }
 
 
-  private String generateWrapPropertyName(String prefix, Schema<?> property) {
+  private String findWrapPropertyType(String prefix, Schema<?> property) {
     // List<List<>>, need to wrap
     if (property.getItems() != null) {
-      return generateWrapPropertyName(prefix + List.class.getSimpleName(), 
property.getItems());
+      return findWrapPropertyType(prefix + List.class.getSimpleName(), 
property.getItems());
     }
 
     // List<Map<>>, need to wrap
-    if (property.getAdditionalItems() != null) {
-      return generateWrapPropertyName(prefix + Map.class.getSimpleName(), 
property.getAdditionalItems());
+    if (property.getAdditionalProperties() != null) {
+      return findWrapPropertyType(prefix + Map.class.getSimpleName(),
+          (Schema<?>) property.getAdditionalProperties());
+    }
+
+    String type = findSchemaType(property);
+    if (type == null) {
+      return null;
     }
 
     // message name cannot have . (package separator)
-    return prefix + 
StringUtils.capitalize(escapeMessageName(convertSwaggerType(property)));
+    return prefix + StringUtils.capitalize(escapeMessageName(type));
   }
 
   public static String escapeMessageName(String name) {
@@ -239,11 +276,6 @@ public class SchemaToProtoGenerator {
   }
 
   private void createMessage(String protoName, Map<String, Schema> properties, 
String... annotations) {
-    if (!messages.add(protoName)) {
-      // already created
-      return;
-    }
-
     for (String annotation : annotations) {
       msgStringBuilder.append("//");
       appendLine(msgStringBuilder, annotation);
@@ -252,11 +284,127 @@ public class SchemaToProtoGenerator {
     int tag = 1;
     for (Entry<String, Schema> entry : properties.entrySet()) {
       Schema property = entry.getValue();
-      String propertyType = convertSwaggerType(property);
+      String propertyType = findSchemaType(property);
+
+      appendLine(msgStringBuilder, "  %s %s = %d;", propertyType, 
entry.getKey(), tag);
+      tag++;
+    }
+    appendLine(msgStringBuilder, "}");
+  }
+
+  public void createMessage(Schema<?> schema) {
+    String ref = schema.get$ref();
+    if (ref != null) {
+      String typeName = 
ref.substring(Components.COMPONENTS_SCHEMAS_REF.length());
+      Schema<?> refSchema = openAPI.getComponents().getSchemas().get(typeName);
+      if (refSchema == null) {
+        throw new IllegalArgumentException("not found ref in components " + 
ref);
+      }
+      if (StringUtils.isEmpty(refSchema.getName())) {
+        refSchema.setName(typeName);
+      }
+      createMessage(refSchema);
+      return;
+    }
+
+    boolean wait = false;
+
+    //array or map
+    if (isArrayOrMap(schema)) {
+      Schema<?> mapOrArrayItem = arrayOrMapItem(schema);
+      if (findSchemaType(mapOrArrayItem) == null) {
+        createMessageTask(mapOrArrayItem);
+        wait = true;
+      } else {
+        if (createMapOrArrayMessageTask(mapOrArrayItem, true, schema)) {
+          wait = true;
+        }
+      }
+    }
+
+    //object
+    if (schema.getProperties() != null) {
+      for (Entry<String, Schema> entry : schema.getProperties().entrySet()) {
+        if (findSchemaType(entry.getValue()) == null) {
+          createMessageTask(entry.getValue());
+          wait = true;
+        } else if (isArrayOrMap(entry.getValue())) {
+          if (createMapOrArrayMessageTask(arrayOrMapItem(entry.getValue()), 
false, null)) {
+            wait = true;
+          }
+        }
+      }
+    }
+
+    if (wait) {
+      IdentifierRunnable runnable = new IdentifierRunnable(schema, () -> 
createMessage(schema));
+      if (!pending.contains(runnable)) {
+        pending.add(runnable);
+      }
+      return;
+    }
+
+    if (findSchemaType(schema) != null) {
+      return;
+    }
+
+    messages.add(schema.getName());
+
+    appendLine(msgStringBuilder, "message %s {", schema.getName());
+    int tag = 1;
+    for (Entry<String, Schema> entry : schema.getProperties().entrySet()) {
+      Schema property = entry.getValue();
+      String propertyType = findSchemaType(property);
 
       appendLine(msgStringBuilder, "  %s %s = %d;", propertyType, 
entry.getKey(), tag);
       tag++;
     }
     appendLine(msgStringBuilder, "}");
   }
+
+  private boolean isArrayOrMap(Schema<?> value) {
+    return value.getItems() != null || value.getAdditionalProperties() != null;
+  }
+
+  private Schema<?> arrayOrMapItem(Schema<?> schema) {
+    return schema.getItems() == null ?
+        (Schema<?>) schema.getAdditionalProperties() : schema.getItems();
+  }
+
+  private void createMessageTask(Schema<?> schema) {
+    IdentifierRunnable runnable = new IdentifierRunnable(schema, () -> 
createMessage(schema));
+    if (!pending.contains(runnable)) {
+      pending.add(runnable);
+    }
+  }
+
+  private boolean createMapOrArrayMessageTask(Schema<?> schema, boolean 
nested, Schema<?> owner) {
+    if (schema.getAdditionalProperties() != null) {
+      String protoName = findWrapPropertyType(Map.class.getSimpleName(),
+          (Schema<?>) schema.getAdditionalProperties());
+      if (messages.add(protoName)) {
+        pending.add(() -> wrapPropertyToMessage(protoName, schema));
+        createMessageTask((Schema<?>) schema.getAdditionalProperties());
+        return true;
+      }
+    }
+    if (schema.getItems() != null) {
+      String protoName = findWrapPropertyType(List.class.getSimpleName(), 
schema.getItems());
+      if (messages.add(protoName)) {
+        pending.add(() -> wrapPropertyToMessage(protoName, schema));
+        createMessageTask(schema.getItems());
+        return true;
+      }
+    }
+    if (nested) {
+      String protoName = owner.getAdditionalProperties() != null ?
+          findWrapPropertyType(Map.class.getSimpleName(), schema) :
+          findWrapPropertyType(List.class.getSimpleName(), schema);
+      if (messages.add(protoName)) {
+        pending.add(() -> wrapPropertyToMessage(protoName, owner));
+        return true;
+      }
+    }
+    return false;
+  }
 }
diff --git 
a/common/common-protobuf/src/main/java/org/apache/servicecomb/codec/protobuf/utils/ScopedProtobufSchemaManager.java
 
b/common/common-protobuf/src/main/java/org/apache/servicecomb/codec/protobuf/utils/ScopedProtobufSchemaManager.java
index 7cb40114e..27ee60b6b 100644
--- 
a/common/common-protobuf/src/main/java/org/apache/servicecomb/codec/protobuf/utils/ScopedProtobufSchemaManager.java
+++ 
b/common/common-protobuf/src/main/java/org/apache/servicecomb/codec/protobuf/utils/ScopedProtobufSchemaManager.java
@@ -100,11 +100,11 @@ public class ScopedProtobufSchemaManager {
   /**
    * get the ProtoMapper from Schema
    */
-  public ProtoMapper getOrCreateProtoMapper(OpenAPI openAPI, String schemaId, 
String name, Schema<?> schema) {
+  public ProtoMapper getOrCreateProtoMapper(OpenAPI openAPI, String schemaId, 
String rootMessageName, Schema<?> schema) {
     SchemaKey schemaKey = new SchemaKey(schemaId, schema);
     return schemaMapperCache.computeIfAbsent(schemaKey, key -> {
       SchemaToProtoGenerator generator = new 
SchemaToProtoGenerator("scb.schema", openAPI,
-          key.schema, name);
+          key.schema, rootMessageName);
       Proto proto = generator.convert();
       ProtoMapperFactory protoMapperFactory = new ProtoMapperFactory();
       return protoMapperFactory.create(proto);
diff --git 
a/common/common-protobuf/src/test/java/org/apache/servicecomb/codec/protobuf/schema/TestSchemaToProtoGenerator.java
 
b/common/common-protobuf/src/test/java/org/apache/servicecomb/codec/protobuf/schema/TestSchemaToProtoGenerator.java
index 4c0e0321f..8daed9b39 100644
--- 
a/common/common-protobuf/src/test/java/org/apache/servicecomb/codec/protobuf/schema/TestSchemaToProtoGenerator.java
+++ 
b/common/common-protobuf/src/test/java/org/apache/servicecomb/codec/protobuf/schema/TestSchemaToProtoGenerator.java
@@ -19,7 +19,9 @@ package org.apache.servicecomb.codec.protobuf.schema;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 
 import 
org.apache.servicecomb.codec.protobuf.internal.converter.ProtoToStringGenerator;
+import org.apache.servicecomb.codec.protobuf.schema.model.SchemaService;
 import 
org.apache.servicecomb.swagger.generator.springmvc.SpringmvcSwaggerGenerator;
+import org.assertj.core.api.Assertions;
 import org.junit.jupiter.api.Test;
 import org.springframework.web.bind.annotation.PostMapping;
 import org.springframework.web.bind.annotation.RequestBody;
@@ -185,5 +187,102 @@ public class TestSchemaToProtoGenerator {
         }
         """.trim(), new ProtoToStringGenerator(proto).protoToString().trim());
   }
+
+  @Test
+  public void testNestedModelCorrect() {
+    SpringmvcSwaggerGenerator generator = new 
SpringmvcSwaggerGenerator(SchemaService.class);
+    OpenAPI openAPI = generator.generate();
+    SchemaToProtoGenerator protoGenerator =
+        new SchemaToProtoGenerator("test.model", openAPI,
+            openAPI.getPaths().get("/testUserInfo").getPost()
+                .getRequestBody().getContent().get(MediaType.APPLICATION_JSON)
+                .getSchema(), "request");
+    Proto proto = protoGenerator.convert();
+    assertEquals("""
+        syntax = "proto3";
+        package test.model;
+                
+        //@WrapProperty
+        message MapString {
+          map<string, string> value = 1;
+        }
+                
+        //@WrapProperty
+        message ListListString {
+          repeated ListString value = 1;
+        }
+                
+        message DeptInfo {
+          string name = 1;
+          string code = 2;
+        }
+                
+        //@WrapProperty
+        message ListString {
+          repeated string value = 1;
+        }
+                
+        message UserInfo {
+          repeated DeptInfo subDeptInfos = 1;
+          repeated MapString extraInfos = 2;
+          repeated ListListString nestedLists = 3;
+        }
+                
+        //@WrapProperty
+        message ListDeptInfo {
+          repeated DeptInfo value = 1;
+        }
+                
+        //@WrapProperty
+        message request {
+          UserInfo value = 1;
+        }
+        """.trim(), new ProtoToStringGenerator(proto).protoToString().trim());
+  }
+
+  @Test
+  public void testListMapTypeCorrect() {
+    SpringmvcSwaggerGenerator generator = new 
SpringmvcSwaggerGenerator(SchemaService.class);
+    OpenAPI openAPI = generator.generate();
+    SchemaToProtoGenerator protoGenerator =
+        new SchemaToProtoGenerator("test.model", openAPI,
+            openAPI.getPaths().get("/testListType").getPost()
+                .getRequestBody().getContent().get(MediaType.APPLICATION_JSON)
+                .getSchema(), "request");
+    Proto proto = protoGenerator.convert();
+    assertEquals("""
+        syntax = "proto3";
+        package test.model;
+               
+        message DeptInfo {
+          string name = 1;
+          string code = 2;
+        }
+               
+        //@WrapProperty
+        message ListDeptInfo {
+          repeated DeptInfo value = 1;
+        }
+               
+        //@WrapProperty
+        message request {
+          repeated DeptInfo value = 1;
+        }
+         """.trim(), new ProtoToStringGenerator(proto).protoToString().trim());
+  }
+
+  @Test
+  public void testCyclicModelWrong() {
+    SpringmvcSwaggerGenerator generator = new 
SpringmvcSwaggerGenerator(SchemaService.class);
+    OpenAPI openAPI = generator.generate();
+    SchemaToProtoGenerator protoGenerator =
+        new SchemaToProtoGenerator("test.model", openAPI,
+            openAPI.getPaths().get("/testCyclic").getPost()
+                .getRequestBody().getContent().get(MediaType.APPLICATION_JSON)
+                .getSchema(), "request");
+    IllegalArgumentException throwable = Assertions.catchThrowableOfType(() -> 
protoGenerator.convert(),
+        IllegalArgumentException.class);
+    assertEquals("Failed to create schema request. May be cyclic object.", 
throwable.getMessage());
+  }
 }
 //CHECKSTYLE:ON
diff --git 
a/common/common-protobuf/src/test/java/org/apache/servicecomb/codec/protobuf/schema/model/CyclicInfo.java
 
b/common/common-protobuf/src/test/java/org/apache/servicecomb/codec/protobuf/schema/model/CyclicInfo.java
new file mode 100644
index 000000000..ae8f2fbd5
--- /dev/null
+++ 
b/common/common-protobuf/src/test/java/org/apache/servicecomb/codec/protobuf/schema/model/CyclicInfo.java
@@ -0,0 +1,38 @@
+/*
+ * 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.codec.protobuf.schema.model;
+
+public class CyclicInfo {
+  private String name;
+  private CyclicInfo child;
+
+  public String getName() {
+    return name;
+  }
+
+  public void setName(String name) {
+    this.name = name;
+  }
+
+  public CyclicInfo getChild() {
+    return child;
+  }
+
+  public void setChild(CyclicInfo child) {
+    this.child = child;
+  }
+}
diff --git 
a/common/common-protobuf/src/test/java/org/apache/servicecomb/codec/protobuf/schema/model/DeptInfo.java
 
b/common/common-protobuf/src/test/java/org/apache/servicecomb/codec/protobuf/schema/model/DeptInfo.java
new file mode 100644
index 000000000..1befcd119
--- /dev/null
+++ 
b/common/common-protobuf/src/test/java/org/apache/servicecomb/codec/protobuf/schema/model/DeptInfo.java
@@ -0,0 +1,38 @@
+/*
+ * 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.codec.protobuf.schema.model;
+
+public class DeptInfo {
+  private String name;
+  private String code;
+
+  public String getName() {
+    return name;
+  }
+
+  public void setName(String name) {
+    this.name = name;
+  }
+
+  public String getCode() {
+    return code;
+  }
+
+  public void setCode(String code) {
+    this.code = code;
+  }
+}
diff --git 
a/common/common-protobuf/src/test/java/org/apache/servicecomb/codec/protobuf/schema/model/SchemaService.java
 
b/common/common-protobuf/src/test/java/org/apache/servicecomb/codec/protobuf/schema/model/SchemaService.java
new file mode 100644
index 000000000..99edc8702
--- /dev/null
+++ 
b/common/common-protobuf/src/test/java/org/apache/servicecomb/codec/protobuf/schema/model/SchemaService.java
@@ -0,0 +1,38 @@
+/*
+ * 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.codec.protobuf.schema.model;
+
+import java.util.List;
+
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+
+@RequestMapping("/schemaService")
+public class SchemaService {
+  @PostMapping("testUserInfo")
+  public void testUserInfo(@RequestBody UserInfo request) {
+  }
+
+  @PostMapping("testListType")
+  public void testListType(@RequestBody List<DeptInfo> request) {
+  }
+
+  @PostMapping("testCyclic")
+  public void testCyclic(@RequestBody CyclicInfo request) {
+  }
+}
diff --git 
a/common/common-protobuf/src/test/java/org/apache/servicecomb/codec/protobuf/schema/model/UserInfo.java
 
b/common/common-protobuf/src/test/java/org/apache/servicecomb/codec/protobuf/schema/model/UserInfo.java
new file mode 100644
index 000000000..37662bb32
--- /dev/null
+++ 
b/common/common-protobuf/src/test/java/org/apache/servicecomb/codec/protobuf/schema/model/UserInfo.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.codec.protobuf.schema.model;
+
+import java.util.List;
+import java.util.Map;
+
+public class UserInfo {
+  private List<DeptInfo> subDeptInfos;
+
+  private List<Map<String, String>> extraInfos;
+
+  private List<List<List<String>>> nestedLists;
+
+  public List<DeptInfo> getSubDeptInfos() {
+    return subDeptInfos;
+  }
+
+  public void setSubDeptInfos(List<DeptInfo> subDeptInfos) {
+    this.subDeptInfos = subDeptInfos;
+  }
+
+  public List<Map<String, String>> getExtraInfos() {
+    return extraInfos;
+  }
+
+  public void setExtraInfos(List<Map<String, String>> extraInfos) {
+    this.extraInfos = extraInfos;
+  }
+
+  public List<List<List<String>>> getNestedLists() {
+    return nestedLists;
+  }
+
+  public void setNestedLists(List<List<List<String>>> nestedLists) {
+    this.nestedLists = nestedLists;
+  }
+}
diff --git 
a/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/codec/param/BodyProcessorCreator.java
 
b/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/codec/param/BodyProcessorCreator.java
index 83e46002a..dcb8aaf97 100644
--- 
a/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/codec/param/BodyProcessorCreator.java
+++ 
b/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/codec/param/BodyProcessorCreator.java
@@ -66,6 +66,8 @@ import jakarta.ws.rs.core.Response.Status;
 public class BodyProcessorCreator implements 
ParamValueProcessorCreator<RequestBody> {
   private static final Logger LOGGER = 
LoggerFactory.getLogger(BodyProcessorCreator.class);
 
+  public static final String REQUEST_BODY_NAME = "X_REQUEST";
+
   public static final String EXT_ID = "protobuf";
 
   public static final String PARAM_TYPE = "body";
@@ -180,14 +182,12 @@ public class BodyProcessorCreator implements 
ParamValueProcessorCreator<RequestB
       }
 
       if (SwaggerConst.PROTOBUF_TYPE.equals(contentType)) {
-        String messageName = (String) requestBody.getExtensions()
-            .get(SwaggerConst.EXT_BODY_NAME);
         ProtoMapper protoMapper = scopedProtobufSchemaManager
             .getOrCreateProtoMapper(openAPI, operationMeta.getSchemaId(),
-                messageName,
+                REQUEST_BODY_NAME,
                 
requestBody.getContent().get(SwaggerConst.PROTOBUF_TYPE).getSchema());
         RootDeserializer<PropertyWrapper<Object>> deserializer = 
protoMapper.getDeserializerSchemaManager()
-            
.createRootDeserializer(protoMapper.getProto().getMessage(messageName), 
targetType);
+            
.createRootDeserializer(protoMapper.getProto().getMessage(REQUEST_BODY_NAME), 
targetType);
         PropertyWrapper<Object> result = 
deserializer.deserialize(inputStream.readAllBytes());
         return result.getValue();
       }
@@ -248,14 +248,12 @@ public class BodyProcessorCreator implements 
ParamValueProcessorCreator<RequestB
      */
     private Buffer createBodyBuffer(String contentType, Object arg) throws 
IOException {
       if (SwaggerConst.PROTOBUF_TYPE.equals(contentType)) {
-        String messageName = (String) requestBody.getExtensions()
-            .get(SwaggerConst.EXT_BODY_NAME);
         ProtoMapper protoMapper = scopedProtobufSchemaManager
             .getOrCreateProtoMapper(openAPI, operationMeta.getSchemaId(),
-                messageName,
+                REQUEST_BODY_NAME,
                 
requestBody.getContent().get(SwaggerConst.PROTOBUF_TYPE).getSchema());
         RootSerializer serializer = protoMapper.getSerializerSchemaManager()
-            
.createRootSerializer(protoMapper.getProto().getMessage(messageName),
+            
.createRootSerializer(protoMapper.getProto().getMessage(REQUEST_BODY_NAME),
                 Object.class);
         Map<String, Object> bodyArg = new HashMap<>(1);
         bodyArg.put("value", arg);
diff --git 
a/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/codec/produce/ProduceProtoBufferProcessor.java
 
b/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/codec/produce/ProduceProtoBufferProcessor.java
index f1c8bc105..61f56fcac 100644
--- 
a/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/codec/produce/ProduceProtoBufferProcessor.java
+++ 
b/common/common-rest/src/main/java/org/apache/servicecomb/common/rest/codec/produce/ProduceProtoBufferProcessor.java
@@ -36,7 +36,7 @@ import io.swagger.v3.oas.models.OpenAPI;
 import io.swagger.v3.oas.models.media.Schema;
 
 public class ProduceProtoBufferProcessor implements ProduceProcessor {
-  public static final String RESPONSE_MESSAGE_NAME = "response";
+  public static final String RESPONSE_MESSAGE_NAME = "X_RESPONSE";
 
   public static final String EXT_ID = "protobuf";
 
diff --git 
a/swagger/swagger-generator/generator-core/src/main/java/org/apache/servicecomb/swagger/SwaggerUtils.java
 
b/swagger/swagger-generator/generator-core/src/main/java/org/apache/servicecomb/swagger/SwaggerUtils.java
index 6f6a591c6..bfc9ea9c5 100644
--- 
a/swagger/swagger-generator/generator-core/src/main/java/org/apache/servicecomb/swagger/SwaggerUtils.java
+++ 
b/swagger/swagger-generator/generator-core/src/main/java/org/apache/servicecomb/swagger/SwaggerUtils.java
@@ -258,7 +258,8 @@ public final class SwaggerUtils {
     result = result ^ (schema.getName() != null ? schema.getName().hashCode() 
: 0);
     result = result ^ (schema.get$ref() != null ? schema.get$ref().hashCode() 
: 0);
     result = result ^ (schema.getItems() != null ? 
schemaHashCode(schema.getItems()) : 0);
-    result = result ^ (schema.getAdditionalItems() != null ? 
schemaHashCode(schema.getAdditionalItems()) : 0);
+    result = result ^ (schema.getAdditionalProperties() != null ?
+        schemaHashCode((Schema<?>) schema.getAdditionalProperties()) : 0);
     result = result ^ (schema.getProperties() != null ? 
propertiesHashCode(schema.getProperties()) : 0);
     return result;
   }
@@ -285,7 +286,7 @@ public final class SwaggerUtils {
         && StringUtils.equals(schema1.getName(), schema2.getName())
         && StringUtils.equals(schema1.get$ref(), schema2.get$ref())
         && schemaEquals(schema1.getItems(), schema2.getItems())
-        && schemaEquals(schema1.getAdditionalItems(), 
schema2.getAdditionalItems())
+        && schemaEquals((Schema<?>) schema1.getAdditionalProperties(), 
(Schema<?>) schema2.getAdditionalProperties())
         && propertiesEquals(schema1.getProperties(), schema2.getProperties());
   }
 

Reply via email to