17
17
18
18
package net .devh .boot .grpc .server .advice ;
19
19
20
+ import static java .util .Objects .requireNonNull ;
21
+
20
22
import java .lang .reflect .InvocationTargetException ;
21
23
import java .lang .reflect .Method ;
22
24
import java .lang .reflect .Parameter ;
25
27
26
28
import org .springframework .lang .Nullable ;
27
29
30
+ import io .grpc .Metadata ;
31
+ import io .grpc .ServerCall ;
32
+ import io .grpc .Status ;
33
+ import io .grpc .StatusException ;
34
+ import io .grpc .StatusRuntimeException ;
28
35
import lombok .extern .slf4j .Slf4j ;
36
+ import net .devh .boot .grpc .server .error .GrpcExceptionInterceptor ;
37
+ import net .devh .boot .grpc .server .error .GrpcExceptionResponseHandler ;
29
38
30
39
/**
31
40
* As part of {@link GrpcAdvice @GrpcAdvice}, when a thrown exception is caught during gRPC calls (via global
32
- * interceptor {@link GrpcAdviceExceptionInterceptor}, then this thrown exception is being handled. By
33
- * {@link GrpcExceptionHandlerMethodResolver} is a mapping between exception and the in case to be executed method
34
- * provided. <br>
35
- * Returned object is declared in {@link GrpcAdvice @GrpcAdvice} classes with annotated methods
36
- * {@link GrpcExceptionHandler @GrpcExceptionHandler}.
41
+ * interceptor {@link GrpcExceptionInterceptor}), then this thrown exception is being handled. The
42
+ * {@link GrpcExceptionHandlerMethodResolver} provides a mapping for exceptions and their respective handler methods.
43
+ *
37
44
* <p>
45
+ * The response is derived from methods annotated with {@link GrpcExceptionHandler} inside {@link GrpcAdvice} beans.
46
+ * </p>
38
47
*
39
48
* @author Andjelko Perisic ([email protected] )
49
+ * @see GrpcAdvice
50
+ * @see GrpcExceptionHandler
40
51
* @see GrpcExceptionHandlerMethodResolver
41
- * @see GrpcAdviceExceptionInterceptor
52
+ * @see GrpcExceptionInterceptor
42
53
*/
43
54
@ Slf4j
44
- public class GrpcAdviceExceptionHandler {
55
+ public class GrpcAdviceExceptionHandler implements GrpcExceptionResponseHandler {
45
56
46
57
private final GrpcExceptionHandlerMethodResolver grpcExceptionHandlerMethodResolver ;
47
58
59
+ /**
60
+ * Creates a new {@link GrpcAdvice} powered {@link GrpcExceptionHandler}.
61
+ *
62
+ * @param grpcExceptionHandlerMethodResolver The method resolver to use.
63
+ */
48
64
public GrpcAdviceExceptionHandler (
49
65
final GrpcExceptionHandlerMethodResolver grpcExceptionHandlerMethodResolver ) {
50
- this .grpcExceptionHandlerMethodResolver = grpcExceptionHandlerMethodResolver ;
66
+ this .grpcExceptionHandlerMethodResolver =
67
+ requireNonNull (grpcExceptionHandlerMethodResolver , "grpcExceptionHandlerMethodResolver" );
68
+ }
69
+
70
+ @ Override
71
+ public void handleError (final ServerCall <?, ?> serverCall , final Throwable error ) {
72
+ try {
73
+ final Object mappedReturnType = handleThrownException (error );
74
+ final Status status = resolveStatus (mappedReturnType );
75
+ final Metadata metadata = resolveMetadata (mappedReturnType );
76
+
77
+ serverCall .close (status , metadata );
78
+ } catch (final Throwable errorWhileResolving ) {
79
+ if (errorWhileResolving != error ) {
80
+ errorWhileResolving .addSuppressed (error );
81
+ }
82
+ handleThrownExceptionByImplementation (serverCall , errorWhileResolving );
83
+ }
84
+ }
85
+
86
+ protected Status resolveStatus (final Object mappedReturnType ) {
87
+ if (mappedReturnType instanceof Status ) {
88
+ return (Status ) mappedReturnType ;
89
+ } else if (mappedReturnType instanceof Throwable ) {
90
+ return Status .fromThrowable ((Throwable ) mappedReturnType );
91
+ }
92
+ throw new IllegalStateException (String .format (
93
+ "Error for mapped return type [%s] inside @GrpcAdvice, it has to be of type: "
94
+ + "[Status, StatusException, StatusRuntimeException, Throwable] " ,
95
+ mappedReturnType ));
96
+ }
97
+
98
+ protected Metadata resolveMetadata (final Object mappedReturnType ) {
99
+ Metadata result = null ;
100
+ if (mappedReturnType instanceof StatusException ) {
101
+ final StatusException statusException = (StatusException ) mappedReturnType ;
102
+ result = statusException .getTrailers ();
103
+ } else if (mappedReturnType instanceof StatusRuntimeException ) {
104
+ final StatusRuntimeException statusException = (StatusRuntimeException ) mappedReturnType ;
105
+ result = statusException .getTrailers ();
106
+ }
107
+ return (result == null ) ? new Metadata () : result ;
108
+ }
109
+
110
+ protected void handleThrownExceptionByImplementation (final ServerCall <?, ?> serverCall , final Throwable throwable ) {
111
+ log .error ("Exception thrown during invocation of annotated @GrpcExceptionHandler method: " , throwable );
112
+ serverCall .close (Status .INTERNAL .withCause (throwable )
113
+ .withDescription ("There was a server error trying to handle an exception" ), new Metadata ());
51
114
}
52
115
53
116
/**
54
117
* Given an exception, a lookup is performed to retrieve mapped method. <br>
55
118
* In case of successful returned method, and matching exception parameter type for given exception, the exception
56
119
* is handed over to retrieved method. Retrieved method is then being invoked.
57
- *
120
+ *
58
121
* @param exception exception to search for
59
- * @param <E> type of exception
60
122
* @return result of invoked mapped method to given exception
61
123
* @throws Throwable rethrows exception if no mapping existent or exceptions raised by implementation
62
124
*/
63
125
@ Nullable
64
- public < E extends Throwable > Object handleThrownException (E exception ) throws Throwable {
126
+ protected Object handleThrownException (final Throwable exception ) throws Throwable {
65
127
log .debug ("Exception caught during gRPC execution: " , exception );
66
128
67
129
final Class <? extends Throwable > exceptionClass = exception .getClass ();
68
- boolean exceptionIsMapped =
69
- grpcExceptionHandlerMethodResolver .isMethodMappedForException (exceptionClass );
130
+ final boolean exceptionIsMapped =
131
+ this . grpcExceptionHandlerMethodResolver .isMethodMappedForException (exceptionClass );
70
132
if (!exceptionIsMapped ) {
71
133
throw exception ;
72
134
}
73
135
74
- Entry <Object , Method > methodWithInstance =
75
- grpcExceptionHandlerMethodResolver .resolveMethodWithInstance (exceptionClass );
76
- Method mappedMethod = methodWithInstance .getValue ();
77
- Object instanceOfMappedMethod = methodWithInstance .getKey ();
78
- Object [] instancedParams = determineInstancedParameters (mappedMethod , exception );
136
+ final Entry <Object , Method > methodWithInstance =
137
+ this . grpcExceptionHandlerMethodResolver .resolveMethodWithInstance (exceptionClass );
138
+ final Method mappedMethod = methodWithInstance .getValue ();
139
+ final Object instanceOfMappedMethod = methodWithInstance .getKey ();
140
+ final Object [] instancedParams = determineInstancedParameters (mappedMethod , exception );
79
141
80
142
return invokeMappedMethodSafely (mappedMethod , instanceOfMappedMethod , instancedParams );
81
143
}
82
144
83
- private < E extends Throwable > Object [] determineInstancedParameters (Method mappedMethod , E exception ) {
145
+ private Object [] determineInstancedParameters (final Method mappedMethod , final Throwable exception ) {
84
146
85
- Parameter [] parameters = mappedMethod .getParameters ();
86
- Object [] instancedParams = new Object [parameters .length ];
147
+ final Parameter [] parameters = mappedMethod .getParameters ();
148
+ final Object [] instancedParams = new Object [parameters .length ];
87
149
88
150
for (int i = 0 ; i < parameters .length ; i ++) {
89
- Class <?> parameterClass = convertToClass (parameters [i ]);
151
+ final Class <?> parameterClass = convertToClass (parameters [i ]);
90
152
if (parameterClass .isAssignableFrom (exception .getClass ())) {
91
153
instancedParams [i ] = exception ;
92
154
break ;
@@ -95,18 +157,18 @@ private <E extends Throwable> Object[] determineInstancedParameters(Method mappe
95
157
return instancedParams ;
96
158
}
97
159
98
- private Class <?> convertToClass (Parameter parameter ) {
99
- Type paramType = parameter .getParameterizedType ();
160
+ private Class <?> convertToClass (final Parameter parameter ) {
161
+ final Type paramType = parameter .getParameterizedType ();
100
162
if (paramType instanceof Class ) {
101
163
return (Class <?>) paramType ;
102
164
}
103
165
throw new IllegalStateException ("Parameter type of method has to be from Class, it was: " + paramType );
104
166
}
105
167
106
168
private Object invokeMappedMethodSafely (
107
- Method mappedMethod ,
108
- Object instanceOfMappedMethod ,
109
- Object [] instancedParams ) throws Throwable {
169
+ final Method mappedMethod ,
170
+ final Object instanceOfMappedMethod ,
171
+ final Object [] instancedParams ) throws Throwable {
110
172
try {
111
173
return mappedMethod .invoke (instanceOfMappedMethod , instancedParams );
112
174
} catch (InvocationTargetException | IllegalAccessException e ) {
0 commit comments