|
24 | 24 |
|
25 | 25 | package org.jenkinsci.plugins.workflow.cps; |
26 | 26 |
|
27 | | -import org.jenkinsci.plugins.workflow.cps.persistence.PersistIn; |
28 | | - |
29 | | -import javax.annotation.concurrent.Immutable; |
| 27 | +import com.google.common.util.concurrent.ListenableFuture; |
| 28 | +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; |
| 29 | +import hudson.ExtensionList; |
| 30 | +import hudson.model.Result; |
| 31 | +import java.io.IOException; |
30 | 32 | import java.io.Serializable; |
31 | 33 | import java.util.ArrayList; |
| 34 | +import java.util.HashSet; |
32 | 35 | import java.util.List; |
33 | | - |
34 | | -import static org.jenkinsci.plugins.workflow.cps.persistence.PersistenceContext.NONE; |
| 36 | +import java.util.Set; |
| 37 | +import java.util.logging.Logger; |
| 38 | +import java.util.stream.Collectors; |
| 39 | +import javax.annotation.concurrent.Immutable; |
| 40 | +import org.jenkinsci.plugins.workflow.cps.persistence.PersistIn; |
| 41 | +import static org.jenkinsci.plugins.workflow.cps.persistence.PersistenceContext.PROGRAM; |
| 42 | +import org.jenkinsci.plugins.workflow.flow.FlowExecution; |
| 43 | +import org.jenkinsci.plugins.workflow.graph.FlowNode; |
| 44 | +import org.jenkinsci.plugins.workflow.steps.BodyInvoker; |
| 45 | +import org.jenkinsci.plugins.workflow.steps.DynamicContext; |
| 46 | +import org.jenkinsci.plugins.workflow.support.DefaultStepContext; |
35 | 47 |
|
36 | 48 | /** |
37 | | - * @author Kohsuke Kawaguchi |
| 49 | + * Holds a set of contextual objects as per {@link BodyInvoker#withContext} in a given scope. |
| 50 | + * Also interprets {@link DynamicContext}. |
38 | 51 | */ |
39 | 52 | @Immutable |
40 | | -@PersistIn(NONE) |
| 53 | +@PersistIn(PROGRAM) |
41 | 54 | final class ContextVariableSet implements Serializable { |
| 55 | + |
| 56 | + private static final Logger LOGGER = Logger.getLogger(ContextVariableSet.class.getName()); |
| 57 | + |
42 | 58 | private final ContextVariableSet parent; |
43 | | - private final List<Object> values = new ArrayList<Object>(); |
| 59 | + private final List<Object> values = new ArrayList<>(); |
44 | 60 |
|
45 | 61 | ContextVariableSet(ContextVariableSet parent) { |
46 | 62 | this.parent = parent; |
47 | 63 | } |
48 | 64 |
|
49 | | - <T> T get(Class<T> type) { |
50 | | - for (ContextVariableSet s=this; s!=null; s=s.parent) { |
51 | | - for (Object v : s.values) { |
52 | | - if (type.isInstance(v)) |
53 | | - return type.cast(v); |
| 65 | + private static final ThreadLocal<Set<DynamicContextQuery>> dynamicContextClasses = ThreadLocal.withInitial(HashSet::new); |
| 66 | + |
| 67 | + private static final class DynamicContextQuery { |
| 68 | + final DynamicContext dynamicContext; |
| 69 | + final Class<?> key; |
| 70 | + DynamicContextQuery(DynamicContext dynamicContext, Class<?> key) { |
| 71 | + this.dynamicContext = dynamicContext; |
| 72 | + this.key = key; |
| 73 | + } |
| 74 | + @Override public boolean equals(Object obj) { |
| 75 | + return obj instanceof DynamicContextQuery && |
| 76 | + dynamicContext == ((DynamicContextQuery) obj).dynamicContext && |
| 77 | + key == ((DynamicContextQuery) obj).key; |
| 78 | + } |
| 79 | + @Override public int hashCode() { |
| 80 | + return dynamicContext.hashCode() ^ key.hashCode(); |
| 81 | + } |
| 82 | + } |
| 83 | + |
| 84 | + interface ThrowingSupplier<T> { |
| 85 | + T get() throws IOException; |
| 86 | + } |
| 87 | + |
| 88 | + <T> T get(Class<T> key, ThrowingSupplier<FlowExecution> execution, ThrowingSupplier<FlowNode> node) throws IOException, InterruptedException { |
| 89 | + for (Object v : values) { |
| 90 | + if (key.isInstance(v)) { |
| 91 | + LOGGER.fine(() -> "found a " + v.getClass().getName() + " in " + this); |
| 92 | + return key.cast(v); |
54 | 93 | } |
55 | 94 | } |
56 | | - return null; |
| 95 | + class Delegate extends DefaultStepContext implements DynamicContext.DelegatedContext { |
| 96 | + @Override protected <T> T doGet(Class<T> key) throws IOException, InterruptedException { |
| 97 | + return ContextVariableSet.this.get(key, execution, node); |
| 98 | + } |
| 99 | + @Override protected FlowExecution getExecution() throws IOException { |
| 100 | + return execution.get(); |
| 101 | + } |
| 102 | + @Override protected FlowNode getNode() throws IOException { |
| 103 | + return node.get(); |
| 104 | + } |
| 105 | + @Override public void onSuccess(Object result) { |
| 106 | + throw new AssertionError(); |
| 107 | + } |
| 108 | + @Override public boolean isReady() { |
| 109 | + throw new AssertionError(); |
| 110 | + } |
| 111 | + @Override public ListenableFuture<Void> saveState() { |
| 112 | + throw new AssertionError(); |
| 113 | + } |
| 114 | + @Override public void setResult(Result r) { |
| 115 | + throw new AssertionError(); |
| 116 | + } |
| 117 | + @Override public BodyInvoker newBodyInvoker() throws IllegalStateException { |
| 118 | + throw new AssertionError(); |
| 119 | + } |
| 120 | + @SuppressFBWarnings(value = "EQ_UNUSUAL", justification = "DefaultStepContext does not delegate to this") |
| 121 | + @SuppressWarnings("EqualsWhichDoesntCheckParameterClass") |
| 122 | + @Override public boolean equals(Object o) { |
| 123 | + throw new AssertionError(); |
| 124 | + } |
| 125 | + @Override public int hashCode() { |
| 126 | + throw new AssertionError(); |
| 127 | + } |
| 128 | + @Override public void onFailure(Throwable t) { |
| 129 | + throw new AssertionError(); |
| 130 | + } |
| 131 | + } |
| 132 | + DynamicContext.DelegatedContext delegate = new Delegate(); |
| 133 | + for (DynamicContext dynamicContext : ExtensionList.lookup(DynamicContext.class)) { |
| 134 | + DynamicContextQuery query = new DynamicContextQuery(dynamicContext, key); |
| 135 | + Set<DynamicContextQuery> dynamicStack = dynamicContextClasses.get(); |
| 136 | + if (dynamicStack.add(query)) { // thus, being newly added to the stack |
| 137 | + try { |
| 138 | + T v = dynamicContext.get(key, delegate); |
| 139 | + if (v != null) { |
| 140 | + LOGGER.fine(() -> "looked up a " + v.getClass().getName() + " from " + dynamicContext + " in " + this); |
| 141 | + return v; |
| 142 | + } |
| 143 | + } finally { |
| 144 | + dynamicStack.remove(query); |
| 145 | + } |
| 146 | + } |
| 147 | + } |
| 148 | + if (parent != null) { |
| 149 | + return parent.get(key, execution, node); |
| 150 | + } else { |
| 151 | + return null; |
| 152 | + } |
| 153 | + } |
| 154 | + |
| 155 | + @Override public String toString() { |
| 156 | + return "ContextVariableSet" + values.stream().map(Object::getClass).map(Class::getName).collect(Collectors.toList()) + (parent != null ? "<" + parent : ""); |
57 | 157 | } |
58 | 158 |
|
59 | 159 | /** |
|
0 commit comments