1919import com .beust .jcommander .JCommander ;
2020import com .beust .jcommander .Parameter ;
2121import com .beust .jcommander .ParameterException ;
22+ import com .google .cloud .functions .BackgroundFunction ;
23+ import com .google .cloud .functions .HttpFunction ;
24+ import com .google .cloud .functions .RawBackgroundFunction ;
2225import com .google .cloud .functions .invoker .BackgroundCloudFunction ;
2326import com .google .cloud .functions .invoker .BackgroundFunctionExecutor ;
2427import com .google .cloud .functions .invoker .BackgroundFunctionSignatureMatcher ;
4851import java .util .logging .Level ;
4952import java .util .logging .LogManager ;
5053import java .util .logging .Logger ;
54+ import java .util .stream .Stream ;
5155import javax .servlet .http .HttpServlet ;
5256import org .eclipse .jetty .server .Server ;
5357import org .eclipse .jetty .servlet .ServletContextHandler ;
@@ -163,12 +167,13 @@ static Optional<Invoker> makeInvoker(Map<String, String> environment, String...
163167 .stream ()
164168 .filter (Objects ::nonNull )
165169 .findFirst ();
170+ ClassLoader functionClassLoader = makeClassLoader (functionClasspath );
166171 Invoker invoker =
167172 new Invoker (
168173 port ,
169174 functionTarget ,
170175 environment .get ("FUNCTION_SIGNATURE_TYPE" ),
171- functionClasspath );
176+ functionClassLoader );
172177 return Optional .of (invoker );
173178 }
174179
@@ -185,20 +190,29 @@ private static boolean isLocalRun() {
185190 return System .getenv ("K_SERVICE" ) == null ;
186191 }
187192
193+ private static ClassLoader makeClassLoader (Optional <String > functionClasspath ) {
194+ ClassLoader runtimeLoader = Invoker .class .getClassLoader ();
195+ if (functionClasspath .isPresent ()) {
196+ ClassLoader parent = new OnlyApiClassLoader (runtimeLoader );
197+ return new URLClassLoader (classpathToUrls (functionClasspath .get ()), parent );
198+ }
199+ return runtimeLoader ;
200+ }
201+
188202 private final Integer port ;
189203 private final String functionTarget ;
190204 private final String functionSignatureType ;
191- private final Optional < String > functionClasspath ;
205+ private final ClassLoader functionClassLoader ;
192206
193207 public Invoker (
194208 Integer port ,
195209 String functionTarget ,
196210 String functionSignatureType ,
197- Optional < String > functionClasspath ) {
211+ ClassLoader functionClassLoader ) {
198212 this .port = port ;
199213 this .functionTarget = functionTarget ;
200214 this .functionSignatureType = functionSignatureType ;
201- this .functionClasspath = functionClasspath ;
215+ this .functionClassLoader = functionClassLoader ;
202216 }
203217
204218 Integer getPort () {
@@ -213,8 +227,8 @@ String getFunctionSignatureType() {
213227 return functionSignatureType ;
214228 }
215229
216- Optional < String > getFunctionClasspath () {
217- return functionClasspath ;
230+ ClassLoader getFunctionClassLoader () {
231+ return functionClassLoader ;
218232 }
219233
220234 public void startServer () throws Exception {
@@ -224,58 +238,34 @@ public void startServer() throws Exception {
224238 context .setContextPath ("/" );
225239 server .setHandler (context );
226240
227- ClassLoader runtimeLoader = getClass ().getClassLoader ();
228- ClassLoader classLoader ;
229- if (functionClasspath .isPresent ()) {
230- ClassLoader parent = new OnlyApiClassLoader (runtimeLoader );
231- classLoader = new URLClassLoader (classpathToUrls (functionClasspath .get ()), parent );
232- } else {
233- classLoader = runtimeLoader ;
234- }
241+ Optional <Class <?>> functionClass = loadFunctionClass ();
235242
236243 HttpServlet servlet ;
237244 if ("http" .equals (functionSignatureType )) {
238- Optional <NewHttpFunctionExecutor > newExecutor =
239- NewHttpFunctionExecutor .forTarget (functionTarget , classLoader );
240- if (newExecutor .isPresent ()) {
241- servlet = newExecutor .get ();
245+ if (functionClass .isPresent ()) {
246+ servlet = NewHttpFunctionExecutor .forClass (functionClass .get ());
242247 } else {
243- FunctionLoader <HttpCloudFunction > loader =
244- new FunctionLoader <>( functionTarget , classLoader , new HttpFunctionSignatureMatcher ());
248+ FunctionLoader <HttpCloudFunction > loader = new FunctionLoader <>(
249+ functionTarget , functionClassLoader , new HttpFunctionSignatureMatcher ());
245250 HttpCloudFunction function = loader .loadUserFunction ();
246251 servlet = new HttpFunctionExecutor (function );
247252 }
248253 } else if ("event" .equals (functionSignatureType )) {
249- Optional <NewBackgroundFunctionExecutor > newExecutor =
250- NewBackgroundFunctionExecutor .forTarget (functionTarget , classLoader );
251- if (newExecutor .isPresent ()) {
252- servlet = newExecutor .get ();
254+ if (functionClass .isPresent ()) {
255+ servlet = NewBackgroundFunctionExecutor .forClass (functionClass .get ());
253256 } else {
254257 FunctionLoader <BackgroundCloudFunction > loader =
255258 new FunctionLoader <>(
256- functionTarget , classLoader , new BackgroundFunctionSignatureMatcher ());
259+ functionTarget , functionClassLoader , new BackgroundFunctionSignatureMatcher ());
257260 BackgroundCloudFunction function = loader .loadUserFunction ();
258261 servlet = new BackgroundFunctionExecutor (function );
259262 }
260263 } else if (functionSignatureType == null ) {
261- Optional <NewHttpFunctionExecutor > httpExecutor =
262- NewHttpFunctionExecutor .forTarget (functionTarget , classLoader );
263- if (httpExecutor .isPresent ()) {
264- servlet = httpExecutor .get ();
264+ if (functionClass .isPresent ()) {
265+ servlet = servletForDeducedSignatureType (functionClass .get ());
265266 } else {
266- Optional <NewBackgroundFunctionExecutor > backgroundExecutor =
267- NewBackgroundFunctionExecutor .forTarget (functionTarget , classLoader );
268- if (backgroundExecutor .isPresent ()) {
269- servlet = backgroundExecutor .get ();
270- } else {
271- String error = String .format (
272- "Could not determine function signature type from target %s. Either this should be"
273- + " a class implementing one of the interfaces in com.google.cloud.functions, or the"
274- + " environment variable FUNCTION_SIGNATURE_TYPE should be set to \" http\" or"
275- + " \" event\" ." ,
276- functionTarget );
277- throw new RuntimeException (error );
278- }
267+ throw new RuntimeException (
268+ "Class " + functionTarget + " does not exist or could not be loaded" );
279269 }
280270 } else {
281271 String error = String .format (
@@ -290,28 +280,71 @@ public void startServer() throws Exception {
290280 server .join ();
291281 }
292282
293- static URL [] classpathToUrls (String classpath ) throws IOException {
283+ private Optional <Class <?>> loadFunctionClass () {
284+ String target = functionTarget ;
285+ while (true ) {
286+ try {
287+ return Optional .of (functionClassLoader .loadClass (target ));
288+ } catch (ClassNotFoundException e ) {
289+ // This might be a nested class like com.example.Foo.Bar. That will actually appear as
290+ // com.example.Foo$Bar as far as Class.forName is concerned. So we try to replace every dot
291+ // from the last to the first with a $ in the hope of finding a class we can load.
292+ int lastDot = target .lastIndexOf ('.' );
293+ if (lastDot < 0 ) {
294+ return Optional .empty ();
295+ }
296+ target = target .substring (0 , lastDot ) + '$' + target .substring (lastDot + 1 );
297+ }
298+ }
299+ }
300+
301+ private HttpServlet servletForDeducedSignatureType (Class <?> functionClass ) {
302+ if (HttpFunction .class .isAssignableFrom (functionClass )) {
303+ return NewHttpFunctionExecutor .forClass (functionClass );
304+ }
305+ if (BackgroundFunction .class .isAssignableFrom (functionClass )
306+ || RawBackgroundFunction .class .isAssignableFrom (functionClass )) {
307+ return NewBackgroundFunctionExecutor .forClass (functionClass );
308+ }
309+ String error = String .format (
310+ "Could not determine function signature type from target %s. Either this should be"
311+ + " a class implementing one of the interfaces in com.google.cloud.functions, or the"
312+ + " environment variable FUNCTION_SIGNATURE_TYPE should be set to \" http\" or"
313+ + " \" event\" ." ,
314+ functionTarget );
315+ throw new RuntimeException (error );
316+ }
317+
318+ static URL [] classpathToUrls (String classpath ) {
294319 String [] components = classpath .split (File .pathSeparator );
295320 List <URL > urls = new ArrayList <>();
296321 for (String component : components ) {
297322 if (component .endsWith (File .separator + "*" )) {
298323 urls .addAll (jarsIn (component .substring (0 , component .length () - 2 )));
299324 } else {
300325 Path path = Paths .get (component );
301- if ( Files . exists ( path )) {
326+ try {
302327 urls .add (path .toUri ().toURL ());
328+ } catch (MalformedURLException e ) {
329+ throw new UncheckedIOException (e );
303330 }
304331 }
305332 }
306333 return urls .toArray (new URL [0 ]);
307334 }
308335
309- private static List <URL > jarsIn (String dir ) throws IOException {
336+ private static List <URL > jarsIn (String dir ) {
310337 Path path = Paths .get (dir );
311338 if (!Files .isDirectory (path )) {
312339 return Collections .emptyList ();
313340 }
314- return Files .list (path )
341+ Stream <Path > stream ;
342+ try {
343+ stream = Files .list (path );
344+ } catch (IOException e ) {
345+ throw new UncheckedIOException (e );
346+ }
347+ return stream
315348 .filter (p -> p .getFileName ().toString ().endsWith (".jar" ))
316349 .map (p -> {
317350 try {
0 commit comments