1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.mycore.user2;
19
20 import java.lang.reflect.Field;
21 import java.lang.reflect.InvocationTargetException;
22 import java.lang.reflect.Method;
23 import java.util.ArrayList;
24 import java.util.Arrays;
25 import java.util.HashMap;
26 import java.util.HashSet;
27 import java.util.List;
28 import java.util.Locale;
29 import java.util.Map;
30 import java.util.Set;
31 import java.util.stream.Collectors;
32
33 import org.apache.logging.log4j.LogManager;
34 import org.apache.logging.log4j.Logger;
35 import org.jdom2.Element;
36 import org.jdom2.transform.JDOMSource;
37 import org.mycore.common.MCRUserInformation;
38 import org.mycore.user2.annotation.MCRUserAttribute;
39 import org.mycore.user2.annotation.MCRUserAttributeJavaConverter;
40
41 import jakarta.xml.bind.JAXBContext;
42 import jakarta.xml.bind.Unmarshaller;
43 import jakarta.xml.bind.annotation.XmlAccessType;
44 import jakarta.xml.bind.annotation.XmlAccessorType;
45 import jakarta.xml.bind.annotation.XmlAttribute;
46 import jakarta.xml.bind.annotation.XmlElement;
47 import jakarta.xml.bind.annotation.XmlElementWrapper;
48 import jakarta.xml.bind.annotation.XmlRootElement;
49 import jakarta.xml.bind.annotation.XmlValue;
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82 public class MCRUserAttributeMapper {
83
84 private static Logger LOGGER = LogManager.getLogger(MCRUserAttributeMapper.class);
85
86 private HashMap<String, List<Attribute>> attributeMapping = new HashMap<>();
87
88 public static MCRUserAttributeMapper instance(Element attributeMapping) {
89 try {
90 JAXBContext jaxb = JAXBContext.newInstance(Mappings.class.getPackage().getName(),
91 Mappings.class.getClassLoader());
92
93 Unmarshaller unmarshaller = jaxb.createUnmarshaller();
94 Mappings mappings = (Mappings) unmarshaller.unmarshal(new JDOMSource(attributeMapping));
95
96 MCRUserAttributeMapper uam = new MCRUserAttributeMapper();
97 uam.attributeMapping.putAll(mappings.getAttributeMap());
98 return uam;
99 } catch (Exception e) {
100 return null;
101 }
102 }
103
104
105
106
107
108
109
110
111 @SuppressWarnings({ "rawtypes", "unchecked" })
112 public boolean mapAttributes(final Object object, final Map<String, ?> attributes) throws Exception {
113 boolean changed = false;
114
115 for (Object annotated : getAnnotated(object)) {
116 MCRUserAttribute attrAnno = null;
117
118 if (annotated instanceof Field) {
119 attrAnno = ((Field) annotated).getAnnotation(MCRUserAttribute.class);
120 } else if (annotated instanceof Method) {
121 attrAnno = ((Method) annotated).getAnnotation(MCRUserAttribute.class);
122 }
123
124 if (attrAnno != null) {
125 final String name = attrAnno.name().isEmpty() ? getAttriutebName(annotated) : attrAnno.name();
126 final List<Attribute> attribs = attributeMapping.get(name);
127
128 if (attributes != null) {
129 for (Attribute attribute : attribs) {
130 if (attributes.containsKey(attribute.mapping)) {
131 Object value = attributes.get(attribute.mapping);
132
133 MCRUserAttributeJavaConverter aConv = null;
134
135 if (annotated instanceof Field) {
136 aConv = ((Field) annotated).getAnnotation(MCRUserAttributeJavaConverter.class);
137 } else if (annotated instanceof Method) {
138 aConv = ((Method) annotated).getAnnotation(MCRUserAttributeJavaConverter.class);
139 }
140
141 Class<? extends MCRUserAttributeConverter> convCls = null;
142 if (attribute.converter != null) {
143 convCls = (Class<? extends MCRUserAttributeConverter>) Class
144 .forName(attribute.converter);
145 } else if (aConv != null) {
146 convCls = aConv.value();
147 }
148
149 if (convCls != null) {
150 MCRUserAttributeConverter converter = convCls.getDeclaredConstructor().newInstance();
151 LOGGER.debug("convert value \"{}\" with \"{}\"", value, converter.getClass().getName());
152 value = converter.convert(value,
153 attribute.separator != null ? attribute.separator : attrAnno.separator(),
154 attribute.getValueMap());
155 }
156
157 if (value != null || ((attrAnno.nullable() || attribute.nullable) && value == null)) {
158 Object oldValue = getValue(object, annotated);
159 if (oldValue != null && oldValue.equals(value)) {
160 continue;
161 }
162
163 if (annotated instanceof Field) {
164 final Field field = (Field) annotated;
165
166 LOGGER.debug("map attribute \"{}\" with value \"{}\" to field \"{}\"",
167 attribute.mapping, value, field.getName());
168
169 field.setAccessible(true);
170 field.set(object, value);
171
172 changed = true;
173 } else if (annotated instanceof Method) {
174 final Method method = (Method) annotated;
175
176 LOGGER.debug("map attribute \"{}\" with value \"{}\" to method \"{}\"",
177 attribute.mapping, value, method.getName());
178
179 method.setAccessible(true);
180 method.invoke(object, value);
181
182 changed = true;
183 }
184 } else {
185 throw new IllegalArgumentException(
186 "A not nullable attribute \"" + name + "\" was null.");
187 }
188 }
189 }
190 }
191 }
192 }
193
194 return changed;
195 }
196
197
198
199
200
201
202 public Set<String> getAttributeNames() {
203 Set<String> mAtt = new HashSet<>();
204
205 for (final String name : attributeMapping.keySet()) {
206 attributeMapping.get(name).forEach(a -> mAtt.add(a.mapping));
207 }
208
209 return mAtt;
210 }
211
212 private List<Object> getAnnotated(final Object obj) {
213 List<Object> al = new ArrayList<>();
214
215 al.addAll(getAnnotatedFields(obj.getClass()));
216 al.addAll(getAnnotatedMethods(obj.getClass()));
217
218 if (obj.getClass().getSuperclass() != null) {
219 al.addAll(getAnnotatedFields(obj.getClass().getSuperclass()));
220 al.addAll(getAnnotatedMethods(obj.getClass().getSuperclass()));
221 }
222
223 return al;
224 }
225
226 private List<Object> getAnnotatedFields(final Class<?> cls) {
227 return Arrays.stream(cls.getDeclaredFields())
228 .filter(field -> field.getAnnotation(MCRUserAttribute.class) != null)
229 .collect(Collectors.toList());
230 }
231
232 private List<Object> getAnnotatedMethods(final Class<?> cls) {
233 return Arrays.stream(cls.getDeclaredMethods())
234 .filter(method -> method.getAnnotation(MCRUserAttribute.class) != null)
235 .collect(Collectors.toList());
236 }
237
238 private String getAttriutebName(final Object annotated) {
239 if (annotated instanceof Field) {
240 return ((Field) annotated).getName();
241 } else if (annotated instanceof Method) {
242 String name = ((Method) annotated).getName();
243 if (name.startsWith("set")) {
244 name = name.substring(3);
245 }
246 return name.substring(0, 1).toLowerCase(Locale.ROOT) + name.substring(1);
247 }
248
249 return null;
250 }
251
252 private Object getValue(final Object object, final Object annotated) throws IllegalArgumentException,
253 IllegalAccessException, InvocationTargetException, NoSuchMethodException, SecurityException {
254 Object value = null;
255
256 if (annotated instanceof Field) {
257 final Field field = (Field) annotated;
258
259 field.setAccessible(true);
260 value = field.get(object);
261 } else if (annotated instanceof Method) {
262 Method method = null;
263 String name = ((Method) annotated).getName();
264 if (name.startsWith("get")) {
265 name = "s" + name.substring(1);
266 method = object.getClass().getMethod(name);
267 }
268
269 if (method != null) {
270 method.setAccessible(true);
271 value = method.invoke(object);
272 }
273 }
274
275 return value;
276 }
277
278 @XmlRootElement(name = "realm")
279 @XmlAccessorType(XmlAccessType.FIELD)
280 private static class Mappings {
281 @XmlElementWrapper(name = "attributeMapping")
282 @XmlElement(name = "attribute")
283 List<Attribute> attributes;
284
285 Map<String, List<Attribute>> getAttributeMap() {
286 return attributes.stream().collect(Collectors.groupingBy(attrib -> attrib.name));
287 }
288 }
289
290 @XmlRootElement(name = "attribute")
291 @XmlAccessorType(XmlAccessType.FIELD)
292 private static class Attribute {
293 @XmlAttribute(required = true)
294 String name;
295
296 @XmlAttribute(required = true)
297 String mapping;
298
299 @XmlAttribute
300 String separator;
301
302 @XmlAttribute
303 boolean nullable;
304
305 @XmlAttribute
306 String converter;
307
308 @XmlElement
309 List<ValueMapping> valueMapping;
310
311 Map<String, String> getValueMap() {
312 if (valueMapping == null) {
313 return null;
314 }
315
316 Map<String, String> map = new HashMap<>();
317 for (ValueMapping vm : valueMapping) {
318 map.put(vm.name, vm.mapping);
319 }
320 return map;
321 }
322 }
323
324 @XmlRootElement(name = "valueMapping")
325 @XmlAccessorType(XmlAccessType.FIELD)
326 private static class ValueMapping {
327 @XmlAttribute(required = true)
328 String name;
329
330 @XmlValue
331 String mapping;
332 }
333 }