package org.sdnplatform.sync.internal.store; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map.Entry; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectReader; import com.fasterxml.jackson.databind.ObjectWriter; import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.dataformat.smile.SmileFactory; import com.fasterxml.jackson.core.type.TypeReference; import org.sdnplatform.sync.IClosableIterator; import org.sdnplatform.sync.IVersion; import org.sdnplatform.sync.Versioned; import org.sdnplatform.sync.error.SerializationException; import org.sdnplatform.sync.error.SyncException; import org.sdnplatform.sync.error.SyncRuntimeException; import org.sdnplatform.sync.internal.util.ByteArray; import org.sdnplatform.sync.internal.util.Pair; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * A store that will serialize and deserialize objects to JSON using Jackson */ public class JacksonStore<K, V> implements IStore<K, V> { protected static Logger logger = LoggerFactory.getLogger(JacksonStore.class); protected static final ObjectMapper mapper = new ObjectMapper(new SmileFactory()); static { mapper.configure(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS, true); } private final IStore<ByteArray, byte[]> delegate; private final ObjectWriter keyWriter; private final ObjectWriter valueWriter; private final ObjectReader keyReader; private final ObjectReader valueReader; private final boolean keyAsTree; private final boolean valueAsTree; public JacksonStore(IStore<ByteArray, byte[]> delegate, Class<K> keyClass, Class<V> valueClass) { super(); this.delegate = delegate; if (keyClass.isAssignableFrom(JsonNode.class)) { keyAsTree = true; this.keyWriter = null; this.keyReader = null; } else { keyAsTree = false; this.keyWriter = mapper.writerWithType(keyClass); this.keyReader = mapper.reader(keyClass); } if (valueClass.isAssignableFrom(JsonNode.class)) { valueAsTree = true; this.valueWriter = null; this.valueReader = null; } else { valueAsTree = false; this.valueWriter = mapper.writerWithType(valueClass); this.valueReader = mapper.reader(valueClass); } } public JacksonStore(IStore<ByteArray, byte[]> delegate, TypeReference<K> keyType, TypeReference<V> valueType) { super(); this.delegate = delegate; keyAsTree = false; valueAsTree = false; this.keyWriter = mapper.writerWithType(keyType); this.keyReader = mapper.reader(keyType); this.valueWriter = mapper.writerWithType(valueType); this.valueReader = mapper.reader(valueType); } // ************ // Store<K,V,T> // ************ @Override public List<Versioned<V>> get(K key) throws SyncException { ByteArray keybytes = getKeyBytes(key); List<Versioned<byte[]>> values = delegate.get(keybytes); return convertValues(values); } @Override public IClosableIterator<Entry<K, List<Versioned<V>>>> entries() { return new JacksonIterator(delegate.entries()); } @Override public void put(K key, Versioned<V> value) throws SyncException { ByteArray keybytes = getKeyBytes(key); byte[] valuebytes = value.getValue() != null ? getValueBytes(value.getValue()) : null; delegate.put(keybytes, new Versioned<byte[]>(valuebytes, value.getVersion())); } @Override public String getName() { return delegate.getName(); } @Override public void close() throws SyncException { delegate.close(); } @Override public List<IVersion> getVersions(K key) throws SyncException { ByteArray keybytes = getKeyBytes(key); return delegate.getVersions(keybytes); } // ************* // Local methods // ************* private ByteArray getKeyBytes(K key) throws SyncException { if (key == null) throw new IllegalArgumentException("Cannot get null key"); try { ByteArray k = null; if (keyAsTree) k = new ByteArray(mapper.writeValueAsBytes(key)); else k = new ByteArray(keyWriter.writeValueAsBytes(key)); if (logger.isTraceEnabled()) { logger.trace("Converted key {} to {}", key, k); } return k; } catch (Exception e) { throw new SerializationException(e); } } private byte[] getValueBytes(V value) throws SyncException { try { byte[] v = null; if (valueAsTree) v = mapper.writeValueAsBytes(value); else v = valueWriter.writeValueAsBytes(value); if (logger.isTraceEnabled()) { logger.trace("Converted value {} to {}", value, Arrays.toString(v)); } return v; } catch (Exception e) { throw new SerializationException(e); } } @SuppressWarnings("unchecked") private V getValueObject(byte[] value) throws SyncException { try { if (value == null) return null; if (valueAsTree) return (V)mapper.readTree(value); else return valueReader.readValue(value); } catch (Exception e) { throw new SerializationException(e); } } @SuppressWarnings("unchecked") private K getKeyObject(ByteArray key) throws SyncException { try { if (keyAsTree) return (K)mapper.readTree(key.get()); else return keyReader.readValue(key.get()); } catch (Exception e) { throw new SerializationException(e); } } private List<Versioned<V>> convertValues(List<Versioned<byte[]>> values) throws SyncException { if (values != null) { List<Versioned<V>> objectvalues = new ArrayList<Versioned<V>>(values.size()); for (Versioned<byte[]> vb : values) { objectvalues.add(new Versioned<V>(getValueObject(vb.getValue()), vb.getVersion())); } return objectvalues; } return null; } private class JacksonIterator implements IClosableIterator<Entry<K, List<Versioned<V>>>> { IClosableIterator<Entry<ByteArray, List<Versioned<byte[]>>>> delegate; public JacksonIterator(IClosableIterator<Entry<ByteArray, List<Versioned<byte[]>>>> delegate) { super(); this.delegate = delegate; } @Override public boolean hasNext() { return delegate.hasNext(); } @Override public Entry<K, List<Versioned<V>>> next() { Entry<ByteArray, List<Versioned<byte[]>>> n = delegate.next(); try { return new Pair<K, List<Versioned<V>>>(getKeyObject(n.getKey()), convertValues(n.getValue())); } catch (SyncException e) { throw new SyncRuntimeException("Failed to construct next value", e); } } @Override public void remove() { delegate.remove(); } @Override public void close() { delegate.close(); } } }