Skip to content

Commit 6db52ef

Browse files
committed
refactored structure & added javadocs
1 parent 6cd7250 commit 6db52ef

File tree

9 files changed

+423
-80
lines changed

9 files changed

+423
-80
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/*
2+
* Copyright (c) 2016-2020 Michael Zhang <[email protected]>
3+
*
4+
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
5+
* documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
6+
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
7+
* permit persons to whom the Software is furnished to do so, subject to the following conditions:
8+
*
9+
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
10+
* Software.
11+
*
12+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
13+
* WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
14+
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
15+
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
16+
*/
17+
18+
package net.devh.boot.grpc.server.autoconfigure;
19+
20+
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
21+
import org.springframework.boot.context.properties.EnableConfigurationProperties;
22+
import org.springframework.context.annotation.Bean;
23+
import org.springframework.context.annotation.Conditional;
24+
import org.springframework.context.annotation.Configuration;
25+
26+
import net.devh.boot.grpc.server.service.exceptionhandling.GrpcExceptionHandler;
27+
import net.devh.boot.grpc.server.service.exceptionhandling.GrpcExceptionHandlerMethodResolver;
28+
import net.devh.boot.grpc.server.service.exceptionhandling.GrpcServiceAdvice;
29+
import net.devh.boot.grpc.server.service.exceptionhandling.GrpcServiceAdviceDiscoverer;
30+
import net.devh.boot.grpc.server.service.exceptionhandling.GrpcServiceAdviceExceptionHandler;
31+
import net.devh.boot.grpc.server.service.exceptionhandling.GrpcServiceAdviceIsPresent;
32+
33+
/**
34+
* The auto configuration that will create necessary beans to provide a proper exception handling via annotations
35+
* {@link GrpcServiceAdvice @GrpcServiceAdvice} and {@link GrpcExceptionHandler @GrpcExceptionHandler}.
36+
* <p>
37+
*
38+
* @author Andjelko Perisic ([email protected])
39+
* @see GrpcServiceAdvice
40+
* @see GrpcExceptionHandler
41+
*/
42+
@Configuration(proxyBeanMethods = false)
43+
@EnableConfigurationProperties
44+
@Conditional(GrpcServiceAdviceIsPresent.class)
45+
@AutoConfigureAfter(GrpcServerAutoConfiguration.class)
46+
public class GrpcExceptionAdviceAutoConfiguration {
47+
48+
@Bean
49+
public GrpcServiceAdviceDiscoverer grpcServiceAdviceDiscoverer() {
50+
return new GrpcServiceAdviceDiscoverer();
51+
}
52+
53+
@Bean
54+
public GrpcExceptionHandlerMethodResolver grpcExceptionHandlerMethodResolver(
55+
final GrpcServiceAdviceDiscoverer grpcServiceAdviceDiscoverer) {
56+
return new GrpcExceptionHandlerMethodResolver(grpcServiceAdviceDiscoverer);
57+
}
58+
59+
@Bean
60+
public GrpcServiceAdviceExceptionHandler grpcServiceAdviceExceptionHandler(
61+
GrpcExceptionHandlerMethodResolver grpcExceptionHandlerMethodResolver) {
62+
return new GrpcServiceAdviceExceptionHandler(grpcExceptionHandlerMethodResolver);
63+
}
64+
65+
}

grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/service/exceptionhandling/GrpcExceptionHandler.java

Lines changed: 46 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,18 +24,60 @@
2424
import java.lang.annotation.Target;
2525

2626
/**
27-
* TODO..
27+
* Methods annotated with {@link GrpcExceptionHandler @GrpcExceptionHandler} are being mapped to a corresponding
28+
* Exception, by declaring either in {@link GrpcExceptionHandler#value() @GrpcExceptionHandler(value = ...)} as value or
29+
* as annotated methods parameter (both is working too).
30+
* <p>
31+
* Return type of annotated methods has to be of type {@link Throwable} or {@link io.grpc.Status}, the latter is wrapped
32+
* up later as {@link io.grpc.StatusRuntimeException}. For more detailed information
33+
* {@link GrpcExceptionHandlerMethodResolver}. <br>
34+
* <p>
35+
*
36+
* As an example, this is the preferred way of handling exception,
37+
*
38+
* <pre>
39+
* {@code @GrpcExceptionHandler
40+
* public Status handleIllegalArgumentException(IllegalArgumentException e){
41+
* return Status.INVALID_ARGUMENT
42+
* .withDescription(e.getMessage())
43+
* .withCause(e);
44+
* }
45+
* }
46+
* </pre>
47+
*
48+
* but the following is also possible, especially if {@link io.grpc.Metadata} has to be returned.
49+
*
50+
* <pre>
51+
* {@code @GrpcExceptionHandler
52+
* public StatusRuntimeException handleIllegalArgumentException(IllegalArgumentException e){
53+
* Status status = Status.INVALID_ARGUMENT
54+
* .withDescription(e.getMessage())
55+
* .withCause(e);
56+
* return status.asRuntimeException();
57+
* }
58+
* }
59+
* </pre>
60+
*
61+
* Further when an {@link Exception} is raised by the application during runtime,
62+
* {@link GrpcServiceAdviceExceptionHandler} interrupts after thrown exception and executes above mentioned annotated
63+
* method which was mapped by {@link GrpcExceptionHandler @GrpcExceptionHandler} inside a class annotated with
64+
* {@link GrpcServiceAdvice @GrpcServiceAdvice}.<br>
65+
* <p>
2866
*
29-
* @author Andjelko ([email protected])
67+
* @author Andjelko Perisic ([email protected])
68+
* @see GrpcServiceAdvice
69+
* @see GrpcExceptionHandlerMethodResolver
70+
* @see GrpcServiceAdviceExceptionHandler
3071
*/
3172
@Documented
3273
@Target(ElementType.METHOD)
3374
@Retention(RetentionPolicy.RUNTIME)
3475
public @interface GrpcExceptionHandler {
3576

3677
/**
37-
* Exceptions handled by the annotated method. If empty, will default to any exceptions listed in the method
38-
* argument list.
78+
* Exceptions handled by the annotated method.
79+
*
80+
* If empty, will default to any exceptions listed in the method argument list.
3981
*/
4082
Class<? extends Throwable>[] value() default {};
4183
}

grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/service/exceptionhandling/GrpcExceptionHandlerMethodResolver.java

Lines changed: 30 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -22,66 +22,44 @@
2222
import java.util.Arrays;
2323
import java.util.HashMap;
2424
import java.util.Map;
25-
import java.util.Set;
26-
import java.util.function.Function;
27-
import java.util.stream.Collectors;
28-
import java.util.stream.Stream;
2925

3026
import org.springframework.beans.factory.InitializingBean;
31-
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
32-
import org.springframework.context.ApplicationContext;
33-
import org.springframework.stereotype.Component;
3427
import org.springframework.util.Assert;
3528

3629
import lombok.extern.slf4j.Slf4j;
3730

3831
/**
39-
* TODO..
32+
* Given an annotated {@link GrpcServiceAdvice @GrpcServiceAdvice} class and annotated methods with
33+
* {@link GrpcExceptionHandler @GrpcExceptionHandler}, {@link GrpcExceptionHandlerMethodResolver} resolves given
34+
* exception type and maps it to the corresponding method to be executed, when this exception is being raised.<br>
35+
* For an example how to make use of it, please have a look {@link GrpcExceptionHandler @GrpcExceptionHandler}.<br>
36+
* <br>
4037
*
41-
* @author Andjelko ([email protected])
38+
* @author Andjelko Perisic ([email protected])
39+
* @see GrpcServiceAdvice
40+
* @see GrpcExceptionHandler
41+
* @see GrpcServiceAdviceExceptionHandler
4242
*/
4343
@Slf4j
44-
@Component
45-
@ConditionalOnBean(annotation = GrpcServiceAdvice.class)
46-
class GrpcExceptionHandlerMethodResolver implements InitializingBean {
44+
public class GrpcExceptionHandlerMethodResolver implements InitializingBean {
4745

4846
private final Map<Class<? extends Throwable>, Method> mappedMethods = new HashMap<>(16);
49-
private final ApplicationContext applicationContext;
5047

51-
private Class<? extends Throwable>[] annotatedExceptions;
52-
private Map<String, Object> annotatedBeans;
48+
private final GrpcServiceAdviceDiscoverer grpcServiceAdviceDiscoverer;
5349

50+
private Class<? extends Throwable>[] annotatedExceptions;
5451

55-
GrpcExceptionHandlerMethodResolver(final ApplicationContext applicationContext) {
56-
this.applicationContext = applicationContext;
52+
public GrpcExceptionHandlerMethodResolver(final GrpcServiceAdviceDiscoverer grpcServiceAdviceDiscoverer) {
53+
this.grpcServiceAdviceDiscoverer = grpcServiceAdviceDiscoverer;
5754
}
5855

5956
@Override
6057
public void afterPropertiesSet() throws Exception {
61-
Set<Class<?>> annotatedClasses = findAllAnnotatedClasses();
62-
Set<Method> annotatedMethods = getAnnotatedMethods(annotatedClasses);
63-
64-
annotatedMethods.forEach(this::checkParamTypesAreAlignedToExceptionTypes);
65-
}
66-
67-
private Set<Class<?>> findAllAnnotatedClasses() {
68-
annotatedBeans = applicationContext.getBeansWithAnnotation(GrpcServiceAdvice.class);
69-
return annotatedBeans.values()
70-
.stream()
71-
.map(Object::getClass)
72-
.collect(Collectors.toSet());
73-
}
74-
75-
private Set<Method> getAnnotatedMethods(Set<Class<?>> annotatedClasses) {
76-
Function<Class<?>, Stream<Method>> extractMethodsFromClass = clazz -> Arrays.stream(clazz.getDeclaredMethods());
77-
return annotatedClasses.stream()
78-
.flatMap(extractMethodsFromClass)
79-
.filter(method -> method.isAnnotationPresent(GrpcExceptionHandler.class))
80-
.collect(Collectors.toSet());
58+
grpcServiceAdviceDiscoverer.getAnnotatedMethods()
59+
.forEach(this::extractAndMapExceptionToMethod);
8160
}
8261

83-
84-
private void checkParamTypesAreAlignedToExceptionTypes(Method method) {
62+
private void extractAndMapExceptionToMethod(Method method) {
8563

8664
GrpcExceptionHandler annotation = method.getDeclaredAnnotation(GrpcExceptionHandler.class);
8765
Assert.notNull(annotation, "@GrpcExceptionHandler annotation not found.");
@@ -107,6 +85,17 @@ private Class<? extends Throwable>[] checkForExceptionType(Class<?>[] methodPara
10785
return paramExceptionTypes;
10886
}
10987

88+
private void addExceptionMapping(Class<? extends Throwable> exceptionType, Method method) {
89+
90+
parameterTypeIsAssignable(exceptionType);
91+
92+
Method oldMethod = mappedMethods.put(exceptionType, method);
93+
if (oldMethod != null && !oldMethod.equals(method)) {
94+
throw new IllegalStateException("Ambiguous @GrpcExceptionHandler method mapped for [" +
95+
exceptionType + "]: {" + oldMethod + ", " + method + "}");
96+
}
97+
}
98+
11099
private void parameterTypeIsAssignable(Class<? extends Throwable> paramType) {
111100

112101
if (annotatedExceptions.length == 0) {
@@ -123,17 +112,6 @@ private void parameterTypeIsAssignable(Class<? extends Throwable> paramType) {
123112
paramType, Arrays.toString(annotatedExceptions)));
124113
}
125114

126-
private void addExceptionMapping(Class<? extends Throwable> exceptionType, Method method) {
127-
128-
parameterTypeIsAssignable(exceptionType);
129-
130-
Method oldMethod = mappedMethods.put(exceptionType, method);
131-
if (oldMethod != null && !oldMethod.equals(method)) {
132-
throw new IllegalStateException("Ambiguous @GrpcExceptionHandler method mapped for [" +
133-
exceptionType + "]: {" + oldMethod + ", " + method + "}");
134-
}
135-
}
136-
137115

138116
public <E extends Throwable> Map.Entry<Object, Method> resolveMethodWithInstance(Class<E> exceptionType) {
139117

@@ -143,7 +121,8 @@ public <E extends Throwable> Map.Entry<Object, Method> resolveMethodWithInstance
143121
}
144122

145123
Class<?> methodClass = value.getDeclaringClass();
146-
Object key = annotatedBeans.values()
124+
Object key = grpcServiceAdviceDiscoverer.getAnnotatedBeans()
125+
.values()
147126
.stream()
148127
.filter(obj -> methodClass.isAssignableFrom(obj.getClass()))
149128
.findFirst()
@@ -163,5 +142,4 @@ private <E extends Throwable> Method extractExtendedThrowable(Class<E> exception
163142
.orElse(null);
164143
}
165144

166-
167145
}

grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/service/exceptionhandling/GrpcServiceAdvice.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,14 @@
2626
import org.springframework.stereotype.Component;
2727

2828
/**
29-
* Special {@link Component @Component} to declare GrpcException Handling.
29+
* Special {@link Component @Component} to declare {@link GrpcExceptionHandler GrpcException Handling}.
3030
*
31-
* @author Andjelko ([email protected])
31+
* Every class annotated with {@link GrpcServiceAdvice @GrpcServiceAdvice} is marked to be scanned for
32+
* {@link GrpcExceptionHandler @GrpcExceptionHandler} annotations.
33+
*
34+
* @author Andjelko Perisic ([email protected])
35+
*
36+
* @see GrpcExceptionHandler
3237
*/
3338
@Target(ElementType.TYPE)
3439
@Retention(RetentionPolicy.RUNTIME)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
/*
2+
* Copyright (c) 2016-2020 Michael Zhang <[email protected]>
3+
*
4+
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
5+
* documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
6+
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
7+
* permit persons to whom the Software is furnished to do so, subject to the following conditions:
8+
*
9+
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
10+
* Software.
11+
*
12+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
13+
* WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
14+
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
15+
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
16+
*/
17+
18+
package net.devh.boot.grpc.server.service.exceptionhandling;
19+
20+
import java.lang.reflect.Method;
21+
import java.util.Arrays;
22+
import java.util.Map;
23+
import java.util.Set;
24+
import java.util.function.Function;
25+
import java.util.stream.Collectors;
26+
import java.util.stream.Stream;
27+
28+
import org.springframework.beans.factory.InitializingBean;
29+
import org.springframework.context.ApplicationContext;
30+
import org.springframework.context.ApplicationContextAware;
31+
32+
import lombok.extern.slf4j.Slf4j;
33+
34+
/**
35+
* A discovery class to find all Beans annotated with {@link GrpcServiceAdvice @GrpcServiceAdvice} and for all found
36+
* beans a second search is performed looking for methods with {@link GrpcExceptionHandler @GrpcExceptionHandler}.<br>
37+
* <br>
38+
*
39+
* @author Andjelko Perisic ([email protected])
40+
* @see GrpcServiceAdvice
41+
* @see GrpcExceptionHandler
42+
*/
43+
@Slf4j
44+
public class GrpcServiceAdviceDiscoverer implements InitializingBean, ApplicationContextAware {
45+
46+
private ApplicationContext applicationContext;
47+
private Map<String, Object> annotatedBeans;
48+
private Set<Class<?>> annotatedClasses;
49+
private Set<Method> annotatedMethods;
50+
51+
52+
Map<String, Object> getAnnotatedBeans() {
53+
return annotatedBeans;
54+
}
55+
56+
Set<Method> getAnnotatedMethods() {
57+
return annotatedMethods;
58+
}
59+
60+
61+
@Override
62+
public void afterPropertiesSet() throws Exception {
63+
64+
annotatedBeans = applicationContext.getBeansWithAnnotation(GrpcServiceAdvice.class);
65+
annotatedClasses = findAllAnnotatedClasses();
66+
annotatedMethods = getAnnotatedMethods(annotatedClasses);
67+
}
68+
69+
private Set<Class<?>> findAllAnnotatedClasses() {
70+
return annotatedBeans.values()
71+
.stream()
72+
.map(Object::getClass)
73+
.collect(Collectors.toSet());
74+
}
75+
76+
private Set<Method> getAnnotatedMethods(Set<Class<?>> annotatedClasses) {
77+
Function<Class<?>, Stream<Method>> extractMethodsFromClass = clazz -> Arrays.stream(clazz.getDeclaredMethods());
78+
return annotatedClasses.stream()
79+
.flatMap(extractMethodsFromClass)
80+
.filter(method -> method.isAnnotationPresent(GrpcExceptionHandler.class))
81+
.collect(Collectors.toSet());
82+
}
83+
84+
boolean isAnnotationPresent() {
85+
86+
return !annotatedClasses.isEmpty() && !annotatedMethods.isEmpty();
87+
}
88+
89+
@Override
90+
public void setApplicationContext(final ApplicationContext applicationContext) {
91+
this.applicationContext = applicationContext;
92+
}
93+
}

0 commit comments

Comments
 (0)