blob: 3beaa0074fb459e3c7155b7fc44835e0e6fdf610 [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;
import static org.apache.servicecomb.foundation.protobuf.internal.ProtoUtils.isAnyField;
import static org.apache.servicecomb.foundation.protobuf.internal.ProtoUtils.isWrapProperty;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.apache.commons.lang3.ClassUtils;
import org.apache.servicecomb.foundation.common.concurrent.ConcurrentHashMapEx;
import org.apache.servicecomb.foundation.common.utils.bean.MapGetter;
import org.apache.servicecomb.foundation.common.utils.bean.MapSetter;
import org.apache.servicecomb.foundation.protobuf.ProtoMapper;
import org.apache.servicecomb.foundation.protobuf.internal.ProtoConst;
import org.apache.servicecomb.foundation.protobuf.internal.ProtoUtils;
import org.apache.servicecomb.foundation.protobuf.internal.bean.PropertyDescriptor;
import org.apache.servicecomb.foundation.protobuf.internal.schema.any.AnySchema;
import org.apache.servicecomb.foundation.protobuf.internal.schema.map.MapEntry;
import org.apache.servicecomb.foundation.protobuf.internal.schema.map.MapSchema;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.type.TypeFactory;
import io.protostuff.SchemaEx;
import io.protostuff.compiler.model.Field;
import io.protostuff.compiler.model.Message;
import io.protostuff.compiler.model.Proto;
import io.protostuff.runtime.FieldMapEx;
import io.protostuff.runtime.FieldSchema;
public abstract class SchemaManager {
protected final ProtoMapper protoMapper;
protected final Proto proto;
// key is canonical message name + ":" + canonical type name
protected final Map<String, SchemaEx<?>> canonicalSchemas = new ConcurrentHashMapEx<>();
public SchemaManager(ProtoMapper protoMapper) {
this.protoMapper = protoMapper;
this.proto = protoMapper.getProto();
}
public Map<String, SchemaEx<?>> getCanonicalSchemas() {
return canonicalSchemas;
}
protected String generateCacheKey(Message message, JavaType javaType) {
return message.getCanonicalName() + ":" + javaType.toCanonical();
}
protected abstract <T> SchemaEx<T> newMessageSchema(Message message, JavaType javaType);
protected abstract <T> SchemaEx<T> newMessageSchema(Message message, Map<String, Type> types);
/**
*
* @param protoField
* @param propertyDescriptor provide getter/setter/javaType
* @return
*/
protected abstract <T> FieldSchema<T> createScalarField(Field protoField, PropertyDescriptor propertyDescriptor);
@SuppressWarnings("unchecked")
protected <T> SchemaEx<T> getOrCreateMessageSchema(Message message, Map<String, Type> types) {
String cacheKey = generateCacheKey(message, ProtoConst.MAP_TYPE);
SchemaEx<T> messageSchema = (SchemaEx<T>) canonicalSchemas.get(cacheKey);
if (messageSchema == null) {
// messageSchema already put into canonicalSchemas inside createMessageSchema
messageSchema = createMessageSchema(message, types);
}
return messageSchema;
}
@SuppressWarnings("unchecked")
protected <T> SchemaEx<T> getOrCreateMessageSchema(Message message, JavaType javaType) {
String cacheKey = generateCacheKey(message, javaType);
SchemaEx<T> messageSchema = (SchemaEx<T>) canonicalSchemas.get(cacheKey);
if (messageSchema == null) {
// messageSchema already put into canonicalSchemas inside createMessageSchema
messageSchema = createMessageSchema(message, javaType);
}
return messageSchema;
}
@SuppressWarnings("unchecked")
protected <T> SchemaEx<T> findSchema(String key) {
return (SchemaEx<T>) canonicalSchemas.get(key);
}
protected <T> SchemaEx<T> createMessageSchema(Message message, Map<String, Type> types) {
String cacheKey = generateCacheKey(message, ProtoConst.MAP_TYPE);
SchemaEx<T> schema = findSchema(cacheKey);
if (schema != null) {
return schema;
}
schema = newMessageSchema(message, types);
canonicalSchemas.put(cacheKey, schema);
schema.init();
return schema;
}
protected <T> SchemaEx<T> createMessageSchema(Message message, JavaType javaType) {
String cacheKey = generateCacheKey(message, javaType);
SchemaEx<T> schema = findSchema(cacheKey);
if (schema != null) {
return schema;
}
schema = newMessageSchema(message, javaType);
canonicalSchemas.put(cacheKey, schema);
schema.init();
return schema;
}
protected <T> FieldSchema<T> createMapFieldSchema(Field protoField, PropertyDescriptor propertyDescriptor) {
JavaType javaType = propertyDescriptor.getJavaType();
if (javaType.isJavaLangObject()) {
javaType = ProtoConst.MAP_TYPE;
}
JavaType entryType = TypeFactory.defaultInstance().constructParametricType(MapEntry.class,
javaType.getKeyType(),
javaType.getContentType());
SchemaEx<Entry<Object, Object>> entrySchema = getOrCreateMessageSchema((Message) protoField.getType(),
entryType);
return new MapSchema<>(protoField, propertyDescriptor, entrySchema);
}
// normal message write from or read to a map
public FieldMapEx<Map<Object, Object>> createMapFields(Message message) {
List<FieldSchema<Map<Object, Object>>> fieldSchemas = new ArrayList<>();
for (Field protoField : message.getFields()) {
PropertyDescriptor propertyDescriptor = new PropertyDescriptor();
propertyDescriptor.setJavaType(ProtoConst.OBJECT_TYPE);
propertyDescriptor.setGetter(new MapGetter<>(protoField.getName()));
propertyDescriptor.setSetter(new MapSetter<>(protoField.getName()));
FieldSchema<Map<Object, Object>> fieldSchema = createSchemaField(protoField, propertyDescriptor);
fieldSchemas.add(fieldSchema);
}
return FieldMapEx.createFieldMap(fieldSchemas);
}
public FieldMapEx<Map<Object, Object>> createMapFields(Message message, Map<String, Type> types) {
List<FieldSchema<Map<Object, Object>>> fieldSchemas = new ArrayList<>();
for (Field protoField : message.getFields()) {
PropertyDescriptor propertyDescriptor = new PropertyDescriptor();
JavaType javaType = getParameterType(types, protoField.getName());
if (javaType.isPrimitive()) {
javaType = TypeFactory.defaultInstance().constructType(ClassUtils.primitiveToWrapper(javaType.getRawClass()));
}
propertyDescriptor.setJavaType(javaType);
propertyDescriptor.setGetter(new MapGetter<>(protoField.getName()));
propertyDescriptor.setSetter(new MapSetter<>(protoField.getName()));
FieldSchema<Map<Object, Object>> fieldSchema = createSchemaField(protoField, propertyDescriptor);
fieldSchemas.add(fieldSchema);
}
return FieldMapEx.createFieldMap(fieldSchemas);
}
private JavaType getParameterType(Map<String, Type> types, String perameterName) {
if (types.get(perameterName) != null) {
return TypeFactory.defaultInstance().constructType(types.get(perameterName));
}
throw new IllegalArgumentException(
String.format("not found type info for parameter name [%s]", perameterName));
}
public <T> FieldSchema<T> createSchemaField(Field protoField, PropertyDescriptor propertyDescriptor) {
// map is a special repeated
if (protoField.isMap()) {
return createMapFieldSchema(protoField, propertyDescriptor);
}
if (protoField.isRepeated()) {
return createRepeatedSchema(protoField, propertyDescriptor);
}
if (isAnyField(protoField)) {
return new AnySchema<>(protoMapper, protoField, propertyDescriptor);
}
if (protoField.getType().isScalar()) {
return createScalarField(protoField, propertyDescriptor);
}
// message
if (protoField.getType().isMessage()) {
SchemaEx<Object> messageSchema = getOrCreateMessageSchema((Message) protoField.getType(),
propertyDescriptor.getJavaType());
if (isWrapProperty((Message) protoField.getType())) {
return new PropertyWrapperAsFieldSchema<>(protoField, propertyDescriptor, messageSchema);
}
return new MessageAsFieldSchema<>(protoField, propertyDescriptor, messageSchema);
}
if (protoField.isOneofPart()) {
throw new IllegalStateException("not IMPL oneof now.");
}
ProtoUtils.throwNotSupportWrite(protoField, propertyDescriptor.getJavaType().getRawClass());
return null;
}
protected abstract <T> FieldSchema<T> createRepeatedSchema(Field protoField, PropertyDescriptor propertyDescriptor);
}