The Java Palisade Client API provides universal resource access to a Palisade cluster.
The design of the API loosely follows that of other well known API's. This design decision was made to provide a familiar feel to accessing Palisade.
Of course there are differences. One of the big differences is that Palisade clients deal with returning resource files and not data in Columnar format.
The URL follows the JDBC specification of not exposing the underlying communication protocol. To this end the scheme is set as pal:[subname]
.
If you are familiar with the JDBC url, then the Palisade URL should be familiar. See the examples below:
Cluster is 'my.cluster', userId is 'alice'
pal://my.cluster?userid=alice
Cluster is 'localhost:8080', requires authentication, userId is 'alice'
pal://eve:password@localhost:8080/cluster?userid=alice
Cluster is 'localhost:8080/cluster', userId is 'alice', use an ssl connection, use an http2 connection, maximum 2hrs between server responses
pal://localhost:8080/cluster?userid=alice&ssl=true&http2=true&poll=7200
Note that any user passed as part of the authority portion of the URL (e.g. "eve" in the above example) will simply be copied to the create Palisade Service and Filtered Resource Service URIs. This use is not the user_id
that is passed as part of the
REST call to the Palisade Service. The user id is passed via a property (service.userid
) or as a query parameter (userid).
The API is split into many different interfaces, which again, are loosely based upon those of JDBC. Some of these interfaces include:
Interface | Description |
---|---|
Client | This is analogous to JDBCs driver. This class provides access to actually open and retrieve a session to the Palisade cluster. Clients are not instantiated directly, but by the ClientManager asking the client whether it supports a given URL. This way the user of the API does not need to know about its implementation. |
Session | This is roughly the same as JDBCs Connection class. A Session provides access to create queries and fetch downloads. At this point there is no security for a session as Palisade does not require it. If this changes in the future, the client API will be unaffected. |
Query | This is the instance that sends the request to the Palisade Service. This is where the client deviates from JDBC as the design for this is (very) loosely based upon Hibernate's Query. |
QueryResponse | Once the query is executes and the Query has returned this via a Future , a stream of Message 's can be retrieved. This class abstracts the underlying mechanisms of how the Filtered Resource Service is accessed. This has no analogue to JDBC or Hibernate as those libraries do not support streams yet. |
Message | Two types of messages can be returned from the Filtered Resource Service and these are abstracted into two subclasses of Message . The design choice was to either have two sub types, or have a single type (resource) that can contain an Error. Either way is not wrong. This could change quite easily if needed. Currently two subclasses exist for Message. These are Error and Resource . |
Download | A Download is retrieved by passing a Resource object to the Session . The Download abstracts the call to the data service and provides access to an InputStream to consume its contents. |
A unit test to assert that resources are returned may look like the following. Note that here we are using Micronaut to create end-points for the following with Palisade:
- Palisade Service
- Filtered Resource Service
- Data Service
Micronaut will find these services and create and then inject an EmbeddedServer to expose them. The embedded server can be used to get the port which it is listening on. The port will be dynamically created enabling parallel tests.
@MicronautTest
class FullTest {
@Inject
EmbeddedServer embeddedServer;
@Test
void testWithDownloadOutsideStream() throws Exception {
var port = "" + embeddedServer.getPort();
(1) var properties = Map.<String, String>of(
"service.userid", "alice",
"service.palisade.port", port,
"service.filteredResource.port", port);
(2) var session = ClientManager.openSession("pal://eve@localhost/cluster", properties);
(3) var query = session.createQuery("good_morning", Map.of("purpose", "Alice's purpose"));
(4) var publisher = query
.execute()
.thenApply(QueryResponse::stream)
.get();
(5) var resources = Flowable.fromPublisher(FlowAdapters.toPublisher(publisher))
.filter(m -> m.getType() == MessageType.RESOURCE)
.map(Resource.class::cast)
.collect(Collectors.toList())
.blockingGet();
assertThat(resources).hasSizeGreaterThan(0);
(6) var resource = resources.get(0);
assertThat(resource.getLeafResourceId()).isEqualTo("resources/test-data-0.txt");
(7) var download = session.fetch(resource);
assertThat(download).isNotNull();
var actual = download.getInputStream();
var expected = Thread.currentThread()
.getContextClassLoader()
.getResourceAsStream("resources/test-data-0.txt");
(8) assertThat(actual).hasSameContentAs(expected);
}
}
- Creates a map of properties to be passed to the client. Here we are overriding the port for the Palisade Service and Filtered Resource Service.
- Uses the
ClientManager
to create aSession
from a Palisade URL. Here we are passing the user via the provided property map. If a user is also passed via a query parameter, this user in the query takes precedence. - A new Query is created by passing a query string and an optional map of properties.
- The query is executed. The request is submitted to Palisade at this point and a
CompleteableFuture
is returned asynchronously. Once Palisade has processed the request, the future will emit aPublisher
ofMessages
instances. - Convert the
java.util.current.Flow.Publisher
to an RxJavaFlowable
in order to apply filtering and retrieval into a collection ofResource
instances. - Use the first resource as a test and make sure it's not null
- Using the session we fetch the resource. A
Download
instance is returned. At this point the request has been sent and received from the Data Service. The download object provides access to anInputStream
. The data is not returned from the server until the input stream is first accessed. - Using AssertJ the two input streams are checked for equality.
Properties can be provided via 2 routes, the url and properties. The DefaultClient specifies that the attributes on the url (query) take precedence over those in the provided property map.
Name | Property | Query Parameter | Required | Description |
---|---|---|---|---|
User ID | service.userid | userid | YES | The user ID is is used as part of a query to the server |
Palisade Service Port | service.palisade.port | psport | NO | If provided will override any port provided on the Palisade URL provided to the session. |
Filtered Resource Service Port | service.filteredResource.port | wsport | NO | If provided will override any port provided on the Palisade URL provided to the session. |
Some properties can be overriden, but for testing.
Note: These properties are not available as a querystring parameter.
Name | Property | Default | Description |
---|---|---|---|
Palisade Service Path | service.palisade.path | palisade/api/registerDataRequest | the path which is appended to the Palisade service URL |
Filtered Resource Service Path | service.filteredResource.path | resource/%t | the path which is appended to the Filtered Resource Service URL |
Data Service Path | service.data.path | read/chunked | the path which is appended to the Data Service URL |
- Immutables - Java annotation processors to generate simple, safe and consistent value objects.
- Jackson - JSON for Java. Handles all the (de)serialisation of objects to/from the Palisade servers.
- Junit5 - Needs no introduction :)
- AssertJ - Excellent testing library
- Logback - Great logging library. Used for testing.
- Micronaut HTTP Server - Used for testing
There is currently a problem with Palisade regarding HTTP/2. For this reason, the client is currently limited to HTTP/1.1 and will not request a protocol upgrade.