/* This file is part of VoltDB. * Copyright (C) 2008-2017 VoltDB Inc. * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ package oneshotkv; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.nio.ByteBuffer; import java.util.Random; import java.util.zip.GZIPInputStream; import java.util.zip.GZIPOutputStream; public class PayloadProcessor { public static class Pair { public final String Key; private final byte[] RawValue; private final byte[] StoreValue; protected Pair(String key, byte[] rawValue, byte[] storeValue) { this.Key = key; this.RawValue = rawValue; this.StoreValue = storeValue; } public long getStoreValueLength() { if (this.StoreValue == null) return this.RawValue.length; return this.StoreValue.length; } public long getRawValueLength() { return this.RawValue.length; } public byte[] getStoreValue() { if (this.StoreValue == null) return this.RawValue; return this.StoreValue; } public byte[] getRawValue() { return this.RawValue; } } private final int KeySize; private final int MinValueSize; private final int MaxValueSize; private final int PoolSize; private final boolean UseCompression; public final String KeyFormat; private final int Entropy; private final Random Rand = new Random(0); /* * Volt deals with 2 megs at the most so 4 megabytes of entropy is plenty */ private final ByteBuffer entropyBytesGlobal = ByteBuffer.allocate (1024 * 1024 * 4); /* * Create a thread local copy of the buffer because multiple threads will be manipulating the position pointers. * Give each thread it's own wrapper around the global byte buffer. */ private final ThreadLocal<ByteBuffer> entropyBytes = new ThreadLocal <ByteBuffer> () { @Override protected ByteBuffer initialValue() { return entropyBytesGlobal.duplicate(); } }; public PayloadProcessor( int keySize, int minValueSize, int maxValueSize, int entropy, int poolSize, boolean useCompression) { this.KeySize = keySize; this.MinValueSize = minValueSize; this.MaxValueSize = maxValueSize; this.PoolSize = poolSize; this.UseCompression = useCompression; this.Entropy = entropy; if (entropy < 1 || entropy > 127) { throw new IllegalArgumentException("Entropy must be a number between 1 and 127"); } while (entropyBytes.get().hasRemaining()) { entropyBytes.get().put((byte)(Rand.nextInt(127) % Entropy)); } // Get the base key format string used to generate keys this.KeyFormat = "K%1$" + String.valueOf(this.KeySize-1) + "s"; } public Pair generateForStore() { final String key = String.format(this.KeyFormat, this.Rand.nextInt(this.PoolSize)); final byte[] rawValue = new byte[this.MinValueSize+this.Rand.nextInt(this.MaxValueSize-this.MinValueSize+1)]; if (entropyBytes.get().remaining() > rawValue.length){ entropyBytes.get().get(rawValue); } else { entropyBytes.get().position(0); entropyBytes.get().get(rawValue); } if (this.UseCompression) return new Pair(key, rawValue, gzip(rawValue)); else return new Pair(key, rawValue, null); } public String [] generateAdjecentKeys( int count) { if (count < 0) return new String [0]; String [] keys = new String[count]; int start = this.Rand.nextInt(this.PoolSize); for (int i = 0; i < count; ++i) { int keyNo = (start + i) % this.PoolSize; keys[i] = String.format(this.KeyFormat, keyNo); } return keys; } synchronized public String generateRandomKeyForRetrieval() { return String.format(this.KeyFormat, this.Rand.nextInt(this.PoolSize)); } public Pair retrieveFromStore(String key, byte[] storeValue) { if (this.UseCompression) return new Pair(key, gunzip(storeValue), storeValue); else return new Pair(key, storeValue, null); } private static byte[] gzip(byte[] bytes) { try { ByteArrayOutputStream baos = new ByteArrayOutputStream((int)(bytes.length * 0.7)); GZIPOutputStream gzos = new GZIPOutputStream(baos); gzos.write(bytes); gzos.close(); return baos.toByteArray(); } catch (Exception e) { e.printStackTrace(); throw new RuntimeException(e); } } private static byte[] gunzip(byte bytes[]) { // Check to see if it's gzip-compressed // GZIP Magic Two-Byte Number: 0x8b1f (35615) if( (bytes != null) && (bytes.length >= 4) ) { int head = (bytes[0] & 0xff) | ((bytes[1] << 8) & 0xff00); if( GZIPInputStream.GZIP_MAGIC == head ) { ByteArrayInputStream bais = null; GZIPInputStream gzis = null; ByteArrayOutputStream baos = null; byte[] buffer = new byte[2048]; int length = 0; try { baos = new ByteArrayOutputStream(); bais = new ByteArrayInputStream( bytes ); gzis = new GZIPInputStream( bais ); while( ( length = gzis.read( buffer ) ) >= 0 ) baos.write(buffer,0,length); // No error? Get new bytes. bytes = baos.toByteArray(); } // end try catch( java.io.IOException e ) { e.printStackTrace(); // Just return originally-decoded bytes } // end catch finally { try{ baos.close(); } catch( Exception e ){} try{ gzis.close(); } catch( Exception e ){} try{ bais.close(); } catch( Exception e ){} } // end finally } // end if: gzipped } // end if: bytes.length >= 2 return bytes; } }