• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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