Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement parsing time based on pattern for @ApolloJsonValue #53

Merged
merged 9 commits into from
Jan 23, 2024
1 change: 1 addition & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ Apollo Java 2.3.0

------------------
* [add an initialize method to avoid DefaultProviderManager's logic being triggered when using custom ProviderManager.](https://github.com/apolloconfig/apollo-java/pull/50)
* [Implement parsing time based on pattern for @ApolloJsonValue](https://github.com/apolloconfig/apollo-java/pull/53)

------------------
All issues and pull requests are [here](https://github.com/apolloconfig/apollo-java/milestone/3?closed=1)
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import com.ctrip.framework.apollo.ConfigChangeListener;
import com.ctrip.framework.apollo.ConfigService;
import com.ctrip.framework.apollo.build.ApolloInjector;
import com.ctrip.framework.apollo.core.utils.StringUtils;
import com.ctrip.framework.apollo.model.ConfigChangeEvent;
import com.ctrip.framework.apollo.spring.property.PlaceholderHelper;
import com.ctrip.framework.apollo.spring.property.SpringValue;
Expand All @@ -33,9 +34,13 @@
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import javax.annotation.Nullable;

import com.google.gson.GsonBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
Expand All @@ -61,7 +66,7 @@ public class ApolloAnnotationProcessor extends ApolloProcessor implements BeanFa

private static final Splitter NAMESPACE_SPLITTER = Splitter.on(NAMESPACE_DELIMITER)
.omitEmptyStrings().trimResults();
private static final Gson GSON = new Gson();
private static final Map<String, Gson> DATEPATTERN_GSON_MAP = new HashMap<>();

private final ConfigUtil configUtil;
private final PlaceholderHelper placeholderHelper;
Expand Down Expand Up @@ -178,6 +183,7 @@ private void processApolloJsonValue(Object bean, String beanName, Field field) {
}

String placeholder = apolloJsonValue.value();
String datePattern = apolloJsonValue.datePattern();
Object propertyValue = this.resolvePropertyValue(beanName, placeholder);
if (propertyValue == null) {
return;
Expand All @@ -186,7 +192,7 @@ private void processApolloJsonValue(Object bean, String beanName, Field field) {
boolean accessible = field.isAccessible();
field.setAccessible(true);
ReflectionUtils
.setField(field, bean, parseJsonValue((String) propertyValue, field.getGenericType()));
.setField(field, bean, parseJsonValue((String) propertyValue, field.getGenericType(), datePattern));
field.setAccessible(accessible);

if (configUtil.isAutoUpdateInjectedSpringPropertiesEnabled()) {
Expand All @@ -206,6 +212,7 @@ private void processApolloJsonValue(Object bean, String beanName, Method method)
}

String placeHolder = apolloJsonValue.value();
String datePattern = apolloJsonValue.datePattern();
Object propertyValue = this.resolvePropertyValue(beanName, placeHolder);
if (propertyValue == null) {
return;
Expand All @@ -218,7 +225,7 @@ private void processApolloJsonValue(Object bean, String beanName, Method method)

boolean accessible = method.isAccessible();
method.setAccessible(true);
ReflectionUtils.invokeMethod(method, bean, parseJsonValue((String) propertyValue, types[0]));
ReflectionUtils.invokeMethod(method, bean, parseJsonValue((String) propertyValue, types[0], datePattern));
method.setAccessible(accessible);

if (configUtil.isAutoUpdateInjectedSpringPropertiesEnabled()) {
Expand All @@ -244,15 +251,22 @@ private Object resolvePropertyValue(String beanName, String placeHolder) {
return propertyValue;
}

private Object parseJsonValue(String json, Type targetType) {
private Object parseJsonValue(String json, Type targetType, String datePattern) {
try {
return GSON.fromJson(json, targetType);
return DATEPATTERN_GSON_MAP.computeIfAbsent(datePattern, this::buildGson).fromJson(json, targetType);
huxleyliau marked this conversation as resolved.
Show resolved Hide resolved
} catch (Throwable ex) {
logger.error("Parsing json '{}' to type {} failed!", json, targetType, ex);
throw ex;
}
}

private Gson buildGson(String datePattern) {
if (StringUtils.isBlank(datePattern)) {
return new Gson();
}
return new GsonBuilder().setDateFormat(datePattern).create();
}

@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.configurableBeanFactory = (ConfigurableBeanFactory) beanFactory;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@
* // in Apollo is someJsonPropertyKey={"someString":"someValue", "someInt":10}.
* &#064;ApolloJsonValue("${someJsonPropertyKey:someDefaultValue}")
* private SomeObject someObject;
* // Suppose SomeObject has a field of type Date named 'time', then the possible config
* // in Apollo is someJsonPropertyKey={"time":"2024/01/04"}.
* &#064;ApolloJsonValue(value="${someJsonPropertyKey:someDefaultValue}", datePattern="yyyy/MM/dd")
* private SomeObject someObject;
* </pre>
*
* Create by zhangzheng on 2018/3/6
Expand All @@ -47,4 +51,9 @@
* The actual value expression: e.g. "${someJsonPropertyKey:someDefaultValue}".
*/
String value();

/**
* The datePattern follows the same rule as required by {@link java.text.SimpleDateFormat}.
*/
String datePattern() default "";
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,21 @@

import com.ctrip.framework.apollo.ConfigChangeListener;
import com.ctrip.framework.apollo.build.ApolloInjector;
import com.ctrip.framework.apollo.core.utils.StringUtils;
import com.ctrip.framework.apollo.model.ConfigChangeEvent;
import com.ctrip.framework.apollo.spring.annotation.ApolloJsonValue;
import com.ctrip.framework.apollo.spring.events.ApolloConfigChangeEvent;
import com.ctrip.framework.apollo.spring.util.SpringInjector;
import com.ctrip.framework.apollo.util.ConfigUtil;
import com.google.gson.Gson;
import java.lang.reflect.Field;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import com.google.gson.GsonBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
Expand All @@ -52,14 +58,14 @@ public class AutoUpdateConfigChangeListener implements ConfigChangeListener,
private TypeConverter typeConverter;
private final PlaceholderHelper placeholderHelper;
private final SpringValueRegistry springValueRegistry;
private final Gson gson;
private final Map<String, Gson> datePatternGsonMap;
private final ConfigUtil configUtil;

public AutoUpdateConfigChangeListener() {
this.typeConverterHasConvertIfNecessaryWithFieldParameter = testTypeConverterHasConvertIfNecessaryWithFieldParameter();
this.placeholderHelper = SpringInjector.getInstance(PlaceholderHelper.class);
this.springValueRegistry = SpringInjector.getInstance(SpringValueRegistry.class);
this.gson = new Gson();
this.datePatternGsonMap = new HashMap<>();
this.configUtil = ApolloInjector.getInstance(ConfigUtil.class);
}

Expand Down Expand Up @@ -107,7 +113,9 @@ private Object resolvePropertyValue(SpringValue springValue) {
.resolvePropertyValue(beanFactory, springValue.getBeanName(), springValue.getPlaceholder());

if (springValue.isJson()) {
value = parseJsonValue((String) value, springValue.getGenericType());
ApolloJsonValue apolloJsonValue = springValue.getField().getAnnotation(ApolloJsonValue.class);
String datePattern = apolloJsonValue != null ? apolloJsonValue.datePattern() : "";
value = parseJsonValue((String) value, springValue.getGenericType(), datePattern);
} else {
if (springValue.isField()) {
// org.springframework.beans.TypeConverter#convertIfNecessary(java.lang.Object, java.lang.Class, java.lang.reflect.Field) is available from Spring 3.2.0+
Expand All @@ -126,15 +134,22 @@ private Object resolvePropertyValue(SpringValue springValue) {
return value;
}

private Object parseJsonValue(String json, Type targetType) {
private Object parseJsonValue(String json, Type targetType, String datePattern) {
try {
return gson.fromJson(json, targetType);
return datePatternGsonMap.computeIfAbsent(datePattern, this::buildGson).fromJson(json, targetType);
} catch (Throwable ex) {
logger.error("Parsing json '{}' to type {} failed!", json, targetType, ex);
throw ex;
}
}

private Gson buildGson(String datePattern) {
if (StringUtils.isBlank(datePattern)) {
return new Gson();
}
return new GsonBuilder().setDateFormat(datePattern).create();
}

private boolean testTypeConverterHasConvertIfNecessaryWithFieldParameter() {
try {
TypeConverter.class.getMethod("convertIfNecessary", Object.class, Class.class, Field.class);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,15 @@

import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;

import com.ctrip.framework.apollo.build.MockInjector;
import com.ctrip.framework.apollo.core.ConfigConsts;
import com.ctrip.framework.apollo.internals.SimpleConfig;
import com.ctrip.framework.apollo.internals.YamlConfigFile;
import com.ctrip.framework.apollo.spring.JavaConfigPlaceholderTest.JsonBean;
import com.ctrip.framework.apollo.spring.JavaConfigPlaceholderTest.JsonDateBean;
import com.ctrip.framework.apollo.spring.XmlConfigPlaceholderTest.TestXmlBean;
import com.ctrip.framework.apollo.spring.annotation.ApolloJsonValue;
import com.ctrip.framework.apollo.spring.annotation.EnableApolloConfig;
Expand Down Expand Up @@ -830,6 +832,8 @@ public void testAutoUpdateWithAllKindsOfDataTypes() throws Exception {
String someNewString = "someNewString";
String someJsonProperty = "[{\"a\":\"astring\", \"b\":10},{\"a\":\"astring2\", \"b\":20}]";
String someNewJsonProperty = "[{\"a\":\"newString\", \"b\":20},{\"a\":\"astring2\", \"b\":20}]";
String someJsonDateProperty = "{\"startTime\":\"2024/01/20\",\"endTime\":\"2024/01/20\"}";;
String someNewJsonDateProperty = "{\"startTime\":\"2024/02/21\",\"endTime\":\"2024/02/21\"}";;

String someDateFormat = "yyyy-MM-dd HH:mm:ss.SSS";
Date someDate = assembleDate(2018, 2, 23, 20, 1, 2, 123);
Expand All @@ -849,6 +853,7 @@ public void testAutoUpdateWithAllKindsOfDataTypes() throws Exception {
properties.setProperty("dateFormat", someDateFormat);
properties.setProperty("dateProperty", simpleDateFormat.format(someDate));
properties.setProperty("jsonProperty", someJsonProperty);
properties.setProperty("jsonDateProperty", someJsonDateProperty);

SimpleConfig config = prepareConfig(ConfigConsts.NAMESPACE_APPLICATION, properties);

Expand All @@ -868,6 +873,7 @@ public void testAutoUpdateWithAllKindsOfDataTypes() throws Exception {
assertEquals(someDate, bean.getDateProperty());
assertEquals("astring", bean.getJsonBeanList().get(0).getA());
assertEquals(10, bean.getJsonBeanList().get(0).getB());
assertEquals("2024-01-20 00:00:00.000", simpleDateFormat.format(bean.getJsonDateBean().getStartTime()));

Properties newProperties = new Properties();
newProperties.setProperty("intProperty", String.valueOf(someNewInt));
Expand All @@ -882,6 +888,7 @@ public void testAutoUpdateWithAllKindsOfDataTypes() throws Exception {
newProperties.setProperty("dateFormat", someDateFormat);
newProperties.setProperty("dateProperty", simpleDateFormat.format(someNewDate));
newProperties.setProperty("jsonProperty", someNewJsonProperty);
newProperties.setProperty("jsonDateProperty", someNewJsonDateProperty);

config.onRepositoryChange(ConfigConsts.NAMESPACE_APPLICATION, newProperties);

Expand All @@ -899,6 +906,7 @@ public void testAutoUpdateWithAllKindsOfDataTypes() throws Exception {
assertEquals(someNewDate, bean.getDateProperty());
assertEquals("newString", bean.getJsonBeanList().get(0).getA());
assertEquals(20, bean.getJsonBeanList().get(0).getB());
assertEquals("2024-02-21 00:00:00.000", simpleDateFormat.format(bean.getJsonDateBean().getStartTime()));
}

@Test
Expand Down Expand Up @@ -1311,6 +1319,9 @@ static class TestAllKindsOfDataTypesBean {
@ApolloJsonValue("${jsonProperty}")
private List<JsonBean> jsonBeanList;

@ApolloJsonValue(value = "${jsonDateProperty}", datePattern = "yyyy/MM/dd")
private JsonDateBean jsonDateBean;

public int getIntProperty() {
return intProperty;
}
Expand Down Expand Up @@ -1354,6 +1365,10 @@ public Date getDateProperty() {
public List<JsonBean> getJsonBeanList() {
return jsonBeanList;
}

public JsonDateBean getJsonDateBean() {
return jsonDateBean;
}
}

static class TestApolloJsonValue {
Expand Down
Loading
Loading