Home | History | Annotate | Download | only in trace
      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
     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    */
     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    */
     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    */
     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 
    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")
    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")
    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      */
    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      */
    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      */
    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      */
    223     public abstract String getValue();
    224 
    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 /.
    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 =.
    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 
    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 
    272   Tracestate() {}
    273 }
    274