Skip to content

Method: validateAttributeMapping(AbstractAttribute)

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.InvalidFieldMappingException;
21: import cz.cvut.kbss.jopa.model.annotations.EnumType;
22: import cz.cvut.kbss.jopa.model.annotations.Enumerated;
23: import cz.cvut.kbss.jopa.model.annotations.SequenceType;
24: import cz.cvut.kbss.jopa.model.annotations.Types;
25: import cz.cvut.kbss.jopa.utils.IdentifierTransformer;
26: import cz.cvut.kbss.jopa.vocabulary.RDF;
27:
28: import java.lang.reflect.Field;
29: import java.lang.reflect.ParameterizedType;
30: import java.lang.reflect.Type;
31: import java.util.Map;
32: import java.util.Set;
33:
34: import static cz.cvut.kbss.jopa.model.PersistenceProperties.IDENTIFIER_TYPES;
35:
36: /**
37: * Verifies that a field's mapping metadata and declaration are valid.
38: */
39: class FieldMappingValidator {
40:
41: void validatePropertiesField(Field field) {
42: assert field != null;
43: if (!Map.class.isAssignableFrom(field.getType())) {
44: throw new InvalidFieldMappingException(field, "@Properties must be of type " + Map.class.getName());
45: }
46: if (isRawType(field.getGenericType())) {
47: throw new InvalidFieldMappingException(field, "@Properties field cannot be a raw map.");
48: }
49: final PropertiesParametersResolver parametersResolver = new PropertiesParametersResolver(field);
50: if (!isValidIdentifierType(parametersResolver.getKeyType())) {
51: throw new InvalidFieldMappingException(field,
52: "@Properties key type is not a valid identifier type. Expected one of " + IDENTIFIER_TYPES);
53: }
54: validatePropertiesValueType(field, parametersResolver.getValueType());
55: }
56:
57: private static boolean isRawType(Type type) {
58: return !(type instanceof ParameterizedType);
59: }
60:
61: private static void validatePropertiesValueType(Field field, Type type) {
62: if (isRawType(type)) {
63: throw new InvalidFieldMappingException(field, "Properties value type cannot be raw");
64: }
65: if (!((ParameterizedType) type).getRawType().equals(Set.class)) {
66: throw new InvalidFieldMappingException(field, "@Properties value type must be a " + Set.class.getName());
67: }
68: }
69:
70: void validateTypesField(Field field) {
71: if (!Set.class.isAssignableFrom(field.getType())) {
72: throw new InvalidFieldMappingException(field, "@Types must be of type " + Set.class.getName());
73: }
74: if (isRawType(field.getGenericType())) {
75: throw new InvalidFieldMappingException(field, "@Types field cannot be raw");
76: }
77: final ParameterizedType typeSpec = (ParameterizedType) field.getGenericType();
78: if (!isValidIdentifierType(typeSpec.getActualTypeArguments()[0])) {
79: throw new InvalidFieldMappingException(field,
80: "@Types field value is not a valid identifier type. Expected one of " + IDENTIFIER_TYPES);
81: }
82: }
83:
84: void validateIdentifierType(Type type) {
85: if (!isValidIdentifierType(type)) {
86: throw new InvalidFieldMappingException(type + " is not a valid identifier type.");
87: }
88: }
89:
90: boolean isValidIdentifierType(Type type) {
91: return type instanceof Class && IdentifierTransformer.isValidIdentifierType((Class<?>) type);
92: }
93:
94: void validateAttributeMapping(AbstractAttribute<?, ?> attribute) {
95: validateAttributeDoesNotMapRdfType(attribute);
96:• switch (attribute.getPersistentAttributeType()) {
97: case OBJECT:
98: validateObjectPropertyEnumMapping(attribute);
99: break;
100: case DATA: // Intentional fall-through
101: case ANNOTATION:
102: validateLexicalFormAttribute(attribute);
103: validateSimpleLiteralField(attribute);
104: validateNotSimpleList(attribute);
105: break;
106: }
107: }
108:
109: private static void validateAttributeDoesNotMapRdfType(AbstractAttribute<?, ?> att) {
110: if (RDF.TYPE.equals(att.getIRI().toString())) {
111: throw new InvalidFieldMappingException(
112: att.getJavaField(),"cannot use rdf:type for property mapping. Use a Set field annotated with " + Types.class.getSimpleName());
113: }
114: }
115:
116: private static void validateLexicalFormAttribute(AbstractAttribute<?, ?> attribute) {
117: if (attribute.isLexicalForm() && !String.class.isAssignableFrom(getBindableType(attribute))) {
118: throw new InvalidFieldMappingException(
119: attribute.getJavaField(), "lexicalForm mapping can be used only on fields of type String.");
120: }
121: }
122:
123: private static void validateSimpleLiteralField(AbstractAttribute<?, ?> attribute) {
124: final Class<?> fieldType = getBindableType(attribute);
125: if (attribute.isSimpleLiteral() && (!String.class.isAssignableFrom(fieldType) && !Enum.class.isAssignableFrom(
126: fieldType) && !attribute.getConverter().supportsAxiomValueType(String.class))) {
127: throw new InvalidFieldMappingException(
128: attribute.getJavaField(),"simpleLiteral mapping can only be used on fields of type String or Enum or using a suitable converter.");
129: }
130: }
131:
132: private static Class<?> getBindableType(AbstractAttribute<?, ?> attribute) {
133: return attribute.isCollection() ? ((AbstractPluralAttribute) attribute).getBindableJavaType() :
134: attribute.getJavaType();
135: }
136:
137: private static void validateObjectPropertyEnumMapping(Attribute<?, ?> attribute) {
138: if (!attribute.getJavaType().isEnum()) {
139: return;
140: }
141: final Enumerated enumeratedAnn = attribute.getJavaField().getAnnotation(Enumerated.class);
142: if (enumeratedAnn == null || enumeratedAnn.value() != EnumType.OBJECT_ONE_OF) {
143: throw new InvalidFieldMappingException(
144: "Attribute " + attribute + " maps an enum but is not annotated with " + Enumerated.class + " with " + EnumType.OBJECT_ONE_OF + " value.");
145: }
146: }
147:
148: private static void validateNotSimpleList(AbstractAttribute<?, ?> attribute) {
149: if (attribute.isCollection() && ((PluralAttribute<?, ?, ?>) attribute).getCollectionType() == CollectionType.LIST) {
150: final ListAttribute<?, ?> la = (ListAttribute<?, ?>) attribute;
151: if (la.getSequenceType() == SequenceType.simple) {
152: throw new InvalidFieldMappingException(attribute.getJavaField(), "simple list attribute must be mapped to an object property.");
153: }
154: }
155: }
156: }