All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.azure.core.util.Context Maven / Gradle / Ivy

There is a newer version: 1.54.1
Show newest version
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

package com.azure.core.util;

import com.azure.core.annotation.Immutable;
import com.azure.core.util.logging.ClientLogger;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;

/**
 * {@code Context} offers a means of passing arbitrary data (key-value pairs) to pipeline policies.
 * Most applications do not need to pass arbitrary data to the pipeline and can pass {@code Context.NONE} or
 * {@code null}.
 * 

* Each context object is immutable. The {@link #addData(Object, Object)} method creates a new * {@code Context} object that refers to its parent, forming a linked list. */ @Immutable public class Context { private static final ClientLogger LOGGER = new ClientLogger(Context.class); private static final Context[] EMPTY_CHAIN = new Context[0]; // All fields must be immutable. // /** * Signifies that no data needs to be passed to the pipeline. */ public static final Context NONE = new Context(null, null, null, 0) { @Override public Optional getData(Object key) { if (key == null) { throw LOGGER.logExceptionAsError(new IllegalArgumentException("key cannot be null")); } return Optional.empty(); } @Override public Map getValues() { return Collections.emptyMap(); } @Override Context[] getContextChain() { return EMPTY_CHAIN; } }; private final Context parent; private final Object key; private final Object value; private final int contextCount; private Map valuesMap; /** * Constructs a new {@link Context} object. * *

Code samples

* * *
     * // Create an empty context having no data
     * Context emptyContext = Context.NONE;
     *
     * // OpenTelemetry context can be optionally passed using PARENT_TRACE_CONTEXT_KEY
     * // when OpenTelemetry context is not provided explicitly, ambient
     * // io.opentelemetry.context.Context.current() is used
     *
     * // Context contextWithSpan = new Context(PARENT_TRACE_CONTEXT_KEY, openTelemetryContext);
     * 
* * * @param key The key with which the specified value should be associated. * @param value The value to be associated with the specified key. * @throws IllegalArgumentException If {@code key} is {@code null}. */ public Context(Object key, Object value) { this.parent = null; this.key = Objects.requireNonNull(key, "'key' cannot be null."); this.value = value; this.contextCount = 1; } private Context(Context parent, Object key, Object value, int contextCount) { this.parent = parent; this.key = key; this.value = value; this.contextCount = contextCount; } Object getKey() { return key; } Object getValue() { return value; } /** * Adds a new immutable {@link Context} object with the specified key-value pair to * the existing {@link Context} chain. * *

Code samples

* * *
     * // Users can pass parent trace context information and additional metadata to attach to spans created by SDKs
     * // using the com.azure.core.util.Context object.
     * final String hostNameValue = "host-name-value";
     * final String entityPathValue = "entity-path-value";
     *
     * // TraceContext represents a tracing solution context type - io.opentelemetry.context.Context for OpenTelemetry.
     * final TraceContext parentContext = TraceContext.root();
     * Context parentSpanContext = new Context(PARENT_TRACE_CONTEXT_KEY, parentContext);
     *
     * // Add a new key value pair to the existing context object.
     * Context updatedContext = parentSpanContext.addData(HOST_NAME_KEY, hostNameValue)
     *     .addData(ENTITY_PATH_KEY, entityPathValue);
     *
     * // Both key values found on the same updated context object
     * System.out.printf("Hostname value: %s%n", updatedContext.getData(HOST_NAME_KEY).get());
     * System.out.printf("Entity Path value: %s%n", updatedContext.getData(ENTITY_PATH_KEY).get());
     * 
* * * @param key The key with which the specified value should be associated. * @param value The value to be associated with the specified key. * @return the new {@link Context} object containing the specified pair added to the set of pairs. * @throws IllegalArgumentException If {@code key} is {@code null}. */ public Context addData(Object key, Object value) { if (key == null) { throw LOGGER.logExceptionAsError(new IllegalArgumentException("key cannot be null")); } return new Context(this, key, value, contextCount + 1); } /** * Creates a new immutable {@link Context} object with all the keys and values provided by * the input {@link Map}. * *

Code samples

* * *
     * final String key1 = "Key1";
     * final String value1 = "first-value";
     * Map<Object, Object> keyValueMap = new HashMap<>();
     * keyValueMap.put(key1, value1);
     *
     * // Create a context using the provided key value pair map
     * Context keyValueContext = Context.of(keyValueMap);
     * System.out.printf("Key1 value %s%n", keyValueContext.getData(key1).get());
     * 
* * * @param keyValues The input key value pairs that will be added to this context. * @return Context object containing all the key-value pairs in the input map. * @throws IllegalArgumentException If {@code keyValues} is {@code null} or empty */ public static Context of(Map keyValues) { if (CoreUtils.isNullOrEmpty(keyValues)) { throw new IllegalArgumentException("Key value map cannot be null or empty"); } Context context = null; for (Map.Entry entry : keyValues.entrySet()) { if (context == null) { context = new Context(entry.getKey(), entry.getValue()); } else { context = context.addData(entry.getKey(), entry.getValue()); } } return context; } /** * Scans the linked-list of {@link Context} objects looking for one with the specified key. * Note that the first key found, i.e. the most recently added, will be returned. * *

Code samples

* * *
     * final String key1 = "Key1";
     * final String value1 = "first-value";
     *
     * // Create a context object with given key and value
     * Context context = new Context(key1, value1);
     *
     * // Look for the specified key in the returned context object
     * Optional<Object> optionalObject = context.getData(key1);
     * if (optionalObject.isPresent()) {
     *     System.out.printf("Key1 value: %s%n", optionalObject.get());
     * } else {
     *     System.out.println("Key1 does not exist or have data.");
     * }
     * 
* * * @param key The key to search for. * @return The value of the specified key if it exists. * @throws IllegalArgumentException If {@code key} is {@code null}. */ public Optional getData(Object key) { if (key == null) { throw LOGGER.logExceptionAsError(new IllegalArgumentException("key cannot be null")); } for (Context c = this; c != null; c = c.parent) { if (key.equals(c.key)) { return Optional.ofNullable(c.value); } // If the contextCount is 1 that means the next parent Context is the NONE Context. // Return Optional.empty now to prevent a meaningless check. if (c.contextCount == 1) { return Optional.empty(); } } // This should never be reached but is required by the compiler. return Optional.empty(); } /** * Scans the linked-list of {@link Context} objects populating a {@link Map} with the values of the context. * *

Code samples

* * *
     * final String key1 = "Key1";
     * final String value1 = "first-value";
     * final String key2 = "Key2";
     * final String value2 = "second-value";
     *
     * Context context = new Context(key1, value1)
     *     .addData(key2, value2);
     *
     * Map<Object, Object> contextValues = context.getValues();
     * if (contextValues.containsKey(key1)) {
     *     System.out.printf("Key1 value: %s%n", contextValues.get(key1));
     * } else {
     *     System.out.println("Key1 does not exist.");
     * }
     *
     * if (contextValues.containsKey(key2)) {
     *     System.out.printf("Key2 value: %s%n", contextValues.get(key2));
     * } else {
     *     System.out.println("Key2 does not exist.");
     * }
     * 
* * * @return A map containing all values of the context linked-list. */ public Map getValues() { if (valuesMap != null) { return valuesMap; } if (contextCount == 1) { this.valuesMap = Collections.singletonMap(key, value); return this.valuesMap; } Map map = new HashMap<>((int) Math.ceil(contextCount / 0.75F)); for (Context pointer = this; pointer != null; pointer = pointer.parent) { if (pointer.key != null) { map.putIfAbsent(pointer.key, pointer.value); } // If the contextCount is 1 that means the next parent Context is the NONE Context. // Break out of the loop to prevent a meaningless check. if (pointer.contextCount == 1) { break; } } this.valuesMap = Collections.unmodifiableMap(map); return this.valuesMap; } /** * Gets the {@link Context Contexts} in the chain of Contexts that this Context is the tail. * * @return The Contexts, in oldest to newest order, in the chain of Contexts that this Context is the tail. */ Context[] getContextChain() { Context[] chain = new Context[contextCount]; int chainPosition = contextCount - 1; for (Context pointer = this; pointer != null; pointer = pointer.parent) { chain[chainPosition--] = pointer; // If the contextCount is 1 that means the next parent Context is the NONE Context. // Break out of the loop to prevent a meaningless check. if (pointer.contextCount == 1) { break; } } return chain; } }