Skip to content

Commit

Permalink
Implement parsing time based on pattern for @ApolloJsonValue (#53)
Browse files Browse the repository at this point in the history
* feat(apollo-client): allow user set data format partition when use @ApolloJsonValue (#2540)

* style(apollo-client): format code by intellij-java-google-style.xml

* test(apollo-client): test @ApolloJsonValue format date

* docs: update change log #49

* Modified according to CodeReview feedback

* Modified CHANGES.md for pr #53

* refactor: make gson map use ConcurrentHashMap
  • Loading branch information
huxleyliau authored Jan 23, 2024
1 parent 51efea6 commit 07ed79b
Show file tree
Hide file tree
Showing 6 changed files with 157 additions and 10 deletions.
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 @@ -34,8 +35,12 @@
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
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 ConcurrentHashMap<>();

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);
} 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.Map;
import java.util.Set;

import com.google.gson.GsonBuilder;
import java.util.concurrent.ConcurrentHashMap;
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 ConcurrentHashMap<>();
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() : StringUtils.EMPTY;
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

0 comments on commit 07ed79b

Please sign in to comment.