diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..5e4f8e3
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,30 @@
+# Compiled class file
+*.class
+
+# Log file
+*.log
+
+# BlueJ files
+*.ctxt
+
+# Mobile Tools for Java (J2ME)
+.mtj.tmp/
+
+# Package Files #
+*.jar
+*.war
+*.nar
+*.ear
+*.zip
+*.tar.gz
+*.rar
+
+# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
+hs_err_pid*
+replay_pid*
+
+.project
+.classpath
+.settings
+
+target/
diff --git a/README.md b/README.md
index 799821e..5cf56e7 100644
--- a/README.md
+++ b/README.md
@@ -1,29 +1,35 @@
-Extism Java-SDK
----
+# Extism Java SDK
-Java SDK for the [extism](https://extism.org/) WebAssembly Plugin-System.
+Java SDK for the [Extism](https://extism.org/) WebAssembly Plugin-System.
-> **Note**: This is an early 1.0 release and is unstable until we hit 1.0. If you are looking to integrate now consider looking at the 0.x version in the [extism/extism](https://github.com/extism/extism/tree/main/java) repo.
+> **Note**: This is an early 1.0 release and is unstable until we hit 1.0 in December. If you are looking for a stable release consider the 0.x version in the [Extism/extism](https://github.com/extism/extism/tree/main/java) repo.
-# Build
+[](https://search.maven.org/artifact/org.extism.sdk/extism)
+[](https://javadoc.io/doc/org.extism.sdk/extism)
-To build the extism java-sdk run the following command:
+## Installation
-```
-mvn clean verify
+### Install the Extism Runtime Dependency
+
+For this library, you first need to install the Extism Runtime. You can [download the shared object directly from a release](https://github.com/extism/extism/releases) or use the [Extism CLI](https://github.com/extism/cli) to install it:
+
+> **Note**: This library has breaking changes and targets 1.0 of the runtime. For the time being, install the runtime from our nightly development builds on git: `sudo extism lib install --version git`.
+
+```bash
+sudo extism lib install latest
+
+#=> Fetching https://github.com/extism/extism/releases/download/v0.5.2/libextism-aarch64-apple-darwin-v0.5.2.tar.gz
+#=> Copying libextism.dylib to /usr/local/lib/libextism.dylib
+#=> Copying extism.h to /usr/local/include/extism.h
```
-# Usage
+### Install Jar
-To use the extism java-sdk you need to add the `org.extism.sdk` dependency to your dependency management and ensure that
-the native extism library is installed on your system. For installing the native library refer to the [extism documentation](https://extism.org/docs/install).
+To use the Extism java-sdk you need to add the `org.extism.sdk` dependency to your dependency management system.
-Instead of installing the native library on your system, you can also download the appropriate library for your platform
-yourself.
-To do that simply download the [extism release](https://github.com/extism/extism/releases) to a `folder` and run your java application with the system property `-Djna.library.path=/path/to/folder`.
+#### Maven
-## Maven
-To use the extism java-sdk with maven you need to add the following dependency to your `pom.xml` file:
+To use the Extism java-sdk with maven you need to add the following dependency to your `pom.xml` file:
```xml
org.extism.sdk
@@ -32,10 +38,180 @@ To use the extism java-sdk with maven you need to add the following dependency t
```
-## Gradle
-To use the extism java-sdk with maven you need to add the following dependency to your `build.gradle` file:
+#### Gradle
+
+To use the Extism java-sdk with maven you need to add the following dependency to your `build.gradle` file:
```
implementation 'org.extism.sdk:extism:1.0.0-rc1'
```
+## Getting Started
+
+This guide should walk you through some of the concepts in Extism and this java library.
+
+### Creating A Plug-in
+
+The primary concept in Extism is the [plug-in](https://extism.org/docs/concepts/plug-in). You can think of a plug-in as a code module stored in a `.wasm` file.
+Since you may not have an Extism plug-in on hand to test, let's load a demo plug-in from the web:
+
+```java
+import org.extism.sdk.manifest.Manifest;
+import org.extism.sdk.wasm.UrlWasmSource;
+import org.extism.sdk.Plugin;
+
+var url = "https://github.com/extism/plugins/releases/latest/download/count_vowels.wasm";
+var manifest = new Manifest(List.of(UrlWasmSource.fromUrl(url)));
+var plugin = new Plugin(manifest, false, null);
+```
+
+> **Note**: See [the Manifest docs](https://www.javadoc.io/doc/org.extism.sdk/extism/latest/org/extism/sdk/manifest/Manifest.html) as it has a rich schema and a lot of options.
+
+### Calling A Plug-in's Exports
+
+This plug-in was written in Rust and it does one thing, it counts vowels in a string. As such, it exposes one "export" function: `count_vowels`. We can call exports using [Plugin#call](https://www.javadoc.io/doc/org.extism.sdk/extism/latest/org/extism/sdk/Plugin.html#call(java.lang.String,byte[]))
+
+```java
+var output = plugin.call("count_vowels", "Hello, World!");
+System.out.println(output);
+// => "{"count": 3, "total": 3, "vowels": "aeiouAEIOU"}"
+```
+
+All exports have a simple interface of bytes-in and bytes-out.
+This plug-in happens to take a string and return a JSON encoded string with a report of results.
+
+### Plug-in State
+
+Plug-ins may be stateful or stateless. Plug-ins can maintain state b/w calls by the use of variables.
+Our count vowels plug-in remembers the total number of vowels it's ever counted in the "total" key in the result.
+You can see this by making subsequent calls to the export:
+
+```java
+var output = plugin.call("count_vowels", "Hello, World!");
+System.out.println(output);
+// => "{"count": 3, "total": 6, "vowels": "aeiouAEIOU"}"
+
+var output = plugin.call("count_vowels", "Hello, World!");
+System.out.println(output);
+// => "{"count": 3, "total": 9, "vowels": "aeiouAEIOU"}"
+```
+
+These variables will persist until this plug-in is freed or you initialize a new one.
+
+### Configuration
+
+Plug-ins may optionally take a configuration object. This is a static way to configure the plug-in.
+Our count-vowels plugin takes an optional configuration to change out which characters are considered vowels. Example:
+
+```java
+var plugin = new Plugin(manifest, false, null);
+var output = plugin.call("count_vowels", "Yellow, World!");
+System.out.println(output);
+// => {"count": 3, "total": 3, "vowels": "aeiouAEIOU"}
+
+// Let's change the vowels config it uses to determine what is a vowel:
+var config = Map.of("vowels", "aeiouyAEIOUY");
+var manifest2 = new Manifest(List.of(UrlWasmSource.fromUrl(url)), null, config);
+var plugin = new Plugin(manifest2, false, null);
+var output = plugin.call("count_vowels", "Yellow, World!");
+System.out.println(output);
+// => {"count": 4, "total": 4, "vowels": "aeiouyAEIOUY"}
+// ^ note count changed to 4 as we configured Y as a vowel this time
+```
+
+### Host Functions
+
+Let's extend our count-vowels example a little bit: Instead of storing the `total` in an ephemeral plug-in var,
+let's store it in a persistent key-value store!
+
+Wasm can't use our app's KV store on its own. This is where [Host Functions](https://extism.org/docs/concepts/host-functions) come in.
+
+[Host functions](https://extism.org/docs/concepts/host-functions) allow us to grant new capabilities to our plug-ins from our application.
+They are simply some java methods you write which can be passed down and invoked from any language inside the plug-in.
+
+Let's load the manifest like usual but load up this `count_vowels_kvstore` plug-in:
+
+```java
+var url = "https://github.com/extism/plugins/releases/latest/download/count_vowels_kvstore.wasm";
+var manifest = new Manifest(List.of(UrlWasmSource.fromUrl(url)));
+var plugin = new Plugin(manifest, false, null);
+```
+
+> *Note*: The source code for this plug-in is [here](https://github.com/extism/plugins/blob/main/count_vowels_kvstore/src/lib.rs)
+> and is written in rust, but it could be written in any of our PDK languages.
+
+Unlike our previous plug-in, this plug-in expects you to provide host functions that satisfy its import interface for a KV store.
+We want to expose two functions to our plugin, `kv_write(String key, Bytes value)` which writes a bytes value to a key and `Bytes kv_read(String key)` which reads the bytes at the given `key`.
+
+```java
+// Our application KV store
+// Pretend this is redis or a database :)
+var kvStore = new HashMap();
+
+ExtismFunction kvWrite = (plugin, params, returns, data) -> {
+ System.out.println("Hello from kv_write Java Function!");
+ var key = plugin.inputString(params[0]);
+ var value = plugin.inputBytes(params[1]);
+ System.out.println("Writing to key " + key);
+ kvStore.put(key, value);
+};
+
+ExtismFunction kvRead = (plugin, params, returns, data) -> {
+ System.out.println("Hello from kv_read Java Function!");
+ var key = plugin.inputString(params[0]);
+ System.out.println("Reading from key " + key);
+ var value = kvStore.get(key);
+ if (value == null) {
+ // default to zeroed bytes
+ var zero = new byte[]{0,0,0,0};
+ plugin.returnBytes(returns[0], zero);
+ } else {
+ plugin.returnBytes(returns[0], value);
+ }
+};
+
+HostFunction kvWriteHostFn = new HostFunction<>(
+ "kv_write",
+ new LibExtism.ExtismValType[]{LibExtism.ExtismValType.I64, LibExtism.ExtismValType.I64},
+ new LibExtism.ExtismValType[0],
+ kvWrite,
+ Optional.empty()
+);
+
+HostFunction kvReadHostFn = new HostFunction<>(
+ "kv_read",
+ new LibExtism.ExtismValType[]{LibExtism.ExtismValType.I64},
+ new LibExtism.ExtismValType[]{LibExtism.ExtismValType.I64},
+ kvRead,
+ Optional.empty()
+);
+
+```
+
+> *Note*: In order to write host functions you should get familiar with the methods on the [ExtismCurrentPlugin](https://www.javadoc.io/doc/org.extism.sdk/extism/latest/org/extism/sdk/ExtismCurrentPlugin.html) class.
+> The `plugin` parameter is an instance of this class.
+
+Now we just need to pass in these function references when creating the plugin:.
+
+```java
+HostFunction[] functions = {kvWriteHostFn, kvReadHostFn};
+var plugin = new Plugin(manifest, false, functions);
+var output = plugin.call("count_vowels", "Hello, World!");
+// => Hello from kv_read Java Function!
+// => Reading from key count-vowels
+// => Hello from kv_write Java Function!
+// => Writing to key count-vowels
+System.out.println(output);
+// => {"count": 3, "total": 3, "vowels": "aeiouAEIOU"}
+```
+
+## Development
+
+# Build
+
+To build the Extism java-sdk run the following command:
+
+```
+mvn clean verify
+```
+
diff --git a/src/main/java/org/extism/sdk/Extism.java b/src/main/java/org/extism/sdk/Extism.java
index 6734652..8e106b3 100644
--- a/src/main/java/org/extism/sdk/Extism.java
+++ b/src/main/java/org/extism/sdk/Extism.java
@@ -33,6 +33,7 @@ public static void setLogFile(Path path, LogLevel level) {
/**
* Invokes the named {@code function} from the {@link Manifest} with the given {@code input}.
+ * This is a convenience method. Prefer initializing and using a {@link Plugin} where possible.
*
* @param manifest the manifest containing the function
* @param function the name of the function to call
diff --git a/src/main/java/org/extism/sdk/wasm/UrlWasmSource.java b/src/main/java/org/extism/sdk/wasm/UrlWasmSource.java
new file mode 100644
index 0000000..9398fdc
--- /dev/null
+++ b/src/main/java/org/extism/sdk/wasm/UrlWasmSource.java
@@ -0,0 +1,50 @@
+package org.extism.sdk.wasm;
+
+/**
+ * WASM Source represented by a url.
+ */
+public class UrlWasmSource implements WasmSource {
+
+ private final String name;
+
+ private final String url;
+
+ private final String hash;
+
+ /**
+ * Provides a quick way to instantiate with just a url
+ *
+ * @param url String url to the wasm file
+ * @return
+ */
+ public static UrlWasmSource fromUrl(String url) {
+ return new UrlWasmSource(null, url, null);
+ }
+
+ /**
+ * Constructor
+ * @param name
+ * @param url
+ * @param hash
+ */
+ public UrlWasmSource(String name, String url, String hash) {
+ this.name = name;
+ this.url = url;
+ this.hash = hash;
+ }
+
+ @Override
+ public String name() {
+ return name;
+ }
+
+ @Override
+ public String hash() {
+ return hash;
+ }
+
+ public String url() {
+ return url;
+ }
+}
+
diff --git a/src/main/java/org/extism/sdk/wasm/WasmSourceResolver.java b/src/main/java/org/extism/sdk/wasm/WasmSourceResolver.java
index f55e667..8b6ab82 100644
--- a/src/main/java/org/extism/sdk/wasm/WasmSourceResolver.java
+++ b/src/main/java/org/extism/sdk/wasm/WasmSourceResolver.java
@@ -4,6 +4,7 @@
import org.extism.sdk.support.Hashing;
import java.io.IOException;
+import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Objects;
@@ -18,7 +19,6 @@ public PathWasmSource resolve(Path path) {
}
public PathWasmSource resolve(String name, Path path) {
-
Objects.requireNonNull(path, "path");
var wasmFile = path.toFile();
diff --git a/src/test/java/org/extism/sdk/PluginTests.java b/src/test/java/org/extism/sdk/PluginTests.java
index 8351267..6ace3fe 100644
--- a/src/test/java/org/extism/sdk/PluginTests.java
+++ b/src/test/java/org/extism/sdk/PluginTests.java
@@ -3,6 +3,7 @@
import com.sun.jna.Pointer;
import org.extism.sdk.manifest.Manifest;
import org.extism.sdk.manifest.MemoryOptions;
+import org.extism.sdk.wasm.UrlWasmSource;
import org.extism.sdk.wasm.WasmSourceResolver;
import org.junit.jupiter.api.Test;
@@ -42,6 +43,68 @@ public void shouldInvokeFunctionFromFileWasmSource() {
assertThat(output).isEqualTo("{\"count\": 3}");
}
+ @Test
+ public void shouldInvokeFunctionFromUrlWasmSource() {
+ var url = "https://github.com/extism/plugins/releases/latest/download/count_vowels.wasm";
+ var config = Map.of("vowels", "aeiouyAEIOUY");
+ var manifest = new Manifest(List.of(UrlWasmSource.fromUrl(url)), null, config);
+ var plugin = new Plugin(manifest, false, null);
+ var output = plugin.call("count_vowels", "Yellow, World!");
+ assertThat(output).isEqualTo("{\"count\":4,\"total\":4,\"vowels\":\"aeiouyAEIOUY\"}");
+ }
+
+// @Test
+// public void shouldInvokeFunctionFromUrlWasmSourceHostFuncs() {
+// var url = "https://github.com/extism/plugins/releases/latest/download/count_vowels_kvstore.wasm";
+// var manifest = new Manifest(List.of(UrlWasmSource.fromUrl(url)));
+//
+// // Our application KV store
+// // Pretend this is redis or a database :)
+// var kvStore = new HashMap();
+//
+// ExtismFunction kvWrite = (plugin, params, returns, data) -> {
+// System.out.println("Hello from Java Host Function!");
+// var key = plugin.inputString(params[0]);
+// var value = plugin.inputBytes(params[1]);
+// System.out.println("Writing to key " + key);
+// kvStore.put(key, value);
+// };
+//
+// ExtismFunction kvRead = (plugin, params, returns, data) -> {
+// System.out.println("Hello from Java Host Function!");
+// var key = plugin.inputString(params[0]);
+// System.out.println("Reading from key " + key);
+// var value = kvStore.get(key);
+// if (value == null) {
+// // default to zeroed bytes
+// var zero = new byte[]{0,0,0,0};
+// plugin.returnBytes(returns[0], zero);
+// } else {
+// plugin.returnBytes(returns[0], value);
+// }
+// };
+//
+// HostFunction kvWriteHostFn = new HostFunction<>(
+// "kv_write",
+// new LibExtism.ExtismValType[]{LibExtism.ExtismValType.I64, LibExtism.ExtismValType.I64},
+// new LibExtism.ExtismValType[0],
+// kvWrite,
+// Optional.empty()
+// );
+//
+// HostFunction kvReadHostFn = new HostFunction<>(
+// "kv_read",
+// new LibExtism.ExtismValType[]{LibExtism.ExtismValType.I64},
+// new LibExtism.ExtismValType[]{LibExtism.ExtismValType.I64},
+// kvRead,
+// Optional.empty()
+// );
+//
+// HostFunction[] functions = {kvWriteHostFn, kvReadHostFn};
+// var plugin = new Plugin(manifest, false, functions);
+// var output = plugin.call("count_vowels", "Hello, World!");
+// }
+
@Test
public void shouldInvokeFunctionFromByteArrayWasmSource() {
var manifest = new Manifest(CODE.byteArrayWasmSource());