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<User>
+ * 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();
+ }
+}