Skip to contentMethod: getUserTypeMapping()
1: /*
2: * Copyright (c) 2017, 2022 Oracle and/or its affiliates. All rights reserved.
3: * Copyright (c) 2019, 2020 Payara Foundation and/or its affiliates. All rights reserved.
4: *
5: * This program and the accompanying materials are made available under the
6: * terms of the Eclipse Public License v. 2.0 which is available at
7: * http://www.eclipse.org/legal/epl-2.0,
8: * or the Eclipse Distribution License v. 1.0 which is available at
9: * http://www.eclipse.org/org/documents/edl-v10.php.
10: *
11: * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
12: */
13:
14: package org.eclipse.yasson.internal;
15:
16: import java.security.AccessController;
17: import java.security.PrivilegedAction;
18: import java.time.format.DateTimeFormatterBuilder;
19: import java.time.temporal.ChronoField;
20: import java.util.Arrays;
21: import java.util.Collections;
22: import java.util.HashMap;
23: import java.util.HashSet;
24: import java.util.List;
25: import java.util.Locale;
26: import java.util.Map;
27: import java.util.Objects;
28: import java.util.Optional;
29: import java.util.Set;
30: import java.util.TreeMap;
31: import java.util.function.Consumer;
32:
33: import jakarta.json.bind.JsonbConfig;
34: import jakarta.json.bind.JsonbException;
35: import jakarta.json.bind.annotation.JsonbDateFormat;
36: import jakarta.json.bind.config.BinaryDataStrategy;
37: import jakarta.json.bind.config.PropertyNamingStrategy;
38: import jakarta.json.bind.config.PropertyOrderStrategy;
39: import jakarta.json.bind.config.PropertyVisibilityStrategy;
40: import jakarta.json.bind.serializer.JsonbSerializer;
41:
42: import org.eclipse.yasson.YassonConfig;
43: import org.eclipse.yasson.internal.model.PropertyModel;
44: import org.eclipse.yasson.internal.model.ReverseTreeMap;
45: import org.eclipse.yasson.internal.model.customization.PropertyOrdering;
46: import org.eclipse.yasson.internal.model.customization.StrategiesProvider;
47: import org.eclipse.yasson.internal.model.customization.VisibilityStrategiesProvider;
48: import org.eclipse.yasson.internal.properties.MessageKeys;
49: import org.eclipse.yasson.internal.properties.Messages;
50:
51: /**
52: * Resolved properties from JSONB config.
53: */
54: @SuppressWarnings("rawtypes")
55: public class JsonbConfigProperties {
56:
57: private static final Map<String, Class<? extends Map>> PROPERTY_ORDER_STRATEGY_MAPS =
58: Map.of(PropertyOrderStrategy.LEXICOGRAPHICAL, TreeMap.class,
59: PropertyOrderStrategy.REVERSE, ReverseTreeMap.class,
60: PropertyOrderStrategy.ANY, HashMap.class);
61:
62: private final JsonbConfig jsonbConfig;
63: private final PropertyVisibilityStrategy propertyVisibilityStrategy;
64: private final PropertyNamingStrategy propertyNamingStrategy;
65: private final PropertyOrdering propertyOrdering;
66: private final JsonbDateFormatter dateFormatter;
67: private final Locale locale;
68: private final String binaryDataStrategy;
69: private final boolean nullable;
70: private final boolean failOnUnknownProperties;
71: private final boolean strictIJson;
72: private final boolean zeroTimeDefaulting;
73: private final boolean requiredCreatorParameters;
74: private final boolean dateInMillisecondsAsString;
75: private final Map<Class<?>, Class<?>> userTypeMapping;
76: private final Class<?> defaultMapImplType;
77: private final JsonbSerializer<Object> nullSerializer;
78: private final Set<Class<?>> eagerInitClasses;
79: private final boolean forceMapArraySerializerForNullKeys;
80:
81: /**
82: * Creates new resolved JSONB config.
83: *
84: * @param jsonbConfig jsonb config
85: */
86: public JsonbConfigProperties(JsonbConfig jsonbConfig) {
87: this.jsonbConfig = jsonbConfig;
88: this.binaryDataStrategy = initBinaryDataStrategy();
89: this.propertyNamingStrategy = initPropertyNamingStrategy();
90: this.propertyVisibilityStrategy = initPropertyVisibilityStrategy();
91: this.propertyOrdering = new PropertyOrdering(initOrderStrategy());
92: this.locale = initConfigLocale();
93: this.dateFormatter = initDateFormatter(this.locale);
94: this.nullable = initConfigNullable();
95: this.failOnUnknownProperties = initConfigFailOnUnknownProperties();
96: this.strictIJson = initStrictJson();
97: this.userTypeMapping = initUserTypeMapping();
98: this.zeroTimeDefaulting = initZeroTimeDefaultingForJavaTime();
99: this.defaultMapImplType = initDefaultMapImplType();
100: this.nullSerializer = initNullSerializer();
101: this.eagerInitClasses = initEagerInitClasses();
102: this.requiredCreatorParameters = initRequiredCreatorParameters();
103: this.forceMapArraySerializerForNullKeys = initForceMapArraySerializerForNullKeys();
104: this.dateInMillisecondsAsString = initDateInMillisecondsAsString();
105: }
106:
107: private Class<? extends Map> initDefaultMapImplType() {
108: //We need to get PropertyOrderStrategy again. This time, if was not set, use ANY to get proper map implementation.
109: //This is intentional!
110: String propertyOrder = getConfigProperty(JsonbConfig.PROPERTY_ORDER_STRATEGY, String.class, PropertyOrderStrategy.ANY);
111: return PROPERTY_ORDER_STRATEGY_MAPS.getOrDefault(propertyOrder, HashMap.class);
112: }
113:
114: private boolean initZeroTimeDefaultingForJavaTime() {
115: return getConfigProperty(YassonConfig.ZERO_TIME_PARSE_DEFAULTING, Boolean.class, false);
116: }
117:
118: @SuppressWarnings("unchecked")
119: private Map<Class<?>, Class<?>> initUserTypeMapping() {
120: return getConfigProperty(YassonConfig.USER_TYPE_MAPPING, Map.class, Collections.emptyMap());
121: }
122:
123: private JsonbDateFormatter initDateFormatter(Locale locale) {
124: final String dateFormat = getGlobalConfigJsonbDateFormat();
125: if (JsonbDateFormat.DEFAULT_FORMAT.equals(dateFormat) || JsonbDateFormat.TIME_IN_MILLIS.equals(dateFormat)) {
126: return new JsonbDateFormatter(dateFormat, locale.toLanguageTag());
127: }
128: DateTimeFormatterBuilder builder = new DateTimeFormatterBuilder().appendPattern(dateFormat);
129: if (isZeroTimeDefaulting()) {
130: builder.parseDefaulting(ChronoField.SECOND_OF_MINUTE, 0);
131: builder.parseDefaulting(ChronoField.MINUTE_OF_HOUR, 0);
132: builder.parseDefaulting(ChronoField.HOUR_OF_DAY, 0);
133: }
134: return new JsonbDateFormatter(builder.toFormatter(locale), dateFormat, locale.toLanguageTag());
135: }
136:
137: private String getGlobalConfigJsonbDateFormat() {
138: return getConfigProperty(JsonbConfig.DATE_FORMAT, String.class, JsonbDateFormat.DEFAULT_FORMAT);
139: }
140:
141: private Consumer<List<PropertyModel>> initOrderStrategy() {
142: return StrategiesProvider.getOrderingFunction(getPropertyOrderStrategy());
143: }
144:
145: private String getPropertyOrderStrategy() {
146: return getConfigProperty(JsonbConfig.PROPERTY_ORDER_STRATEGY, String.class, PropertyOrderStrategy.LEXICOGRAPHICAL);
147: }
148:
149: private PropertyNamingStrategy initPropertyNamingStrategy() {
150: final Optional<Object> property = jsonbConfig.getProperty(JsonbConfig.PROPERTY_NAMING_STRATEGY);
151: if (property.isEmpty()) {
152: return StrategiesProvider.getPropertyNamingStrategy(PropertyNamingStrategy.IDENTITY);
153: }
154: Object propertyNamingStrategy = property.get();
155: if (propertyNamingStrategy instanceof String) {
156: return StrategiesProvider.getPropertyNamingStrategy((String) propertyNamingStrategy);
157: } else if (!(propertyNamingStrategy instanceof PropertyNamingStrategy)) {
158: throw new JsonbException(Messages.getMessage(MessageKeys.PROPERTY_NAMING_STRATEGY_INVALID));
159: }
160: return (PropertyNamingStrategy) property.get();
161: }
162:
163: private PropertyVisibilityStrategy initPropertyVisibilityStrategy() {
164: final Optional<Object> property = jsonbConfig.getProperty(JsonbConfig.PROPERTY_VISIBILITY_STRATEGY);
165: if (property.isEmpty()) {
166: return null;
167: }
168: final Object propertyVisibilityStrategy = property.get();
169: if (propertyVisibilityStrategy instanceof String) {
170: return VisibilityStrategiesProvider.getStrategy((String) propertyVisibilityStrategy);
171: } else if (!(propertyVisibilityStrategy instanceof PropertyVisibilityStrategy)) {
172: throw new JsonbException("JsonbConfig.PROPERTY_VISIBILITY_STRATEGY must be instance of " + PropertyVisibilityStrategy.class);
173: }
174: return (PropertyVisibilityStrategy) propertyVisibilityStrategy;
175: }
176:
177: private String initBinaryDataStrategy() {
178: if (getConfigProperty(JsonbConfig.STRICT_IJSON, Boolean.class, false)) {
179: return BinaryDataStrategy.BASE_64_URL;
180: }
181: return getConfigProperty(JsonbConfig.BINARY_DATA_STRATEGY, String.class, BinaryDataStrategy.BYTE);
182: }
183:
184: private boolean initConfigNullable() {
185: return getConfigProperty(JsonbConfig.NULL_VALUES, Boolean.class, false);
186: }
187:
188: private boolean initConfigFailOnUnknownProperties() {
189: return getConfigProperty(YassonConfig.FAIL_ON_UNKNOWN_PROPERTIES, Boolean.class, false);
190: }
191:
192: private boolean initRequiredCreatorParameters() {
193: String sysProp = AccessController.doPrivileged((PrivilegedAction<String>)
194: () -> System.getProperty(JsonbConfig.CREATOR_PARAMETERS_REQUIRED));
195:
196: if (sysProp != null) {
197: return Boolean.parseBoolean(sysProp);
198: }
199: return getConfigProperty(JsonbConfig.CREATOR_PARAMETERS_REQUIRED, Boolean.class, false);
200: }
201:
202: private boolean initDateInMillisecondsAsString() {
203: String sysProp = AccessController.doPrivileged((PrivilegedAction<String>)
204: () -> System.getProperty(YassonConfig.DATE_TIME_IN_MILLIS_AS_A_STRING));
205:
206: if (sysProp != null) {
207: return Boolean.parseBoolean(sysProp);
208: }
209: return getConfigProperty(YassonConfig.DATE_TIME_IN_MILLIS_AS_A_STRING, Boolean.class, false);
210: }
211:
212: @SuppressWarnings("unchecked")
213: private JsonbSerializer<Object> initNullSerializer() {
214: return jsonbConfig.getProperty(YassonConfig.NULL_ROOT_SERIALIZER)
215: .map(o -> {
216: if (!(o instanceof JsonbSerializer)) {
217: throw new JsonbException("YassonConfig.NULL_ROOT_SERIALIZER must be instance of " + JsonbSerializer.class
218: + "<Object>");
219: }
220: return (JsonbSerializer<Object>) o;
221: }).orElse(null);
222: }
223:
224: private Set<Class<?>> initEagerInitClasses() {
225: Optional<Object> property = jsonbConfig.getProperty(YassonConfig.EAGER_PARSE_CLASSES);
226: if (property.isEmpty()) {
227: return Collections.emptySet();
228: }
229: Object eagerInitClasses = property.get();
230: if (!(eagerInitClasses instanceof Class<?>[])) {
231: throw new JsonbException("YassonConfig.EAGER_PARSE_CLASSES must be instance of Class<?>[]");
232: }
233: return new HashSet<>(Arrays.asList((Class<?>[]) eagerInitClasses));
234: }
235:
236: private boolean initForceMapArraySerializerForNullKeys() {
237: return getConfigProperty(YassonConfig.FORCE_MAP_ARRAY_SERIALIZER_FOR_NULL_KEYS, Boolean.class, false);
238: }
239:
240: /**
241: * Gets nullable from {@link JsonbConfig}.
242: * If true null values are serialized to json.
243: *
244: * @return Configured nullable
245: */
246: public boolean getConfigNullable() {
247: return nullable;
248: }
249:
250: /**
251: * Gets unknown properties flag from {@link JsonbConfig}.
252: * If false, {@link JsonbException} is not thrown for deserialization, when json key
253: * cannot be mapped to class property.
254: *
255: * @return {@link JsonbException} is risen on unknown property. Default is true even if
256: * not set in json config.
257: */
258: public boolean getConfigFailOnUnknownProperties() {
259: return failOnUnknownProperties;
260: }
261:
262: private <T> T getConfigProperty(String propertyName, Class<T> propertyType, T defaultValue) {
263: Objects.requireNonNull(defaultValue, "Default value cannot be null");
264: return jsonbConfig.getProperty(propertyName)
265: .or(() -> Optional.of(defaultValue))
266: .filter(propertyType::isInstance)
267: .map(propertyType::cast)
268: .orElseThrow(() -> new JsonbException(Messages.getMessage(MessageKeys.JSONB_CONFIG_PROPERTY_INVALID_TYPE,
269: propertyName,
270: propertyType.getSimpleName())));
271: }
272:
273: /**
274: * Checks for binary data strategy to use.
275: *
276: * @return Binary data strategy.
277: */
278: public String getBinaryDataStrategy() {
279: return binaryDataStrategy;
280: }
281:
282: /**
283: * Converts string locale to {@link Locale}.
284: *
285: * @param locale Locale to convert.
286: * @return {@link Locale} instance.
287: */
288: public Locale getLocale(String locale) {
289: if (locale.equals(JsonbDateFormat.DEFAULT_LOCALE)) {
290: return this.locale;
291: }
292: return Locale.forLanguageTag(locale);
293: }
294:
295: /**
296: * Gets locale from {@link JsonbConfig}.
297: *
298: * @return Configured locale.
299: */
300: private Locale initConfigLocale() {
301: return getConfigProperty(JsonbConfig.LOCALE, Locale.class, Locale.getDefault());
302: }
303:
304: private boolean initStrictJson() {
305: return getConfigProperty(JsonbConfig.STRICT_IJSON, Boolean.class, false);
306: }
307:
308: /**
309: * Gets property visibility strategy.
310: *
311: * @return Property visibility strategy.
312: */
313: public PropertyVisibilityStrategy getPropertyVisibilityStrategy() {
314: return propertyVisibilityStrategy;
315: }
316:
317: /**
318: * Gets property naming strategy.
319: *
320: * @return Property naming strategy.
321: */
322: public PropertyNamingStrategy getPropertyNamingStrategy() {
323: return propertyNamingStrategy;
324: }
325:
326: /**
327: * Gets instantiated shared config date formatter.
328: *
329: * @return Date formatter.
330: */
331: public JsonbDateFormatter getConfigDateFormatter() {
332: return dateFormatter;
333: }
334:
335: /**
336: * Gets property ordering component.
337: *
338: * @return Component for ordering properties.
339: */
340: public PropertyOrdering getPropertyOrdering() {
341: return propertyOrdering;
342: }
343:
344: /**
345: * If strict IJSON patterns should be used.
346: *
347: * @return if IJSON is enabled
348: */
349: public boolean isStrictIJson() {
350: return strictIJson;
351: }
352:
353: /**
354: * User type mapping for map interface to implementation classes.
355: *
356: * @return User type mapping.
357: */
358: public Map<Class<?>, Class<?>> getUserTypeMapping() {
359: return userTypeMapping;
360: }
361:
362: /**
363: * <p>Makes parsing dates defaulting to zero hour, minute and second.
364: * This will made available to parse patterns like yyyy.MM.dd to
365: * {@link java.util.Date}, {@link java.util.Calendar}, {@link java.time.Instant} {@link java.time.LocalDate}
366: * or even {@link java.time.ZonedDateTime}.
367: * <p>If time zone is not set in the pattern than UTC time zone is used.
368: * So for example json value 2018.01.01 becomes 2018.01.01 00:00:00 UTC when parsed
369: * to instant {@link java.time.Instant}.
370: *
371: * @return true if time should be defaulted to zero.
372: */
373: public boolean isZeroTimeDefaulting() {
374: return zeroTimeDefaulting;
375: }
376:
377: /**
378: * Default {@link java.util.Map} implementation to use, based on order strategy.
379: *
380: * @return map impl type
381: */
382: public Class<?> getDefaultMapImplType() {
383: return defaultMapImplType;
384: }
385:
386: public JsonbSerializer<Object> getNullSerializer() {
387: return nullSerializer;
388: }
389:
390: public boolean hasRequiredCreatorParameters() {
391: return requiredCreatorParameters;
392: }
393:
394: public Set<Class<?>> getEagerInitClasses() {
395: return eagerInitClasses;
396: }
397:
398: /**
399: * Whether the MapToEntriesArraySerializer is selected when a null key
400: * is detected in a map.
401: *
402: * @return false or true
403: */
404: public boolean isForceMapArraySerializerForNullKeys() {
405: return forceMapArraySerializerForNullKeys;
406: }
407:
408: public boolean isDateInMillisecondsAsString() {
409: return dateInMillisecondsAsString;
410: }
411: }