-
Notifications
You must be signed in to change notification settings - Fork 127
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
Basic example of a java sse plugin #13
Basic example of a java sse plugin #13
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In general, without having read the code in detail, I suggest trying to split the code over several classes/files, f.e. server (lifecycle) related code in one file, plugin impl. in another etc. if there is a way to do this that makes sense :)
examples/java/PluginServer.java
Outdated
.parseFrom(metadata.get(Metadata.Key.of("qlik-functionrequestheader-bin", BINARY_BYTE_MARSHALLER))); //Java 8 | ||
logger.finest("Function request header."); | ||
logHeader(responseHeaders); //Java 8 | ||
if(header.getFunctionId()==5) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
spaces surrounding e.g. assignment operator, comparison (<, >, == etc.). I'll leave this one comment, but there are several places throughout the file where this should be fixed.
examples/java/PluginServer.java
Outdated
private ServerSideExtension.BundledRows evalScript(ServerSideExtension.ScriptRequestHeader header) { | ||
|
||
logger.fine("evalScript called"); | ||
ServerSideExtension.BundledRows.Builder builder = ServerSideExtension.BundledRows.newBuilder(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A more descriptive name for builder would be nice, that indicates what is being built when you f.e. see builder.build()
examples/java/PluginServer.java
Outdated
return builder.build(); | ||
} | ||
|
||
private ServerSideExtension.BundledRows evalScript(ServerSideExtension.ScriptRequestHeader header, ServerSideExtension.BundledRows bundledRows) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems like much code in common with previous method. Could it be refactored to avoid code duplication?
examples/java/PluginServer.java
Outdated
} | ||
|
||
//final ServerSideExtension.BundledRows.Builder builder = ServerSideExtension.BundledRows.newBuilder(); | ||
//&& header.getFunctionType()==ServerSideExtension.FunctionType.SCALAR |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove code that is commented out.
examples/java/README.md
Outdated
The plugin is partly asynchronous since several method calls can be made to the plugin at the same time. But a `StreamObserver` (which sends data to and from the plugin) can only handle one call at a time, and they need | ||
to be in a specific order, therefore the plugin cannot be fully asynchronous. | ||
|
||
## Prerequisites |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this have to be done by the user? Can't we have this structure from the beginning, to make it easier?
examples/java/README.md
Outdated
**In a command prompt:** go to the grpc-java directory, run `gradlew.bat build` and `gradlew.bat install`. This installs grpc in your local maven repository (usually located at `C:/Users/<user>/.m2`). | ||
|
||
## Changing the default logging | ||
Move the `javapluginlogger.properties` file to the `my-app/target` directory (it needs to be in the same directory as the `my-app-1.0-SNAPSHOT.jar` file). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't know maven, but I suspect that this file could be moved automatically during the build?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not sure I remember enough Java, but isn't it best practice to use package declaration(s) and matching folder structure(s)? So, f.e. create a structure (under /src/main/java) like: com/qlik/sse and declare a package com.qlik.sse in PluginServer.java?
examples/java/README.md
Outdated
In this file you can edit the settings for the logging. There are different [levels](https://docs.oracle.com/javase/7/docs/api/java/util/logging/Level.html) of logging depending on how much | ||
information you want (`OFF`, `SEVERE`, `WARNING`, `INFO`, `CONFIG`, `FINE`, `FINER`, `FINEST`, `ALL`). | ||
|
||
* `.level`: Sets the default level for all loggers, in this case they are set to info. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think a short comment about logging, with a reference to the logger documentation is sufficient.
examples/java/README.md
Outdated
Install Qlik Sense Desktop June release or later. | ||
To be able to connect to Sense you need to specify which port Sense should look for a plugin at. Go to `Documents/Qlik/Sense` and create or edit the `Settings.ini` file so that it includes | ||
the line `SSEPlugin=javaPlugin,localhost:50071`. `javaPlugin` is the name of the plugin. This can be whatever you want but since the name of the plugin in the example app is set to `javaPlugin` the | ||
example will not work if you set it to something else (unless you change the name of the plugin in every call to the plugin in the app). `50071` is the port. You should change this to `<Port number>` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Keep it short, like: "'javaPlugin' is an arbitrary name, that needs to match the name used in Sense, when referencing the plugin."
examples/java/README.md
Outdated
in the `generated-sources` folder. | ||
|
||
### If you are having trouble... | ||
It is possible to install the protobuff and the grpc packages manually if they for some reason are not downloaded by maven when you run `mvn package`. If you need to do this, install the protocol buffer first |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
protobuf
examples/java/README.md
Outdated
with input parameters if it is not an aggregation and if the script can be evaluated for one row of input parameters independently of the other rows of parameters. The scripts being evaluated however are JavaScript scripts | ||
since there is no "eval" function in java. | ||
|
||
Synchronous/asynchronous: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For me, this section doesn't provide much useful info for the purpose of running an example plugin.
I have now split the code into three files and updated the documentation to reflect that. I have also (hopefully) fixed the problems pointed out by @qlikmats except for the part about storing packages as com/qlik/sse since maven automatically stores them in the local repository when you run mvn package. |
} catch (Exception e) { | ||
logger.log(Level.WARNING, "Invalid port, using default value: " + port); | ||
} | ||
} else if (args[i].equals("--pemDir")) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What if --pemDir is given as e.g. "./some/dir"? Since it's concatenated with the .pem file name, the dir needs to have a trailing slash, right? See if there is some path utility library that can handle this, so dir can be given both with and without trailing slash.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I hadn't thought about that, fixed now.
.setPluginVersion("v1.0.0") | ||
.addFunctions(ServerSideExtension.FunctionDefinition.newBuilder() | ||
.setName("HelloWorld") | ||
.setFunctionId(0) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Define constants for function IDs since they are also switched upon elsewhere in the code.
switch(functionId) { | ||
case 2: responseObserver.onNext(bundledRowsBuilder.addRows(ServerSideExtension.Row.newBuilder().addDuals(ServerSideExtension.Dual.newBuilder().setNumData(sum(columnSum)))).build()); | ||
break; | ||
case 3: responseObserver.onNext(bundledRowsBuilder.addRows(ServerSideExtension.Row.newBuilder().addDuals(ServerSideExtension.Dual.newBuilder().setStrData(stringBuilder.toString()))).build()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the lines are too long.
}; | ||
} | ||
|
||
private ServerSideExtension.BundledRows evalScript(ServerSideExtension.ScriptRequestHeader header, ServerSideExtension.BundledRows bundledRows) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Too long line.
return bundledRowsBuilder.build(); | ||
} | ||
|
||
private boolean evalScript(String script, ServerSideExtension.BundledRows.Builder bundledRowsBuilder, ScriptEngine engine, ServerSideExtension.DataType returnType) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Too long line.
ServerSideExtension.FunctionRequestHeader header = ServerSideExtension.FunctionRequestHeader | ||
.parseFrom(metadata.get(Metadata.Key.of("qlik-functionrequestheader-bin", BINARY_BYTE_MARSHALLER))); //Java 8 | ||
logger.finest("Function request header."); | ||
logHeader(responseHeaders); //Java 8 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What does the "Java 8" comments mean?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That you need java 8 for this to work. Should I remove it or make the comment more clear?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Prerequisites already states Java 8 as a requirement, so I think it can be removed.
@@ -0,0 +1,131 @@ | |||
/* |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The proto file is already included in the repository as proto/ServerSideExtension.proto. Is it not possible for you to refer to that one instead of including another copy of it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Both yes and no. As I have understood it, standard Maven practice is to include the .proto file like this (and if you add tests you add another copy of it). I can probably find another way to do it. Then it is a question about if it should be possible to download just the basic_example folder or if you need to download everything to run the example?
# Running the example | ||
|
||
## Prerequisites | ||
This example was created using maven, so the easiest way to run the example is to install [maven](http://maven.apache.org/ ). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maven is written with capital 'M'.
## Prerequisites | ||
This example was created using maven, so the easiest way to run the example is to install [maven](http://maven.apache.org/ ). | ||
|
||
There are other ways to do this. If you don't use maven, what you need is the protocol buffer package and the grpc package (in that order) and the protoc |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What does 'this' refer to? Building the example?
## Generate the java plugin (and the classes from the .proto file) | ||
**In a command prompt:** go to the `basic_example` directory and run `mvn package`. | ||
|
||
Now, got to the `target` directory in the `basic_example` directory. In the `target` folder, you can find a jar file, that is the plugin. The code generated from the `.proto` file can be found |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
'go to the..'
Add an sse java plugin to later be used as a basic example.
Add evaluate script to the plugin so that it can be used as a basic example showing how to do script evaluation in a java plugin.
Add more functions that can be executed to make the example more useful. Also add the pom.xml file to show which dependencies are needed.
Add functions for showing how the cache can be turned of to be consistent with other basic examples.
Add a properties file for the logging so that the plugin user can set how much logging will be done. Also add a secure conection so that a secure connection with Qlik Sense Server is possible.
Add documentaion so that it becomes easier for others to create their own sse java plugin. Some cleanup to make the code easier to read.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
add a .gitignore file in basic_example folder with the following content:
dependency-reduced-pom.xml
/target/*
!/target/javapluginlogger.properties
I was asked to review this (I guess) because we mainly work with Java and that we have made a prototype with a Java SSE. So here are my comments. It is great to get an example to start with! It will save a lot of time building SSEs using Java. One thing that is a bit awkward in Java when building SSEs is the way incoming metadata is handled compared to how grpc is integrated in other languages. It would be helpful to explain the purpose of the interceptor and the thread local storage used for metadata. That is a good insight that it takes some time to get when starting building Java SSEs. A comment in the code explaining this mechanism in PluginServerInterceptor and JavaPlugin would be good. Folders and package names must match. Normally the structure used here should not compile. I guess the build script does something unconventional to make it compile. Do not import classes in same package. They are automatically visible. Use package names starting with a lower case letter. The correct thing to use here would be something like com.qlik.sse.basicExample but for an example it might be easier with folder hierarchies that are not that deep so qlik.basicExample is ok. That would imply that the source files would be in src/main/java/qlik/basicExample. |
@cjpersson Thank you for your comments! On the metadata explanation, would it be better to have a more detailed explanation in the documentation or do you want that explanation in the code? or both? |
@@ -0,0 +1,99 @@ | |||
package com.qlik.sse.basicExample; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
package names should be all lowercase (https://docs.oracle.com/javase/tutorial/java/package/namingpkgs.html)
@@ -0,0 +1,405 @@ | |||
package com.qlik.sse.basicExample; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
package names should be all lowercase (https://docs.oracle.com/javase/tutorial/java/package/namingpkgs.html)
@@ -0,0 +1,69 @@ | |||
package com.qlik.sse.basicExample; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
package names should be all lowercase (https://docs.oracle.com/javase/tutorial/java/package/namingpkgs.html)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Correct the package names when you get an opportunity.
Split the code so that it becomes easier to read and to make it easier to update it in the future. Update package names and edit files to follow Java standards.
Status
Information
To-do list