1 /* 2 * Copyright 2018, OpenCensus Authors 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package io.opencensus.trace; 18 19 import com.google.auto.value.AutoValue; 20 import io.opencensus.common.ExperimentalApi; 21 import io.opencensus.internal.Utils; 22 import java.util.ArrayList; 23 import java.util.Collections; 24 import java.util.List; 25 import javax.annotation.concurrent.Immutable; 26 27 /** 28 * Carries tracing-system specific context in a list of key-value pairs. TraceState allows different 29 * vendors propagate additional information and inter-operate with their legacy Id formats. 30 * 31 * <p>Implementation is optimized for a small list of key-value pairs. 32 * 33 * <p>Key is opaque string up to 256 characters printable. It MUST begin with a lowercase letter, 34 * and can only contain lowercase letters a-z, digits 0-9, underscores _, dashes -, asterisks *, and 35 * forward slashes /. 36 * 37 * <p>Value is opaque string up to 256 characters printable ASCII RFC0020 characters (i.e., the 38 * range 0x20 to 0x7E) except comma , and =. 39 * 40 * @since 0.16 41 */ 42 @Immutable 43 @AutoValue 44 @ExperimentalApi 45 public abstract class Tracestate { 46 private static final int KEY_MAX_SIZE = 256; 47 private static final int VALUE_MAX_SIZE = 256; 48 private static final int MAX_KEY_VALUE_PAIRS = 32; 49 50 /** 51 * Returns the value to which the specified key is mapped, or null if this map contains no mapping 52 * for the key. 53 * 54 * @param key with which the specified value is to be associated 55 * @return the value to which the specified key is mapped, or null if this map contains no mapping 56 * for the key. 57 * @since 0.16 58 */ 59 @javax.annotation.Nullable get(String key)60 public String get(String key) { 61 for (Entry entry : getEntries()) { 62 if (entry.getKey().equals(key)) { 63 return entry.getValue(); 64 } 65 } 66 return null; 67 } 68 69 /** 70 * Returns a {@link List} view of the mappings contained in this {@code TraceState}. 71 * 72 * @return a {@link List} view of the mappings contained in this {@code TraceState}. 73 * @since 0.16 74 */ getEntries()75 public abstract List<Entry> getEntries(); 76 77 /** 78 * Returns a {@code Builder} based on an empty {@code Tracestate}. 79 * 80 * @return a {@code Builder} based on an empty {@code Tracestate}. 81 * @since 0.16 82 */ builder()83 public static Builder builder() { 84 return new Builder(Builder.EMPTY); 85 } 86 87 /** 88 * Returns a {@code Builder} based on this {@code Tracestate}. 89 * 90 * @return a {@code Builder} based on this {@code Tracestate}. 91 * @since 0.16 92 */ toBuilder()93 public Builder toBuilder() { 94 return new Builder(this); 95 } 96 97 /** 98 * Builder class for {@link MessageEvent}. 99 * 100 * @since 0.16 101 */ 102 @ExperimentalApi 103 public static final class Builder { 104 private final Tracestate parent; 105 @javax.annotation.Nullable private ArrayList<Entry> entries; 106 107 // Needs to be in this class to avoid initialization deadlock because super class depends on 108 // subclass (the auto-value generate class). 109 private static final Tracestate EMPTY = create(Collections.<Entry>emptyList()); 110 Builder(Tracestate parent)111 private Builder(Tracestate parent) { 112 Utils.checkNotNull(parent, "parent"); 113 this.parent = parent; 114 this.entries = null; 115 } 116 117 /** 118 * Adds or updates the {@code Entry} that has the given {@code key} if it is present. The new 119 * {@code Entry} will always be added in the front of the list of entries. 120 * 121 * @param key the key for the {@code Entry} to be added. 122 * @param value the value for the {@code Entry} to be added. 123 * @return this. 124 * @since 0.16 125 */ 126 @SuppressWarnings("nullness") set(String key, String value)127 public Builder set(String key, String value) { 128 // Initially create the Entry to validate input. 129 Entry entry = Entry.create(key, value); 130 if (entries == null) { 131 // Copy entries from the parent. 132 entries = new ArrayList<Entry>(parent.getEntries()); 133 } 134 for (int i = 0; i < entries.size(); i++) { 135 if (entries.get(i).getKey().equals(entry.getKey())) { 136 entries.remove(i); 137 // Exit now because the entries list cannot contain duplicates. 138 break; 139 } 140 } 141 // Inserts the element at the front of this list. 142 entries.add(0, entry); 143 return this; 144 } 145 146 /** 147 * Removes the {@code Entry} that has the given {@code key} if it is present. 148 * 149 * @param key the key for the {@code Entry} to be removed. 150 * @return this. 151 * @since 0.16 152 */ 153 @SuppressWarnings("nullness") remove(String key)154 public Builder remove(String key) { 155 Utils.checkNotNull(key, "key"); 156 if (entries == null) { 157 // Copy entries from the parent. 158 entries = new ArrayList<Entry>(parent.getEntries()); 159 } 160 for (int i = 0; i < entries.size(); i++) { 161 if (entries.get(i).getKey().equals(key)) { 162 entries.remove(i); 163 // Exit now because the entries list cannot contain duplicates. 164 break; 165 } 166 } 167 return this; 168 } 169 170 /** 171 * Builds a TraceState by adding the entries to the parent in front of the key-value pairs list 172 * and removing duplicate entries. 173 * 174 * @return a TraceState with the new entries. 175 * @since 0.16 176 */ build()177 public Tracestate build() { 178 if (entries == null) { 179 return parent; 180 } 181 return Tracestate.create(entries); 182 } 183 } 184 185 /** 186 * Immutable key-value pair for {@code Tracestate}. 187 * 188 * @since 0.16 189 */ 190 @Immutable 191 @AutoValue 192 @ExperimentalApi 193 public abstract static class Entry { 194 /** 195 * Creates a new {@code Entry} for the {@code Tracestate}. 196 * 197 * @param key the Entry's key. 198 * @param value the Entry's value. 199 * @since 0.16 200 */ create(String key, String value)201 public static Entry create(String key, String value) { 202 Utils.checkNotNull(key, "key"); 203 Utils.checkNotNull(value, "value"); 204 Utils.checkArgument(validateKey(key), "Invalid key %s", key); 205 Utils.checkArgument(validateValue(value), "Invalid value %s", value); 206 return new AutoValue_Tracestate_Entry(key, value); 207 } 208 209 /** 210 * Returns the key {@code String}. 211 * 212 * @return the key {@code String}. 213 * @since 0.16 214 */ getKey()215 public abstract String getKey(); 216 217 /** 218 * Returns the value {@code String}. 219 * 220 * @return the value {@code String}. 221 * @since 0.16 222 */ getValue()223 public abstract String getValue(); 224 Entry()225 Entry() {} 226 } 227 228 // Key is opaque string up to 256 characters printable. It MUST begin with a lowercase letter, and 229 // can only contain lowercase letters a-z, digits 0-9, underscores _, dashes -, asterisks *, and 230 // forward slashes /. validateKey(String key)231 private static boolean validateKey(String key) { 232 if (key.length() > KEY_MAX_SIZE 233 || key.isEmpty() 234 || key.charAt(0) < 'a' 235 || key.charAt(0) > 'z') { 236 return false; 237 } 238 for (int i = 1; i < key.length(); i++) { 239 char c = key.charAt(i); 240 if (!(c >= 'a' && c <= 'z') 241 && !(c >= '0' && c <= '9') 242 && c != '_' 243 && c != '-' 244 && c != '*' 245 && c != '/') { 246 return false; 247 } 248 } 249 return true; 250 } 251 252 // Value is opaque string up to 256 characters printable ASCII RFC0020 characters (i.e., the range 253 // 0x20 to 0x7E) except comma , and =. validateValue(String value)254 private static boolean validateValue(String value) { 255 if (value.length() > VALUE_MAX_SIZE || value.charAt(value.length() - 1) == ' ' /* '\u0020' */) { 256 return false; 257 } 258 for (int i = 0; i < value.length(); i++) { 259 char c = value.charAt(i); 260 if (c == ',' || c == '=' || c < ' ' /* '\u0020' */ || c > '~' /* '\u007E' */) { 261 return false; 262 } 263 } 264 return true; 265 } 266 create(List<Entry> entries)267 private static Tracestate create(List<Entry> entries) { 268 Utils.checkState(entries.size() <= MAX_KEY_VALUE_PAIRS, "Invalid size"); 269 return new AutoValue_Tracestate(Collections.unmodifiableList(entries)); 270 } 271 Tracestate()272 Tracestate() {} 273 } 274