Java tutorial
/* * Copyright 2016-17, OpenCensus 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 io.opencensus.implcore.tags.propagation; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Charsets; import com.google.common.io.ByteArrayDataOutput; import com.google.common.io.ByteStreams; import io.opencensus.implcore.internal.VarInt; import io.opencensus.implcore.tags.TagMapImpl; import io.opencensus.implcore.tags.TagValueWithMetadata; import io.opencensus.tags.InternalUtils; import io.opencensus.tags.Tag; import io.opencensus.tags.TagContext; import io.opencensus.tags.TagKey; import io.opencensus.tags.TagMetadata; import io.opencensus.tags.TagMetadata.TagTtl; import io.opencensus.tags.TagValue; import io.opencensus.tags.propagation.TagContextDeserializationException; import io.opencensus.tags.propagation.TagContextSerializationException; import java.nio.BufferUnderflowException; import java.nio.ByteBuffer; import java.util.HashMap; import java.util.Iterator; import java.util.Map; /** * Methods for serializing and deserializing {@link TagContext}s. * * <p>The format defined in this class is shared across all implementations of OpenCensus. It allows * tags to propagate across requests. * * <p>OpenCensus tag context encoding: * * <ul> * <li>Tags are encoded in single byte sequence. The version 0 format is: * <li>{@code <version_id><encoded_tags>} * <li>{@code <version_id> == a single byte, value 0} * <li>{@code <encoded_tags> == (<tag_field_id><tag_encoding>)*} * <ul> * <li>{@code <tag_field_id>} == a single byte, value 0 * <li>{@code <tag_encoding>}: * <ul> * <li>{@code <tag_key_len><tag_key><tag_val_len><tag_val>} * <ul> * <li>{@code <tag_key_len>} == varint encoded integer * <li>{@code <tag_key>} == tag_key_len bytes comprising tag key name * <li>{@code <tag_val_len>} == varint encoded integer * <li>{@code <tag_val>} == tag_val_len bytes comprising UTF-8 string * </ul> * </ul> * </ul> * </ul> */ final class SerializationUtils { private static final TagMetadata METADATA_UNLIMITED_PROPAGATION = TagMetadata .create(TagTtl.UNLIMITED_PROPAGATION); private SerializationUtils() { } @VisibleForTesting static final int VERSION_ID = 0; @VisibleForTesting static final int TAG_FIELD_ID = 0; // This size limit only applies to the bytes representing tag keys and values. @VisibleForTesting static final int TAGCONTEXT_SERIALIZED_SIZE_LIMIT = 8192; // Serializes a TagContext to the on-the-wire format. // Encoded tags are of the form: <version_id><encoded_tags> static byte[] serializeBinary(TagContext tags) throws TagContextSerializationException { // Use a ByteArrayDataOutput to avoid needing to handle IOExceptions. final ByteArrayDataOutput byteArrayDataOutput = ByteStreams.newDataOutput(); byteArrayDataOutput.write(VERSION_ID); int totalChars = 0; // Here chars are equivalent to bytes, since we're using ascii chars. for (Iterator<Tag> i = InternalUtils.getTags(tags); i.hasNext();) { Tag tag = i.next(); if (TagTtl.NO_PROPAGATION.equals(tag.getTagMetadata().getTagTtl())) { continue; } totalChars += tag.getKey().getName().length(); totalChars += tag.getValue().asString().length(); encodeTag(tag, byteArrayDataOutput); } if (totalChars > TAGCONTEXT_SERIALIZED_SIZE_LIMIT) { throw new TagContextSerializationException( "Size of TagContext exceeds the maximum serialized size " + TAGCONTEXT_SERIALIZED_SIZE_LIMIT); } return byteArrayDataOutput.toByteArray(); } // Deserializes input to TagContext based on the binary format standard. // The encoded tags are of the form: <version_id><encoded_tags> static TagMapImpl deserializeBinary(byte[] bytes) throws TagContextDeserializationException { try { if (bytes.length == 0) { // Does not allow empty byte array. throw new TagContextDeserializationException("Input byte[] can not be empty."); } ByteBuffer buffer = ByteBuffer.wrap(bytes).asReadOnlyBuffer(); int versionId = buffer.get(); if (versionId > VERSION_ID || versionId < 0) { throw new TagContextDeserializationException( "Wrong Version ID: " + versionId + ". Currently supports version up to: " + VERSION_ID); } return new TagMapImpl(parseTags(buffer)); } catch (BufferUnderflowException exn) { throw new TagContextDeserializationException(exn.toString()); // byte array format error. } } private static Map<TagKey, TagValueWithMetadata> parseTags(ByteBuffer buffer) throws TagContextDeserializationException { Map<TagKey, TagValueWithMetadata> tags = new HashMap<TagKey, TagValueWithMetadata>(); int limit = buffer.limit(); int totalChars = 0; // Here chars are equivalent to bytes, since we're using ascii chars. while (buffer.position() < limit) { int type = buffer.get(); if (type == TAG_FIELD_ID) { TagKey key = createTagKey(decodeString(buffer)); TagValue val = createTagValue(key, decodeString(buffer)); totalChars += key.getName().length(); totalChars += val.asString().length(); tags.put(key, TagValueWithMetadata.create(val, METADATA_UNLIMITED_PROPAGATION)); } else { // Stop parsing at the first unknown field ID, since there is no way to know its length. // TODO(sebright): Consider storing the rest of the byte array in the TagContext. break; } } if (totalChars > TAGCONTEXT_SERIALIZED_SIZE_LIMIT) { throw new TagContextDeserializationException( "Size of TagContext exceeds the maximum serialized size " + TAGCONTEXT_SERIALIZED_SIZE_LIMIT); } return tags; } // TODO(sebright): Consider exposing a TagKey name validation method to avoid needing to catch an // IllegalArgumentException here. private static final TagKey createTagKey(String name) throws TagContextDeserializationException { try { return TagKey.create(name); } catch (IllegalArgumentException e) { throw new TagContextDeserializationException("Invalid tag key: " + name, e); } } // TODO(sebright): Consider exposing a TagValue validation method to avoid needing to catch // an IllegalArgumentException here. private static final TagValue createTagValue(TagKey key, String value) throws TagContextDeserializationException { try { return TagValue.create(value); } catch (IllegalArgumentException e) { throw new TagContextDeserializationException("Invalid tag value for key " + key + ": " + value, e); } } private static final void encodeTag(Tag tag, ByteArrayDataOutput byteArrayDataOutput) { byteArrayDataOutput.write(TAG_FIELD_ID); encodeString(tag.getKey().getName(), byteArrayDataOutput); encodeString(tag.getValue().asString(), byteArrayDataOutput); } private static final void encodeString(String input, ByteArrayDataOutput byteArrayDataOutput) { putVarInt(input.length(), byteArrayDataOutput); byteArrayDataOutput.write(input.getBytes(Charsets.UTF_8)); } private static final void putVarInt(int input, ByteArrayDataOutput byteArrayDataOutput) { byte[] output = new byte[VarInt.varIntSize(input)]; VarInt.putVarInt(input, output, 0); byteArrayDataOutput.write(output); } private static final String decodeString(ByteBuffer buffer) { int length = VarInt.getVarInt(buffer); StringBuilder builder = new StringBuilder(); for (int i = 0; i < length; i++) { builder.append((char) buffer.get()); } return builder.toString(); } }