Wednesday, 27 June 2018

Making Java objects queryable by Infinispan remote clients

The following is a common question amongst Infinispan community users:
How do I make my Java objects queryable by remote clients? 

Annotation Method


The simplest way is to take advantage Infinispan Protostream annotations to mark your objects queryable and decide how each object field should be indexed. Example:

import org.infinispan.protostream.annotations.ProtoDoc;
import org.infinispan.protostream.annotations.ProtoField;
@ProtoDoc("@Indexed")
public class Pokemon {
@ProtoDoc("@Field")
@ProtoField(number = 10)
String name;
@ProtoDoc("@Field")
@ProtoField(number = 20)
String type1;
}
Then, the ProtoSchemaBuilder can inspect the annotated class and derive a Google Protocol Buffers schema file from it. Example:

RemoteCacheManager remote = …
SerializationContext serialCtx =
ProtoStreamMarshaller.getSerializationContext(remote);
ProtoSchemaBuilder protoSchemaBuilder = new ProtoSchemaBuilder();
String protoFile = protoSchemaBuilder
.fileName(fileName)
.addClass(Pokemon.class)
.packageName("pokemons")
.build(serialCtx);
Finally, the schema file needs to be registered in the “___protobuf_metadata” cache:

RemoteCache<String, String> metadataCache =
remote.getCache(PROTOBUF_METADATA_CACHE_NAME);
metadataCache.put(fileName, protoFile);
String filesWithErrors = metadataCache.get(ERRORS_KEY_SUFFIX);
if (filesWithErrors != null)
throw new AssertionError("Error in proto file(s): " + filesWithErrors);
else
System.out.println("Added schema file: " + fileName);
Although this is by far the easiest way to make your Java objects queryable, this method might not always be viable. For example, you might not be able to modify the Java object classes to add the annotations. For such use cases, a more verbose method is available that does not require modifying the source code of the Java object.

Plain Object Method


For example, given this Java object:

public class CryptoCurrency {
public final String description;
public final Integer rank;
....
}
view raw plain-pojo.java hosted with ❤ by GitHub
A Protocol Buffers schema must be defined where comments are used to define the object as queryable and decide how each field is indexed:

package crypto;
/**
* @Indexed
*/
message CryptoCurrency {
/**
* @Field
*/
required string description = 10;
/**
* @Field
*/
required uint32 rank = 20;
}
view raw crypto.proto hosted with ❤ by GitHub
This method also requires a Protostream message marshaller to be defined which specifies how each field is serialized/deserialized:

import org.infinispan.protostream.MessageMarshaller;
public class CryptoCurrencyMarshaller implements MessageMarshaller<CryptoCurrency> {
@Override
public CryptoCurrency readFrom(ProtoStreamReader reader) throws IOException {
String description = reader.readString("description");
Integer rank = reader.readInt("rank");
return new CryptoCurrency(description, rank);
}
@Override
public void writeTo(ProtoStreamWriter writer, CryptoCurrency obj) throws IOException {
writer.writeString("description", obj.description);
writer.writeInt("rank", obj.rank);
}
...
}
view raw marshaller.java hosted with ❤ by GitHub
This method still requires the Protocol Buffers schema to be registered remotely, but on top of that, the schema and marshaller need to be registered in the client:

SerializationContext serialCtx = ...
String protoFile = read(CryptoCurrency.class.getResourceAsStream("/crypto.proto"));
metadataCache.put("crypto.proto", protoFile);
...
serialCtx.registerProtoFiles(FileDescriptorSource.fromResources("/crypto.proto"));
serialCtx.registerMarshaller(new CryptoCurrencyMarshaller());
Clearly, this second method is a lot more verbose and more laborious when refactoring. If any changes are made to the Java object, the marshaller and Protocol Buffer schema need to also be changed accordingly. This is done automatically in the first method.

Both methods are demonstrated in full in the queryable-pojos demo.

Cheers
Galder

No comments:

Post a Comment