Serialization

By default, a Cache<K,V> instance will serialize and deserialize objects using the JDK’s ObjectInputStream and ObjectOutputStream. Note that this requires the stored objects to implement Java’s Serializable interface.

To use custom serialization and deserialization methods, create implementations of the CacheSerializer and CacheDeserializer abstract classes.

The following sample uses AES-256 bit encryption and decryption to demonstrate a custom serialization and deserialization implementation. The object being serialized is a “SimpleObject” that contains a string, an int, and a long. The CacheSerializer implementation will turn a SimpleObject into a byte array and then encrypt that byte array using AES256-bit encryption. The CacheDeserializer implementation will decrypt a byte array, and then hydrate a SimpleObject instance.

package com.scaleout.client.samples.caching;

import com.scaleout.client.GridConnection;
import com.scaleout.client.caching.*;

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.security.Key;

public class EncryptionSample {
    // this is a sample 32-byte key, *** do not use this ***
    static final byte[] SECRET_KEY_VALUE = new byte[] {
            'T', 'H', 'I', 'S', 'I', 'S', 'N', 'O', 'T', 'A',
            'S', 'E', 'C', 'U', 'R', 'E', 'K', 'E', 'Y', '!',
            '!', '!', '!', '!', '!', '!', '!', '!', '!', '!', '!', '!'};

    static final String AES_ALGORITHM = "AES";

    static class SimpleObject {
        String simpleString;
        int simpleInt;
        long simpleLong;
        public SimpleObject(String str, int i, long l) {
            simpleString = str;
            simpleInt = i;
            simpleLong = l;
        }

        @Override
        public String toString() {
            return "SimpleObject{" +
                    "simpleString='" + simpleString + '\'' +
                    ", simpleInt=" + simpleInt +
                    ", simpleLong=" + simpleLong +
                    '}';
        }
    }

    /**
     * This class turns a SimpleObject into an AES-256 bit encrypted byte array
     */
    static class SimpleObjectEncryptingSerializer extends CacheSerializer<SimpleObject> {
        @Override
        public byte[] serialize(SimpleObject simpleObject) throws SerializationException {
            byte[] strAsBytes = simpleObject.simpleString.getBytes(StandardCharsets.UTF_8);
            byte[] intAsBytes = ByteBuffer.allocate(4).putInt(simpleObject.simpleInt).array();
            byte[] longAsBytes = ByteBuffer.allocate(8).putLong(simpleObject.simpleLong).array();
            byte[] strLengthAsBytes = ByteBuffer.allocate(4).putInt(strAsBytes.length).array();
            byte[] simpleObjAsBytes = ByteBuffer.allocate(strAsBytes.length + intAsBytes.length + longAsBytes.length + strLengthAsBytes.length)
                    .put(strLengthAsBytes)
                    .put(strAsBytes)
                    .put(intAsBytes)
                    .put(longAsBytes)
                    .array();
            // encrypt the byte[]
            try {
                Key key = new SecretKeySpec(SECRET_KEY_VALUE, AES_ALGORITHM);
                Cipher c = Cipher.getInstance(AES_ALGORITHM);
                c.init(Cipher.ENCRYPT_MODE, key);
                return c.doFinal(simpleObjAsBytes);
            } catch (Exception e) {
                throw new SerializationException(e.getMessage());
            }
        }
    }

    /**
     * This class decrypts an AES-256 bit encrypted byte array into a SimpleObject
     */
    static class SimpleObjectDecryptingDeserializer extends CacheDeserializer<SimpleObject> {
        @Override
        public SimpleObject deserialize(byte[] bytes) throws DeserializationException {
            try {
                // decrypt the byte[]
                Key key = new SecretKeySpec(SECRET_KEY_VALUE, AES_ALGORITHM);
                Cipher c = Cipher.getInstance(AES_ALGORITHM);
                c.init(Cipher.DECRYPT_MODE, key);

                // read each value from the decrypted byte[]
                String str = null;
                int i = 0;
                long l = 0L;
                ByteBuffer buffer = ByteBuffer.wrap(c.doFinal(bytes));
                int strLen = buffer.getInt();
                byte[] strAsBytes = new byte[strLen];
                buffer.get(strAsBytes);
                str = new String(strAsBytes, StandardCharsets.UTF_8);
                i = buffer.getInt();
                l = buffer.getLong();
                return new SimpleObject(str, i, l);
            } catch (Exception e) {
                throw new DeserializationException(e.getMessage());
            }

        }
    }

    public static void main(String[] args) throws Exception {
        GridConnection connection = GridConnection.connect("bootstrapGateways=server1:721,server2:721;");
        Cache<String, SimpleObject> cache = new CacheBuilder<String, SimpleObject>(connection, "example", String.class)
                // supply the custom serializer to the builder
                .customSerialization(new SimpleObjectEncryptingSerializer(), new SimpleObjectDecryptingDeserializer())
                .build();
        SimpleObject object = new SimpleObject("foo", 5, 23L);
        CacheResponse<String, SimpleObject> response = cache.add("my_simple_obj_key", object);
        if(response.getStatus() == RequestStatus.ObjectAdded) {
            System.out.println("Object encrypted with AES-256 bit encryption and added to cache.");
        } else {
            System.out.println("Unexpected request status: " + response.getStatus());
        }

        response = cache.read("my_simple_obj_key");
        if(response.getStatus() == RequestStatus.ObjectRetrieved) {
            System.out.println("Object retrieved from cache and decrypted.");
        } else {
            System.out.println("Unexpected request status: " + response.getStatus());
        }
    }
}