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 The Narayana Authors
SPDX-License-Identifier: Apache-2.0
*/
package io.narayana.lra;
import jakarta.ws.rs.container.ContainerResponseContext;
import jakarta.ws.rs.core.MultivaluedMap;
import jakarta.ws.rs.core.UriBuilder;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Stack;
import java.util.concurrent.ConcurrentHashMap;
import static io.narayana.lra.LRAConstants.PARENT_LRA_PARAM_NAME;
import static io.narayana.lra.LRAConstants.QUERY_FIELD_SEPARATOR;
import static io.narayana.lra.LRAConstants.QUERY_PAIR_SEPARATOR;
import static org.eclipse.microprofile.lra.annotation.ws.rs.LRA.LRA_HTTP_CONTEXT_HEADER;
// similar to ThreadActionData except it need to be available on the client side
// for use by NarayanaLRAClient and ServerLRAFilter
public class Current {
private static final ThreadLocal lraContexts = new ThreadLocal<>();
/**
* Use this cache to prevent from {@link ThreadLocal} spilling. This cache is incremented when the request filter
* method is invoked and decremented when the response filter method is invoked. In this sense, if the response
* filter method runs on different thread (meaning it doesn't clear the right {@link ThreadLocal}) we still remove
* it from the cache.
*/
private static final Map activeLRACache = new ConcurrentHashMap<>();
@SuppressWarnings("ConstantConditions")
public static void addActiveLRACache(URI lraId) {
if (lraId != null && activeLRACache.containsKey(lraId)) {
activeLRACache.compute(lraId, (k, v) -> v + 1);
} else {
activeLRACache.put(lraId, 1);
}
}
public static void removeActiveLRACache(URI lraId) {
if (lraId != null && activeLRACache.containsKey(lraId)) {
activeLRACache.compute(lraId, (k, v) -> v - 1);
if (activeLRACache.get(lraId) == 0) {
activeLRACache.remove(lraId);
}
}
}
private final Stack stack;
private Map state;
private Current(URI url) {
stack = new Stack<>();
stack.push(url);
}
public static Object putState(String key, Object value) {
Current current = lraContexts.get();
if (current != null) {
return current.updateState(key, value);
}
return null;
}
public static Object getState(String key) {
Current current = lraContexts.get();
if (current != null && current.state != null) {
return current.state.get(key);
}
return null;
}
private static String getParents(URI uri) {
String query = uri.getQuery();
if (query != null) {
for (String nvpair : query.split(QUERY_PAIR_SEPARATOR)) {
if (nvpair.startsWith(PARENT_LRA_PARAM_NAME + QUERY_FIELD_SEPARATOR)) {
return nvpair.split(QUERY_FIELD_SEPARATOR)[1];
}
}
}
return null;
}
// construct the LRA URI including the parent hierarchy as a query parameter
public static URI buildFullLRAUrl(String baseURI, URI parentId) throws URISyntaxException {
// is the parent part of a hierarchy
String parents = Current.getParents(parentId); // gets the hierarchy form the query param
// we have the hierarchy so remove the query parameter
String gParent = new URI(parentId.getScheme(),
parentId.getAuthority(),
parentId.getPath(),
null, // skip the query string
parentId.getFragment())
.toASCIIString();
if (parents != null) {
gParent += parents + ","; // , separated list of the hierarchy
}
return UriBuilder.fromUri(baseURI).queryParam(PARENT_LRA_PARAM_NAME, gParent).build();
}
// given a URL extract the immediate parent of
public static String getFirstParent(URI parent) throws UnsupportedEncodingException {
String query = parent == null ? null : parent.getQuery();
if (query != null) {
for (String param : query.split(QUERY_PAIR_SEPARATOR)) {
if (param.startsWith(PARENT_LRA_PARAM_NAME + QUERY_FIELD_SEPARATOR)) {
String parents = param.split(QUERY_FIELD_SEPARATOR, 2)[1];
// parents is a comma separated list of parents (the first one is the direct parent)
if (parents != null) {
String[] pa = parents.split(",");
if (pa.length > 0) {
return URLDecoder.decode(pa[0], StandardCharsets.UTF_8);
}
}
break;
}
}
}
return null;
}
public Object updateState(String key, Object value) {
if (state == null) {
state = new HashMap<>();
}
return state.put(key, value);
}
private static void clearContext(Current current) {
if (current.state != null) {
current.state.clear();
}
lraContexts.set(null);
}
public static URI peek() {
Current current = lraContexts.get();
URI peek = current != null ? current.stack.peek() : null;
if (peek != null && !activeLRACache.containsKey(peek)) {
// we cleaned the Current on different thread, so we need to clear the context
// that was set by previous request filter and wasn't cleaned by the response filter
Current.popAll();
return null;
}
return peek;
}
public static URI pop() {
Current current = lraContexts.get();
URI lraId = null;
if (current != null) {
lraId = current.stack.pop(); // there must be at least one
if (current.stack.empty()) {
clearContext(current);
}
}
return lraId;
}
// dissassociate an LRA from the callers thread (including any child LRAs)
public static boolean pop(URI lra) {
Current current = lraContexts.get();
if (current == null || !current.stack.contains(lra)) {
return false;
}
current.stack.remove(lra);
// pop children
// since child LRAs are contingent upon the parent, popping a parent should also pop the children
// check every LRA associated with the calling thread and if it is a child of lra then pop it
// the lra that is being popped is a parent of nextLRA:
current.stack.removeIf(nextLRA -> isParentOf(lra, nextLRA));
if (current.stack.empty()) {
clearContext(current);
}
return true;
}
/*
* return true if child is nested under parent
* ie if child contains a query param matching child
*/
private static boolean isParentOf(URI parent, URI child) {
String qs = child.getQuery();
if (qs == null) {
return false; // child is top level
}
String theParent = parent.toASCIIString();
String[] params = qs.split(QUERY_PAIR_SEPARATOR);
for (String param : params) {
String[] nvp = param.split(QUERY_FIELD_SEPARATOR);
if (nvp.length == 2 && nvp[0].contains(PARENT_LRA_PARAM_NAME)) { // ignore null parameter values
// Child has a parent. See if its parent matches theParent:
String parentCandidate = URLDecoder.decode(nvp[1], StandardCharsets.UTF_8);
if (parentCandidate.contains(theParent) || theParent.contains(parentCandidate)) {
return true;
}
}
}
return false;
}
/**
* push the current context onto the stack of contexts for this thread
* @param lraId id of context to push (must not be null)
*/
public static void push(URI lraId) {
Current current = lraContexts.get();
if (current == null) {
lraContexts.set(new Current(lraId));
} else {
if (!current.stack.contains(lraId)) {
current.stack.push(lraId);
}
}
}
public static List