Library Version 12.1.3.0

Specific Binding
Prev | Chapter 8. Avro Bindings | Next

## Specific Binding

The `SpecificAvroBinding` interface represents values as instances of a generated Avro class which implements `SpecificRecord`. A single schema binding is created using `AvroCatalog.getSpecificBinding()`. A multiple schema binding is created using `AvroCatalog.getSpecificMultiBinding()`.

Avro-specific classes provide type safety and ease of use. Properties are accessed through getter/setter methods with parameters and return values of the correct type, as defined in the schema.

Further, when using Avro-specific classes, the client application does not need to be aware of the Avro schema at runtime. The generated class has an internal reference to its associated schema, and this schema is used by the binding, so the application does not need to explicitly supply a schema. Schemas are supplied at build time, when the source code for the specific class is generated.

The disadvantage to using specific bindings is that the set of specific classes is fixed at build time. Applications that wish to treat values generically, or dynamically based on the schema, should use a generic or JSON binding instead.

### Generating Specific Avro Classes

When you use a specific binding, you provide your schema to your application using a generated class. You can do this using the `org.apache.avro.compiler.specific.SchemaTask` tool.

An example Ant file that uses `SchemaTask` can be found in `KVHOME/examples/avro/generate-specific.xml`. It automatically downloads all library dependencies required to generate the Avro class, and then creates generated classes for all schema found in the local directory. The example assumes your schema is contained in flat text files that use the `avsc` suffix.

For example, suppose you had a schema defined in a file named `my-schema.avsc`. Then to generate an Avro-specific class, place `generate-specific.xml` and `my-schema.avsc` in the same directory and run:

    ant -f generate-specific.xml

After Ant downloads all the necessary library dependencies, it will create whatever generated classes are required by the contents of `my-schema.avsc`. The generated classes are named after the `name` field in the Avro schema. So if you had the (trivial) schema:

    {
        "type": "record",
        "name": "MyInfoString",
        "namespace": "avro",
        "fields": [
            {"name": "ID", "type": "int"}
        ]
    }

the generated class name would be `MyInfoString.java`. It contains getter and setter methods that you can use to access the fields in the schema. For example, a generated class for the above schema would contain the fields `MyInfoString.setID()` and `MyInfoString.getID()`.

### Using Avro-specific Bindings

To use a schema encapsulated in a generated Avro-specific class, you first provide the schema to the store as described in Managing Avro Schema in the Store.

After that, you access the schema using the generated class. To do this, you need handles to the store, an Avro catalog, and a specific binding:

    package avro;

    import oracle.kv.KVStore;
    import oracle.kv.ValueVersion;
    import oracle.kv.avro.AvroCatalog;
    import oracle.kv.avro.SpecificAvroBinding;

    ...

    private final KVStore store;
    private final AvroCatalog catalog;
    private final SpecificAvroBinding<MyInfoString> binding;

    ...

    /* store configuration and open omitted for brevity */

    ...

    catalog = store.getAvroCatalog();
    binding = catalog.getSpecificBinding(MyInfoString.class);

Having done that, you can use the binding to serialize and deserialize value objects that conform to the generated class' schema. For example, suppose you performed a store `get()` and retrieved a `ValueVersion`. Then you can access its information in the following way:

    final MyInfoString member;
    final int ID;

    member = binding.toObject(valueVersion.getValue());

    System.out.println("ID: " + member.getID());

To serialize the object for placement in the store, you use the generated class' setter method, as well as the binding's `toValue()` method:

    member.setID(2045);
    /* key creation omitted */
    store.put(key, binding.toValue(member));

### Using Multiple Avro-specific Bindings

If your client code needs to use multiple specific bindings, then you use `AvroCatalog.getSpecificMultiBinding()` to support the multiple schemas. For example, suppose your client code required the following two schemas:

    {
     "type": "record",
     "namespace": "avro",
     "name": "PersonInfo",
     "fields": [
       { "name": "first", "type": "string" },
       { "name": "last", "type": "string" },
       { "name": "age", "type": "int" }
     ]
    }

    {
     "type": "record",
     "namespace": "avro",
     "name": "AnimalInfo",
     "fields": [
       { "name": "species", "type": "string"},
       { "name": "name", "type": "string"},
       { "name": "age", "type": "int"}
     ]
    }

Then put `Avro.PersonInfo` in a file (call it `PersonSchema.avsc`) and `Avro.AnimalInfo` in a second file (`AnimalSchema.avsc`). Add these schemas to your store using the command line interface.

Next, generate your specific schema classes using:

    ant -f generate-specific.xml

as discussed in Generating Specific Avro Classes. This results in two generated classes: `PersonInfo.java` and `AnimalInfo.java`. To use these classes in your client code, you can use a single binding, which you create using `AvroCatalog.getSpecificMultiBinding()`. Note that to do this, you must use `org.apache.avro.specific.SpecificRecord`:

    package avro;

    import java.util.Arrays;

    import oracle.kv.Key;
    import oracle.kv.ValueVersion;
    import oracle.kv.avro.AvroCatalog;
    import oracle.kv.avro.SpecificAvroBinding;

    import org.apache.avro.specific.SpecificRecord;

    ...

    private final KVStore store;
    private final AvroCatalog catalog;
    private final SpecificAvroBinding<SpecificRecord> binding;

    /*
     * Store creation is skipped for the sake of brevity.
     */

    ...

    catalog = store.getAvroCatalog();
    binding = catalog.getSpecificMultiBinding();

You can then create `PersonInfo` and `AnimalInfo` objects, serialize them, and write them to the store, like this:

    final Key key1 = Key.createKey(Arrays.asList("person", "11"));
    final Key key2 = Key.createKey(Arrays.asList("animal", "11"));

    final PersonInfo pi = new PersonInfo();
    pi.setFirst("Jack");
    pi.setLast("Smith");
    pi.setAge(38);
    store.put(key1, binding.toValue(pi));

    final AnimalInfo ai = new AnimalInfo();
    ai.setSpecies("Dog");
    ai.setName("Bowzer");
    ai.setAge(6);
    store.put(key2, binding.toValue(ai));

Retrieval of the stored objects is performed with a normal store `get()`. However, to deserialize the retrieved objects you must identify the object's type. You can do this using either the Java `instanceof` operator, or by examining the schema's name, as follows:

    final ValueVersion vv = store.get(someKey);
    if (vv != null) {
        SpecificRecord sr = binding.toObject(vv.getValue());
        if (sr.getSchema().getFullName().equals("avro.PersonInfo")) {
            PersonInfo o = (PersonInfo) sr;
            /*
             * The object is now deserialized. You can access the object's
             * data using the specific class' getXXX() methods. For example,
             * o.getFirst().
             */
        } else {
            AnimalInfo o = (AnimalInfo) sr;
            /*
             * The object is now deserialized. You can access the object's
             * data using the specific class' getXXX() methods. For example,
             * o.getSpecies().
             */
        }

Prev | Up | Next
Generic Binding | Home | JSON Bindings