1 package org.oxerr.spring.cache.redis.scored.score.resolver.annotated;
2
3 import java.io.IOException;
4 import java.lang.annotation.Annotation;
5 import java.lang.reflect.AnnotatedElement;
6 import java.lang.reflect.Field;
7 import java.lang.reflect.InvocationTargetException;
8 import java.lang.reflect.Method;
9 import java.util.HashMap;
10 import java.util.List;
11 import java.util.Map;
12 import java.util.Objects;
13 import java.util.Optional;
14
15 import org.apache.commons.lang3.reflect.FieldUtils;
16 import org.apache.commons.lang3.reflect.MethodUtils;
17 import org.oxerr.spring.cache.redis.scored.score.resolver.ScoreResolver;
18 import org.oxerr.spring.cache.redis.scored.score.resolver.ScoreUtils;
19 import org.springframework.core.Ordered;
20 import org.springframework.core.annotation.Order;
21 import org.springframework.lang.NonNull;
22 import org.springframework.lang.Nullable;
23
24 public class AnnotatedScoreResolver implements ScoreResolver {
25
26 private static final long serialVersionUID = 2021082001L;
27
28 private transient Map<Class<?>, AnnotatedElementWrapper<? extends AnnotatedElement>> annotatedElements;
29
30 private Class<? extends Annotation> annotationType;
31
32 public AnnotatedScoreResolver(Class<? extends Annotation> annotationType) {
33 this.annotationType = annotationType;
34 this.annotatedElements = new HashMap<>();
35 }
36
37 @Override
38 public Optional<Double> resolveScore(@Nullable Object value) {
39 if (value == null) {
40 return Optional.empty();
41 } else {
42 return getScore(value);
43 }
44 }
45
46 private Optional<Double> getScore(@NonNull Object value) {
47 Optional<Double> score = Optional.empty();
48
49 try {
50 score = this.getScoreInternal(value);
51 } catch (IllegalAccessException | InvocationTargetException e) {
52 throw new IllegalArgumentException(e);
53 }
54
55 return score;
56 }
57
58 private Optional<Double> getScoreInternal(@NonNull Object value)
59 throws IllegalAccessException, InvocationTargetException {
60 final Class<?> valueType = value.getClass();
61 final Object version = this.getAnnotatedElement(valueType).readValue(value);
62 return Optional.ofNullable(extractScore(version));
63 }
64
65 private AnnotatedElementWrapper<? extends AnnotatedElement> getAnnotatedElement(Class<?> valueType) {
66 AnnotatedElementWrapper<? extends AnnotatedElement> annotatedElement = this.annotatedElements.get(valueType);
67 if (annotatedElement == null) {
68 annotatedElement = resolveAnnotatedElement(valueType);
69 this.annotatedElements.put(valueType, annotatedElement);
70 }
71 return annotatedElement;
72 }
73
74 private AnnotatedElementWrapper<? extends AnnotatedElement> resolveAnnotatedElement(Class<?> valueType) {
75 final AnnotatedElementWrapper<? extends AnnotatedElement> annotatedElement;
76
77 final List<Method> methods = MethodUtils.getMethodsListWithAnnotation(valueType, annotationType, true, true);
78
79 if (!methods.isEmpty()) {
80 final Method method;
81 if (methods.size() == 1) {
82 method = methods.get(0);
83 } else {
84 method = methods.stream()
85 .map(OrderedAnnotatedElement::new)
86 .min((a, b) -> Integer.compare(a.getOrder(), b.getOrder()))
87 .map(OrderedAnnotatedElement::getAnnotatedElement)
88 .orElseThrow(IllegalArgumentException::new);
89 }
90 method.setAccessible(true);
91 annotatedElement = AnnotatedElementWrapper.of(method, AnnotatedElementWrapper.Type.METHOD);
92 } else {
93 final List<Field> fields = FieldUtils.getFieldsListWithAnnotation(valueType, annotationType);
94 if (!fields.isEmpty()) {
95 final Field field;
96 if (fields.size() == 1) {
97 field = fields.get(0);
98 } else {
99 field = fields.stream()
100 .map(OrderedAnnotatedElement::new)
101 .min((a, b) -> Integer.compare(a.getOrder(), b.getOrder()))
102 .map(OrderedAnnotatedElement::getAnnotatedElement)
103 .orElseThrow(IllegalArgumentException::new);
104 }
105 field.setAccessible(true);
106 annotatedElement = AnnotatedElementWrapper.of(field, AnnotatedElementWrapper.Type.FIELD);
107 } else {
108 annotatedElement = AnnotatedElementWrapper.empty();
109 }
110 }
111
112 return annotatedElement;
113 }
114
115 private static class AnnotatedElementWrapper<T extends AnnotatedElement> {
116
117
118
119
120 private static final AnnotatedElementWrapper<?> EMPTY = new AnnotatedElementWrapper<>();
121
122 private final T annotatedElement;
123
124 public enum Type {
125 METHOD,
126 FIELD;
127 }
128
129 private final Type type;
130
131 private AnnotatedElementWrapper() {
132 this.annotatedElement = null;
133 this.type = null;
134 }
135
136 public static <T extends AnnotatedElement> AnnotatedElementWrapper<T> empty() {
137 @SuppressWarnings("unchecked")
138 AnnotatedElementWrapper<T> t = (AnnotatedElementWrapper<T>) EMPTY;
139 return t;
140 }
141
142 private AnnotatedElementWrapper(T annotatedElement, Type type) {
143 this.annotatedElement = Objects.requireNonNull(annotatedElement);
144 this.type = Objects.requireNonNull(type);
145 }
146
147 public static <T extends AnnotatedElement> AnnotatedElementWrapper<T> of(T value, Type type) {
148 return new AnnotatedElementWrapper<>(value, type);
149 }
150
151 private boolean isPresent() {
152 return this.annotatedElement != null;
153 }
154
155 public Object readValue(Object object) throws IllegalAccessException, InvocationTargetException {
156 Object value;
157 if (isPresent()) {
158 if (this.type == Type.METHOD) {
159 value = ((Method) this.annotatedElement).invoke(object);
160 } else {
161 value = ((Field) this.annotatedElement).get(object);
162 }
163 } else {
164 value = null;
165 }
166 return value;
167 }
168
169 }
170
171 private class OrderedAnnotatedElement<T extends AnnotatedElement> implements Ordered {
172
173 private final T annotatedElement;
174 private final int order;
175
176 public OrderedAnnotatedElement(T annotatedElement) {
177 this.annotatedElement = annotatedElement;
178 this.order = Optional.ofNullable(annotatedElement.getAnnotation(Order.class))
179 .map(Order::value)
180 .orElse(Ordered.LOWEST_PRECEDENCE);
181 }
182
183 public T getAnnotatedElement() {
184 return annotatedElement;
185 }
186
187 @Override
188 public int getOrder() {
189 return order;
190 }
191
192 }
193
194 protected Double extractScore(Object version) {
195 return ScoreUtils.extractScore(version);
196 }
197
198 private void writeObject(java.io.ObjectOutputStream out) throws IOException {
199 out.writeUTF(this.annotationType.getName());
200 }
201
202 @SuppressWarnings("unchecked")
203 private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException {
204 this.annotationType = (Class<? extends Annotation>) Class.forName(in.readUTF());
205 this.annotatedElements = new HashMap<>();
206 }
207
208 }