blob: 49b2154cbbcd1a24ad20fd64d41afc066a773069 [file] [log] [blame]
/*
* 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.serializer;
import java.io.IOException;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.apache.servicecomb.foundation.common.concurrent.ConcurrentHashMapEx;
import org.apache.servicecomb.foundation.protobuf.ProtoMapper;
import org.apache.servicecomb.foundation.protobuf.internal.ProtoUtils;
import org.apache.servicecomb.foundation.protobuf.internal.bean.BeanDescriptor;
import org.apache.servicecomb.foundation.protobuf.internal.bean.PropertyDescriptor;
import com.fasterxml.jackson.databind.JavaType;
import io.protostuff.InputEx;
import io.protostuff.OutputEx;
import io.protostuff.SchemaEx;
import io.protostuff.compiler.model.Field;
import io.protostuff.compiler.model.Message;
import io.protostuff.runtime.FieldMapEx;
import io.protostuff.runtime.FieldSchema;
/**
* <pre>
* map.put("user", new User())
* root write from map, but user should write from pojo
* so one schema should support dynamic and concrete logic at the same time
* </pre>
*/
public class MessageWriteSchema<T> implements SchemaEx<T> {
protected final ProtoMapper protoMapper;
protected final Message message;
private final JavaType javaType;
// mostly, one message only relate to one pojo
private final Class<T> mainPojoCls;
private FieldMapEx<T> mainPojoFieldMaps;
private FieldMapEx<Map<Object, Object>> mapFieldMaps;
// if not equals to mainPojoCls, then will find from pojoFieldMaps
private final Map<Class<?>, FieldMapEx<?>> pojoFieldMaps = new ConcurrentHashMapEx<>();
@SuppressWarnings("unchecked")
public MessageWriteSchema(ProtoMapper protoMapper, Message message, JavaType javaType) {
this.protoMapper = protoMapper;
this.message = message;
this.javaType = javaType;
this.mainPojoCls = (Class<T>) javaType.getRawClass();
}
public Message getMessage() {
return message;
}
@Override
public T newMessage() {
throw new UnsupportedOperationException();
}
@Override
public String messageName() {
return message.getName();
}
public JavaType getJavaType() {
return javaType;
}
public Class<T> getMainPojoCls() {
return mainPojoCls;
}
public FieldMapEx<T> getMainPojoFieldMaps() {
return mainPojoFieldMaps;
}
@Override
public void init() {
if (ProtoUtils.isWrapProperty(message)) {
this.mainPojoFieldMaps = createPropertyWrapperFields(javaType);
return;
}
this.mainPojoFieldMaps = createPojoFields(javaType);
}
private FieldMapEx<T> createPropertyWrapperFields(JavaType javaType) {
Field protoField = message.getField(1);
PropertyDescriptor propertyDescriptor = new PropertyDescriptor();
propertyDescriptor.setName(protoField.getName());
propertyDescriptor.setJavaType(javaType);
FieldSchema<T> fieldSchema = protoMapper.getSerializerSchemaManager()
.createSchemaField(protoField, propertyDescriptor);
return FieldMapEx.createFieldMap(Arrays.asList(fieldSchema));
}
private FieldMapEx<T> createPojoFields(Type type) {
SerializerSchemaManager serializerSchemaManager = protoMapper.getSerializerSchemaManager();
BeanDescriptor beanDescriptor = protoMapper.getBeanDescriptorManager().getOrCreateBeanDescriptor(type);
List<FieldSchema<T>> fieldSchemas = new ArrayList<>();
for (Field protoField : message.getFields()) {
PropertyDescriptor propertyDescriptor = beanDescriptor.getPropertyDescriptors().get(protoField.getName());
if (propertyDescriptor == null) {
continue;
}
Object getter = propertyDescriptor.getGetter();
if (getter == null) {
continue;
}
FieldSchema<T> fieldSchema = serializerSchemaManager.createSchemaField(protoField, propertyDescriptor);
fieldSchemas.add(fieldSchema);
}
return FieldMapEx.createFieldMap(fieldSchemas);
}
@SuppressWarnings("unchecked")
@Override
public void writeTo(OutputEx output, Object value) throws IOException {
if (value instanceof Map) {
writeFromMap(output, (Map<String, Object>) value);
return;
}
if (mainPojoCls == value.getClass()) {
writeFromMainPojo(output, (T) value);
return;
}
writeDynamicPojo(output, value);
}
private void writeFromMainPojo(OutputEx output, T value) throws IOException {
for (FieldSchema<T> fieldSchema : mainPojoFieldMaps.getFields()) {
fieldSchema.getAndWriteTo(output, value);
}
}
@SuppressWarnings("unchecked")
private <T> void writeDynamicPojo(OutputEx output, Object dynamicValue) throws IOException {
FieldMapEx<T> fieldMapEx = (FieldMapEx<T>) this.pojoFieldMaps
.computeIfAbsent(dynamicValue.getClass(), this::createPojoFields);
T value = (T) dynamicValue;
for (FieldSchema<T> fieldSchema : fieldMapEx.getFields()) {
fieldSchema.getAndWriteTo(output, value);
}
}
protected final void writeFromMap(OutputEx output, Map<String, Object> map) throws IOException {
if (mapFieldMaps == null) {
mapFieldMaps = protoMapper.getSerializerSchemaManager().createMapFields(message);
}
for (Entry<String, Object> entry : map.entrySet()) {
if (entry.getValue() == null) {
continue;
}
FieldSchema<Map<Object, Object>> fieldSchema = mapFieldMaps.getFieldByName(entry.getKey());
if (fieldSchema != null) {
fieldSchema.writeTo(output, entry.getValue());
}
}
}
@Override
public void mergeFrom(InputEx input, T message) throws IOException {
throw new UnsupportedOperationException();
}
}