Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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<Map<String, Object>> {
abstract class AbstractInvocationContext implements ArcInvocationContext {

private static final Object[] EMPTY_PARAMS = new Object[0];

Expand All @@ -23,25 +21,24 @@ abstract class AbstractInvocationContext implements ArcInvocationContext, Suppli
protected final List<InterceptorInvocation> 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<Map<String, Object>> contextData;
protected ContextDataMap contextData;

protected AbstractInvocationContext(Object target, Method method,
Constructor<?> constructor,
Object[] parameters, LazyValue<Map<String, Object>> contextData,
Object[] parameters, ContextDataMap contextData,
Set<Annotation> interceptorBindings, List<InterceptorInvocation> 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<String, Object> getContextData() {
return contextData.get();
return contextData;
}

@Override
Expand Down Expand Up @@ -125,11 +122,4 @@ public Constructor<?> getConstructor() {
return constructor;
}

@Override
public Map<String, Object> get() {
Map<String, Object> result = new HashMap<String, Object>();
result.put(ArcInvocationContext.KEY_INTERCEPTOR_BINDINGS, interceptorBindings);
return result;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -26,7 +25,7 @@ class AroundInvokeInvocationContext extends AbstractInvocationContext {
private final Function<InvocationContext, Object> aroundInvokeForward;

AroundInvokeInvocationContext(Object target, Method method, Object[] parameters,
LazyValue<Map<String, Object>> contextData, Set<Annotation> interceptorBindings, int position,
ContextDataMap contextData, Set<Annotation> interceptorBindings, int position,
List<InterceptorInvocation> chain, Function<InvocationContext, Object> aroundInvokeForward) {
super(target, method, null, parameters, contextData, interceptorBindings, chain);
this.position = position;
Expand Down
Original file line number Diff line number Diff line change
@@ -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<String, Object> {

private final Set<Annotation> interceptorBindings;
private HashMap<String, Object> delegate; //important to lazily initialize this

ContextDataMap(Set<Annotation> interceptorBindings) {
this.interceptorBindings = Objects.requireNonNull(interceptorBindings);
}

private Map<String, Object> getDelegateForRead() {
if (delegate == null) {
return Collections.EMPTY_MAP;
} else {
return delegate;
}
}

private Map<String, Object> 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<Map.Entry<String, Object>> entrySet() {
final AbstractMap.SimpleImmutableEntry<String, Object> firstEntry = new AbstractMap.SimpleImmutableEntry<>(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be lazily allocated (and reused) as well, given that users cannot modify it?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes it could - but I don't think it's worth it: hopefully nobody is using entrySet heavily on this map.

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<String> 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() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason to use so many raw types in this class? I get a lot of useless warnings in my IDE ;-)

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);
}

}