4
4
import cloud .localstack .awssdkv1 .lambda .KinesisEventParser ;
5
5
import cloud .localstack .awssdkv1 .lambda .S3EventParser ;
6
6
7
+ import cloud .localstack .lambda_handler .HandlerNameParseResult ;
8
+ import cloud .localstack .lambda_handler .MultipleMatchingHandlersException ;
9
+ import cloud .localstack .lambda_handler .NoMatchingHandlerException ;
7
10
import com .amazonaws .services .lambda .runtime .Context ;
8
11
import com .amazonaws .services .lambda .runtime .RequestHandler ;
9
12
import com .amazonaws .services .lambda .runtime .RequestStreamHandler ;
20
23
import java .io .ByteArrayOutputStream ;
21
24
import java .io .OutputStream ;
22
25
import java .lang .reflect .InvocationTargetException ;
26
+ import java .lang .reflect .Method ;
23
27
import java .lang .reflect .ParameterizedType ;
24
28
import java .lang .reflect .Type ;
25
29
import java .nio .charset .StandardCharsets ;
@@ -58,21 +62,25 @@ public static void main(String[] args) throws Exception {
58
62
List <Map <String ,Object >> records = (List <Map <String , Object >>) get (map , "Records" );
59
63
Object inputObject = map ;
60
64
61
- Object handler ;
65
+ String handlerName ;
62
66
if (args .length == 2 ) {
63
- handler = getHandler ( args [0 ]) ;
67
+ handlerName = args [0 ];
64
68
} else {
65
69
String handlerEnvVar = System .getenv ("_HANDLER" );
66
70
if (handlerEnvVar == null ) {
67
71
System .err .println ("Handler must be provided by '_HANDLER' environment variable" );
68
72
System .exit (1 );
69
73
}
70
- handler = getHandler ( handlerEnvVar ) ;
74
+ handlerName = handlerEnvVar ;
71
75
}
76
+ HandlerNameParseResult parseResult = parseHandlerName (handlerName );
77
+ Object handler = getHandler (parseResult .getClassName ());
78
+ String handlerMethodName = parseResult .getHandlerMethod ();
79
+ Method handlerMethod = handlerMethodName != null ? getHandlerMethodByName (handler , handlerMethodName ) : null ;
72
80
if (records == null ) {
73
- Optional <Object > deserialisedInput = getInputObject (reader , fileContent , handler );
74
- if (deserialisedInput .isPresent ()) {
75
- inputObject = deserialisedInput .get ();
81
+ Optional <Object > deserializedInput = getInputObject (reader , fileContent , handler , handlerMethod );
82
+ if (deserializedInput .isPresent ()) {
83
+ inputObject = deserializedInput .get ();
76
84
}
77
85
} else {
78
86
if (records .stream ().anyMatch (record -> record .containsKey ("kinesis" ) || record .containsKey ("Kinesis" ))) {
@@ -92,7 +100,7 @@ public static void main(String[] args) throws Exception {
92
100
snsRecord .setTimestamp (new DateTime ());
93
101
r .setSns (snsRecord );
94
102
}
95
- } else if (records .stream ().filter (record -> record .containsKey ("dynamodb" )). count () > 0 ) {
103
+ } else if (records .stream ().anyMatch (record -> record .containsKey ("dynamodb" ))) {
96
104
inputObject = DDBEventParser .parse (records );
97
105
} else if (records .stream ().anyMatch (record -> record .containsK
F438
ey ("s3" ))) {
98
106
inputObject = S3EventParser .parse (records );
@@ -102,9 +110,14 @@ public static void main(String[] args) throws Exception {
102
110
}
103
111
104
112
Context ctx = new LambdaContext (UUID .randomUUID ().toString ());
105
- if (handler instanceof RequestHandler ) {
106
- Object result = ((RequestHandler <Object , ?>) handler ).handleRequest (inputObject , ctx );
107
- // try turning the output into json
113
+ if (handlerMethod != null || handler instanceof RequestHandler ) {
114
+ Object result ;
115
+ if (handlerMethod != null ) {
116
+ // use reflection to load handler method from class
117
+ result = handlerMethod .invoke (handler , inputObject , ctx );
118
+ } else {
119
+ result = ((RequestHandler <Object , ?>) handler ).handleRequest (inputObject , ctx );
120
+ }
108
121
try {
109
122
result = new ObjectMapper ().writeValueAsString (result );
110
123
} catch (JsonProcessingException jsonException ) {
@@ -115,36 +128,89 @@ public static void main(String[] args) throws Exception {
115
128
} else if (handler instanceof RequestStreamHandler ) {
116
129
OutputStream os = new ByteArrayOutputStream ();
117
130
((RequestStreamHandler ) handler ).handleRequest (
118
- new StringInputStream (fileContent ), os , ctx );
131
+ new StringInputStream (fileContent ), os , ctx );
119
132
System .out .println (os );
120
133
}
121
134
}
122
135
123
- private static Optional <Object > getInputObject (ObjectMapper mapper , String objectString , Object handler ) {
136
+ /**
137
+ * Returns the method matching the specified name implemented in the given handler object class
138
+ * @param handler Handler the method in question belongs to
139
+ * @param handlerMethodName Name of the method we are looking for in the handler
140
+ * @return Method object for the method with the given method name
141
+ * @throws MultipleMatchingHandlersException Thrown when multiple methods in the given handler exist for the given name
142
+ * @throws NoMatchingHandlerException Thrown if no method in the handler is matching the given name
143
+ */
144
+ private static Method getHandlerMethodByName (Object handler , String handlerMethodName ) throws MultipleMatchingHandlersException , NoMatchingHandlerException {
145
+ List <Method > handlerMethods = Arrays .stream (handler .getClass ().getMethods ())
146
+ .filter (method -> method .getName ().equals (handlerMethodName ))
147
+ .collect (Collectors .toList ());
148
+ if (handlerMethods .size () > 1 ) {
149
+ throw new MultipleMatchingHandlersException ("Multiple matching headers: " + handlerMethods );
150
+ } else if (handlerMethods .isEmpty ()) {
151
+ throw new NoMatchingHandlerException ("No matching handlers for method name: "
152
+ + handlerMethodName );
153
+ }
154
+ return handlerMethods .get (0 );
155
+ }
156
+
157
+ /**
158
+ * Getting the input object for the handler function.
159
+ * @param mapper ObjectMapper that maps the objectString into the target parameter type
160
+ * @param objectString Object we got from the lambda invocation
161
+ * @param handler Handler object we need to get the correct input type
162
+ * @param handlerMethod Handler method we need to get the correct input type
163
+ * @return Optional of the input object for the lambda handler
164
+ */
165
+ private static Optional <Object > getInputObject (ObjectMapper mapper , String objectString , Object handler , Method handlerMethod ) {
124
166
Optional <Object > inputObject = Optional .empty ();
125
167
try {
126
- Optional <Type > handlerInterface = Arrays .stream (handler .getClass ().getGenericInterfaces ())
127
- .filter (genericInterface ->
128
- ((ParameterizedType ) genericInterface ).getRawType ().equals (RequestHandler .class ))
129
- .findFirst ();
130
- if (handlerInterface .isPresent ()) {
131
- Class <?> handlerInputType = Class .forName (((ParameterizedType ) handlerInterface .get ())
132
- .getActualTypeArguments ()[0 ].getTypeName ());
168
+ if (handlerMethod != null ) {
169
+ Class <?> handlerInputType = Class .forName (handlerMethod .getParameterTypes ()[0 ].getName ());
133
170
inputObject = Optional .of (mapper .readerFor (handlerInputType ).readValue (objectString ));
171
+ } else {
172
+ Optional <Type > handlerInterface = Arrays .stream (handler .getClass ().getGenericInterfaces ())
173
+ .filter (genericInterface ->
174
+ ((ParameterizedType ) genericInterface ).getRawType ().equals (RequestHandler .class ))
175
+ .findFirst ();
176
+ if (handlerInterface .isPresent ()) {
177
+ Class <?> handlerInputType = Class .forName (((ParameterizedType ) handlerInterface .get ())
178
+ .getActualTypeArguments ()[0 ].getTypeName ());
179
+ inputObject = Optional .of (mapper .readerFor (handlerInputType ).readValue (objectString ));
180
+ }
134
181
}
135
182
} catch (Exception genericException ) {
136
183
// do nothing
137
184
}
138
185
return inputObject ;
139
186
}
140
187
188
+ /**
189
+ * Parses the handler name
190
+ * Depending on the string, the result handlerMethod can be null
191
+ * @param handlerName Handler name in the format "java.package.class::handlerMethodName" or "java.package.class"
192
+ * @return Result containing the class name, and the handler method if specified
193
+ */
194
+ private static HandlerNameParseResult parseHandlerName (String handlerName ) {
195
+ String [] split = handlerName .split ("::" , 2 );
196
+ String className = split [0 ];
197
+ String handlerMethod = split .length > 1 ? split [1 ] : null ;
198
+ return new HandlerNameParseResult (className , handlerMethod );
199
+ }
200
+
201
+
202
+ /**
203
+ * Returns a instance of the class specified by handler name
204
+ * @param handlerName name (including package information) of the class to load and instantiate
205
+ * @return New object of handlerName class
206
+ */
141
207
private static Object getHandler (String handlerName ) throws NoSuchMethodException , IllegalAccessException ,
142
- InvocationTargetException , InstantiationException , ClassNotFoundException {
208
+ InvocationTargetException , InstantiationException , ClassNotFoundException {
143
209
Class <?> clazz = Class .forName (handlerName );
144
210
return clazz .getConstructor ().newInstance ();
145
211
}
146
212
147
- public static <T > T get (Map <String ,T > map , String key ) {
213
+ public static <T > T get (Map <String , T > map , String key ) {
148
214
T result = map .get (key );
149
215
if (result != null ) {
150
216
return result ;
0 commit comments