blob: 7bec69d767741cb21eb0b55b3d72f0a3e5884995 [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.config.priority;
import static org.apache.servicecomb.foundation.common.utils.LambdaMetafactoryUtils.createObjectSetter;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.apache.servicecomb.config.inject.InjectProperties;
import org.apache.servicecomb.config.inject.InjectProperty;
import org.apache.servicecomb.config.inject.PlaceholderResolver;
import org.apache.servicecomb.foundation.common.concurrent.ConcurrentHashMapEx;
import org.apache.servicecomb.foundation.common.utils.JsonUtils;
import org.apache.servicecomb.foundation.common.utils.LambdaMetafactoryUtils;
import org.apache.servicecomb.foundation.common.utils.bean.Setter;
import org.springframework.stereotype.Component;
import com.fasterxml.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.introspect.BeanPropertyDefinition;
import com.fasterxml.jackson.databind.type.TypeFactory;
/**
* must create by PriorityPropertyManager<br>
* or register to PriorityPropertyManager manually<br>
* <br>
* ${} or ${not-exist-key} is valid key in archaius<br>
* so this wrapper mechanism will not throw exception even can not find value by placeholder
*/
@Component
public class ConfigObjectFactory {
private final PriorityPropertyFactory propertyFactory;
private static final Map<Class<?>, JavaType> classCache = new ConcurrentHashMapEx<>();
private static final Map<JavaType, BeanDescription> javaTypeCache = new ConcurrentHashMapEx<>();
private static final Map<BeanPropertyDefinition, Setter<Object, Object>> beanDescriptionCache = new ConcurrentHashMapEx<>();
public ConfigObjectFactory(PriorityPropertyFactory propertyFactory) {
this.propertyFactory = propertyFactory;
}
public PriorityPropertyFactory getPropertyFactory() {
return propertyFactory;
}
public <T> ConfigObject<T> create(Class<T> cls, Map<String, Object> parameters) {
try {
return create(cls.getDeclaredConstructor().newInstance(), parameters);
} catch (Throwable e) {
throw new IllegalStateException("create config object failed, class=" + cls.getName(), e);
}
}
public <T> ConfigObject<T> create(T instance, Map<String, Object> parameters) {
String prefix = initPrefix(instance.getClass());
List<ConfigObjectProperty> properties = createProperties(instance, prefix, parameters);
return new ConfigObject<>(instance, properties);
}
private String initPrefix(Class<?> cls) {
InjectProperties injectProperties = cls.getAnnotation(InjectProperties.class);
if (injectProperties == null) {
return "";
}
String prefix = injectProperties.prefix();
if (prefix.isEmpty()) {
return "";
}
return prefix + ".";
}
public List<ConfigObjectProperty> createProperties(Object instance, String prefix, Map<String, Object> parameters) {
List<ConfigObjectProperty> properties = new ArrayList<>();
JavaType javaType = classCache.computeIfAbsent(instance.getClass(), TypeFactory.defaultInstance()::constructType);
BeanDescription beanDescription = javaTypeCache.computeIfAbsent(javaType,
JsonUtils.OBJ_MAPPER.getSerializationConfig()::introspect);
for (BeanPropertyDefinition propertyDefinition : beanDescription.findProperties()) {
if (propertyDefinition.getField() == null) {
continue;
}
if (propertyDefinition.getSetter() == null && !propertyDefinition.getField().isPublic()) {
continue;
}
Setter<Object, Object> setter = beanDescriptionCache.computeIfAbsent(propertyDefinition,
LambdaMetafactoryUtils::createObjectSetter);
PriorityProperty<?> priorityProperty = createPriorityProperty(propertyDefinition.getField().getAnnotated(),
prefix, parameters);
setter.set(instance, priorityProperty.getValue());
properties.add(new ConfigObjectProperty(setter, priorityProperty));
}
return properties;
}
private PriorityProperty<?> createPriorityProperty(Field field, String prefix, Map<String, Object> parameters) {
String[] keys = collectPropertyKeys(field, prefix, parameters);
Class<?> fieldCls = field.getType();
switch (fieldCls.getName()) {
case "int":
return createIntProperty(field, keys, 0);
case "java.lang.Integer":
return createIntProperty(field, keys, null);
case "long":
return createLongProperty(field, keys, 0L);
case "java.lang.Long":
return createLongProperty(field, keys, null);
case "java.lang.String":
return createStringProperty(field, keys);
case "float":
return createFloatProperty(field, keys, 0f);
case "java.lang.Float":
return createFloatProperty(field, keys, null);
case "double":
return createDoubleProperty(field, keys, 0.0);
case "java.lang.Double":
return createDoubleProperty(field, keys, null);
case "boolean":
return createBooleanProperty(field, keys, false);
case "java.lang.Boolean":
return createBooleanProperty(field, keys, null);
default:
throw new IllegalStateException("not support, field=" + field);
}
}
private PriorityProperty<?> createStringProperty(Field field, String[] keys) {
String defaultValue = null;
InjectProperty injectProperty = field.getAnnotation(InjectProperty.class);
if (injectProperty != null) {
if (!injectProperty.defaultValue().isEmpty()) {
defaultValue = injectProperty.defaultValue();
}
}
return propertyFactory.getOrCreate(String.class, null, defaultValue, keys);
}
private PriorityProperty<?> createDoubleProperty(Field field, String[] keys, Double defaultValue) {
InjectProperty injectProperty = field.getAnnotation(InjectProperty.class);
if (injectProperty != null) {
if (!injectProperty.defaultValue().isEmpty()) {
defaultValue = Double.parseDouble(injectProperty.defaultValue());
}
}
return propertyFactory.getOrCreate(Double.class, null, defaultValue, keys);
}
private PriorityProperty<?> createFloatProperty(Field field, String[] keys, Float defaultValue) {
InjectProperty injectProperty = field.getAnnotation(InjectProperty.class);
if (injectProperty != null) {
if (!injectProperty.defaultValue().isEmpty()) {
defaultValue = Float.parseFloat(injectProperty.defaultValue());
}
}
return propertyFactory.getOrCreate(Float.class, null, defaultValue, keys);
}
private PriorityProperty<?> createBooleanProperty(Field field, String[] keys, Boolean defaultValue) {
InjectProperty injectProperty = field.getAnnotation(InjectProperty.class);
if (injectProperty != null) {
if (!injectProperty.defaultValue().isEmpty()) {
defaultValue = Boolean.parseBoolean(injectProperty.defaultValue());
}
}
return propertyFactory.getOrCreate(Boolean.class, null, defaultValue, keys);
}
private PriorityProperty<?> createLongProperty(Field field, String[] keys, Long defaultValue) {
InjectProperty injectProperty = field.getAnnotation(InjectProperty.class);
if (injectProperty != null) {
if (!injectProperty.defaultValue().isEmpty()) {
defaultValue = Long.parseLong(injectProperty.defaultValue());
}
}
return propertyFactory.getOrCreate(Long.class, null, defaultValue, keys);
}
private PriorityProperty<?> createIntProperty(Field field, String[] keys, Integer defaultValue) {
InjectProperty injectProperty = field.getAnnotation(InjectProperty.class);
if (injectProperty != null) {
if (!injectProperty.defaultValue().isEmpty()) {
defaultValue = Integer.parseInt(injectProperty.defaultValue());
}
}
return propertyFactory.getOrCreate(Integer.class, null, defaultValue, keys);
}
private String[] collectPropertyKeys(Field field, String prefix, Map<String, Object> parameters) {
String propertyPrefix = prefix;
String[] keys = new String[]{field.getName()};
InjectProperty injectProperty = field.getAnnotation(InjectProperty.class);
if (injectProperty != null) {
if (!injectProperty.prefix().isEmpty()) {
propertyPrefix = injectProperty.prefix() + ".";
}
if (injectProperty.keys().length != 0) {
keys = injectProperty.keys();
}
}
List<String> finalKeys = new ArrayList<>();
for (String key : keys) {
List<String> resolvedKeys = new PlaceholderResolver().replace(propertyPrefix + key, parameters);
finalKeys.addAll(resolvedKeys);
}
return finalKeys.toArray(new String[0]);
}
}