package com.kryptnostic.kodex.v1.marshalling; import java.io.IOException; import java.nio.ByteBuffer; import java.util.concurrent.TimeUnit; import java.util.zip.DataFormatException; import java.util.zip.Deflater; import java.util.zip.Inflater; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.base.Preconditions; import com.google.common.base.Stopwatch; import com.kryptnostic.kodex.v1.crypto.keys.KodexMarshaller; import com.kryptnostic.kodex.v1.serialization.jackson.KodexObjectMapperFactory; public class JacksonKodexMarshaller<T> implements KodexMarshaller<T> { private final Class<T> clazz; private final ObjectMapper mapper; protected static final int INTEGER_BYTES = Integer.SIZE / Byte.SIZE; private static final Logger logger = LoggerFactory.getLogger( JacksonKodexMarshaller.class ); public JacksonKodexMarshaller( Class<T> clazz ) { this( clazz, KodexObjectMapperFactory.getObjectMapper() ); } public JacksonKodexMarshaller( Class<T> clazz, ObjectMapper mapper ) { this.clazz = clazz; this.mapper = mapper; } @Override public T fromBytes( byte[] bytes ) throws IOException { Stopwatch watch = Stopwatch.createStarted(); final Inflater inflater = new Inflater(); ByteBuffer in = ByteBuffer.wrap( bytes ); int uncompressedLength = in.getInt(); byte[] compressedBytes = new byte[ bytes.length - INTEGER_BYTES ]; byte[] uncompressedBytes = new byte[ uncompressedLength ]; in.get( compressedBytes ); inflater.setInput( compressedBytes ); int resultingLength = 0; try { resultingLength = inflater.inflate( uncompressedBytes ); } catch ( DataFormatException e ) { throw new IOException( e ); } Preconditions.checkState( resultingLength == uncompressedLength, "Expected length and decompressed length do not match." ); logger.trace( "[PROFILE] Inflating {} bytes to {} bytes took {} ms", compressedBytes.length, uncompressedLength, watch.elapsed( TimeUnit.MILLISECONDS ) ); watch.reset().start(); T obj = mapper.readValue( uncompressedBytes, clazz ); logger.trace( "[PROFILE] Unmarshalling took {} ms", watch.elapsed( TimeUnit.MILLISECONDS ) ); return obj; } @Override public byte[] toBytes( T object ) throws IOException { final Deflater deflater = new Deflater( Deflater.BEST_COMPRESSION ); byte[] input = mapper.writeValueAsBytes( object ); byte[] output = new byte[ input.length << 1 ]; deflater.setInput( input ); deflater.finish(); int compressedBytes = deflater.deflate( output, 0, output.length, Deflater.FULL_FLUSH ); ByteBuffer o = ByteBuffer.allocate( INTEGER_BYTES + compressedBytes ); o.putInt( input.length ); o.put( output, 0, compressedBytes ); return o.array(); } }