diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/AbstractInvocationContext.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/AbstractInvocationContext.java index f9b31077d403c..909907ebb43f5 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/AbstractInvocationContext.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/AbstractInvocationContext.java @@ -6,14 +6,12 @@ import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; -import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; -import java.util.function.Supplier; -abstract class AbstractInvocationContext implements ArcInvocationContext, Supplier> { +abstract class AbstractInvocationContext implements ArcInvocationContext { private static final Object[] EMPTY_PARAMS = new Object[0]; @@ -23,25 +21,24 @@ abstract class AbstractInvocationContext implements ArcInvocationContext, Suppli protected final List chain; protected Object target; protected Object[] parameters; - // The map is initialized lazily but we need to use a holder so that all interceptors in the chain can access the same data - protected LazyValue> contextData; + protected ContextDataMap contextData; protected AbstractInvocationContext(Object target, Method method, Constructor constructor, - Object[] parameters, LazyValue> contextData, + Object[] parameters, ContextDataMap contextData, Set interceptorBindings, List chain) { this.target = target; this.method = method; this.constructor = constructor; this.parameters = parameters != null ? parameters : EMPTY_PARAMS; - this.contextData = contextData != null ? contextData : new LazyValue<>(this); + this.contextData = contextData != null ? contextData : new ContextDataMap(interceptorBindings); this.interceptorBindings = interceptorBindings; this.chain = chain; } @Override public Map getContextData() { - return contextData.get(); + return contextData; } @Override @@ -125,11 +122,4 @@ public Constructor getConstructor() { return constructor; } - @Override - public Map get() { - Map result = new HashMap(); - result.put(ArcInvocationContext.KEY_INTERCEPTOR_BINDINGS, interceptorBindings); - return result; - } - } diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/AroundInvokeInvocationContext.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/AroundInvokeInvocationContext.java index ac6d116a286dd..070659d479686 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/AroundInvokeInvocationContext.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/AroundInvokeInvocationContext.java @@ -4,7 +4,6 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.List; -import java.util.Map; import java.util.Set; import java.util.function.Function; import javax.interceptor.InvocationContext; @@ -26,7 +25,7 @@ class AroundInvokeInvocationContext extends AbstractInvocationContext { private final Function aroundInvokeForward; AroundInvokeInvocationContext(Object target, Method method, Object[] parameters, - LazyValue> contextData, Set interceptorBindings, int position, + ContextDataMap contextData, Set interceptorBindings, int position, List chain, Function aroundInvokeForward) { super(target, method, null, parameters, contextData, interceptorBindings, chain); this.position = position; diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/ContextDataMap.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/ContextDataMap.java new file mode 100644 index 0000000000000..0ce1b7abe5aea --- /dev/null +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/ContextDataMap.java @@ -0,0 +1,163 @@ +package io.quarkus.arc.impl; + +import io.quarkus.arc.ArcInvocationContext; +import java.lang.annotation.Annotation; +import java.util.AbstractMap; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import javax.interceptor.InvocationContext; + +/** + * This Map implementation is specifically optimised to implement + * {@link InvocationContext#getContextData()}. + * We try to leverage the fact that in most use cases, while there is a high + * chance for integration points to need to read the content of this map, + * write operations are less likely. So we only allocate an actual underlying + * HashMap on a write operation. + * In addition, an {@link ArcInvocationContext} always allows to return the set of + * interceptor bindings via {@link ArcInvocationContext#KEY_INTERCEPTOR_BINDINGS}; + * this is implemented while avoiding a write operation on the underlying Map. + * While one goal is efficiency, another goal is safety: while the CDI specification + * allows any interceptor to make changes to this Map, we specifically disallow + * 3rd party code to make changes to the interceptor bindings of the platform. + */ +final class ContextDataMap implements Map { + + private final Set interceptorBindings; + private HashMap delegate; //important to lazily initialize this + + ContextDataMap(Set interceptorBindings) { + this.interceptorBindings = Objects.requireNonNull(interceptorBindings); + } + + private Map getDelegateForRead() { + if (delegate == null) { + return Collections.EMPTY_MAP; + } else { + return delegate; + } + } + + private Map getDelegateForWrite(final int sizeHint) { + if (delegate == null) { + this.delegate = new HashMap<>(sizeHint, 1.0f); + } + return delegate; + } + + // ** Implement methods from the Map interface ** + + @Override + public void clear() { + throw new UnsupportedOperationException("Not allowed to clear the context data map"); + } + + @Override + public boolean containsKey(Object key) { + if (ArcInvocationContext.KEY_INTERCEPTOR_BINDINGS.equals(key)) { + return true; + } + return getDelegateForRead().containsKey(key); + } + + @Override + public boolean containsValue(Object value) { + if (interceptorBindings.equals(value)) { + return true; + } + return getDelegateForRead().containsValue(value); + } + + @Override + public Set> entrySet() { + final AbstractMap.SimpleImmutableEntry firstEntry = new AbstractMap.SimpleImmutableEntry<>( + ArcInvocationContext.KEY_INTERCEPTOR_BINDINGS, interceptorBindings); + if (delegate == null) { + return Collections.singleton(firstEntry); + } else { + HashSet entries = new HashSet(delegate.size() + 1); + entries.addAll(delegate.entrySet()); + entries.add(firstEntry); + return entries; + } + } + + @Override + public Object get(Object key) { + if (ArcInvocationContext.KEY_INTERCEPTOR_BINDINGS.equals(key)) { + return interceptorBindings; + } + return getDelegateForRead().get(key); + } + + @Override + public boolean isEmpty() { + return false; + } + + @Override + public Set keySet() { + if (delegate == null) { + return Collections.singleton(ArcInvocationContext.KEY_INTERCEPTOR_BINDINGS); + } else { + HashSet set = new HashSet(delegate.size() + 1); + set.addAll(delegate.keySet()); + set.add(ArcInvocationContext.KEY_INTERCEPTOR_BINDINGS); + return set; + } + } + + @Override + public void putAll(Map m) { + if (m.containsKey(ArcInvocationContext.KEY_INTERCEPTOR_BINDINGS)) { + throw new IllegalArgumentException( + "Not allowed to put key '" + ArcInvocationContext.KEY_INTERCEPTOR_BINDINGS + "' in the context data map"); + } + getDelegateForWrite(m.size()).putAll(m); + } + + @Override + public Object remove(Object key) { + if (ArcInvocationContext.KEY_INTERCEPTOR_BINDINGS.equals(key)) { + throw new IllegalArgumentException("Not allowed to remove key '" + ArcInvocationContext.KEY_INTERCEPTOR_BINDINGS + + "' from the context data map"); + } else if (delegate == null) { + return null; + } else { + return delegate.remove(key); + } + } + + @Override + public int size() { + return getDelegateForRead().size() + 1; + } + + @Override + public Collection values() { + if (delegate == null) { + return Collections.singleton(interceptorBindings); + } else { + ArrayList list = new ArrayList(delegate.size() + 1); + list.addAll(delegate.values()); + list.add(interceptorBindings); + return list; + } + } + + @Override + public Object put(String key, Object value) { + if (ArcInvocationContext.KEY_INTERCEPTOR_BINDINGS.equals(key)) { + throw new IllegalArgumentException( + "Not allowed to put key '" + ArcInvocationContext.KEY_INTERCEPTOR_BINDINGS + "' in the context data map"); + } + return getDelegateForWrite(1).put(key, value); + } + +}