/* * Copyright 2016 KairosDB Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.kairosdb.datastore.cassandra; import me.prettyprint.cassandra.serializers.AbstractSerializer; import org.kairosdb.core.datapoints.LegacyDataPointFactory; import org.kairosdb.util.StringPool; import java.nio.ByteBuffer; import java.nio.charset.Charset; import java.util.SortedMap; public class DataPointsRowKeySerializer extends AbstractSerializer<DataPointsRowKey> { public static final Charset UTF8 = Charset.forName("UTF-8"); private StringPool m_stringPool; public DataPointsRowKeySerializer() { this(false); } public DataPointsRowKeySerializer(boolean poolStrings) { if (poolStrings) m_stringPool = new StringPool(); } /** If we are pooling strings the string from the pool will be returned. @param str string @return returns the string or what's in the string pool if using a string pool */ private String getString(String str) { if (m_stringPool != null) return (m_stringPool.getString(str)); else return (str); } @Override public ByteBuffer toByteBuffer(DataPointsRowKey dataPointsRowKey) { ByteBuffer buffer = dataPointsRowKey.getSerializedBuffer(); if (buffer != null) { buffer = buffer.duplicate(); } else { int size = 8; //size of timestamp byte[] metricName = dataPointsRowKey.getMetricName().getBytes(UTF8); size += metricName.length; size++; //Add one for null at end of string //if the data type is null then we are creating a row key for the old //format - this is for delete operations byte[] dataType = null; String dataTypeStr = dataPointsRowKey.getDataType(); if (!dataTypeStr.equals(LegacyDataPointFactory.DATASTORE_TYPE)) { dataType = dataPointsRowKey.getDataType().getBytes(UTF8); size += dataType.length; size += 2; //for null marker and datatype size } byte[] tagString = generateTagString(dataPointsRowKey.getTags()).getBytes(UTF8); size += tagString.length; buffer = ByteBuffer.allocate(size); buffer.put(metricName); //Metric name is put in this way for sorting purposes buffer.put((byte) 0x0); buffer.putLong(dataPointsRowKey.getTimestamp()); if (dataType != null) { if (dataPointsRowKey.isEndSearchKey()) buffer.put((byte) 0xFF); //Only used for serialization of end search keys else buffer.put((byte) 0x0); //Marks the beginning of datatype buffer.put((byte) dataType.length); buffer.put(dataType); } buffer.put(tagString); buffer.flip(); dataPointsRowKey.setSerializedBuffer(buffer); buffer = buffer.duplicate(); } return buffer; } private StringBuilder escapeAppend(StringBuilder sb, String value, char escape) { int startPos = 0; for (int i = 0; i < value.length(); i++) { char ch = value.charAt(i); if (ch == ':' || ch == '=') { sb.append(value, startPos, i); sb.append(escape).append(ch); startPos = i + 1; } } if (startPos <= value.length()) { sb.append(value, startPos, value.length()); } return sb; } private String unEscape(CharSequence source, int start, int end, char escape) { int startPos = start; StringBuilder sb = new StringBuilder(end - start); for (int i = start; i < end; i++) { char ch = source.charAt(i); if (ch == escape) { sb.append(source, startPos, i); i++; //Skip next char as it was escaped startPos = i; } } if (startPos <= end) { sb.append(source, startPos, end); } return sb.toString(); } private String generateTagString(SortedMap<String, String> tags) { StringBuilder sb = new StringBuilder(); for (String key : tags.keySet()) { //Escape tag names using : escapeAppend(sb, key, ':').append("="); //Escape tag values using = escapeAppend(sb, tags.get(key), '=').append(":"); } return (sb.toString()); } private void extractTags(DataPointsRowKey rowKey, String tagString) { int mark = 0; int position = 0; String tag = null; String value; for (position = 0; position < tagString.length(); position ++) { if (tag == null) { if (tagString.charAt(position) == '=') { tag = unEscape(tagString, mark, position, ':'); mark = position +1; } if (tagString.charAt(position) == ':') { position ++; } } else { if (tagString.charAt(position) == ':') { value = unEscape(tagString, mark, position, '='); mark = position +1; rowKey.addTag(getString(tag), getString(value)); tag = null; } if (tagString.charAt(position) == '=') { position ++; } } } } @Override public DataPointsRowKey fromByteBuffer(ByteBuffer byteBuffer) { int start = byteBuffer.position(); byteBuffer.mark(); //Find null while (byteBuffer.get() != 0x0); int nameSize = (byteBuffer.position() - start) -1; byteBuffer.reset(); byte[] metricName = new byte[nameSize]; byteBuffer.get(metricName); byteBuffer.get(); //Skip the null long timestamp = byteBuffer.getLong(); //Check for datatype marker which ia a null byteBuffer.mark(); //default to legacy type String dataType = LegacyDataPointFactory.DATASTORE_TYPE; if (byteBuffer.get() == 0x0) { int dtSize = byteBuffer.get(); byte[] dataTypeBytes = new byte[dtSize]; byteBuffer.get(dataTypeBytes); dataType = new String(dataTypeBytes, UTF8); } else { byteBuffer.reset(); } DataPointsRowKey rowKey = new DataPointsRowKey(getString(new String(metricName, UTF8)), timestamp, dataType); byte[] tagString = new byte[byteBuffer.remaining()]; byteBuffer.get(tagString); String tags = new String(tagString, UTF8); extractTags(rowKey, tags); return rowKey; } }