Skip to content

Commit 4b59791

Browse files
committed
ready to release the harness Java SDK 0.3.0
1 parent 68a8539 commit 4b59791

File tree

76 files changed

+5540
-2
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

76 files changed

+5540
-2
lines changed

.gitignore

Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,74 @@
1+
# Created by .ignore support plugin (hsz.mobi)
2+
### Java template
13
*.class
2-
*.log
4+
5+
# BlueJ files
6+
*.ctxt
7+
8+
# Mobile Tools for Java (J2ME)
9+
.mtj.tmp/
10+
11+
# Package Files #
12+
*.jar
13+
*.war
14+
*.ear
15+
16+
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
17+
hs_err_pid*
18+
### JetBrains template
19+
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
20+
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
21+
22+
# User-specific stuff:
23+
.idea/tasks.xml
24+
25+
# Sensitive or high-churn files:
26+
.idea/dataSources/
27+
.idea/dataSources.ids
28+
.idea/dataSources.xml
29+
.idea/dataSources.local.xml
30+
.idea/sqlDataSources.xml
31+
.idea/dynamic.xml
32+
.idea/uiDesigner.xml
33+
34+
# Gradle:
35+
.idea/gradle.xml
36+
# Mongo Explorer plugin:
37+
.idea/mongoSettings.xml
38+
39+
## File-based project format:
40+
*.iws
41+
42+
## Plugin-specific files:
43+
44+
# IntelliJ
45+
/out/
46+
47+
# mpeltonen/sbt-idea plugin
48+
.idea_modules/
49+
50+
# JIRA plugin
51+
atlassian-ide-plugin.xml
52+
53+
# Crashlytics plugin (for Android Studio and IntelliJ)
54+
com_crashlytics_export_strings.xml
55+
crashlytics.properties
56+
crashlytics-build.properties
57+
fabric.properties
58+
### Maven template
59+
target/
60+
pom.xml.tag
61+
pom.xml.releaseBackup
62+
pom.xml.versionsBackup
63+
pom.xml.next
64+
release.properties
65+
dependency-reduced-pom.xml
66+
buildNumber.properties
67+
.mvn/timing.properties
68+
69+
# Exclude maven wrapper
70+
!/.mvn/wrapper/maven-wrapper.jar
71+
/.idea/
72+
/java-sdk.iml
73+
*.iml
74+
/data/*

README.md

Lines changed: 191 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,191 @@
1-
# harness-java-sdk
1+
# Harness Java SDK
2+
3+
This implements an SDK for use in the client side for Harness Events and Queries. The Administration part of the REST API is currently only implemented in the Python SDK for use in the CLI.
4+
5+
# Requirements
6+
7+
- Java 8+
8+
- Maven 3+
9+
10+
# Build From Source
11+
12+
- **Get the Source:**
13+
14+
```
15+
git pull https://github.com/actionml/harness-java-sdk.git
16+
```
17+
- **Build:** Navigate to the root of the source containing `pom.xml` and execute:
18+
19+
```
20+
mvn clean install
21+
```
22+
23+
This will create the jar necessary to include in the client and also populate the `~/.m2` cache for use as a dependency when creating a client application, like the examples and integration tests.
24+
25+
# Java SDK Features
26+
27+
- Supports all Harness Server REST APIs for Events and Queries
28+
- Does **not** currently implement the Admin REST APIs, which are implemented in the Python SDK and used in the CLI
29+
- Packages JSON for REST call
30+
- Implements SSL and Auth
31+
- Modeled after the PredictionIO Java SDK API where possible
32+
- Based on [http-akka client](http://doc.akka.io/docs/akka-http/current/java/http/introduction.html#http-client-api)
33+
- Synchronous and async client APIs
34+
35+
## Sending Events
36+
37+
Perhaps the most important thing to note about sending events is that the SDKs support asynchronous APIs. This almost always more high performance than blocking an event send to wait for a response before the next send. **However**: This is not compatible with some Engines that require events to be guaranteed to be processed in the order they are sent. For instance the Contextual Bandit must get a new testGroup created before receiving conversion events for the group. Therefore we show how to use the Asynchronous sendEvent in a blocking manner to avoid this problem. Async and sync methods can be mixed on a send by send basis as long as the Engine supports this, for instance the CB needs to have the testGroup creation sent in a synchronous blocking manner but once this has been processed new usage events can be sent asynchronously--check your Engine for it's requirements. Most Lambda Engines see events as streams ordered by timestamps so can operate completely asynchronous, some Kappa Engine need to get ordered events.
38+
39+
### Java 8 Functional Asynchronous Event Send
40+
41+
Using Java 8 functional style conventions an example event sending code looks like this:
42+
43+
EventClient client = new EventClient(engineId, "localhost", 9090);
44+
45+
Event event = new Event()
46+
.eventId("ed15537661f2492cab64615096c93160")
47+
.event("$set")
48+
.entityType("testGroup")
49+
.entityId("9")
50+
.properties(ImmutableMap.of(
51+
"testPeriodStart", "2016-07-12T00:00:00.000+09:00",
52+
"testPeriodEnd", "2016-08-31T00:00:00.000+09:00",
53+
"pageVariants", ImmutableList.of("17", "18")
54+
))
55+
.eventTime(new DateTime("2016-07-12T16:08:49.677+09:00"))
56+
.creationTime(new DateTime("2016-07-12T07:09:58.273Z"));
57+
58+
59+
log.info("Send event {}", event.toJsonString());
60+
61+
client.sendEvent(event).whenComplete((response, throwable) -> {
62+
log.info("Response: {}", response);
63+
if (throwable != null) {
64+
log.error("Create event error", throwable);
65+
}
66+
client.close();
67+
});
68+
69+
70+
The important bit in creating a client:
71+
72+
EventClient client = new EventClient(engineId, "localhost", 9090);
73+
74+
The engineId must be a string that uniquely ids the server-side engine.
75+
76+
Use the Event builder for PredictionIO style events:
77+
78+
Event event = new Event()
79+
.eventId("ed15537661f2492cab64615096c93160")
80+
.event("$set")
81+
...
82+
83+
Or create your events as JSON when PIO formatting is not desired and send the JSON via:
84+
85+
event = "{\"some\":\"json\"}"
86+
client.createEvents(events).thenApply(pairs -> {
87+
long duration = System.currentTimeMillis() - start;
88+
Map<String, Long> counting = pairs.stream()
89+
.map(p -> p.second().first().toString())
90+
.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
91+
92+
log.info("Responses: " + pairs.size());
93+
log.info("Finished: " + duration);
94+
counting.forEach((code, cnt) -> log.info("Status " + code + ": " + cnt));
95+
return pairs.size();
96+
}).whenComplete((size, throwable) -> {
97+
log.info("Complete: " + size);
98+
log.info("Close client");
99+
client.close();
100+
});
101+
102+
103+
and send the event:
104+
105+
client.sendEvent(event)
106+
107+
### Making the Async Event Send Synchronous
108+
109+
We have only to add a `.get()` and code to catch possible exceptions to make the asynch methods synchronous:
110+
111+
try {
112+
// using the .get() forces the code to wait for the response and so is blocking
113+
Pair<Integer, String> p =
114+
((CompletableFuture<Pair<Integer, String>>) client.sendEvent(event)).get();
115+
log.info("Sent event: " + event + "\nResponse code: " + p.first().toString());
116+
} catch (InterruptedException | ExecutionException e) {
117+
log.error("Error in client.sendEvent waiting for a response ", e);
118+
}
119+
120+
This uses the `sendEvent(String event)` taking a JSON string as the
121+
Event definition.
122+
123+
**Note**: Checking for errors is important since you will receive them for many reasons and they are self-describing. See the [REST Specification](https://github.com/actionml/harness/blob/master/rest_spec.md) for a description of the response codes specifics.
124+
125+
## Sending Queries
126+
127+
Queries are specific to each template so are created directly from JSON strings:
128+
129+
String engineId = "test_resource";
130+
QueryClient client = new QueryClient(engineId, "localhost", 8080);
131+
132+
String query = "{\"user\": \"user-1\",\"groupId\": \"group-1\"}";
133+
134+
try {
135+
System.out.println("Send query: " + query);
136+
long start = System.currentTimeMillis();
137+
client.sendQuery(query).whenComplete((queryResult, throwable) -> {
138+
long duration = System.currentTimeMillis() - start;
139+
if (throwable == null) {
140+
System.out.println("Receive eventIds: " +
141+
queryResult.toString() + ", " + duration + " ms.");
142+
} else {
143+
System.err.println(throwable.getMessage());
144+
}
145+
client.close();
146+
});
147+
148+
} catch (Exception e) {
149+
e.printStackTrace();
150+
}
151+
152+
First create the `QueryClient`
153+
154+
QueryClient client = new QueryClient(engineId, "localhost", 8080);
155+
156+
Then send the JSON string to the query endpoint for the correct engine
157+
158+
client.sendQuery(query)
159+
160+
Since Query formats are always defined by the specific Engine there is no builder for them, they must be sent as raw JSON. The code to process a specific response must be in the Java Promise that is after the `.whenCompleted` or using the `.get()` method similar to the `sendEvent` function, the query can be done synchronously.
161+
162+
# Security
163+
164+
Read no further if you are not using TLS/SSL and Authentication.
165+
166+
When using TLS/SSL or when Harness is running with authentication required, setup as shown below. This is not necessary with connection level security, or where the network environment does not require extra security.
167+
168+
Not also that you cannot have a secure deployment without both TLS and Auth. TLS allows Harness to be trusted by the client and Auth allows the client to be trusted by Harness. Using both it is possible to connect from a mobile device or browser directly to an instance of Harness.
169+
170+
## Setup TLS/SSL
171+
172+
The Java SDK works with or without TLS. In either case a `.pem` certificate must be provided, even if it is not used. The cert file is used too encrypt data to a specific Harness server, which has the correct key installed. See [harness-config.md](harness-config.md) for a description of how to setup the Harness Server and Python CLI for use with TLS/SSL.
173+
174+
The Java SDK has 3 methods for supplying the `.pem` file:
175+
176+
1. **Change the config** in `harness/java-sdk/src/main/resources/akka.conf` to point to the `.pem` file on the desired machine and recompile the client for use on a specific machine.
177+
- **Add an environment variable** by putting `export HARNESS_SERVER_CERT_PATH=/path/to/pem-file` in one of the shell startup files like `~/.profile` or `~/.bashrc` as per recommendations for your OS.
178+
- **Pass in the location** when creating any client. All clients have an optional parameter for setting the path to the `.pem` file.
179+
180+
The method falls back from #3 to #2 to #1 and if a `.pem` file is not found there will always be an exception thrown.
181+
182+
Harness is built with a `.pem` and `.jks` file for localhost execution of TLS with pointers to these files are setup in the correct places. By default Harness does not use TLS but to try TLS with localhost all you have to do is turn it on in `bin/harness-env` and start Harness. The CLI will use TLS to communicate with Harness on localhost and the Java SDK tests will work.
183+
184+
To use a custom certificate with the Java SDK, just make sure it can find the `.pem` at runtime.
185+
186+
**Note about how this is done**: The Akka java client requires a certificate file on startup before the Harnress SDK Clients are constructed so a dummy pem file is built into the SDK for localhost. This can be used with localhost TLS but is most likely to be replaced using the above methods. The #3 methods uses the dummy pem file to initialize and then overrides it with the one passed in to the SDK client constructors. Works fine.
187+
188+
## Using Auth from the Client
189+
190+
When Harness is running in "Authentication Required" mode a **User** and **Secret** must have been created on the Harness Server using the CLI or REST interface. When Harness is first started it is in non-TLS mode, with no need of an admin user, so create one before turning on authentication and make a note of the user-id and secret needed. The User must have **Permission** to access the resource/engineId used in the client code examples above and must have the role **Client** or **Admin**. See the [CLI docs](commands.md) for more details
191+

clean_harness_mongo.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
db = db.getSiblingDB('backup_harness_meta_store')
2+
db.dropDatabase()
3+
db = db.getSiblingDB('harness_meta_store')
4+
db.copyDatabase("harness_meta_store", "backup_harness_meta_store")
5+
db.dropDatabase()
6+
db = db.getSiblingDB('harness_test_shared_users')
7+
db.dropDatabase()
8+
db = db.getSiblingDB('test_cb')
9+
db.dropDatabase()
10+
db = db.getSiblingDB('test_cb_2')
11+
db.dropDatabase()
12+
db = db.getSiblingDB('test_nh')
13+
db.dropDatabase()
14+
quit()

0 commit comments

Comments
 (0)