Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ArC - introduce the CurrentContextFactory abstraction #22319

Merged
merged 1 commit into from
Apr 12, 2022
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 @@ -512,7 +512,8 @@ public BeanContainerBuildItem generateResources(ArcConfig config, ArcRecorder re
LiveReloadBuildItem liveReloadBuildItem,
BuildProducer<GeneratedResourceBuildItem> generatedResource,
BuildProducer<BytecodeTransformerBuildItem> bytecodeTransformer,
List<ReflectiveBeanClassBuildItem> reflectiveBeanClasses) throws Exception {
List<ReflectiveBeanClassBuildItem> reflectiveBeanClasses,
Optional<CurrentContextFactoryBuildItem> currentContextFactory) throws Exception {

for (ValidationErrorBuildItem validationError : validationErrors) {
for (Throwable error : validationError.getValues()) {
Expand Down Expand Up @@ -593,7 +594,8 @@ public void registerSubclass(DotName beanClassName, String subclassName) {
reflectiveClasses.produce(new ReflectiveClassBuildItem(true, false, binding.name().toString()));
}

ArcContainer container = recorder.getContainer(shutdown);
ArcContainer container = recorder.initContainer(shutdown,
currentContextFactory.isPresent() ? currentContextFactory.get().getFactory() : null);
BeanContainer beanContainer = recorder.initBeanContainer(container,
beanContainerListenerBuildItems.stream().map(BeanContainerListenerBuildItem::getBeanContainerListener)
.collect(Collectors.toList()));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package io.quarkus.arc.deployment;

import io.quarkus.arc.CurrentContextFactory;
import io.quarkus.builder.item.SimpleBuildItem;
import io.quarkus.runtime.RuntimeValue;

/**
* An extension can provide a custom {@link CurrentContextFactory}.
*/
public final class CurrentContextFactoryBuildItem extends SimpleBuildItem {

private final RuntimeValue<CurrentContextFactory> factory;

public CurrentContextFactoryBuildItem(RuntimeValue<CurrentContextFactory> factory) {
this.factory = factory;
}

public RuntimeValue<CurrentContextFactory> getFactory() {
return factory;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

import io.quarkus.arc.Arc;
import io.quarkus.arc.ArcContainer;
import io.quarkus.arc.CurrentContextFactory;
import io.quarkus.arc.InjectableBean;
import io.quarkus.arc.InjectableBean.Kind;
import io.quarkus.arc.impl.ArcContainerImpl;
Expand All @@ -36,8 +37,9 @@ public class ArcRecorder {
*/
public static volatile Map<String, Supplier<?>> supplierMap;

public ArcContainer getContainer(ShutdownContext shutdown) throws Exception {
ArcContainer container = Arc.initialize();
public ArcContainer initContainer(ShutdownContext shutdown, RuntimeValue<CurrentContextFactory> currentContextFactory)
throws Exception {
ArcContainer container = Arc.initialize(currentContextFactory != null ? currentContextFactory.getValue() : null);
shutdown.addShutdownTask(new Runnable() {
@Override
public void run() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,23 @@ public final class Arc {

private static final AtomicReference<ArcContainerImpl> INSTANCE = new AtomicReference<>();

public static ArcContainer initialize() {
return initialize(null);
}

/**
*
* @param currentContextFactory
* @return the initialized container
*/
public static ArcContainer initialize() {
public static ArcContainer initialize(CurrentContextFactory currentContextFactory) {
ArcContainerImpl container = INSTANCE.get();
if (container == null) {
synchronized (INSTANCE) {
container = INSTANCE.get();
if (container == null) {
container = new ArcContainerImpl();
// Set the container instance first because Arc.container() can be used within ArcContainerImpl.init()
container = new ArcContainerImpl(currentContextFactory);
INSTANCE.set(container);
container.init();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -179,4 +179,11 @@ public interface ArcContainer {
* @return the default executor service
*/
ExecutorService getExecutorService();

/**
*
* @return the factory
* @see CurrentContext
*/
CurrentContextFactory getCurrentContextFactory();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package io.quarkus.arc;

import io.quarkus.arc.InjectableContext.ContextState;

/**
* Represents the current context of a normal scope.
*
* @param <T>
* @see CurrentContextFactory
*/
public interface CurrentContext<T extends ContextState> {

/**
*
* @return the current state
*/
T get();

/**
* Sets the current state.
*
* @param state
*/
void set(T state);

/**
* Removes the current state.
*/
void remove();

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package io.quarkus.arc;

import io.quarkus.arc.InjectableContext.ContextState;
import java.lang.annotation.Annotation;

/**
* This factory can be used to create a new {@link CurrentContext} for a normal scope, e.g. for
* {@link javax.enterprise.context.RequestScoped}. It's usually not necessary for shared contexts, such as
* {@link javax.enterprise.context.ApplicationScoped}.
*/
public interface CurrentContextFactory {

<T extends ContextState> CurrentContext<T> create(Class<? extends Annotation> scope);

}
Original file line number Diff line number Diff line change
Expand Up @@ -85,9 +85,6 @@ default boolean isNormal() {
return getScope().isAnnotationPresent(NormalScope.class);
}

/**
*
*/
interface ContextState {

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import io.quarkus.arc.ArcContainer;
import io.quarkus.arc.Components;
import io.quarkus.arc.ComponentsProvider;
import io.quarkus.arc.CurrentContextFactory;
import io.quarkus.arc.InjectableBean;
import io.quarkus.arc.InjectableContext;
import io.quarkus.arc.InjectableDecorator;
Expand Down Expand Up @@ -46,6 +47,7 @@
import javax.enterprise.context.Destroyed;
import javax.enterprise.context.Initialized;
import javax.enterprise.context.NormalScope;
import javax.enterprise.context.RequestScoped;
import javax.enterprise.event.Event;
import javax.enterprise.inject.AmbiguousResolutionException;
import javax.enterprise.inject.Any;
Expand Down Expand Up @@ -95,7 +97,9 @@ public class ArcContainerImpl implements ArcContainer {

private volatile ExecutorService executorService;

public ArcContainerImpl() {
private final CurrentContextFactory currentContextFactory;

public ArcContainerImpl(CurrentContextFactory currentContextFactory) {
id = String.valueOf(ID_GENERATOR.incrementAndGet());
running = new AtomicBoolean(true);
List<InjectableBean<?>> beans = new ArrayList<>();
Expand All @@ -105,10 +109,12 @@ public ArcContainerImpl() {
List<InjectableObserverMethod<?>> observers = new ArrayList<>();
Map<Class<? extends Annotation>, Set<Annotation>> transitiveInterceptorBindings = new HashMap<>();
Map<String, Set<String>> qualifierNonbindingMembers = new HashMap<>();
this.currentContextFactory = currentContextFactory == null ? new ThreadLocalCurrentContextFactory()
: currentContextFactory;

applicationContext = new ApplicationContext();
singletonContext = new SingletonContext();
requestContext = new RequestContext();
requestContext = new RequestContext(this.currentContextFactory.create(RequestScoped.class));
contexts = new HashMap<>();
putContext(requestContext);
putContext(applicationContext);
Expand Down Expand Up @@ -342,6 +348,11 @@ public void setExecutor(ExecutorService executor) {
this.executorService = executor;
}

@Override
public CurrentContextFactory getCurrentContextFactory() {
return currentContextFactory;
}

@Override
public String toString() {
return "ArcContainerImpl [id=" + id + ", running=" + running + ", beans=" + beans.size() + ", observers="
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.quarkus.arc.impl;

import io.quarkus.arc.ContextInstanceHandle;
import io.quarkus.arc.CurrentContext;
import io.quarkus.arc.InjectableBean;
import io.quarkus.arc.ManagedContext;
import io.quarkus.arc.impl.EventImpl.Notifier;
Expand Down Expand Up @@ -33,14 +34,14 @@ class RequestContext implements ManagedContext {

private static final Logger LOGGER = Logger.getLogger(RequestContext.class.getPackage().getName());

// It's a normal scope so there may be no more than one mapped instance per contextual type per thread
private final ThreadLocal<RequestContextState> currentContext = new ThreadLocal<>();
private final CurrentContext<RequestContextState> currentContext;

private final LazyValue<Notifier<Object>> initializedNotifier;
private final LazyValue<Notifier<Object>> beforeDestroyedNotifier;
private final LazyValue<Notifier<Object>> destroyedNotifier;

public RequestContext() {
public RequestContext(CurrentContext<RequestContextState> currentContext) {
this.currentContext = currentContext;
this.initializedNotifier = new LazyValue<>(RequestContext::createInitializedNotifier);
this.beforeDestroyedNotifier = new LazyValue<>(RequestContext::createBeforeDestroyedNotifier);
this.destroyedNotifier = new LazyValue<>(RequestContext::createDestroyedNotifier);
Expand All @@ -62,17 +63,16 @@ public <T> T getIfActive(Contextual<T> contextual, Function<Contextual<T>, Creat
}
RequestContextState ctxState = currentContext.get();
if (ctxState == null) {
// Thread local not set - context is not active!
// Context is not active!
return null;
}
Map<Contextual<?>, ContextInstanceHandle<?>> ctxMap = currentContext.get().value;
ContextInstanceHandle<T> instance = (ContextInstanceHandle<T>) ctxMap.get(contextual);
ContextInstanceHandle<T> instance = (ContextInstanceHandle<T>) ctxState.map.get(contextual);
if (instance == null) {
CreationalContext<T> creationalContext = creationalContextFun.apply(contextual);
// Bean instance does not exist - create one if we have CreationalContext
instance = new ContextInstanceHandleImpl<T>((InjectableBean<T>) contextual,
contextual.create(creationalContext), creationalContext);
ctxMap.put(contextual, instance);
ctxState.map.put(contextual, instance);
}
return instance.get();
}
Expand All @@ -82,7 +82,6 @@ public <T> T get(Contextual<T> contextual, CreationalContext<T> creationalContex
T result = getIfActive(contextual,
CreationalContextImpl.unwrap(Objects.requireNonNull(creationalContext, "CreationalContext must not be null")));
if (result == null) {
// Thread local not set - context is not active!
throw new ContextNotActiveException();
}
return result;
Expand All @@ -96,12 +95,11 @@ public <T> T get(Contextual<T> contextual) {
if (!Scopes.scopeMatches(this, bean)) {
throw Scopes.scopeDoesNotMatchException(this, bean);
}
Map<Contextual<?>, ContextInstanceHandle<?>> ctx = currentContext.get().value;
if (ctx == null) {
// Thread local not set - context is not active!
RequestContextState state = currentContext.get();
if (state == null) {
throw new ContextNotActiveException();
}
ContextInstanceHandle<T> instance = (ContextInstanceHandle<T>) ctx.get(contextual);
ContextInstanceHandle<T> instance = (ContextInstanceHandle<T>) state.map.get(contextual);
return instance == null ? null : instance.get();
}

Expand All @@ -112,12 +110,12 @@ public boolean isActive() {

@Override
public void destroy(Contextual<?> contextual) {
Map<Contextual<?>, ContextInstanceHandle<?>> ctx = currentContext.get().value;
if (ctx == null) {
// Thread local not set - context is not active!
RequestContextState state = currentContext.get();
if (state == null) {
// Context is not active
throw new ContextNotActiveException();
}
ContextInstanceHandle<?> instance = ctx.remove(contextual);
ContextInstanceHandle<?> instance = state.map.remove(contextual);
if (instance != null) {
instance.destroy();
}
Expand All @@ -131,7 +129,7 @@ public void activate(ContextState initialState) {
fireIfNotEmpty(initializedNotifier);
} else {
if (initialState instanceof RequestContextState) {
currentContext.set(((RequestContextState) initialState));
currentContext.set((RequestContextState) initialState);
} else {
throw new IllegalArgumentException("Invalid initial state: " + initialState.getClass().getName());
}
Expand All @@ -140,20 +138,16 @@ public void activate(ContextState initialState) {

@Override
public ContextState getState() {
RequestContextState ctx = currentContext.get();
if (ctx == null) {
RequestContextState state = currentContext.get();
if (state == null) {
// Thread local not set - context is not active!
throw new ContextNotActiveException();
}
return ctx;
return state;
}

public ContextState getStateIfActive() {
RequestContextState ctx = currentContext.get();
if (ctx == null) {
return null;
}
return ctx;
return currentContext.get();
}

@Override
Expand All @@ -175,31 +169,26 @@ public void destroy(ContextState state) {
if (state instanceof RequestContextState) {
RequestContextState reqState = ((RequestContextState) state);
reqState.isValid.set(false);
destroy(reqState.value);
} else {
throw new IllegalArgumentException("Invalid state: " + state.getClass().getName());
}
}

private void destroy(Map<Contextual<?>, ContextInstanceHandle<?>> currentContext) {
if (currentContext != null) {
synchronized (currentContext) {
synchronized (state) {
Map<Contextual<?>, ContextInstanceHandle<?>> map = ((RequestContextState) state).map;
// Fire an event with qualifier @BeforeDestroyed(RequestScoped.class) if there are any observers for it
try {
fireIfNotEmpty(beforeDestroyedNotifier);
} catch (Exception e) {
LOGGER.warn("An error occurred during delivery of the @BeforeDestroyed(RequestScoped.class) event", e);
}
//Performance: avoid an iterator on the map elements
currentContext.forEach(this::destroyContextElement);
map.forEach(this::destroyContextElement);
// Fire an event with qualifier @Destroyed(RequestScoped.class) if there are any observers for it
try {
fireIfNotEmpty(destroyedNotifier);
} catch (Exception e) {
LOGGER.warn("An error occurred during delivery of the @Destroyed(RequestScoped.class) event", e);
}
currentContext.clear();
map.clear();
}
} else {
throw new IllegalArgumentException("Invalid state implementation: " + state.getClass().getName());
}
}

Expand Down Expand Up @@ -238,17 +227,17 @@ private static Notifier<Object> createDestroyedNotifier() {

static class RequestContextState implements ContextState {

private final ConcurrentMap<Contextual<?>, ContextInstanceHandle<?>> value;
private final Map<Contextual<?>, ContextInstanceHandle<?>> map;
private final AtomicBoolean isValid;

RequestContextState(ConcurrentMap<Contextual<?>, ContextInstanceHandle<?>> value) {
this.value = value;
this.map = Objects.requireNonNull(value);
this.isValid = new AtomicBoolean(true);
}

@Override
public Map<InjectableBean<?>, Object> getContextualInstances() {
return value.values().stream()
return map.values().stream()
.collect(Collectors.toUnmodifiableMap(ContextInstanceHandle::getBean, ContextInstanceHandle::get));
}

Expand Down
Loading