Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,17 @@ public class FunctionLoader<T extends CloudFunction> {
private final String functionTarget;
private final ClassLoader classLoader;
private final FunctionSignatureMatcher<T> matcher;
private final ClassNotFoundException newStyleException;

public FunctionLoader(
String functionTarget,
ClassLoader classLoader,
FunctionSignatureMatcher<T> matcher) {
FunctionSignatureMatcher<T> matcher,
ClassNotFoundException newStyleException) {
this.functionTarget = functionTarget;
this.classLoader = classLoader;
this.matcher = matcher;
this.newStyleException = newStyleException;
}

/**
Expand All @@ -40,7 +43,7 @@ public FunctionLoader(
public T loadUserFunction() throws Exception {
int lastDotIndex = functionTarget.lastIndexOf(".");
if (lastDotIndex == -1) {
throw new ClassNotFoundException(functionTarget);
throw newStyleException;
}
String targetClassName = functionTarget.substring(0, lastDotIndex);
String targetMethodName = functionTarget.substring(lastDotIndex + 1);
Expand All @@ -50,7 +53,8 @@ public T loadUserFunction() throws Exception {
} catch (ClassNotFoundException e) {
throw new RuntimeException(
"Could not load either " + functionTarget + " (new form) or "
+ targetClassName + " (old form)");
+ targetClassName + " (old form)",
newStyleException);
}

Object targetInstance = targetClass.getDeclaredConstructor().newInstance();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import com.beust.jcommander.JCommander;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.ParameterException;
import com.google.auto.value.AutoOneOf;
import com.google.cloud.functions.BackgroundFunction;
import com.google.cloud.functions.HttpFunction;
import com.google.cloud.functions.RawBackgroundFunction;
Expand Down Expand Up @@ -249,34 +250,48 @@ public void startServer() throws Exception {
servletContextHandler.setContextPath("/");
server.setHandler(NotFoundHandler.forServlet(servletContextHandler));

Optional<Class<?>> functionClass = loadFunctionClass();
ClassOrException functionClass = loadFunctionClass();

HttpServlet servlet;
if ("http".equals(functionSignatureType)) {
if (functionClass.isPresent()) {
servlet = NewHttpFunctionExecutor.forClass(functionClass.get());
} else {
FunctionLoader<HttpCloudFunction> loader = new FunctionLoader<>(
functionTarget, functionClassLoader, new HttpFunctionSignatureMatcher());
HttpCloudFunction function = loader.loadUserFunction();
servlet = new HttpFunctionExecutor(function);
switch (functionClass.getKind()) {
case LOADED_CLASS:
servlet = NewHttpFunctionExecutor.forClass(functionClass.loadedClass());
break;
default:
FunctionLoader<HttpCloudFunction> loader =
new FunctionLoader<>(
functionTarget,
functionClassLoader,
new HttpFunctionSignatureMatcher(),
functionClass.exception());
HttpCloudFunction function = loader.loadUserFunction();
servlet = new HttpFunctionExecutor(function);
}
} else if ("event".equals(functionSignatureType)) {
if (functionClass.isPresent()) {
servlet = NewBackgroundFunctionExecutor.forClass(functionClass.get());
} else {
FunctionLoader<BackgroundCloudFunction> loader =
new FunctionLoader<>(
functionTarget, functionClassLoader, new BackgroundFunctionSignatureMatcher());
BackgroundCloudFunction function = loader.loadUserFunction();
servlet = new BackgroundFunctionExecutor(function);
switch (functionClass.getKind()) {
case LOADED_CLASS:
servlet = NewBackgroundFunctionExecutor.forClass(functionClass.loadedClass());
break;
default:
FunctionLoader<BackgroundCloudFunction> loader =
new FunctionLoader<>(
functionTarget,
functionClassLoader,
new BackgroundFunctionSignatureMatcher(),
functionClass.exception());
BackgroundCloudFunction function = loader.loadUserFunction();
servlet = new BackgroundFunctionExecutor(function);
}
} else if (functionSignatureType == null) {
if (functionClass.isPresent()) {
servlet = servletForDeducedSignatureType(functionClass.get());
} else {
throw new RuntimeException(
"Class " + functionTarget + " does not exist or could not be loaded");
switch (functionClass.getKind()) {
case LOADED_CLASS:
servlet = servletForDeducedSignatureType(functionClass.loadedClass());
break;
default:
throw new RuntimeException(
"Class " + functionTarget + " does not exist or could not be loaded",
functionClass.exception());
}
} else {
String error = String.format(
Expand All @@ -291,18 +306,44 @@ public void startServer() throws Exception {
server.join();
}

private Optional<Class<?>> loadFunctionClass() {
@AutoOneOf(ClassOrException.Kind.class)
abstract static class ClassOrException {
enum Kind {LOADED_CLASS, EXCEPTION}

abstract Kind getKind();

abstract Class<?> loadedClass();

abstract ClassNotFoundException exception();

static ClassOrException of(Class<?> c) {
return AutoOneOf_Invoker_ClassOrException.loadedClass(c);
}

static ClassOrException of(ClassNotFoundException e) {
return AutoOneOf_Invoker_ClassOrException.exception(e);
}
}

private ClassOrException loadFunctionClass() {
String target = functionTarget;
ClassNotFoundException firstException = null;
while (true) {
try {
return Optional.of(functionClassLoader.loadClass(target));
return ClassOrException.of(functionClassLoader.loadClass(target));
} catch (ClassNotFoundException e) {
if (firstException == null) {
firstException = e;
}
// This might be a nested class like com.example.Foo.Bar. That will actually appear as
// com.example.Foo$Bar as far as Class.forName is concerned. So we try to replace every dot
// from the last to the first with a $ in the hope of finding a class we can load.
int lastDot = target.lastIndexOf('.');
if (lastDot < 0) {
return Optional.empty();
// We're going to try to load the function target using the old form, classname.method.
// But it's at least as likely that the class does exist, but we failed to load it for
// some other reason. So ensure that we keep the exception to show to the user.
return ClassOrException.of(firstException);
}
target = target.substring(0, lastDot) + '$' + target.substring(lastDot + 1);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,11 @@ public void adder() throws Exception {
HttpServletResponse res = Mockito.mock(HttpServletResponse.class);
String fullTarget = "com.google.cloud.functions.invoker.BackgroundFunctionTest$" + target;
FunctionLoader<BackgroundCloudFunction> loader =
new FunctionLoader<>(fullTarget, getClass().getClassLoader(),
new BackgroundFunctionSignatureMatcher());
new FunctionLoader<>(
fullTarget,
getClass().getClassLoader(),
new BackgroundFunctionSignatureMatcher(),
null);
BackgroundCloudFunction function = loader.loadUserFunction();
BackgroundFunctionExecutor executor = new BackgroundFunctionExecutor(function);
Mockito.when(req.getReader())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public void adder() throws Exception {
"com.google.cloud.functions.invoker.HttpFunctionTest$HttpWriter.writeResponse";
String requestData = "testData";
FunctionLoader<HttpCloudFunction> loader = new FunctionLoader<>(
fullTarget, getClass().getClassLoader(), new HttpFunctionSignatureMatcher());
fullTarget, getClass().getClassLoader(), new HttpFunctionSignatureMatcher(), null);
HttpCloudFunction function = loader.loadUserFunction();
HttpFunctionExecutor executor = new HttpFunctionExecutor(function);
Mockito.when(req.getParameter("data")).thenReturn(requestData);
Expand Down