Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
package org.elasticsearch.common.util.concurrent;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.elasticsearch.action.support.ContextPreservingActionListener;
import org.elasticsearch.client.OriginSettingClient;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.io.stream.Writeable;
import org.elasticsearch.common.settings.Setting;
import org.elasticsearch.common.settings.Setting.Property;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.core.Tuple;
import org.elasticsearch.http.HttpTransportSettings;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collector;
import java.util.stream.Stream;
import static org.elasticsearch.http.HttpTransportSettings.SETTING_HTTP_MAX_WARNING_HEADER_COUNT;
import static org.elasticsearch.http.HttpTransportSettings.SETTING_HTTP_MAX_WARNING_HEADER_SIZE;
import static org.elasticsearch.tasks.Task.HEADERS_TO_COPY;
/**
* A ThreadContext is a map of string headers and a transient map of keyed objects that are associated with
* a thread. It allows to store and retrieve header information across method calls, network calls as well as threads spawned from a
* thread that has a {@link ThreadContext} associated with. Threads spawned from a {@link org.elasticsearch.threadpool.ThreadPool}
* have out of the box support for {@link ThreadContext} and all threads spawned will inherit the {@link ThreadContext} from the thread
* that it is forking from.". Network calls will also preserve the senders headers automatically.
*
* Consumers of ThreadContext usually don't need to interact with adding or stashing contexts. Every elasticsearch thread is managed by
* a thread pool or executor being responsible for stashing and restoring the threads context. For instance if a network request is
* received, all headers are deserialized from the network and directly added as the headers of the threads {@link ThreadContext}
* (see {@link #readHeaders(StreamInput)}. In order to not modify the context that is currently active on this thread the network code
* uses a try/with pattern to stash it's current context, read headers into a fresh one and once the request is handled or a handler thread
* is forked (which in turn inherits the context) it restores the previous context. For instance:
*
*
* // current context is stashed and replaced with a default context
* try (StoredContext context = threadContext.stashContext()) {
* threadContext.readHeaders(in); // read headers into current context
* if (fork) {
* threadPool.execute(() -> request.handle()); // inherits context
* } else {
* request.handle();
* }
* }
* // previous context is restored on StoredContext#close()
*
*
*/
public final class ThreadContext implements Writeable {
public static final String PREFIX = "request.headers";
public static final Setting DEFAULT_HEADERS_SETTING = Setting.groupSetting(PREFIX + ".", Property.NodeScope);
/**
* Name for the {@link #stashWithOrigin origin} attribute.
*/
public static final String ACTION_ORIGIN_TRANSIENT_NAME = "action.origin";
private static final Logger logger = LogManager.getLogger(ThreadContext.class);
private static final ThreadContextStruct DEFAULT_CONTEXT = new ThreadContextStruct();
private final Map defaultHeader;
private final ThreadLocal threadLocal;
private final int maxWarningHeaderCount;
private final long maxWarningHeaderSize;
/**
* Creates a new ThreadContext instance
* @param settings the settings to read the default request headers from
*/
public ThreadContext(Settings settings) {
this.defaultHeader = buildDefaultHeaders(settings);
this.threadLocal = ThreadLocal.withInitial(() -> DEFAULT_CONTEXT);
this.maxWarningHeaderCount = SETTING_HTTP_MAX_WARNING_HEADER_COUNT.get(settings);
this.maxWarningHeaderSize = SETTING_HTTP_MAX_WARNING_HEADER_SIZE.get(settings).getBytes();
}
/**
* Removes the current context and resets a default context. The removed context can be
* restored by closing the returned {@link StoredContext}.
*/
public StoredContext stashContext() {
final ThreadContextStruct context = threadLocal.get();
/**
* X-Opaque-ID should be preserved in a threadContext in order to propagate this across threads.
* This is needed so the DeprecationLogger in another thread can see the value of X-Opaque-ID provided by a user.
* The same is applied to Task.TRACE_ID.
* Otherwise when context is stash, it should be empty.
*/
if (HEADERS_TO_COPY.stream().anyMatch(header -> context.requestHeaders.containsKey(header))) {
Map map = headers(context, HEADERS_TO_COPY);
ThreadContextStruct threadContextStruct = DEFAULT_CONTEXT.putHeaders(map);
threadLocal.set(threadContextStruct);
} else {
threadLocal.set(DEFAULT_CONTEXT);
}
return () -> {
// If the node and thus the threadLocal get closed while this task
// is still executing, we don't want this runnable to fail with an
// uncaught exception
threadLocal.set(context);
};
}
private Map headers(ThreadContextStruct context, Set headersToCopy) {
Map map = new HashMap<>(headersToCopy.size(), 1);
for (String header : headersToCopy) {
if (context.requestHeaders.containsKey(header)) {
map.put(header, context.requestHeaders.get(header));
}
}
return map;
}
/**
* Captures the current thread context as writeable, allowing it to be serialized out later
*/
public Writeable captureAsWriteable() {
final ThreadContextStruct context = threadLocal.get();
return out -> context.writeTo(out, defaultHeader);
}
/**
* Removes the current context and resets a default context marked with as
* originating from the supplied string. The removed context can be
* restored by closing the returned {@link StoredContext}. Callers should
* be careful to save the current context before calling this method and
* restore it any listeners, likely with
* {@link ContextPreservingActionListener}. Use {@link OriginSettingClient}
* which can be used to do this automatically.
*
* Without security the origin is ignored, but security uses it to authorize
* actions that are made up of many sub-actions. These actions call
* {@link #stashWithOrigin} before performing on behalf of a user that
* should be allowed even if the user doesn't have permission to perform
* those actions on their own.
*
* For example, a user might not have permission to GET from the tasks index
* but the tasks API will perform a get on their behalf using this method
* if it can't find the task in memory.
*/
public StoredContext stashWithOrigin(String origin) {
final ThreadContext.StoredContext storedContext = stashContext();
putTransient(ACTION_ORIGIN_TRANSIENT_NAME, origin);
return storedContext;
}
/**
* Removes the current context and resets a new context that contains a merge of the current headers and the given headers.
* The removed context can be restored when closing the returned {@link StoredContext}. The merge strategy is that headers
* that are already existing are preserved unless they are defaults.
*/
public StoredContext stashAndMergeHeaders(Map headers) {
final ThreadContextStruct context = threadLocal.get();
Map newHeader = new HashMap<>(headers);
newHeader.putAll(context.requestHeaders);
threadLocal.set(DEFAULT_CONTEXT.putHeaders(newHeader));
return () -> threadLocal.set(context);
}
/**
* Just like {@link #stashContext()} but no default context is set.
* @param preserveResponseHeaders if set to true the response headers of the restore thread will be preserved.
*/
public StoredContext newStoredContext(boolean preserveResponseHeaders) {
return newStoredContext(preserveResponseHeaders, Collections.emptyList());
}
/**
* Just like {@link #stashContext()} but no default context is set. Instead, the {@code transientHeadersToClear} argument can be used
* to clear specific transient headers in the new context. All headers (with the possible exception of {@code responseHeaders}) are
* restored by closing the returned {@link StoredContext}.
*
* @param preserveResponseHeaders if set to true the response headers of the restore thread will be preserved.
*/
public StoredContext newStoredContext(boolean preserveResponseHeaders, Collection transientHeadersToClear) {
final ThreadContextStruct originalContext = threadLocal.get();
// clear specific transient headers from the current context
Map newTransientHeaders = null;
for (String transientHeaderToClear : transientHeadersToClear) {
if (originalContext.transientHeaders.containsKey(transientHeaderToClear)) {
if (newTransientHeaders == null) {
newTransientHeaders = new HashMap<>(originalContext.transientHeaders);
}
newTransientHeaders.remove(transientHeaderToClear);
}
}
if (newTransientHeaders != null) {
ThreadContextStruct threadContextStruct = new ThreadContextStruct(
originalContext.requestHeaders,
originalContext.responseHeaders,
newTransientHeaders,
originalContext.isSystemContext,
originalContext.warningHeadersSize
);
threadLocal.set(threadContextStruct);
}
// this is the context when this method returns
final ThreadContextStruct newContext = threadLocal.get();
return () -> {
if (preserveResponseHeaders && threadLocal.get() != newContext) {
threadLocal.set(originalContext.putResponseHeaders(threadLocal.get().responseHeaders));
} else {
threadLocal.set(originalContext);
}
};
}
/**
* Returns a supplier that gathers a {@link #newStoredContext(boolean)} and restores it once the
* returned supplier is invoked. The context returned from the supplier is a stored version of the
* suppliers callers context that should be restored once the originally gathered context is not needed anymore.
* For instance this method should be used like this:
*
*
* Supplier<ThreadContext.StoredContext> restorable = context.newRestorableContext(true);
* new Thread() {
* public void run() {
* try (ThreadContext.StoredContext ctx = restorable.get()) {
* // execute with the parents context and restore the threads context afterwards
* }
* }
*
* }.start();
*
*
* @param preserveResponseHeaders if set to true the response headers of the restore thread will be preserved.
* @return a restorable context supplier
*/
public Supplier newRestorableContext(boolean preserveResponseHeaders) {
return wrapRestorable(newStoredContext(preserveResponseHeaders));
}
/**
* Same as {@link #newRestorableContext(boolean)} but wraps an existing context to restore.
* @param storedContext the context to restore
*/
public Supplier wrapRestorable(StoredContext storedContext) {
return () -> {
StoredContext context = newStoredContext(false);
storedContext.restore();
return context;
};
}
@Override
public void writeTo(StreamOutput out) throws IOException {
threadLocal.get().writeTo(out, defaultHeader);
}
/**
* Reads the headers from the stream into the current context
*/
public void readHeaders(StreamInput in) throws IOException {
setHeaders(readHeadersFromStream(in));
}
public void setHeaders(Tuple