diff --git a/docs/src/main/asciidoc/adapters/gcp-intro.adoc b/docs/src/main/asciidoc/adapters/gcp-intro.adoc index e44de79d7..c656366ad 100644 --- a/docs/src/main/asciidoc/adapters/gcp-intro.adoc +++ b/docs/src/main/asciidoc/adapters/gcp-intro.adoc @@ -36,7 +36,7 @@ Start by adding the Maven plugin provided as part of the Google Functions Framew function-maven-plugin 0.9.1 - org.springframework.cloud.function.adapter.gcp.FunctionInvoker + org.springframework.cloud.function.adapter.gcp.GcfJarLauncher 8080 @@ -66,67 +66,44 @@ curl http://localhost:8080/ -d "hello" As of March 2020, Google Cloud Functions for Java is in Alpha. You can get on the https://docs.google.com/forms/d/e/1FAIpQLScC98jGi7CfG0n3UYlj7Xad8XScvZC8-BBOg7Pk3uSZx_2cdQ/viewform[whitelist] to try it out. -To deploy to Google Cloud Function, you need to produce a fat jar using the Shade plugin, rather than the Spring Boot plugin. +In order to use the adapter, first add the dependency to your pom.xml: + +[source, xml] +---- + + org.springframework.cloud + spring-cloud-function-adapter-gcp + +---- + +Then, add the `spring-boot-maven-plugin` with `spring-cloud-function-adapter-gcp` as a dependency. +The extra dependency is used for `spring-boot-maven-plugin` to package your function in the correct JAR format for deployment on Google Cloud Functions. -First, if you already have the Spring Boot plugin in your `pom.xml`, *remove* it: [source, xml] ---- - - ----- - -Then, *add* the Shade Plugin configuration to generate a fat jar when you run the `mvn package` command. - -[source, xml] ----- - - org.apache.maven.plugins - maven-shade-plugin - - - package - - shade - - - true - target/deploy - gcp - - - META-INF/spring.handlers - - - META-INF/spring.factories - - - META-INF/spring.schemas - - - - com.example.CloudFunctionMain - - - - - + + target/deploy + + + + org.springframework.cloud + spring-cloud-function-adapter-gcp + + ---- -IMPORTANT: If both Spring Boot plugin and Shade plugin are present, Shade plugin may be shading a Spring Boot produced JAR, resulting in a Fat JAR that's unusable in Google Cloud Function. Don't forget to remove the Spring Boot plugin! - Package the application. ---- mvn package ---- -You should see the fat jar in `target/deploy` directory. +You should see the resulting JAR in `target/deploy` directory. +This JAR is correctly formatted for deployment to Google Cloud Functions. Make sure that you have the https://cloud.google.com/sdk/install[Cloud SDK CLI] installed. @@ -134,7 +111,7 @@ From the project base directory run the following command to deploy. ---- gcloud alpha functions deploy function-sample-gcp \ ---entry-point org.springframework.cloud.function.adapter.gcp.FunctionInvoker \ +--entry-point org.springframework.cloud.function.adapter.gcp.GcfJarLauncher \ --runtime java11 \ --trigger-http \ --source target/deploy \ diff --git a/spring-cloud-function-adapters/spring-cloud-function-adapter-gcp/pom.xml b/spring-cloud-function-adapters/spring-cloud-function-adapter-gcp/pom.xml index 91a204c04..a35b1d77d 100644 --- a/spring-cloud-function-adapters/spring-cloud-function-adapter-gcp/pom.xml +++ b/spring-cloud-function-adapters/spring-cloud-function-adapter-gcp/pom.xml @@ -26,7 +26,6 @@ com.google.cloud.functions functions-framework-api ${google.cloud.functions.api.version} - provided com.google.code.gson @@ -36,6 +35,17 @@ org.springframework.cloud spring-cloud-function-context + + + + org.springframework.boot + spring-boot-loader-tools + + + org.springframework.boot + spring-boot-loader + + org.springframework.boot diff --git a/spring-cloud-function-adapters/spring-cloud-function-adapter-gcp/src/main/java/org/springframework/cloud/function/adapter/gcp/GcfJarLauncher.java b/spring-cloud-function-adapters/spring-cloud-function-adapter-gcp/src/main/java/org/springframework/cloud/function/adapter/gcp/GcfJarLauncher.java new file mode 100644 index 000000000..9e7a1d393 --- /dev/null +++ b/spring-cloud-function-adapters/spring-cloud-function-adapter-gcp/src/main/java/org/springframework/cloud/function/adapter/gcp/GcfJarLauncher.java @@ -0,0 +1,54 @@ +/* + * Copyright 2018-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.function.adapter.gcp; + +import com.google.cloud.functions.HttpFunction; +import com.google.cloud.functions.HttpRequest; +import com.google.cloud.functions.HttpResponse; + +import org.springframework.boot.loader.JarLauncher; +import org.springframework.boot.loader.jar.JarFile; + +/** + * The launcher class written at the top-level of the output JAR to be deployed to + * Google Cloud Functions. This is the entry point to the function when run from JAR. + * + * @author Ray Tsang + * @author Daniel Zou + */ +public class GcfJarLauncher extends JarLauncher implements HttpFunction { + + private final ClassLoader loader; + + private final HttpFunction delegate; + + public GcfJarLauncher() throws Exception { + JarFile.registerUrlProtocolHandler(); + + this.loader = createClassLoader(getClassPathArchivesIterator()); + + Class clazz = this.loader + .loadClass("org.springframework.cloud.function.adapter.gcp.FunctionInvoker"); + this.delegate = (HttpFunction) clazz.getConstructor().newInstance(); + } + @Override + public void service(HttpRequest httpRequest, HttpResponse httpResponse) throws Exception { + Thread.currentThread().setContextClassLoader(this.loader); + delegate.service(httpRequest, httpResponse); + } +} + diff --git a/spring-cloud-function-adapters/spring-cloud-function-adapter-gcp/src/main/java/org/springframework/cloud/function/adapter/gcp/layout/GcfJarLayout.java b/spring-cloud-function-adapters/spring-cloud-function-adapter-gcp/src/main/java/org/springframework/cloud/function/adapter/gcp/layout/GcfJarLayout.java new file mode 100644 index 000000000..874e0c66a --- /dev/null +++ b/spring-cloud-function-adapters/spring-cloud-function-adapter-gcp/src/main/java/org/springframework/cloud/function/adapter/gcp/layout/GcfJarLayout.java @@ -0,0 +1,55 @@ +/* + * Copyright 2018-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.function.adapter.gcp.layout; + +import java.io.IOException; + +import org.springframework.boot.loader.tools.CustomLoaderLayout; +import org.springframework.boot.loader.tools.Layouts; +import org.springframework.boot.loader.tools.LoaderClassesWriter; +import org.springframework.cloud.function.adapter.gcp.GcfJarLauncher; + +/** + * A custom JAR Layout that writes GCF adapter classes to the top-level of the output JAR + * for deploying to GCF. + * + * @author Ray Tsang + * @author Daniel Zou + */ +public class GcfJarLayout extends Layouts.Jar implements CustomLoaderLayout { + + private static final String LAUNCHER_NAME = GcfJarLauncher.class.getCanonicalName(); + + @Override + public String getLauncherClassName() { + return LAUNCHER_NAME; + } + + @Override + public boolean isExecutable() { + return false; + } + + @Override + public void writeLoadedClasses(LoaderClassesWriter writer) throws IOException { + writer.writeLoaderClasses(); + + String jarName = LAUNCHER_NAME.replaceAll("\\.", "/") + ".class"; + writer.writeEntry( + jarName, GcfJarLauncher.class.getResourceAsStream("/" + jarName)); + } +} diff --git a/spring-cloud-function-adapters/spring-cloud-function-adapter-gcp/src/main/java/org/springframework/cloud/function/adapter/gcp/layout/GcfJarLayoutFactory.java b/spring-cloud-function-adapters/spring-cloud-function-adapter-gcp/src/main/java/org/springframework/cloud/function/adapter/gcp/layout/GcfJarLayoutFactory.java new file mode 100644 index 000000000..87eacef00 --- /dev/null +++ b/spring-cloud-function-adapters/spring-cloud-function-adapter-gcp/src/main/java/org/springframework/cloud/function/adapter/gcp/layout/GcfJarLayoutFactory.java @@ -0,0 +1,36 @@ +/* + * Copyright 2018-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.function.adapter.gcp.layout; + +import java.io.File; + +import org.springframework.boot.loader.tools.Layout; +import org.springframework.boot.loader.tools.LayoutFactory; + +/** + * Factory boilerplate class that constructs {@link GcfJarLayout}. + * + * @author Ray Tsang + * @author Daniel Zou + */ +public class GcfJarLayoutFactory implements LayoutFactory { + + @Override + public Layout getLayout(File source) { + return new GcfJarLayout(); + } +} diff --git a/spring-cloud-function-adapters/spring-cloud-function-adapter-gcp/src/main/resources/META-INF/spring.factories b/spring-cloud-function-adapters/spring-cloud-function-adapter-gcp/src/main/resources/META-INF/spring.factories new file mode 100644 index 000000000..76392ffda --- /dev/null +++ b/spring-cloud-function-adapters/spring-cloud-function-adapter-gcp/src/main/resources/META-INF/spring.factories @@ -0,0 +1,2 @@ +org.springframework.boot.loader.tools.LayoutFactory=\ +org.springframework.cloud.function.adapter.gcp.layout.GcfJarLayoutFactory diff --git a/spring-cloud-function-samples/function-sample-gcp/README.adoc b/spring-cloud-function-samples/function-sample-gcp/README.adoc index f12b939a4..000f0aa3c 100644 --- a/spring-cloud-function-samples/function-sample-gcp/README.adoc +++ b/spring-cloud-function-samples/function-sample-gcp/README.adoc @@ -35,7 +35,7 @@ Run the following command from the project root to deploy. ---- gcloud alpha functions deploy function-sample-gcp \ ---entry-point org.springframework.cloud.function.adapter.gcp.FunctionInvoker \ +--entry-point org.springframework.cloud.function.adapter.gcp.GcfJarLauncher \ --runtime java11 \ --trigger-http \ --source target/deploy \ diff --git a/spring-cloud-function-samples/function-sample-gcp/pom.xml b/spring-cloud-function-samples/function-sample-gcp/pom.xml index 30d53f22a..02463d804 100644 --- a/spring-cloud-function-samples/function-sample-gcp/pom.xml +++ b/spring-cloud-function-samples/function-sample-gcp/pom.xml @@ -2,21 +2,28 @@ + 4.0.0 + io.spring.sample function-sample-gcp + 2.0.0.RELEASE + jar + + function-sample-gcp - spring-cloud-function-samples - org.springframework.cloud - 3.1.0.BUILD-SNAPSHOT + org.springframework.boot + spring-boot-starter-parent + 2.3.0.BUILD-SNAPSHOT + org.springframework.cloud spring-cloud-function-adapter-gcp - ${project.version} + 3.1.0.BUILD-SNAPSHOT @@ -45,49 +52,30 @@ + + org.springframework.boot + spring-boot-maven-plugin + + target/deploy + + + + org.springframework.cloud + spring-cloud-function-adapter-gcp + 3.1.0.BUILD-SNAPSHOT + + + + com.google.cloud.functions function-maven-plugin 0.9.1 - org.springframework.cloud.function.adapter.gcp.FunctionInvoker + org.springframework.cloud.function.adapter.gcp.GcfJarLauncher 8080 - - - org.apache.maven.plugins - maven-shade-plugin - - - package - - shade - - - true - target/deploy - gcp - - - META-INF/spring.handlers - - - META-INF/spring.factories - - - META-INF/spring.schemas - - - - com.example.CloudFunctionMain - - - - - - -