Skip to content

Method: registerConverter(Class, ConverterWrapper)

1: /*
2: * JOPA
3: * Copyright (C) 2024 Czech Technical University in Prague
4: *
5: * This library is free software; you can redistribute it and/or
6: * modify it under the terms of the GNU Lesser General Public
7: * License as published by the Free Software Foundation; either
8: * version 3.0 of the License, or (at your option) any later version.
9: *
10: * This library is distributed in the hope that it will be useful,
11: * but WITHOUT ANY WARRANTY; without even the implied warranty of
12: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13: * Lesser General Public License for more details.
14: *
15: * You should have received a copy of the GNU Lesser General Public
16: * License along with this library.
17: */
18: package cz.cvut.kbss.jopa.model.metamodel;
19:
20: import cz.cvut.kbss.jopa.exception.InstantiationException;
21: import cz.cvut.kbss.jopa.exception.InvalidConverterException;
22: import cz.cvut.kbss.jopa.exception.InvalidFieldMappingException;
23: import cz.cvut.kbss.jopa.model.AttributeConverter;
24: import cz.cvut.kbss.jopa.model.MultilingualString;
25: import cz.cvut.kbss.jopa.model.annotations.Convert;
26: import cz.cvut.kbss.jopa.model.annotations.Sequence;
27: import cz.cvut.kbss.jopa.oom.converter.ConverterWrapper;
28: import cz.cvut.kbss.jopa.oom.converter.CustomConverterWrapper;
29: import cz.cvut.kbss.jopa.oom.converter.ObjectOneOfEnumConverter;
30: import cz.cvut.kbss.jopa.oom.converter.OrdinalEnumConverter;
31: import cz.cvut.kbss.jopa.oom.converter.StringEnumConverter;
32: import cz.cvut.kbss.jopa.oom.converter.ToLexicalFormConverter;
33: import cz.cvut.kbss.jopa.oom.converter.ToMultilingualStringConverter;
34: import cz.cvut.kbss.jopa.oom.converter.ToRdfLiteralConverter;
35: import cz.cvut.kbss.jopa.utils.ReflectionUtils;
36:
37: import java.lang.reflect.ParameterizedType;
38: import java.util.Arrays;
39: import java.util.List;
40: import java.util.Optional;
41:
42: /**
43: * Determines potential converters which may be used on a field.
44: * <p>
45: * Currently, only built-in converters for data and annotation property attributes are supported, but in the future,
46: * custom converters should be also supported.
47: */
48: public class ConverterResolver {
49:
50: private final Converters converters;
51:
52: ConverterResolver(Converters converters) {
53: this.converters = converters;
54: }
55:
56: /**
57: * Determines converter which should be used for transformation of values to and from the specified field.
58: * <p>
59: * Beside custom converters, the system supports a number of built-in converters, which ensure that e.g. widening
60: * conversion or mapping to Java 8 Date/Time API is supported.
61: * <p>
62: * The order of priorities of converter resolution is:
63: * <ol>
64: * <li>Custom converter declared on attribute with {@link Convert}</li>
65: * <li>Custom automatically applied converter declared with {@link cz.cvut.kbss.jopa.model.annotations.Converter}</li>
66: * <li>Built-in converter</li>
67: * </ol>
68: *
69: * @param field The field for which converter should be determined
70: * @param config Mapping configuration extracted during metamodel building
71: * @return Possible converter instance to be used for transformation of values of the specified field. Returns empty
72: * {@code Optional} if no suitable converter is found (or needed)
73: */
74: public Optional<ConverterWrapper<?, ?>> resolveConverter(PropertyInfo field, PropertyAttributes config) {
75: final Class<?> attValueType = config.getType().getJavaType();
76: final Optional<ConverterWrapper<?, ?>> localCustomConverter = resolveCustomConverter(field, config);
77: if (localCustomConverter.isPresent()) {
78: return localCustomConverter;
79: }
80: final Optional<ConverterWrapper<?, ?>> globalCustomConverter = converters.getCustomConverter(attValueType);
81: if (globalCustomConverter.isPresent()) {
82: return globalCustomConverter;
83: }
84: if (attValueType.isEnum()) {
85: return Optional.of(createEnumConverter(attValueType, config));
86: }
87: if (config.hasDatatype()) {
88: verifyTypeIsString(field, attValueType);
89: return Optional.of(new ToRdfLiteralConverter(config.getDatatype()));
90: }
91: if (config.isLexicalForm()) {
92: return Optional.of(new ToLexicalFormConverter());
93: }
94: if (isMultilingualReferencedList(attValueType, field)) {
95: return Optional.of(new ToMultilingualStringConverter());
96: }
97: return Converters.getDefaultConverter(attValueType);
98: }
99:
100: private static ConverterWrapper<?, ?> createEnumConverter(Class<?> valueType, PropertyAttributes pa) {
101: return switch (pa.getEnumType()) {
102: case OBJECT_ONE_OF -> new ObjectOneOfEnumConverter(valueType);
103: case ORDINAL -> new OrdinalEnumConverter(valueType);
104: default -> new StringEnumConverter(valueType);
105: };
106: }
107:
108: private static void verifyTypeIsString(PropertyInfo field, Class<?> attValueType) {
109: if (!attValueType.equals(String.class)) {
110: throw new InvalidFieldMappingException(
111: "Attributes with explicit datatype identifier must have values of type String. " +
112: "The provided attribute " + field + " has type " + attValueType);
113: }
114: }
115:
116: private static Optional<ConverterWrapper<?, ?>> resolveCustomConverter(PropertyInfo field,
117: PropertyAttributes config) {
118: final Convert convertAnn = field.getAnnotation(Convert.class);
119: if (convertAnn == null || convertAnn.disableConversion()) {
120: return Optional.empty();
121: }
122: if (config.getPersistentAttributeType() == Attribute.PersistentAttributeType.OBJECT) {
123: throw new InvalidConverterException("Attribute converters cannot be declared attributes mapped to object properties.");
124: }
125: return Optional.of(createCustomConverter(convertAnn.converter()));
126: }
127:
128: public static ConverterWrapper<?, ?> createCustomConverter(Class<?> converterType) {
129: if (!AttributeConverter.class.isAssignableFrom(converterType)) {
130: throw new InvalidConverterException(
131: "Specified converter type " + converterType + " does not implement " + AttributeConverter.class);
132: }
133: try {
134: final AttributeConverter<?, ?> converter =
135: (AttributeConverter<?, ?>) ReflectionUtils.instantiateUsingDefaultConstructor(converterType);
136: return new CustomConverterWrapper(converter, resolveConverterAxiomType(converterType));
137: } catch (InstantiationException e) {
138: throw new InvalidConverterException("Unable to instantiate attribute converter.", e);
139: }
140: }
141:
142: private static boolean isMultilingualReferencedList(Class<?> elemType, PropertyInfo field) {
143: return MultilingualString.class.isAssignableFrom(elemType)
144: && field.getAnnotation(Sequence.class) != null
145: && List.class.isAssignableFrom(field.field().getType());
146: }
147:
148: public static Class<?> resolveConverterAttributeType(Class<?> converterType) {
149: final ParameterizedType typeSpec = getConverterGenerics(converterType);
150: assert typeSpec.getActualTypeArguments().length == 2;
151: return (Class<?>) typeSpec.getActualTypeArguments()[0];
152: }
153:
154: private static ParameterizedType getConverterGenerics(Class<?> converterType) {
155: return (ParameterizedType) Arrays.stream(converterType.getGenericInterfaces())
156: .filter(t -> t instanceof ParameterizedType && ((ParameterizedType) t).getRawType()
157: .equals(AttributeConverter.class))
158: .findAny().orElseThrow(
159: () -> new InvalidConverterException(
160: "Specified converter type " + converterType + " does not implement " + AttributeConverter.class.getSimpleName()));
161: }
162:
163: private static Class<?> resolveConverterAxiomType(Class<?> converterType) {
164: final ParameterizedType typeSpec = getConverterGenerics(converterType);
165: assert typeSpec.getActualTypeArguments().length == 2;
166: return (Class<?>) typeSpec.getActualTypeArguments()[1];
167:
168: }
169:
170: /**
171: * Alternative method for resolving converter. Can be used when the persistent attribute type is not relevant and/or
172: * the class {@link PropertyAttributes} is not used for attribute configuration, e.g. for attributes defined by a
173: * query.
174: * <p>
175: * Determines converter which should be used for transformation of values to and from the specified type of field.
176: * <p>
177: * Beside custom converters, the system supports a number of built-in converters, which ensure that e.g. widening
178: * conversion or mapping to Java 8 Date/Time API is supported.
179: *
180: * @param type attribute type as defined in {@link cz.cvut.kbss.jopa.model.metamodel.Type} (not to be confused with
181: * {@link java.lang.reflect.Type})
182: * @return Possible converter instance to be used for transformation of values of the specified field. Returns empty
183: * {@code Optional} if no suitable converter is found (or needed)
184: * @see cz.cvut.kbss.jopa.model.metamodel.QueryAttribute
185: */
186: public Optional<ConverterWrapper<?, ?>> resolveConverter(Type<?> type) {
187: final Class<?> attValueType = type.getJavaType();
188: if (attValueType.isEnum()) {
189: return Optional.of(new StringEnumConverter(attValueType));
190: }
191:
192: return converters.getCustomConverter(attValueType);
193: }
194:
195: /**
196: * Registers the specified converter for automatically converting values to the specified attribute type.
197: *
198: * @param attributeType Entity attribute type to convert to/from
199: * @param converter Converter instance
200: */
201: public void registerConverter(Class<?> attributeType, ConverterWrapper<?, ?> converter) {
202: converters.registerConverter(attributeType, converter);
203: }
204: }