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

Basic example of a java sse plugin #13

Merged
merged 7 commits into from
Oct 18, 2017
Merged

Basic example of a java sse plugin #13

merged 7 commits into from
Oct 18, 2017

Conversation

LinneaStovring-Nielsen
Copy link
Contributor

Status

[ ] Under development
[x] Waiting for code review
[x] Waiting for merge

Information

[ ] Contains breaking changes
[ ] Contains new API(s)
[x] Contains documentation
[ ] Contains test
[x] Contains examples

To-do list

[x] [The secure connection in the example is statically linked to OpenSSL and APR-lib. It is possible to do this dynamically, see https://github.com/grpc/grpc-java/blob/master/SECURITY.md. Have anyone done this? ]

@qlikmats qlikmats self-requested a review September 27, 2017 12:53
Copy link
Contributor

@qlikmats qlikmats left a 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 :)

.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) {
Copy link
Contributor

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.

private ServerSideExtension.BundledRows evalScript(ServerSideExtension.ScriptRequestHeader header) {

logger.fine("evalScript called");
ServerSideExtension.BundledRows.Builder builder = ServerSideExtension.BundledRows.newBuilder();
Copy link
Contributor

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()

return builder.build();
}

private ServerSideExtension.BundledRows evalScript(ServerSideExtension.ScriptRequestHeader header, ServerSideExtension.BundledRows bundledRows) {
Copy link
Contributor

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?

}

//final ServerSideExtension.BundledRows.Builder builder = ServerSideExtension.BundledRows.newBuilder();
//&& header.getFunctionType()==ServerSideExtension.FunctionType.SCALAR
Copy link
Contributor

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.

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
Copy link
Contributor

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?

**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).
Copy link
Contributor

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?

Copy link
Contributor

@qlikmats qlikmats left a 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?

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.
Copy link
Contributor

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.

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>`
Copy link
Contributor

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."

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
Copy link
Contributor

Choose a reason for hiding this comment

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

protobuf

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:
Copy link
Contributor

@qlikmats qlikmats Sep 28, 2017

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.

@LinneaStovring-Nielsen
Copy link
Contributor Author

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")) {
Copy link
Contributor

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.

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 hadn't thought about that, fixed now.

.setPluginVersion("v1.0.0")
.addFunctions(ServerSideExtension.FunctionDefinition.newBuilder()
.setName("HelloWorld")
.setFunctionId(0)
Copy link
Contributor

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());
Copy link
Contributor

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) {
Copy link
Contributor

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) {
Copy link
Contributor

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
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 "Java 8" comments 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.

That you need java 8 for this to work. Should I remove it or make the comment more clear?

Copy link
Contributor

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 @@
/*
Copy link
Contributor

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?

Copy link
Contributor Author

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/ ).
Copy link
Contributor

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
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 '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
Copy link
Contributor

Choose a reason for hiding this comment

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

'go to the..'

LinneaStovring-Nielsen and others added 6 commits October 5, 2017 12:50
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.
Copy link
Contributor

@tobiaslindulf tobiaslindulf left a 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

@cjpersson
Copy link

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.

@LinneaStovring-Nielsen
Copy link
Contributor Author

@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;
Copy link
Contributor

Choose a reason for hiding this comment

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

@@ -0,0 +1,405 @@
package com.qlik.sse.basicExample;
Copy link
Contributor

Choose a reason for hiding this comment

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

@@ -0,0 +1,69 @@
package com.qlik.sse.basicExample;
Copy link
Contributor

Choose a reason for hiding this comment

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

Copy link
Contributor

@qlikmats qlikmats left a 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.
@LinneaStovring-Nielsen LinneaStovring-Nielsen merged commit ae64fde into qlik-oss:master Oct 18, 2017
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.

5 participants