diff --git a/docs/modules/ROOT/examples/components/wasm.yml b/docs/modules/ROOT/examples/components/wasm.yml new file mode 100644 index 000000000000..345ced8ad789 --- /dev/null +++ b/docs/modules/ROOT/examples/components/wasm.yml @@ -0,0 +1,13 @@ +# Do not edit directly! +# This file was generated by camel-quarkus-maven-plugin:update-extension-doc-page +cqArtifactId: camel-quarkus-wasm +cqArtifactIdBase: wasm +cqNativeSupported: true +cqStatus: Stable +cqDeprecated: false +cqJvmSince: 3.10.0 +cqNativeSince: 3.10.0 +cqCamelPartName: wasm +cqCamelPartTitle: Wasm +cqCamelPartDescription: Invoke Wasm functions. +cqExtensionPageTitle: Wasm diff --git a/docs/modules/ROOT/examples/languages/wasm.yml b/docs/modules/ROOT/examples/languages/wasm.yml new file mode 100644 index 000000000000..ea1160eba246 --- /dev/null +++ b/docs/modules/ROOT/examples/languages/wasm.yml @@ -0,0 +1,13 @@ +# Do not edit directly! +# This file was generated by camel-quarkus-maven-plugin:update-extension-doc-page +cqArtifactId: camel-quarkus-wasm +cqArtifactIdBase: wasm +cqNativeSupported: true +cqStatus: Stable +cqDeprecated: false +cqJvmSince: 3.10.0 +cqNativeSince: 3.10.0 +cqCamelPartName: wasm +cqCamelPartTitle: Wasm +cqCamelPartDescription: Call a wasm (web assembly) function. +cqExtensionPageTitle: Wasm diff --git a/docs/modules/ROOT/nav.adoc b/docs/modules/ROOT/nav.adoc index 4b27eec7c8e8..72d7e3529988 100644 --- a/docs/modules/ROOT/nav.adoc +++ b/docs/modules/ROOT/nav.adoc @@ -303,6 +303,7 @@ *** xref:reference/extensions/vertx.adoc[Vert.x] *** xref:reference/extensions/vertx-http.adoc[Vert.x HTTP Client] *** xref:reference/extensions/vertx-websocket.adoc[Vert.x WebSocket] +*** xref:reference/extensions/wasm.adoc[Wasm] *** xref:reference/extensions/weather.adoc[Weather] *** xref:reference/extensions/web3j.adoc[Web3j Ethereum Blockchain] *** xref:reference/extensions/wordpress.adoc[Wordpress] diff --git a/docs/modules/ROOT/pages/reference/extensions/wasm.adoc b/docs/modules/ROOT/pages/reference/extensions/wasm.adoc new file mode 100644 index 000000000000..67459efb8acd --- /dev/null +++ b/docs/modules/ROOT/pages/reference/extensions/wasm.adoc @@ -0,0 +1,64 @@ +// Do not edit directly! +// This file was generated by camel-quarkus-maven-plugin:update-extension-doc-page +[id="extensions-wasm"] += Wasm +:linkattrs: +:cq-artifact-id: camel-quarkus-wasm +:cq-native-supported: true +:cq-status: Experimental +:cq-status-deprecation: Experimental +:cq-description: Invoke Wasm functions. +:cq-deprecated: false +:cq-jvm-since: 3.10.0 +:cq-native-since: 3.10.0 + +ifeval::[{doc-show-badges} == true] +[.badges] +[.badge-key]##JVM since##[.badge-supported]##3.10.0## [.badge-key]##Native since##[.badge-supported]##3.10.0## +endif::[] + +Invoke Wasm functions. + +[id="extensions-wasm-whats-inside"] +== What's inside + +* xref:{cq-camel-components}::wasm-component.adoc[Wasm component], URI syntax: `wasm:functionName` +* xref:{cq-camel-components}:languages:wasm-language.adoc[Wasm language] + +Please refer to the above links for usage and configuration details. + +[id="extensions-wasm-maven-coordinates"] +== Maven coordinates + +https://{link-quarkus-code-generator}/?extension-search=camel-quarkus-wasm[Create a new project with this extension on {link-quarkus-code-generator}, window="_blank"] + +Or add the coordinates to your existing project: + +[source,xml] +---- + + org.apache.camel.quarkus + camel-quarkus-wasm + +---- +ifeval::[{doc-show-user-guide-link} == true] +Check the xref:user-guide/index.adoc[User guide] for more information about writing Camel Quarkus applications. +endif::[] + +[id="extensions-wasm-usage"] +== Usage +[id="extensions-wasm-usage-reading-wasm-modules-from-the-classpath-in-native-mode"] +== Reading Wasm modules from the classpath in native mode + +When Wasm modules are read from the classpath (the default) in native mode, you must ensure that the module(s) are added to the native image. +You can do this via a configuration property in `application.properties`. + +For example, assuming `.wasm` files are read from a classpath location of `was/modules`. + +[source,properties] +---- +quarkus.native.resources.includes = wasm/modules/*.wasm +---- + +More information about selecting resources for inclusion in the native executable can be found in the xref:user-guide/native-mode.adoc#embedding-resource-in-native-executable[native mode user guide]. + diff --git a/extensions/pom.xml b/extensions/pom.xml index d3378acef907..2bac0815a196 100644 --- a/extensions/pom.xml +++ b/extensions/pom.xml @@ -251,6 +251,7 @@ vertx vertx-http vertx-websocket + wasm weather xchange xj diff --git a/extensions/wasm/deployment/pom.xml b/extensions/wasm/deployment/pom.xml new file mode 100644 index 000000000000..3934aa4debde --- /dev/null +++ b/extensions/wasm/deployment/pom.xml @@ -0,0 +1,67 @@ + + + + 4.0.0 + + org.apache.camel.quarkus + camel-quarkus-wasm-parent + 3.10.0-SNAPSHOT + ../pom.xml + + + camel-quarkus-wasm-deployment + Camel Quarkus :: Wasm :: Deployment + + + + org.apache.camel.quarkus + camel-quarkus-core-deployment + + + org.apache.camel.quarkus + camel-quarkus-wasm + + + io.quarkus + quarkus-jackson-deployment + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${quarkus.version} + + + + + + + + diff --git a/extensions/wasm/deployment/src/main/java/org/apache/camel/quarkus/component/wasm/deployment/WasmProcessor.java b/extensions/wasm/deployment/src/main/java/org/apache/camel/quarkus/component/wasm/deployment/WasmProcessor.java new file mode 100644 index 000000000000..85bd4b6f1f24 --- /dev/null +++ b/extensions/wasm/deployment/src/main/java/org/apache/camel/quarkus/component/wasm/deployment/WasmProcessor.java @@ -0,0 +1,37 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 + * + * http://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.apache.camel.quarkus.component.wasm.deployment; + +import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.builditem.FeatureBuildItem; +import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem; +import org.apache.camel.wasm.WasmSupport.Wrapper; + +class WasmProcessor { + + private static final String FEATURE = "camel-wasm"; + + @BuildStep + FeatureBuildItem feature() { + return new FeatureBuildItem(FEATURE); + } + + @BuildStep + ReflectiveClassBuildItem registerForReflection() { + return ReflectiveClassBuildItem.builder(Wrapper.class).methods(false).fields(true).build(); + } +} diff --git a/extensions/wasm/pom.xml b/extensions/wasm/pom.xml new file mode 100644 index 000000000000..5aaf2266c5a1 --- /dev/null +++ b/extensions/wasm/pom.xml @@ -0,0 +1,39 @@ + + + + 4.0.0 + + org.apache.camel.quarkus + camel-quarkus-extensions + 3.10.0-SNAPSHOT + ../pom.xml + + + camel-quarkus-wasm-parent + Camel Quarkus :: Wasm + pom + + + deployment + runtime + + diff --git a/extensions/wasm/runtime/pom.xml b/extensions/wasm/runtime/pom.xml new file mode 100644 index 000000000000..86b09f0fbf5b --- /dev/null +++ b/extensions/wasm/runtime/pom.xml @@ -0,0 +1,106 @@ + + + + 4.0.0 + + org.apache.camel.quarkus + camel-quarkus-wasm-parent + 3.10.0-SNAPSHOT + ../pom.xml + + + camel-quarkus-wasm + Camel Quarkus :: Wasm :: Runtime + Invoke Wasm functions. + + + 3.10.0 + 3.10.0 + experimental + + + + + org.apache.camel.quarkus + camel-quarkus-core + + + org.apache.camel + camel-wasm + + + io.quarkus + quarkus-jackson + + + + + + + io.quarkus + quarkus-extension-maven-plugin + + + org.apache.maven.plugins + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${quarkus.version} + + + + + + + + + + + full + + + !quickly + + + + + + org.apache.camel.quarkus + camel-quarkus-maven-plugin + + + update-extension-doc-page + + update-extension-doc-page + + process-classes + + + + + + + + diff --git a/extensions/wasm/runtime/src/main/doc/usage.adoc b/extensions/wasm/runtime/src/main/doc/usage.adoc new file mode 100644 index 000000000000..c23e17d000df --- /dev/null +++ b/extensions/wasm/runtime/src/main/doc/usage.adoc @@ -0,0 +1,13 @@ +== Reading Wasm modules from the classpath in native mode + +When Wasm modules are read from the classpath (the default) in native mode, you must ensure that the module(s) are added to the native image. +You can do this via a configuration property in `application.properties`. + +For example, assuming `.wasm` files are read from a classpath location of `was/modules`. + +[source,properties] +---- +quarkus.native.resources.includes = wasm/modules/*.wasm +---- + +More information about selecting resources for inclusion in the native executable can be found in the xref:user-guide/native-mode.adoc#embedding-resource-in-native-executable[native mode user guide]. diff --git a/extensions/wasm/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions/wasm/runtime/src/main/resources/META-INF/quarkus-extension.yaml new file mode 100644 index 000000000000..1cc8713d255f --- /dev/null +++ b/extensions/wasm/runtime/src/main/resources/META-INF/quarkus-extension.yaml @@ -0,0 +1,32 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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 +# +# http://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. +# + +# This is a generated file. Do not edit directly! +# To re-generate, run the following command from the top level directory: +# +# mvn -N cq:update-quarkus-metadata +# +--- +name: "Camel Wasm" +description: "Invoke Wasm functions" +metadata: + icon-url: "https://raw.githubusercontent.com/apache/camel-website/main/antora-ui-camel/src/img/logo-d.svg" + guide: "https://camel.apache.org/camel-quarkus/latest/reference/extensions/wasm.html" + categories: + - "integration" + status: + - "experimental" diff --git a/integration-tests/pom.xml b/integration-tests/pom.xml index 49d789438000..fd537fff1eec 100644 --- a/integration-tests/pom.xml +++ b/integration-tests/pom.xml @@ -236,6 +236,7 @@ velocity vertx vertx-websocket + wasm weather xchange xj diff --git a/integration-tests/wasm/README.adoc b/integration-tests/wasm/README.adoc new file mode 100644 index 000000000000..89734acabbf9 --- /dev/null +++ b/integration-tests/wasm/README.adoc @@ -0,0 +1,13 @@ +== Recompiling the test functions + +First install the Rust toolchain: + +https://rustup.rs/ + +Then compile functions.wasm: + +[source,shell] +---- +cd rust +./build.sh +---- diff --git a/integration-tests/wasm/pom.xml b/integration-tests/wasm/pom.xml new file mode 100644 index 000000000000..7693c7642066 --- /dev/null +++ b/integration-tests/wasm/pom.xml @@ -0,0 +1,133 @@ + + + + 4.0.0 + + org.apache.camel.quarkus + camel-quarkus-build-parent-it + 3.10.0-SNAPSHOT + ../../poms/build-parent-it/pom.xml + + + camel-quarkus-integration-test-wasm + Camel Quarkus :: Integration Tests :: Wasm + Integration tests for Camel Quarkus Wasm extension + + + + org.apache.camel.quarkus + camel-quarkus-direct + + + org.apache.camel.quarkus + camel-quarkus-wasm + + + io.quarkus + quarkus-resteasy + + + io.quarkus + quarkus-resteasy-jackson + + + + + io.quarkus + quarkus-junit5 + test + + + io.rest-assured + rest-assured + test + + + + + + native + + + native + + + + native + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + + integration-test + verify + + + + + + + + + virtualDependencies + + + !noVirtualDependencies + + + + + + org.apache.camel.quarkus + camel-quarkus-direct-deployment + ${project.version} + pom + test + + + * + * + + + + + org.apache.camel.quarkus + camel-quarkus-wasm-deployment + ${project.version} + pom + test + + + * + * + + + + + + + + diff --git a/integration-tests/wasm/rust/Cargo.lock b/integration-tests/wasm/rust/Cargo.lock new file mode 100644 index 000000000000..5c446d78d16c --- /dev/null +++ b/integration-tests/wasm/rust/Cargo.lock @@ -0,0 +1,124 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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 +# +# http://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. +# + +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "base64-serde" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba368df5de76a5bea49aaf0cf1b39ccfbbef176924d1ba5db3e4135216cbe3c7" +dependencies = [ + "base64", + "serde", +] + +[[package]] +name = "functions" +version = "0.1.0" +dependencies = [ + "base64", + "base64-serde", + "serde", + "serde_json", +] + +[[package]] +name = "itoa" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" + +[[package]] +name = "proc-macro2" +version = "1.0.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95fc56cda0b5c3325f5fbbd7ff9fda9e02bb00bb3dac51252d2f1bfa1cb8cc8c" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "ryu" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" + +[[package]] +name = "serde" +version = "1.0.195" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63261df402c67811e9ac6def069e4786148c4563f4b50fd4bf30aa370d626b02" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.195" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46fe8f8603d81ba86327b23a2e9cdf49e1255fb94a4c5f297f6ee0547178ea2c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.111" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "176e46fa42316f18edd598015a5166857fc835ec732f5215eac6b7bdbf0a84f4" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "syn" +version = "2.0.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" diff --git a/integration-tests/wasm/rust/Cargo.toml b/integration-tests/wasm/rust/Cargo.toml new file mode 100644 index 000000000000..248633353e38 --- /dev/null +++ b/integration-tests/wasm/rust/Cargo.toml @@ -0,0 +1,37 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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 +# +# http://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] +name = "functions" +version = "0.1.0" +authors = ["camel.apache.org"] +edition = "2024" + +[lib] +crate-type = ["cdylib"] +path = "functions.rs" + +[dependencies] +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +base64 = "0.21.7" +base64-serde = "0.7.0" + +[profile.release] +opt-level = 0 +lto = "off" +codegen-units = 1 \ No newline at end of file diff --git a/integration-tests/wasm/rust/build.sh b/integration-tests/wasm/rust/build.sh new file mode 100755 index 000000000000..9ba8a495ef74 --- /dev/null +++ b/integration-tests/wasm/rust/build.sh @@ -0,0 +1,25 @@ +#! /bin/bash +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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 +# +# http://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. +# + +set -euxo pipefail + +rustup target add wasm32-unknown-unknown + +cargo build --target wasm32-unknown-unknown --release + +cp target/wasm32-unknown-unknown/release/functions.wasm ../src/test/resources diff --git a/integration-tests/wasm/rust/functions.rs b/integration-tests/wasm/rust/functions.rs new file mode 100644 index 000000000000..017d59845a3d --- /dev/null +++ b/integration-tests/wasm/rust/functions.rs @@ -0,0 +1,134 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one or more +// contributor license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright ownership. +// The ASF licenses this file to You 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 +// +// http://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. +// + +use std::mem; +use std::slice; +use std::collections::HashMap; + +use serde::{Deserialize, Serialize}; +use base64_serde::base64_serde_type; + +base64_serde_type!(Base64Standard, base64::engine::general_purpose::STANDARD); + +#[derive(Serialize, Deserialize)] +struct Message { + headers: HashMap, + + #[serde(with = "Base64Standard")] + body: Vec, +} + +#[cfg_attr(all(target_arch = "wasm32"), export_name = "alloc")] +#[no_mangle] +pub extern "C" fn alloc(size: u32) -> *mut u8 { + let mut buf = Vec::with_capacity(size as usize); + let ptr = buf.as_mut_ptr(); + + // tell Rust not to clean this up + mem::forget(buf); + + ptr +} + +#[cfg_attr(all(target_arch = "wasm32"), export_name = "dealloc")] +#[no_mangle] +pub unsafe extern "C" fn dealloc(ptr: &mut u8, len: i32) { + // Retakes the pointer which allows its memory to be freed. + let _ = Vec::from_raw_parts(ptr, 0, len as usize); +} + +#[cfg_attr(all(target_arch = "wasm32"), export_name = "process")] +#[no_mangle] +pub extern fn process(ptr: u32, len: u32) -> u64 { + let bytes = unsafe { + slice::from_raw_parts_mut( + ptr as *mut u8, + len as usize) + }; + + let mut msg: Message = serde_json::from_slice(bytes).unwrap(); + msg.body = String::from_utf8(msg.body).unwrap().to_uppercase().as_bytes().to_vec(); + + let out_vec = serde_json::to_vec(&msg).unwrap(); + let out_len = out_vec.len(); + let out_ptr = alloc(out_len as u32); + + unsafe { + std::ptr::copy_nonoverlapping( + out_vec.as_ptr(), + out_ptr, + out_len as usize) + }; + + return ((out_ptr as u64) << 32) | out_len as u64; +} + + +#[cfg_attr(all(target_arch = "wasm32"), export_name = "process_err")] +#[no_mangle] +pub extern fn process_err(ptr: u32, len: u32) -> u64 { + return fail(ptr, len) +} + +#[cfg_attr(all(target_arch = "wasm32"), export_name = "transform")] +#[no_mangle] +pub extern fn transform(ptr: u32, len: u32) -> u64 { + let bytes = unsafe { + slice::from_raw_parts_mut( + ptr as *mut u8, + len as usize) + }; + + let msg: Message = serde_json::from_slice(bytes).unwrap(); + let res = String::from_utf8(msg.body).unwrap().to_uppercase().as_bytes().to_vec(); + + let out_len = res.len(); + let out_ptr = alloc(out_len as u32); + + unsafe { + std::ptr::copy_nonoverlapping( + res.as_ptr(), + out_ptr, + out_len as usize) + }; + + return ((out_ptr as u64) << 32) | out_len as u64; +} + +#[cfg_attr(all(target_arch = "wasm32"), export_name = "transform_err")] +#[no_mangle] +pub extern fn transform_err(ptr: u32, len: u32) -> u64 { + return fail(ptr, len) +} + + +pub fn fail(_ptr: u32, _len: u32) -> u64 { + let res = "this is an error"; + let mut out_len = res.len(); + let out_ptr = alloc(out_len as u32); + + unsafe { + std::ptr::copy_nonoverlapping( + res.as_ptr(), + out_ptr, + out_len as usize) + }; + + out_len |= 1 << 31; + + return ((out_ptr as u64) << 32) | out_len as u64; +} \ No newline at end of file diff --git a/integration-tests/wasm/src/main/java/org/apache/camel/quarkus/component/wasm/it/WasmResource.java b/integration-tests/wasm/src/main/java/org/apache/camel/quarkus/component/wasm/it/WasmResource.java new file mode 100644 index 000000000000..f8c3de1e5e78 --- /dev/null +++ b/integration-tests/wasm/src/main/java/org/apache/camel/quarkus/component/wasm/it/WasmResource.java @@ -0,0 +1,67 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 + * + * http://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.apache.camel.quarkus.component.wasm.it; + +import java.util.Map; + +import jakarta.inject.Inject; +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import org.apache.camel.Exchange; +import org.apache.camel.Message; +import org.apache.camel.ProducerTemplate; +import org.jboss.resteasy.annotations.jaxrs.QueryParam; + +@Path("/wasm") +public class WasmResource { + @Inject + ProducerTemplate producerTemplate; + + @Path("/execute") + @POST + @Consumes(MediaType.TEXT_PLAIN) + @Produces(MediaType.APPLICATION_JSON) + public Response executeWasmToUpper(@QueryParam("endpointUri") String endpointUri, String body) { + Exchange result = producerTemplate.request(endpointUri, exchange -> { + Message message = exchange.getMessage(); + message.setBody(body); + message.setHeader("foo", "bar"); + }); + + Exception exception = result.getException(); + if (exception == null) { + Message message = result.getMessage(); + return Response.ok().entity(Map.of( + "body", message.getBody(String.class), + "foo", message.getHeader("foo", String.class))) + .build(); + + } else { + String message = exception.getMessage(); + if (exception.getCause() instanceof RuntimeException) { + message = exception.getCause().getMessage(); + } + return Response.serverError() + .entity(Map.of("exception", message)) + .build(); + } + } +} diff --git a/integration-tests/wasm/src/main/java/org/apache/camel/quarkus/component/wasm/it/WasmRoutes.java b/integration-tests/wasm/src/main/java/org/apache/camel/quarkus/component/wasm/it/WasmRoutes.java new file mode 100644 index 000000000000..9bcf0442bab2 --- /dev/null +++ b/integration-tests/wasm/src/main/java/org/apache/camel/quarkus/component/wasm/it/WasmRoutes.java @@ -0,0 +1,67 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 + * + * http://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.apache.camel.quarkus.component.wasm.it; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +import org.apache.camel.builder.RouteBuilder; + +public class WasmRoutes extends RouteBuilder { + static final Path WASM_MODULE_PATH = Paths.get("target/wasm/modules"); + static final Path WASM_FUNCTIONS = WASM_MODULE_PATH.resolve("functions.wasm"); + + @Override + public void configure() throws Exception { + copyFunctionsToFilesystem(); + + from("direct:executeFunctionFromClasspath") + .toD("wasm:process?module=wasm/modules/functions.wasm"); + + from("direct:executeFunctionFromFile") + .toD("wasm:process?module=file:target/wasm/modules/functions.wasm"); + + from("direct:executeFunctionError") + .toD("wasm:process_err?module=wasm/modules/functions.wasm"); + + from("direct:executeFunctionViaLanguageFromClasspath") + .transform() + .wasm("transform", "wasm/modules/functions.wasm"); + + from("direct:executeFunctionViaLanguageFromFile") + .transform() + .wasm("transform", "file:target/wasm/modules/functions.wasm"); + + from("direct:executeFunctionViaLanguageError") + .transform() + .wasm("transform_err", "wasm/modules/functions.wasm"); + } + + static void copyFunctionsToFilesystem() throws IOException { + Files.createDirectories(WASM_MODULE_PATH); + try (InputStream stream = Thread.currentThread().getContextClassLoader() + .getResourceAsStream("wasm/modules/functions.wasm")) { + if (stream == null) { + throw new RuntimeException("Failed to read functions.wasm from the classpath"); + } + Files.copy(stream, WASM_FUNCTIONS); + } + } +} diff --git a/integration-tests/wasm/src/main/resources/application.properties b/integration-tests/wasm/src/main/resources/application.properties new file mode 100644 index 000000000000..39ac46228c76 --- /dev/null +++ b/integration-tests/wasm/src/main/resources/application.properties @@ -0,0 +1,18 @@ +## --------------------------------------------------------------------------- +## Licensed to the Apache Software Foundation (ASF) under one or more +## contributor license agreements. See the NOTICE file distributed with +## this work for additional information regarding copyright ownership. +## The ASF licenses this file to You 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 +## +## http://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. +## --------------------------------------------------------------------------- + +quarkus.native.resources.includes = wasm/modules/*.wasm \ No newline at end of file diff --git a/integration-tests/wasm/src/main/resources/wasm/modules/functions.wasm b/integration-tests/wasm/src/main/resources/wasm/modules/functions.wasm new file mode 100755 index 000000000000..c8b9e5c2838c Binary files /dev/null and b/integration-tests/wasm/src/main/resources/wasm/modules/functions.wasm differ diff --git a/integration-tests/wasm/src/test/java/org/apache/camel/quarkus/component/wasm/it/WasmIT.java b/integration-tests/wasm/src/test/java/org/apache/camel/quarkus/component/wasm/it/WasmIT.java new file mode 100644 index 000000000000..81a246517d8a --- /dev/null +++ b/integration-tests/wasm/src/test/java/org/apache/camel/quarkus/component/wasm/it/WasmIT.java @@ -0,0 +1,24 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 + * + * http://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.apache.camel.quarkus.component.wasm.it; + +import io.quarkus.test.junit.QuarkusIntegrationTest; + +@QuarkusIntegrationTest +class WasmIT extends WasmTest { + +} diff --git a/integration-tests/wasm/src/test/java/org/apache/camel/quarkus/component/wasm/it/WasmTest.java b/integration-tests/wasm/src/test/java/org/apache/camel/quarkus/component/wasm/it/WasmTest.java new file mode 100644 index 000000000000..7135b1a96967 --- /dev/null +++ b/integration-tests/wasm/src/test/java/org/apache/camel/quarkus/component/wasm/it/WasmTest.java @@ -0,0 +1,84 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 + * + * http://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.apache.camel.quarkus.component.wasm.it; + +import java.io.IOException; +import java.util.Set; + +import io.quarkus.test.junit.QuarkusTest; +import io.restassured.RestAssured; +import io.restassured.http.ContentType; +import org.apache.commons.io.FileUtils; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import static org.apache.camel.quarkus.component.wasm.it.WasmRoutes.WASM_MODULE_PATH; +import static org.hamcrest.Matchers.is; + +@QuarkusTest +class WasmTest { + @AfterAll + public static void afterAll() throws IOException { + FileUtils.deleteDirectory(WASM_MODULE_PATH.toFile()); + } + + @ParameterizedTest + @MethodSource("executeFunctionUris") + void executeToUpperFunction(String endpoint) { + String message = "hello camel quarkus wasm"; + RestAssured.given() + .queryParam("endpointUri", "direct:" + endpoint) + .contentType(ContentType.TEXT) + .body(message) + .post("/wasm/execute") + .then() + .statusCode(200) + .body( + "body", is(message.toUpperCase()), + "foo", is("bar")); + } + + @ParameterizedTest + @MethodSource("executeFunctionErrorUris") + void executeToUpperFunctionError(String endpoint) { + String message = "hello camel quarkus wasm"; + RestAssured.given() + .queryParam("endpointUri", "direct:" + endpoint) + .contentType(ContentType.TEXT) + .body(message) + .post("/wasm/execute") + .then() + .statusCode(500) + .body( + "exception", is("this is an error")); + } + + static Set executeFunctionUris() { + return Set.of( + "executeFunctionFromClasspath", + "executeFunctionFromFile", + "executeFunctionViaLanguageFromClasspath", + "executeFunctionViaLanguageFromFile"); + } + + static Set executeFunctionErrorUris() { + return Set.of( + "executeFunctionError", + "executeFunctionViaLanguageError"); + } +} diff --git a/pom.xml b/pom.xml index e78d5e6a0774..d0919f5219db 100644 --- a/pom.xml +++ b/pom.xml @@ -594,6 +594,7 @@ .github/actions/** **/my-property release-utils/*.sh + **/*.wasm CAMEL_PROPERTIES_STYLE @@ -610,9 +611,11 @@ SCRIPT_STYLE SCRIPT_STYLE XML_STYLE + DOUBLESLASH_STYLE CAMEL_PROPERTIES_STYLE CAMEL_PROPERTIES_STYLE CAMEL_PROPERTIES_STYLE + SCRIPT_STYLE SLASHSTAR_STYLE XML_STYLE diff --git a/poms/bom/pom.xml b/poms/bom/pom.xml index c0cdae9fd3fe..87896caabfb6 100644 --- a/poms/bom/pom.xml +++ b/poms/bom/pom.xml @@ -2629,6 +2629,11 @@ camel-vertx-websocket ${camel.version} + + org.apache.camel + camel-wasm + ${camel.version} + org.apache.camel camel-weather @@ -5938,6 +5943,16 @@ camel-quarkus-vertx-websocket-deployment ${camel-quarkus.version} + + org.apache.camel.quarkus + camel-quarkus-wasm + ${camel-quarkus.version} + + + org.apache.camel.quarkus + camel-quarkus-wasm-deployment + ${camel-quarkus.version} + org.apache.camel.quarkus camel-quarkus-weather diff --git a/poms/bom/src/main/generated/flattened-full-pom.xml b/poms/bom/src/main/generated/flattened-full-pom.xml index 3774a4bb1aba..f7db52e0d2a0 100644 --- a/poms/bom/src/main/generated/flattened-full-pom.xml +++ b/poms/bom/src/main/generated/flattened-full-pom.xml @@ -2566,6 +2566,11 @@ camel-vertx-websocket 4.5.0 + + org.apache.camel + camel-wasm + 4.5.0 + org.apache.camel camel-weather @@ -5863,6 +5868,16 @@ camel-quarkus-vertx-websocket-deployment 3.10.0-SNAPSHOT + + org.apache.camel.quarkus + camel-quarkus-wasm + 3.10.0-SNAPSHOT + + + org.apache.camel.quarkus + camel-quarkus-wasm-deployment + 3.10.0-SNAPSHOT + org.apache.camel.quarkus camel-quarkus-weather diff --git a/poms/bom/src/main/generated/flattened-reduced-pom.xml b/poms/bom/src/main/generated/flattened-reduced-pom.xml index d0a5e0c4fe92..797c1459ce41 100644 --- a/poms/bom/src/main/generated/flattened-reduced-pom.xml +++ b/poms/bom/src/main/generated/flattened-reduced-pom.xml @@ -2566,6 +2566,11 @@ camel-vertx-websocket 4.5.0 + + org.apache.camel + camel-wasm + 4.5.0 + org.apache.camel camel-weather @@ -5863,6 +5868,16 @@ camel-quarkus-vertx-websocket-deployment 3.10.0-SNAPSHOT + + org.apache.camel.quarkus + camel-quarkus-wasm + 3.10.0-SNAPSHOT + + + org.apache.camel.quarkus + camel-quarkus-wasm-deployment + 3.10.0-SNAPSHOT + org.apache.camel.quarkus camel-quarkus-weather diff --git a/poms/bom/src/main/generated/flattened-reduced-verbose-pom.xml b/poms/bom/src/main/generated/flattened-reduced-verbose-pom.xml index 9923dafcf446..70fbc8225a3b 100644 --- a/poms/bom/src/main/generated/flattened-reduced-verbose-pom.xml +++ b/poms/bom/src/main/generated/flattened-reduced-verbose-pom.xml @@ -2566,6 +2566,11 @@ camel-vertx-websocket 4.5.0 + + org.apache.camel + camel-wasm + 4.5.0 + org.apache.camel camel-weather @@ -5863,6 +5868,16 @@ camel-quarkus-vertx-websocket-deployment 3.10.0-SNAPSHOT + + org.apache.camel.quarkus + camel-quarkus-wasm + 3.10.0-SNAPSHOT + + + org.apache.camel.quarkus + camel-quarkus-wasm-deployment + 3.10.0-SNAPSHOT + org.apache.camel.quarkus camel-quarkus-weather diff --git a/tooling/scripts/test-categories.yaml b/tooling/scripts/test-categories.yaml index b9e0fca93b2a..e31a39eec4cb 100644 --- a/tooling/scripts/test-categories.yaml +++ b/tooling/scripts/test-categories.yaml @@ -111,6 +111,7 @@ group-06: - quartz - saga - sjms-qpid-amqp-client + - wasm group-07: - dataformats-json-grouped - dropbox