You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@servicecomb.apache.org by li...@apache.org on 2018/10/10 03:54:26 UTC

[incubator-servicecomb-java-chassis] 06/09: [SCB-918][WIP] create complex schemas

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

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

commit f724c64e34459cd73053f0cae4ae9f284063705e
Author: wujimin <wu...@huawei.com>
AuthorDate: Thu Sep 27 22:27:17 2018 +0800

    [SCB-918][WIP] create complex schemas
---
 .../protobuf/internal/schema/AnyEntry.java         |  39 +++++++
 .../protobuf/internal/schema/AnyEntrySchema.java   | 119 +++++++++++++++++++++
 .../protobuf/internal/schema/AnySchema.java        | 102 ++++++++++++++++++
 .../protobuf/internal/schema/FieldSchema.java      | 114 ++++++++++++++++++++
 .../protobuf/internal/schema/MapEntrySchema.java   |  63 +++++++++++
 .../protobuf/internal/schema/MapSchema.java        |  62 +++++++++++
 .../internal/schema/MessageAsFieldSchema.java      |  50 +++++++++
 .../protobuf/internal/schema/RepeatedSchema.java   |  69 ++++++++++++
 .../protobuf/internal/schema/TestAnySchema.java    | 102 ++++++++++++++++++
 .../protobuf/internal/schema/TestMapSchema.java    |  44 ++++++++
 .../internal/schema/TestMessageSchema.java         |  76 +++++++++++++
 .../internal/schema/TestRepeatedSchema.java        |  50 +++++++++
 12 files changed, 890 insertions(+)

diff --git a/foundations/foundation-protobuf/src/main/java/org/apache/servicecomb/foundation/protobuf/internal/schema/AnyEntry.java b/foundations/foundation-protobuf/src/main/java/org/apache/servicecomb/foundation/protobuf/internal/schema/AnyEntry.java
new file mode 100644
index 0000000..afd5307
--- /dev/null
+++ b/foundations/foundation-protobuf/src/main/java/org/apache/servicecomb/foundation/protobuf/internal/schema/AnyEntry.java
@@ -0,0 +1,39 @@
+/*
+ * 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.foundation.protobuf.internal.schema;
+
+public class AnyEntry {
+  private String typeUrl;
+
+  private byte[] value;
+
+  public String getTypeUrl() {
+    return typeUrl;
+  }
+
+  public void setTypeUrl(String typeUrl) {
+    this.typeUrl = typeUrl;
+  }
+
+  public byte[] getValue() {
+    return value;
+  }
+
+  public void setValue(byte[] value) {
+    this.value = value;
+  }
+}
diff --git a/foundations/foundation-protobuf/src/main/java/org/apache/servicecomb/foundation/protobuf/internal/schema/AnyEntrySchema.java b/foundations/foundation-protobuf/src/main/java/org/apache/servicecomb/foundation/protobuf/internal/schema/AnyEntrySchema.java
new file mode 100644
index 0000000..f4828e8
--- /dev/null
+++ b/foundations/foundation-protobuf/src/main/java/org/apache/servicecomb/foundation/protobuf/internal/schema/AnyEntrySchema.java
@@ -0,0 +1,119 @@
+/*
+ * 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.foundation.protobuf.internal.schema;
+
+import java.io.IOException;
+import java.util.Map;
+
+import org.apache.servicecomb.foundation.protobuf.ProtoMapper;
+import org.apache.servicecomb.foundation.protobuf.RootSerializer;
+import org.apache.servicecomb.foundation.protobuf.internal.ProtoConst;
+
+import io.protostuff.CustomSchema;
+import io.protostuff.Input;
+import io.protostuff.Output;
+
+public class AnyEntrySchema extends CustomSchema<Object> {
+  private final ProtoMapper protoMapper;
+
+  public AnyEntrySchema(ProtoMapper protoMapper) {
+    super(null);
+    this.protoMapper = protoMapper;
+  }
+
+  @Override
+  public boolean isInitialized(Object message) {
+    return true;
+  }
+
+  @Override
+  public Object newMessage() {
+    return new AnyEntry();
+  }
+
+  @Override
+  public void mergeFrom(Input input, Object message) throws IOException {
+    input.readFieldNumber(null);
+    String typeUrl = input.readString();
+
+    input.readFieldNumber(null);
+    byte[] bytes = input.readByteArray();
+
+    input.readFieldNumber(null);
+
+    AnyEntry anyEntry = (AnyEntry) message;
+    anyEntry.setTypeUrl(typeUrl);
+    anyEntry.setValue(bytes);
+  }
+
+  protected String getInputActualTypeName(Object input) {
+    if (!(input instanceof Map)) {
+      return input.getClass().getSimpleName();
+    }
+
+    // @JsonTypeInfo(use = Id.NAME)
+    Object actualTypeName = ((Map<?, ?>) input).get(ProtoConst.JSON_ID_NAME);
+    if (actualTypeName != null && actualTypeName instanceof String) {
+      return (String) actualTypeName;
+    }
+
+    return null;
+  }
+
+  /**
+   * <pre>
+   * if message is type of CustomGeneric&lt;User&gt;
+   * we can not get any information of "User" from message.getClass()
+   *
+   * when use with ServiceComb
+   * proto definition convert from swagger, the proto type will be "CustomGenericUser"
+   * is not match to "CustomGeneric"
+   * so message will be serialized with json schema
+   * </pre>
+   * @param output
+   * @param message
+   * @throws IOException
+   */
+  @Override
+  public void writeTo(Output output, Object message) throws IOException {
+    String actualTypeName = getInputActualTypeName(message);
+    RootSerializer actualValueSerializer = protoMapper.findRootSerializer(actualTypeName);
+    if (actualValueSerializer != null) {
+      standardPack(output, message, actualValueSerializer);
+      return;
+    }
+
+    // not standard, protobuf can not support or not define this type , just extend
+    jsonExtend(output, message);
+  }
+
+  protected void standardPack(Output output, Object message, RootSerializer actualValueSerializer) throws IOException {
+    output.writeString(1,
+        ProtoConst.PACK_SCHEMA + actualValueSerializer.getSchema().getMessage().getCanonicalName(),
+        false);
+
+    byte[] bytes = actualValueSerializer.serialize(message);
+    output.writeByteArray(2, bytes, false);
+  }
+
+  protected void jsonExtend(Output output, Object input) throws IOException {
+    output.writeString(1, ProtoConst.JSON_SCHEMA + input.getClass().getName(), false);
+
+    byte[] bytes = protoMapper.getJsonMapper().writeValueAsBytes(input);
+    output.writeByteArray(2, bytes, false);
+  }
+}
diff --git a/foundations/foundation-protobuf/src/main/java/org/apache/servicecomb/foundation/protobuf/internal/schema/AnySchema.java b/foundations/foundation-protobuf/src/main/java/org/apache/servicecomb/foundation/protobuf/internal/schema/AnySchema.java
new file mode 100644
index 0000000..cae8c36
--- /dev/null
+++ b/foundations/foundation-protobuf/src/main/java/org/apache/servicecomb/foundation/protobuf/internal/schema/AnySchema.java
@@ -0,0 +1,102 @@
+/*
+ * 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.foundation.protobuf.internal.schema;
+
+import java.io.IOException;
+import java.util.Map;
+
+import org.apache.servicecomb.foundation.common.concurrent.ConcurrentHashMapEx;
+import org.apache.servicecomb.foundation.protobuf.ProtoMapper;
+import org.apache.servicecomb.foundation.protobuf.RootDeserializer;
+import org.apache.servicecomb.foundation.protobuf.internal.ProtoConst;
+
+import com.fasterxml.jackson.databind.JavaType;
+
+import io.protostuff.Input;
+import io.protostuff.Output;
+import io.protostuff.compiler.model.Field;
+import io.protostuff.compiler.model.Message;
+
+public class AnySchema extends FieldSchema {
+  private final ProtoMapper protoMapper;
+
+  private final AnyEntrySchema anyEntrySchema;
+
+  // key is message canonical name
+  private final Map<String, RootDeserializer> rootDeserializers = new ConcurrentHashMapEx<>();
+
+  public AnySchema(ProtoMapper protoMapper, Field protoField) {
+    super(protoField);
+    this.protoMapper = protoMapper;
+    this.anyEntrySchema = new AnyEntrySchema(protoMapper);
+  }
+
+  @Override
+  public Object readFrom(Input input) throws IOException {
+    AnyEntry anyEntry = (AnyEntry) input.mergeObject(null, anyEntrySchema);
+    if (anyEntry.getTypeUrl().startsWith(ProtoConst.PACK_SCHEMA)) {
+      return standardUnpack(anyEntry.getTypeUrl(), anyEntry.getValue());
+    }
+
+    return jsonExtendMergeFrom(anyEntry.getTypeUrl(), anyEntry.getValue());
+  }
+
+  @Override
+  public void mergeFrom(Input input, Object message) throws IOException {
+    Object anyValue = readFrom(input);
+    setter.set(message, anyValue);
+  }
+
+  @SuppressWarnings("unchecked")
+  protected Object standardUnpack(String typeUrl, byte[] bytes) throws IOException {
+    String msgCanonicalName = typeUrl.substring(ProtoConst.PACK_SCHEMA.length());
+    RootDeserializer valueDeserializer = rootDeserializers
+        .computeIfAbsent(msgCanonicalName, this::createRootDeserializerFromCanonicaName);
+    Object value = valueDeserializer.deserialize(bytes);
+    if (value instanceof Map) {
+      ((Map<String, Object>) value).put(ProtoConst.JSON_ID_NAME, valueDeserializer.getSchema().messageName());
+    }
+    return value;
+  }
+
+  protected RootDeserializer createRootDeserializerFromCanonicaName(String msgCanonicalName) {
+    Message message = protoMapper.getMessageFromCanonicaName(msgCanonicalName);
+    if (message == null) {
+      throw new IllegalStateException(
+          "can not find proto message to create deserializer, name=" + msgCanonicalName);
+    }
+
+    JavaType javaType = protoMapper.getAnyTypes().get(msgCanonicalName);
+    if (javaType == null) {
+      javaType = ProtoConst.MAP_TYPE;
+    }
+    return protoMapper.createRootDeserializer(javaType, message);
+  }
+
+  protected Object jsonExtendMergeFrom(String typeUrl, byte[] bytes) throws IOException {
+    return protoMapper.getJsonMapper().readValue(bytes, Object.class);
+  }
+
+  @Override
+  public void writeTo(Output output, Object value) throws IOException {
+    if (value == null) {
+      return;
+    }
+
+    output.writeObject(number, value, anyEntrySchema, repeated);
+  }
+}
diff --git a/foundations/foundation-protobuf/src/main/java/org/apache/servicecomb/foundation/protobuf/internal/schema/FieldSchema.java b/foundations/foundation-protobuf/src/main/java/org/apache/servicecomb/foundation/protobuf/internal/schema/FieldSchema.java
new file mode 100644
index 0000000..ad83bf0
--- /dev/null
+++ b/foundations/foundation-protobuf/src/main/java/org/apache/servicecomb/foundation/protobuf/internal/schema/FieldSchema.java
@@ -0,0 +1,114 @@
+/*
+ * 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.foundation.protobuf.internal.schema;
+
+import java.io.IOException;
+
+import org.apache.servicecomb.foundation.common.utils.bean.Getter;
+import org.apache.servicecomb.foundation.common.utils.bean.Setter;
+import org.apache.servicecomb.foundation.protobuf.internal.bean.BeanFactory;
+import org.apache.servicecomb.foundation.protobuf.internal.schema.serializer.ProtoStreamOutput;
+
+import com.google.common.annotations.VisibleForTesting;
+
+import io.protostuff.Input;
+import io.protostuff.Output;
+import io.protostuff.Pipe;
+import io.protostuff.compiler.model.Field;
+import io.protostuff.compiler.model.Type;
+
+public abstract class FieldSchema extends io.protostuff.runtime.Field<Object> {
+  protected final Field protoField;
+
+  protected Getter getter;
+
+  protected Setter setter;
+
+  protected BeanFactory factory;
+
+  public FieldSchema(Field protoField) {
+    super(ProtoSchemaUtils.convert(protoField.getType()),
+        protoField.getTag(),
+        protoField.getName(),
+        protoField.isRepeated(),
+        null);
+    this.protoField = protoField;
+  }
+
+  public Field getProtoField() {
+    return protoField;
+  }
+
+  public Getter getGetter() {
+    return getter;
+  }
+
+  public void setGetter(Getter getter) {
+    this.getter = getter;
+  }
+
+  public Setter getSetter() {
+    return setter;
+  }
+
+  public void setSetter(Setter setter) {
+    this.setter = setter;
+  }
+
+  public void setFactory(BeanFactory factory) {
+    this.factory = factory;
+  }
+
+  protected void throwNotSupportValue(Object value) throws IllegalStateException {
+    throw new IllegalStateException(
+        String.format("not support serialize from %s to proto %s, field=%s:%s",
+            value.getClass().getName(),
+            protoField.getTypeName(),
+            ((Type) protoField.getParent()).getCanonicalName(),
+            protoField.getName()));
+  }
+
+  @SuppressWarnings("unchecked")
+  protected <T> T getOrCreateFieldValue(Object message) {
+    Object value = getter.get(message);
+    if (value == null) {
+      value = this.factory.create();
+      setter.set(message, value);
+    }
+    return (T) value;
+  }
+
+  @Override
+  protected void transfer(Pipe pipe, Input input, Output output, boolean repeated) {
+    throw new UnsupportedOperationException();
+  }
+
+  public abstract Object readFrom(Input input) throws IOException;
+
+  @VisibleForTesting
+  public byte[] writeTo(Object message) throws IOException {
+    ProtoStreamOutput output = new ProtoStreamOutput();
+    writeTo(output, message);
+    return output.toBytes();
+  }
+
+  @Override
+  public abstract void writeTo(Output output, Object message) throws IOException;
+
+  @Override
+  public abstract void mergeFrom(Input input, Object message) throws IOException;
+}
diff --git a/foundations/foundation-protobuf/src/main/java/org/apache/servicecomb/foundation/protobuf/internal/schema/MapEntrySchema.java b/foundations/foundation-protobuf/src/main/java/org/apache/servicecomb/foundation/protobuf/internal/schema/MapEntrySchema.java
new file mode 100644
index 0000000..a969f25
--- /dev/null
+++ b/foundations/foundation-protobuf/src/main/java/org/apache/servicecomb/foundation/protobuf/internal/schema/MapEntrySchema.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.foundation.protobuf.internal.schema;
+
+import java.io.IOException;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import io.protostuff.CustomSchema;
+import io.protostuff.Input;
+import io.protostuff.Output;
+
+public class MapEntrySchema extends CustomSchema<Object> {
+  private final FieldSchema keySchema;
+
+  private final FieldSchema valueSchema;
+
+  public MapEntrySchema(FieldSchema keySchema, FieldSchema valueSchema) {
+    super(null);
+    this.keySchema = keySchema;
+    this.valueSchema = valueSchema;
+  }
+
+  @Override
+  public boolean isInitialized(Object message) {
+    return true;
+  }
+
+  @SuppressWarnings("unchecked")
+  @Override
+  public void mergeFrom(Input input, Object message) throws IOException {
+    input.readFieldNumber(null);
+    Object key = keySchema.readFrom(input);
+
+    input.readFieldNumber(null);
+    Object value = valueSchema.readFrom(input);
+
+    input.readFieldNumber(null);
+
+    ((Map<Object, Object>) message).put(key, value);
+  }
+
+  @SuppressWarnings("unchecked")
+  @Override
+  public void writeTo(Output output, Object message) throws IOException {
+    keySchema.writeTo(output, ((Entry<Object, Object>) message).getKey());
+    valueSchema.writeTo(output, ((Entry<Object, Object>) message).getValue());
+  }
+}
diff --git a/foundations/foundation-protobuf/src/main/java/org/apache/servicecomb/foundation/protobuf/internal/schema/MapSchema.java b/foundations/foundation-protobuf/src/main/java/org/apache/servicecomb/foundation/protobuf/internal/schema/MapSchema.java
new file mode 100644
index 0000000..bee0e4b
--- /dev/null
+++ b/foundations/foundation-protobuf/src/main/java/org/apache/servicecomb/foundation/protobuf/internal/schema/MapSchema.java
@@ -0,0 +1,62 @@
+/*
+ * 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.foundation.protobuf.internal.schema;
+
+import java.io.IOException;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import io.protostuff.Input;
+import io.protostuff.Output;
+import io.protostuff.compiler.model.Field;
+
+public class MapSchema extends FieldSchema {
+  private final MapEntrySchema entrySchema;
+
+  public MapSchema(Field protoField, FieldSchema keySchema, FieldSchema valueSchema) {
+    super(protoField);
+    this.entrySchema = new MapEntrySchema(keySchema, valueSchema);
+  }
+
+  @Override
+  public Object readFrom(Input input) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public void mergeFrom(Input input, Object message) throws IOException {
+    Map<Object, Object> map = getOrCreateFieldValue(message);
+    input.mergeObject(map, entrySchema);
+  }
+
+  @Override
+  public void writeTo(Output output, Object value) throws IOException {
+    if (value == null) {
+      return;
+    }
+
+    @SuppressWarnings("unchecked")
+    Map<Object, Object> map = (Map<Object, Object>) value;
+    for (Entry<Object, Object> entry : map.entrySet()) {
+      if (entry.getKey() == null || entry.getValue() == null) {
+        continue;
+      }
+
+      output.writeObject(number, entry, entrySchema, true);
+    }
+  }
+}
diff --git a/foundations/foundation-protobuf/src/main/java/org/apache/servicecomb/foundation/protobuf/internal/schema/MessageAsFieldSchema.java b/foundations/foundation-protobuf/src/main/java/org/apache/servicecomb/foundation/protobuf/internal/schema/MessageAsFieldSchema.java
new file mode 100644
index 0000000..6ab4b8e
--- /dev/null
+++ b/foundations/foundation-protobuf/src/main/java/org/apache/servicecomb/foundation/protobuf/internal/schema/MessageAsFieldSchema.java
@@ -0,0 +1,50 @@
+/*
+ * 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.foundation.protobuf.internal.schema;
+
+import java.io.IOException;
+
+import io.protostuff.Input;
+import io.protostuff.Output;
+import io.protostuff.Schema;
+import io.protostuff.compiler.model.Field;
+
+public class MessageAsFieldSchema extends FieldSchema {
+  private Schema<Object> schema;
+
+  public MessageAsFieldSchema(Field protoField, Schema<Object> schema) {
+    super(protoField);
+    this.schema = schema;
+  }
+
+  @Override
+  public void writeTo(Output output, Object value) throws IOException {
+    output.writeObject(number, value, schema, false);
+  }
+
+  @Override
+  public Object readFrom(Input input) throws IOException {
+    return input.mergeObject(null, schema);
+  }
+
+  @Override
+  public void mergeFrom(Input input, Object message) throws IOException {
+    Object existing = getter.get(message);
+    Object fieldValue = input.mergeObject(existing, schema);
+    setter.set(message, fieldValue);
+  }
+}
diff --git a/foundations/foundation-protobuf/src/main/java/org/apache/servicecomb/foundation/protobuf/internal/schema/RepeatedSchema.java b/foundations/foundation-protobuf/src/main/java/org/apache/servicecomb/foundation/protobuf/internal/schema/RepeatedSchema.java
new file mode 100644
index 0000000..fad4564
--- /dev/null
+++ b/foundations/foundation-protobuf/src/main/java/org/apache/servicecomb/foundation/protobuf/internal/schema/RepeatedSchema.java
@@ -0,0 +1,69 @@
+/*
+ * 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.foundation.protobuf.internal.schema;
+
+import java.io.IOException;
+import java.util.Collection;
+
+import io.protostuff.Input;
+import io.protostuff.Output;
+import io.protostuff.compiler.model.Field;
+
+public class RepeatedSchema extends FieldSchema {
+  private final FieldSchema elementSchema;
+
+  public RepeatedSchema(Field protoField, FieldSchema elementSchema) {
+    super(protoField);
+    this.elementSchema = elementSchema;
+  }
+
+  @Override
+  public void writeTo(Output output, Object value) throws IOException {
+    if (value == null) {
+      return;
+    }
+
+    if (value instanceof Collection) {
+      @SuppressWarnings("unchecked")
+      Collection<Object> list = (Collection<Object>) value;
+      for (Object element : list) {
+        elementSchema.writeTo(output, element);
+      }
+      return;
+    }
+
+    if (value.getClass().isArray()) {
+      for (Object element : (Object[]) value) {
+        elementSchema.writeTo(output, element);
+      }
+      return;
+    }
+
+    throwNotSupportValue(value);
+  }
+
+  @Override
+  public Object readFrom(Input input) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public void mergeFrom(Input input, Object message) throws IOException {
+    Collection<Object> collection = getOrCreateFieldValue(message);
+    collection.add(elementSchema.readFrom(input));
+  }
+}
diff --git a/foundations/foundation-protobuf/src/test/java/org/apache/servicecomb/foundation/protobuf/internal/schema/TestAnySchema.java b/foundations/foundation-protobuf/src/test/java/org/apache/servicecomb/foundation/protobuf/internal/schema/TestAnySchema.java
new file mode 100644
index 0000000..fb2868e
--- /dev/null
+++ b/foundations/foundation-protobuf/src/test/java/org/apache/servicecomb/foundation/protobuf/internal/schema/TestAnySchema.java
@@ -0,0 +1,102 @@
+/*
+ * 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.foundation.protobuf.internal.schema;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.servicecomb.foundation.protobuf.RootDeserializer;
+import org.apache.servicecomb.foundation.protobuf.internal.TestSchemaBase;
+import org.apache.servicecomb.foundation.protobuf.internal.model.ProtobufRoot;
+import org.apache.servicecomb.foundation.protobuf.internal.model.Root;
+import org.hamcrest.Matchers;
+import org.junit.Assert;
+import org.junit.Test;
+
+import com.google.protobuf.Any;
+
+public class TestAnySchema extends TestSchemaBase {
+  public TestAnySchema() {
+    initField("any");
+  }
+
+  @Test
+  public void empty() throws Throwable {
+    Assert.assertEquals(0, serFieldSchema.writeTo(null).length);
+  }
+
+  @Test
+  public void anys_pack() throws IOException {
+    builder
+        .addAnys(Any.pack(ProtobufRoot.User.newBuilder().setName("n1").build()))
+        .addAnys(Any.pack(ProtobufRoot.User.newBuilder().setName("n2").build()));
+    check();
+  }
+
+  @Test
+  public void anys_json() throws IOException {
+    Root root = new Root();
+    root.setAnys(Arrays.asList("abc", "123"));
+
+    scbRootBytes = rootSerializer.serialize(root);
+    root = rootDeserializer.deserialize(scbRootBytes);
+    Assert.assertThat(root.getAnys(), Matchers.contains("abc", "123"));
+  }
+
+  @Test
+  public void pack() throws Throwable {
+    builder.setAny(Any.pack(ProtobufRoot.User.newBuilder().setName("n1").build()));
+    check();
+
+    Map<String, Object> map = new HashMap<>();
+    map.put("@type", "User");
+    map.put("name", "n1");
+    Root root = new Root();
+    root.setAny(map);
+    Assert.assertArrayEquals(protobufBytes, rootSerializer.serialize(root));
+  }
+
+  @SuppressWarnings("unchecked")
+  @Test
+  public void json_fromMapWithoutType() throws Throwable {
+    Map<String, Object> map = new HashMap<>();
+    map.put("name", "n1");
+    Root root = new Root();
+    root.setAny(map);
+
+    scbRootBytes = rootSerializer.serialize(root);
+    root = rootDeserializer.deserialize(scbRootBytes);
+    Assert.assertThat(root.getAny(), Matchers.instanceOf(Map.class));
+    Assert.assertThat((Map<? extends String, ? extends String>) root.getAny(), Matchers.hasEntry("name", "n1"));
+
+    RootDeserializer deserializer = protoMapper.createRootDeserializer(Map.class, "Root");
+    map = deserializer.deserialize(scbRootBytes);
+    Assert.assertThat((Map<? extends String, ? extends String>) map.get("any"), Matchers.hasEntry("name", "n1"));
+  }
+
+  @Test
+  public void json() throws Throwable {
+    Root root = new Root();
+    root.setAny("abc");
+
+    scbRootBytes = rootSerializer.serialize(root);
+    root = rootDeserializer.deserialize(scbRootBytes);
+    Assert.assertEquals("abc", root.getAny());
+  }
+}
diff --git a/foundations/foundation-protobuf/src/test/java/org/apache/servicecomb/foundation/protobuf/internal/schema/TestMapSchema.java b/foundations/foundation-protobuf/src/test/java/org/apache/servicecomb/foundation/protobuf/internal/schema/TestMapSchema.java
new file mode 100644
index 0000000..cdd9e52
--- /dev/null
+++ b/foundations/foundation-protobuf/src/test/java/org/apache/servicecomb/foundation/protobuf/internal/schema/TestMapSchema.java
@@ -0,0 +1,44 @@
+/*
+ * 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.foundation.protobuf.internal.schema;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.servicecomb.foundation.protobuf.internal.TestSchemaBase;
+import org.apache.servicecomb.foundation.protobuf.internal.model.ProtobufRoot;
+import org.junit.Test;
+
+public class TestMapSchema extends TestSchemaBase {
+  @Test
+  public void ssMap() throws Throwable {
+    Map<String, String> ssMap = new HashMap<>();
+    ssMap.put("k1", "v1");
+    ssMap.put("k2", "v2");
+    builder.putAllSsMap(ssMap);
+
+    check();
+  }
+
+  @Test
+  public void spMap() throws Throwable {
+    builder.putSpMap("k1", ProtobufRoot.User.newBuilder().setName("n1").build());
+    builder.putSpMap("k2", ProtobufRoot.User.newBuilder().setName("n2").build());
+
+    check();
+  }
+}
diff --git a/foundations/foundation-protobuf/src/test/java/org/apache/servicecomb/foundation/protobuf/internal/schema/TestMessageSchema.java b/foundations/foundation-protobuf/src/test/java/org/apache/servicecomb/foundation/protobuf/internal/schema/TestMessageSchema.java
new file mode 100644
index 0000000..8da3a5d
--- /dev/null
+++ b/foundations/foundation-protobuf/src/test/java/org/apache/servicecomb/foundation/protobuf/internal/schema/TestMessageSchema.java
@@ -0,0 +1,76 @@
+/*
+ * 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.foundation.protobuf.internal.schema;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import org.apache.servicecomb.foundation.protobuf.RootDeserializer;
+import org.apache.servicecomb.foundation.protobuf.internal.TestSchemaBase;
+import org.apache.servicecomb.foundation.protobuf.internal.model.CustomGeneric;
+import org.apache.servicecomb.foundation.protobuf.internal.model.ProtobufRoot;
+import org.apache.servicecomb.foundation.protobuf.internal.model.User;
+import org.hamcrest.Matchers;
+import org.junit.Assert;
+import org.junit.Test;
+
+import com.fasterxml.jackson.databind.JavaType;
+import com.fasterxml.jackson.databind.type.TypeFactory;
+
+public class TestMessageSchema extends TestSchemaBase {
+  @Test
+  public void empty() throws Throwable {
+    check();
+
+    Assert.assertArrayEquals(protobufBytes, rootSerializer.serialize(null));
+  }
+
+  @Test
+  public void generic() throws Throwable {
+    JavaType javaType = TypeFactory.defaultInstance().constructParametricType(CustomGeneric.class, User.class);
+    RootDeserializer genericDeserializer = protoMapper.createRootDeserializer(javaType, "Root");
+
+    builder.setUser(ProtobufRoot.User.newBuilder().setName("name1").build());
+    check(genericDeserializer, mapRootDeserializer, rootSerializer, false);
+
+    @SuppressWarnings("unchecked")
+    CustomGeneric<User> generic = (CustomGeneric<User>) scbRoot;
+    Assert.assertThat(generic.user, Matchers.instanceOf(User.class));
+  }
+
+  @Test
+  public void normal() throws Throwable {
+    builder.setString("abc");
+    builder.setInt64(1L);
+    builder.setUser(ProtobufRoot.User.newBuilder().setName("name").build());
+
+    check();
+
+    // map
+    Map<String, Object> map = new LinkedHashMap<>();
+    map.put("int64", 1);
+    map.put("string", "abc");
+
+    Map<String, Object> userMap = new LinkedHashMap<>();
+    userMap.put("name", "name");
+    map.put("user", userMap);
+
+    map.put("notExist", null);
+
+    Assert.assertArrayEquals(protobufBytes, rootSerializer.serialize(map));
+  }
+}
diff --git a/foundations/foundation-protobuf/src/test/java/org/apache/servicecomb/foundation/protobuf/internal/schema/TestRepeatedSchema.java b/foundations/foundation-protobuf/src/test/java/org/apache/servicecomb/foundation/protobuf/internal/schema/TestRepeatedSchema.java
new file mode 100644
index 0000000..48e4c05
--- /dev/null
+++ b/foundations/foundation-protobuf/src/test/java/org/apache/servicecomb/foundation/protobuf/internal/schema/TestRepeatedSchema.java
@@ -0,0 +1,50 @@
+/*
+ * 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.foundation.protobuf.internal.schema;
+
+import java.util.Arrays;
+import java.util.List;
+
+import org.apache.servicecomb.foundation.protobuf.internal.TestSchemaBase;
+import org.apache.servicecomb.foundation.protobuf.internal.model.ProtobufRoot;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class TestRepeatedSchema extends TestSchemaBase {
+  public static class RootWithArray {
+    public String[] sList;
+  }
+
+  @Test
+  public void sList() throws Throwable {
+    List<String> sList = Arrays.asList("v1", "v2");
+    builder.addAllSList(sList);
+    check();
+
+    RootWithArray rootWithArray = new RootWithArray();
+    rootWithArray.sList = (String[]) sList.toArray();
+    Assert.assertArrayEquals(protobufBytes, rootSerializer.serialize(rootWithArray));
+  }
+
+  @Test
+  public void pList() throws Throwable {
+    builder.addPList(ProtobufRoot.User.newBuilder().setName("name1").build());
+    builder.addPList(ProtobufRoot.User.newBuilder().setName("name2").build());
+
+    check();
+  }
+}