View Javadoc

1   package com.atlassian.vcache.internal.core;
2   
3   import com.atlassian.vcache.internal.NameValidator;
4   import com.atlassian.vcache.internal.RequestContext;
5   import org.slf4j.Logger;
6   import org.slf4j.LoggerFactory;
7   
8   import java.util.Optional;
9   import java.util.function.Supplier;
10  
11  import static com.atlassian.vcache.internal.NameValidator.requireValidPartitionIdentifier;
12  import static java.util.Objects.requireNonNull;
13  
14  /**
15   * Implementation of {@link Supplier} for {@link RequestContext} that manages an instance
16   * per thread.
17   * <p>
18   * Notes:
19   * </p>
20   * <ul>
21   * <li>
22   * This class scopes thread-local variables to the instance. So two instances of
23   * {@link ThreadLocalRequestContextSupplier} will have separate instances of
24   * {@link ThreadLocal}.
25   * </li>
26   * <li>
27   * Using a lenient supplier <b>will break</b> {@link com.atlassian.vcache.TransactionalExternalCache}s. So bewarned.
28   * </li>
29   * <li>
30   * Before a thread can call {@link #get()}, the method {@link #initThread(String)} must
31   * have been called, unless a lenient supplier is created.
32   * </li>
33   * <li>
34   * The methods {@link #initThread(String)} and {@link #clearThread()} are not re-entrant.
35   * Each call to {@link #initThread(String)} must be paired with a subsequent call to {@link #clearThread()}.
36   * </li>
37   * </ul>
38   *
39   * @since 1.0.0
40   */
41  public class ThreadLocalRequestContextSupplier implements Supplier<RequestContext> {
42      private static final Logger log = LoggerFactory.getLogger(ThreadLocalRequestContextSupplier.class);
43      private final ThreadLocal<RequestContext> threadRequestContexts = new ThreadLocal<>();
44      private final Optional<Supplier<String>> lenientPartitionIdSupplier;
45  
46      private ThreadLocalRequestContextSupplier(Optional<Supplier<String>> lenientPartitionIdSupplier) {
47          this.lenientPartitionIdSupplier = requireNonNull(lenientPartitionIdSupplier);
48      }
49  
50      /**
51       * Returns a strict supplier.
52       */
53      public static ThreadLocalRequestContextSupplier strictSupplier() {
54          return new ThreadLocalRequestContextSupplier(Optional.empty());
55      }
56  
57      /**
58       * Returns a lenient supplier, which <b>will break</b> {@link com.atlassian.vcache.TransactionalExternalCache}s.
59       *
60       * @param partitionIdSupplier the supplier of partitionId's.
61       */
62      public static ThreadLocalRequestContextSupplier lenientSupplier(Supplier<String> partitionIdSupplier) {
63          log.warn("A lenient supplier has been created, TransactionalExternalCaches are now broken");
64          return new ThreadLocalRequestContextSupplier(Optional.of(partitionIdSupplier));
65      }
66  
67      @Override
68      public RequestContext get() {
69          final RequestContext current = threadRequestContexts.get();
70          if (current == null) {
71              if (!lenientPartitionIdSupplier.isPresent()) {
72                  log.error("Asked for request context when not initialised!");
73                  throw new IllegalStateException("Thread has not been initialised.");
74              }
75  
76              log.debug("Asked for request context when not initialised, returning a lenient one.");
77              return new LenientRequestContext();
78          }
79  
80          return current;
81      }
82  
83      /**
84       * Initialises the thread's {@link RequestContext}.
85       *
86       * @param partitionId the identifier for the partition. Will be validated using
87       *                    {@link NameValidator#requireValidPartitionIdentifier(String)}.
88       */
89      public void initThread(String partitionId) {
90          final RequestContext current = threadRequestContexts.get();
91          if (current != null) {
92              log.error(
93                      "Asked to initialise thread {} that is already initialised!",
94                      Thread.currentThread().getName());
95              throw new IllegalStateException(
96                      "Thread '" + Thread.currentThread().getName() + "' has already been initialised.");
97          }
98  
99          log.trace("Initialise request context");
100         threadRequestContexts.set(new DefaultRequestContext(requireValidPartitionIdentifier(partitionId)));
101     }
102 
103     /**
104      * Clears the thread's {@link RequestContext}.
105      */
106     public void clearThread() {
107         final RequestContext current = threadRequestContexts.get();
108         if (current == null) {
109             log.warn("Asked to clear a thread that is already clear!");
110         }
111 
112         log.trace("Clear request context");
113         threadRequestContexts.remove();
114     }
115 
116     /**
117      * Implementation that knows and remembers nothing.
118      */
119     private class LenientRequestContext implements RequestContext {
120         @Override
121         public String partitionIdentifier() {
122             return requireValidPartitionIdentifier(lenientPartitionIdSupplier.get().get());
123         }
124 
125         @Override
126         public <T> T computeIfAbsent(Object key, Supplier<T> supplier) {
127             return supplier.get();
128         }
129 
130         @Override
131         public <T> Optional<T> get(Object key) {
132             return Optional.empty();
133         }
134     }
135 }