Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(java-sdk): Create Java Host SDK #122

Merged
merged 45 commits into from
Dec 19, 2022
Merged

feat(java-sdk): Create Java Host SDK #122

merged 45 commits into from
Dec 19, 2022

Conversation

bhelx
Copy link
Contributor

@bhelx bhelx commented Dec 2, 2022

Closes #117

java/pom.xml Outdated Show resolved Hide resolved
java/pom.xml Outdated Show resolved Hide resolved
bhelx and others added 6 commits December 2, 2022 13:11
We now resolve the extism library dynamically based on the rules
defined in com.sun.jna.NativeLibrary.

For tests we resolve the library relative to the cwd based on the
jna.library.path system property set to ../target/release.

Improves #117
- Refactor Plugin
- Make `Plugin` and `Context` `AutoClosable` to automatically free
resources in TWR blocks
- Add missing javadoc
- Upgrade to Junit 5
- Reorganize packages
- Use more meaningful names
- Introduce `ExtismException`
- Make JSON serialization pluggable
- Lower required java version to Java 17

Improves #117
Copy link
Contributor

@thomasdarimont thomasdarimont left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Before finishing this, we could address the following points:

public List<String> allowedHosts;
public Map<String, String> config;

private static Gson _gson;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
private static Gson _gson;
private static final Gson _gson;

@@ -0,0 +1,15 @@
package org.extism.sdk;

public class ExtismException extends RuntimeException {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need to add some javadoc here

* @param pluginIndex
* @param json
* @param jsonLength
* @return
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what does the boolean return mean?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same goes for what I wrote for #call

* @param withWASI
* @return
*/
boolean extism_plugin_update(Pointer contextPointer, int pluginIndex, byte[] wasm, int length, boolean withWASI);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does the boolean return means?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same goes for what I wrote for #call

*
* @param contextPointer
* @param pluginIndex
* @return
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.. return the length of the output data in bytes.

}

public static String toJson(Manifest manifest) {
return GSON.toJson(manifest);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could remove the GSON dependency and implement a simple hand-crafted JSON serialization for the Manifest.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On the fence about it. I think eventually we will need to parse too. I'd be open to removing it but I can't say how long that will be feasible.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Quick idea:

  1. Introduce a JsonConverter interface that can be used whenever we need to read / write json data. An instance of JsonConverter could be made available to the Plugin via the Context.
interface JsonConverter {

    <T> String toJson(T object);
    <T> T fromJson(String json, Class<T> type);
}
  1. Keep the gson library as dependency, but allow it to be excluded via dependency-excludes on the consumer side.

  2. Implement a GsonJsonConverter implementation based on JsonSerializer

  3. Introduce a ContextFactory where a JsonConverter can be configured centrally with a #setJsonConverter(..) method. Then offer something like ContextFactory#newContext() to create new Context classes.

With this in place, if users prefer another JSON library and don't want to pull in gson, they could exclude it with a maven dependency exclude and simply configure their own JsonConverter implementation, e.g. based on
fasterxml jackson.

Perhaps this looks a bit over-engineered, but I think having a ContextFactory to control creation of Context which could allow to share
some additional information might be useful.

Alternative:
Keep the JsonSerializer class as is and only use it internally. If we need to read json data, just add another method like:

public <T> T fromJson(String json, Class<T> object) {
   return GSON.fromJson(json, object);
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that's a good idea. I know how complex dependency clashing can get in Java. But I think it's fine to wait until after the first release. It should not affect the interface.

* @param data the byte array representing the WASM code
* @param hash
*/
public record ByteArrayWasmSource(String name, byte[] data, String hash) implements WasmSource {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need the hash? What hash should be used here?

*/
public interface WasmSource {

String name();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will every implementation of WasmSource have a String hash?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a json schema for the manifest and a page explaining the fields: https://extism.org/docs/concepts/manifest/

hash is optional.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, I'll take a look.


import static org.assertj.core.api.Assertions.assertThat;

public class ExtismTest {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to add more tests for the remaining functions.

@Test
public void shouldInvokeNamedFunction() {

var wasmFile = Paths.get("src", "test", "resources", "code.wasm").toFile();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of copying the code.wasm in the java project, we could also refer to "../wasm/code.wasm".

java/pom.xml Outdated
<groupId>org.extism.sdk</groupId>
<artifactId>extism</artifactId>
<packaging>jar</packaging>
<version>1.0-SNAPSHOT</version>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should the version be aligned with the overall project version?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, the Host SDKs will be versioned independently based on semver.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, then I'd recommend to follow a semver approach and use 3 component version number.

java/pom.xml Outdated
<version>5.12.1</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we actually need an JSON serialization library at all? If we only need to serialize the Manifest to JSON, perhaps a hand-crafted helper method will be sufficient.

* @param plugin
* @return
*/
public String error(Plugin plugin) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps an additional method like public String error() that explicitly returns the context error would be useful here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should be an internal method I think. I'm going to capture these errors then turn them into exceptions

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense, then protected is fine, the error can be used as the message of ExtismException.

@bhelx
Copy link
Contributor Author

bhelx commented Dec 6, 2022

@thomasdarimont in the past, i've published java libraries to OSSRH: https://central.sonatype.org/publish/publish-guide/#releasing-to-central

Is this still the best place to publish?

@thomasdarimont
Copy link
Contributor

@bhelx Yes, releasing to maven central is still current, btw. the process of publishing releases can be simplified a lot by using a tool like jreleaser which makes publishing libraries to maven central a breeze.

@thomasdarimont
Copy link
Contributor

I think it would also make sense to use a common source code formatter to format the code properly, otherwise a slightly diffenent IDE setup will cause formatting noise in pull requests.

Perhaps a tool like maven-checkstyle could help here.
A checkstyle example config with sentive values can be found here and here (maven).

java/pom.xml Outdated
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's usually recommended to let libraries pull in their dependencies explicitly, instead of creating a jar-with-dependencies.

java/pom.xml Outdated
</configuration>
</plugin>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is the maven-assembly-plugin needed at all? The jar builds fine without it.

java/pom.xml Outdated
<packaging>jar</packaging>
<version>1.0-SNAPSHOT</version>
<name>extism</name>
<url>https://github.com/extism/extism</url>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The project is lacking some metadata (organization, licensing, description, developer information) etc. that I think is necessary for releasing to maven central.

See here for a more complete library with complete maven metadata

@bhelx
Copy link
Contributor Author

bhelx commented Dec 6, 2022 via email

@thomasdarimont
Copy link
Contributor

I think I addressed most of the points I raised in the PR #147.
I kept the GSON dependency and did some additional refactorings.

@thomasdarimont
Copy link
Contributor

For additional tests I need a few more example wasm files:

  • an example for reading a value from the manifest config and returning that as a result
  • a function that fails with an internal error

Perhaps you could create a generic "test" wasm file with multiple functions that could be used by all modules for consistent testing instead of having code.wasm in every sub-project.

thomasdarimont and others added 3 commits December 7, 2022 20:07
- Context#error is now protected and used to extract the error message
for the ExtismException
- ByteArrayWasmSource is now correctly serialized (wasm data is now
encoded base64)
- Renamed ManifestMemory to MemoryOptions to align with rust codebase
- Introduced WasmSourceResolver to load wasm source
- Added unit tests
- Revised Unit tests
- Polished javadoc
- Added necessary metadata for release to maven central
- Removed maven-assembly-plugin as we want to allow consumers to control
the dependencies
@thomasdarimont
Copy link
Contributor

It seems as the ci fails due to unrelated issues in the Python SDK, perhaps a rebase with main would solve that.

@thomasdarimont
Copy link
Contributor

thomasdarimont commented Dec 8, 2022

Just added another PR: #150

I think the libray is now in good shape, however I recommend to address the ``FIXME` comments (in the non-test) code before releasing it.

I think the ManifestHttpRequest is not used at the moment and the code.wasm doesn't seem to have a function that can be used to test this. So we should either remove the class and add it later or add an example test and wasm file that uses the class.

@thomasdarimont
Copy link
Contributor

thomasdarimont commented Dec 9, 2022

Btw. the current state of the java-sdk requires, that users manually obtain the matching native extism library for their platform and store the library in a folder. This folder then needs to provided to the JVM via a System property e.g. -Djna.library.path=/path/to/folder. or the lib must be located in the OS specific library search paths.

This should be documented in the readme.md of the SDK.

Alternatively, we could package the matching library for various platforms directly in the jar and use a tool like native-lib-loader to load the proper library for the given OS dynamically. Would it be possible to provide the extism libraries for multiple os/platforms before the maven build runs?

How do we communicate to the user which version of the extism library is needed for the given java-sdk version?

nilslice
nilslice previously approved these changes Dec 19, 2022
@bhelx bhelx merged commit f34fa8b into main Dec 19, 2022
@bhelx bhelx deleted the java-sdk branch December 19, 2022 16:55
@bhelx
Copy link
Contributor Author

bhelx commented Dec 19, 2022

So this branch has been hanging our CI on the python jobs. We think it was just something with this branch and not the code. hoping that merging will fix the problem. If not we may revert

@bhelx
Copy link
Contributor Author

bhelx commented Dec 19, 2022

Wow it's going green now: #175
turns out this branch was just haunted.

Thanks @thomasdarimont ! I'm going to work on docs now and some final cleanup items and push this out tomorrow

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Java SDK?
4 participants