-
Notifications
You must be signed in to change notification settings - Fork 0
/
ErrorHandlerFactory.java
356 lines (316 loc) · 15.3 KB
/
ErrorHandlerFactory.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
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
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
package com.bellotapps.utils.error_handler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanInitializationException;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.core.ResolvableType;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.core.type.filter.AssignableTypeFilter;
import org.springframework.core.type.filter.TypeFilter;
import org.springframework.util.ClassUtils;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* Object in charge of creating an {@link ErrorHandler}.
*/
public class ErrorHandlerFactory {
/**
* The {@link Logger} object.
*/
private static final Logger LOGGER = LoggerFactory.getLogger(ErrorHandlerFactory.class);
/**
* An error message to be used when something goes wrong when trying to create the error handler.
*/
private final static String ERROR_MESSAGE = "Could not create an error handler";
/**
* {@link ClassLoader} used to get {@link ExceptionHandler} classes in package scanning phase.
*/
private final ClassLoader classLoader;
/**
* The {@link BeanFactory} used to check if there are beans of the scanned {@link ExceptionHandler}s.
* This is used in order to use beans instead of own created {@link ExceptionHandler}s.
*/
private final BeanFactory beanFactory;
/**
* A {@link ClassPathScanningCandidateComponentProvider}
* used for scanning packages in search of classes definitions.
*/
private final ClassPathScanningCandidateComponentProvider scanner;
/**
* A {@link Map} holding cached {@link ExceptionHandler}s for a given package name.
*/
private final Map<String, List<ExceptionHandler<? extends Throwable>>> cachedHandlers;
/**
* Constructor.
*
* @param classLoader The {@link ClassLoader} used to scan packages.
* @param beanFactory The {@link BeanFactory} used to get beans (if they exists)
* of the scanned {@link ExceptionHandler}s
*/
public ErrorHandlerFactory(ClassLoader classLoader, BeanFactory beanFactory) {
this.classLoader = classLoader;
this.beanFactory = beanFactory;
this.scanner = new ClassPathScanningCandidateComponentProvider(false);
// Scan for classes implementing ExceptionHandler interface, and annotated with ExceptionHandlerObject.
this.scanner.addIncludeFilter(new ExceptionHandlerObjectAnnotatedAndExceptionHandlerAssignableTypeFilter());
this.cachedHandlers = new ConcurrentHashMap<>();
}
/**
* Clears the cache stored in this factory
* (i.e will make it perform package scanning again when asking for an error handler).
*/
public void resetCache() {
this.cachedHandlers.clear();
}
/**
* Clears the cache for the given {@code packages}.
*
* @param packages The packages whose cache will be cleared.
*/
public void resetCache(String... packages) {
resetCache(Arrays.asList(packages));
}
/**
* Clears the cache for the given {@code packages}.
*
* @param packages The packages whose cache will be cleared.
*/
public void resetCache(Collection<String> packages) {
packages.forEach(this.cachedHandlers::remove);
}
/**
* Creates an {@link ErrorHandler}, scanning for {@link ExceptionHandler} in the given {@code packages}.
*
* @param packages The packages to be scanned for {@link ExceptionHandler}s.
* @return The created {@link ErrorHandler}.
* @see ExceptionHandlerObject
* @see ExceptionHandler
*/
public ErrorHandler createErrorHandler(String... packages) {
return createErrorHandler(Arrays.asList(packages));
}
/**
* Creates an {@link ErrorHandler}, scanning for {@link ExceptionHandler} in the given {@code packages}.
*
* @param packages The packages to be scanned for {@link ExceptionHandler}s.
* @return The created {@link ErrorHandler}.
* @see ExceptionHandlerObject
* @see ExceptionHandler
*/
public ErrorHandler createErrorHandler(Collection<String> packages) {
// Perform package scanning for those not cached
final Map<String, List<ExceptionHandler<? extends Throwable>>> foundedHandlers = packages.stream()
.filter(pkg -> !cachedHandlers.containsKey(pkg))
.collect(Collectors.toMap(Function.identity(),
pkg -> scanPackage(pkg)
.stream()
.map(klass -> new ExceptionHandlerGetter<>(klass, beanFactory))
.map(ExceptionHandlerGetter::getHandler)
.collect(Collectors.toList())));
// Save in cache those handlers that have been found
this.cachedHandlers.putAll(foundedHandlers);
// Get stored handlers
final List<ExceptionHandler<? extends Throwable>> handlers = cachedHandlers.values()
.stream()
.flatMap(Collection::stream)
.collect(Collectors.toList());
// Create the new ErrorHandler
return new ErrorHandlerImpl(handlers);
}
/**
* Scans the given package, searching for {@link ExceptionHandler}s in it (according to the {@code scanner}).
*
* @param pkg The package to be scanned.
* @return The classes founded in the given package
* matching the restrictions to be a valid {@link ExceptionHandler}.
* @see ExceptionHandlerObject
* @see ExceptionHandler
*/
private Set<Class<?>> scanPackage(String pkg) {
return this.scanner.findCandidateComponents(pkg)
.stream()
.map(beanDef -> ClassUtils.resolveClassName(beanDef.getBeanClassName(), this.classLoader))
.collect(Collectors.toSet());
}
/**
* Helper class to get extensions of {@link ExceptionHandler} of a given {@link Throwable}, in a type-safe way.
*
* @param <T> The concrete type of {@link ExceptionHandler}.
*/
private final static class ExceptionHandlerGetter<T extends ExceptionHandler<? extends Throwable>> {
/**
* The class of {@link ExceptionHandler} extension.
*/
private final Class<T> handlerClass;
/**
* The {@link BeanFactory} used to get a bean of the given {@code handlerClass}.
*/
private final BeanFactory beanFactory;
/**
* Constructor.
*
* @param handlerClass The class of {@link ExceptionHandler} extension.
* @param beanFactory The {@link BeanFactory} used to get a bean of the given {@code handlerClass}.
*/
private ExceptionHandlerGetter(Class<?> handlerClass, BeanFactory beanFactory) {
//noinspection unchecked
this.handlerClass = (Class<T>) handlerClass;
this.beanFactory = beanFactory;
}
/**
* Retrieves an {@link ExceptionHandler} of the {@link Class} of {@link ExceptionHandler}
* this {@link ExceptionHandlerGetter} was created for,
* trying to get a spring bean or instantiating it
*
* @return An {@link ExceptionHandler} of the given {@link Class}.
*/
private T getHandler() {
return searchForBean().orElse(instantiate());
}
/**
* Tries to get a spring bean for the {@link Class} of {@link ExceptionHandler}
* this {@link ExceptionHandlerGetter} was created for.
* If a bean is found, it will be wrapped in the returned {@link Optional}.
* Otherwise, the {@link Optional} will be empty.
* In case of unexpected errors, a {@link BeanInitializationException} will be thrown.
*
* @return An {@link Optional} containing the found bean,
* or empty in case no bean (or multiple beans) were found.
* @throws BeanInitializationException In case some unexpected error occurred when searching for the bean.
*/
private Optional<T> searchForBean()
throws BeanInitializationException {
try {
final T handler = beanFactory.getBean(handlerClass);
logBeanFound(handlerClass);
return Optional.of(handler);
} catch (NoUniqueBeanDefinitionException e) {
// More than one bean exist for the given handler class, so we create our own as we don't know which to use.
logMultipleBeans(handlerClass);
return Optional.empty();
} catch (NoSuchBeanDefinitionException e) {
// No bean for the given handler class, so we create our own handler of the given class.
logNoBeanFound(handlerClass);
return Optional.empty();
} catch (BeansException e) {
logBeansException(handlerClass);
throw new BeanInitializationException(ERROR_MESSAGE, e);
} catch (Throwable e) {
logUnexpectedErrorWhenSearchingForBeans(handlerClass, e);
throw new BeanInitializationException(ERROR_MESSAGE, e);
}
}
/**
* Creates an instance of the {@link Class} of {@link ExceptionHandler}
* this {@link ExceptionHandlerGetter} was created for.
*/
private T instantiate() {
try {
final Constructor<T> constructor = handlerClass.getDeclaredConstructor(); // Get default constructor
constructor.setAccessible(true);
final T handler = constructor.newInstance();
constructor.setAccessible(false);
return handler;
} catch (NoSuchMethodException e) {
LOGGER.error("No default constructor for class {}", handlerClass);
throw new BeanInitializationException(ERROR_MESSAGE, e);
} catch (InstantiationException e) {
LOGGER.error("Could not instantiate class {}. Is this class abstract?", handlerClass);
throw new BeanInitializationException(ERROR_MESSAGE, e);
} catch (IllegalAccessException e) {
LOGGER.debug("Could not access default constructor of class {}", handlerClass);
LOGGER.error("Could not instantiate class {}", handlerClass);
throw new BeanInitializationException(ERROR_MESSAGE, e);
} catch (InvocationTargetException e) {
LOGGER.error("Could not instantiate exception handler {} as an exception was thrown " +
"while executing its constructor.", handlerClass);
throw new BeanInitializationException(ERROR_MESSAGE, e);
}
}
/**
* Logs that a bean of the given {@link ExceptionHandler} class has bean found.
*
* @param handlerClass The class of the bean that has been found.
*/
private static void logBeanFound(Class<? extends ExceptionHandler<? extends Throwable>> handlerClass) {
@SuppressWarnings("unchecked") final Class<? extends Throwable> throwableClass =
(Class<? extends Throwable>) ResolvableType.forClass(ExceptionHandler.class, handlerClass)
.getGeneric(0)
.resolve();
LOGGER.info("Found bean of {} for throwable {}", handlerClass.getName(), throwableClass.getName());
}
/**
* Logs that several beans of the given {@link ExceptionHandler} class has bean found.
*
* @param handlerClass The class of the beans that has been found.
*/
private static void logMultipleBeans(Class<? extends ExceptionHandler<? extends Throwable>> handlerClass) {
LOGGER.warn("More than one bean exist for class {}. Will instantiate own handler", handlerClass);
}
/**
* Logs that no bean was found of the given {@link ExceptionHandler} class.
*
* @param handlerClass The class of the not found beans.
*/
private static void logNoBeanFound(Class<? extends ExceptionHandler<? extends Throwable>> handlerClass) {
LOGGER.debug("No bean for class {}. Will create one", handlerClass);
}
/**
* Logs that there were errors when trying to find a bean of the given {@link ExceptionHandler} class,
* so it was not possible to get one of it.
*
* @param handlerClass The class of the bean that could not be gotten due to errors.
*/
private static void logBeansException(Class<? extends ExceptionHandler<? extends Throwable>> handlerClass) {
LOGGER.error("Could not get bean for class {}", handlerClass);
}
/**
* Logs that there were an unexpected error when trying to get a bean.
*
* @param klass The {@link Class} for which an unexpected error occurred when trying to get a bean for it.
* @param e The {@link Throwable} that was thrown in the process.
*/
private static void logUnexpectedErrorWhenSearchingForBeans(Class<?> klass, Throwable e) {
LOGGER.error("Some unexpected error occurred when trying to get an Exception Handler for class {}.", klass);
LOGGER.error("Please, report this issue.", e);
}
}
/**
* Custom filter to get classes annotated with {@link ExceptionHandlerObject},
* and that implement the {@link ExceptionHandler} interface.
*/
private static class ExceptionHandlerObjectAnnotatedAndExceptionHandlerAssignableTypeFilter implements TypeFilter {
/**
* Annotation type filter to get those classes annotated with {@link ExceptionHandlerObject}.
*/
private final AnnotationTypeFilter exceptionHandlerObjectAnnotationFilter;
/**
* Assignable type filter to get those classes that implement {@link ExceptionHandler} interface.
*/
private final AssignableTypeFilter assignableFromExceptionHandlerInterfaceFilter;
/**
* Constructor.
*/
private ExceptionHandlerObjectAnnotatedAndExceptionHandlerAssignableTypeFilter() {
this.exceptionHandlerObjectAnnotationFilter = new AnnotationTypeFilter(ExceptionHandlerObject.class);
this.assignableFromExceptionHandlerInterfaceFilter = new AssignableTypeFilter(ExceptionHandler.class);
}
@Override
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)
throws IOException {
return exceptionHandlerObjectAnnotationFilter.match(metadataReader, metadataReaderFactory)
&& assignableFromExceptionHandlerInterfaceFilter.match(metadataReader, metadataReaderFactory);
}
}
}