| /* |
| * 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); |
| } |