diff --git a/impl/src/main/java/org/jboss/cdi/tck/tests/build/compatible/extensions/customNormalScope/Command.java b/impl/src/main/java/org/jboss/cdi/tck/tests/build/compatible/extensions/customNormalScope/Command.java new file mode 100644 index 000000000..6c3fa5231 --- /dev/null +++ b/impl/src/main/java/org/jboss/cdi/tck/tests/build/compatible/extensions/customNormalScope/Command.java @@ -0,0 +1,10 @@ +package org.jboss.cdi.tck.tests.build.compatible.extensions.customNormalScope; + +/** + * A command. Should not be a bean. The {@link CommandExecutor CommandExecutor} + * should be used to execute a command with automatic activation/deactivation of the command context. + */ +@FunctionalInterface +public interface Command { + void execute(); +} diff --git a/impl/src/main/java/org/jboss/cdi/tck/tests/build/compatible/extensions/customNormalScope/CommandContext.java b/impl/src/main/java/org/jboss/cdi/tck/tests/build/compatible/extensions/customNormalScope/CommandContext.java new file mode 100644 index 000000000..84163df5a --- /dev/null +++ b/impl/src/main/java/org/jboss/cdi/tck/tests/build/compatible/extensions/customNormalScope/CommandContext.java @@ -0,0 +1,104 @@ +package org.jboss.cdi.tck.tests.build.compatible.extensions.customNormalScope; + +import jakarta.enterprise.context.ContextNotActiveException; +import jakarta.enterprise.context.spi.AlterableContext; +import jakarta.enterprise.context.spi.Contextual; +import jakarta.enterprise.context.spi.CreationalContext; + +import java.lang.annotation.Annotation; +import java.util.HashMap; +import java.util.Map; + +public class CommandContext implements AlterableContext { + private final ThreadLocal, ContextualInstance>> currentContext = new ThreadLocal<>(); + private final ThreadLocal currentCommandExecution = new ThreadLocal<>(); + + public Class getScope() { + return CommandScoped.class; + } + + public T get(Contextual contextual, CreationalContext creationalContext) { + Map, ContextualInstance> store = currentContext.get(); + + if (store == null) { + throw new ContextNotActiveException(); + } + + ContextualInstance instance = (ContextualInstance) store.get(contextual); + if (instance == null && creationalContext != null) { + instance = new ContextualInstance(contextual.create(creationalContext), creationalContext, contextual); + store.put(contextual, instance); + } + return instance != null ? instance.get() : null; + } + + public T get(Contextual contextual) { + return get(contextual, null); + } + + public boolean isActive() { + return currentContext.get() != null; + } + + public void destroy(Contextual contextual) { + Map, ContextualInstance> ctx = currentContext.get(); + if (ctx == null) { + return; + } + ContextualInstance contextualInstance = ctx.remove(contextual); + if (contextualInstance != null) { + contextualInstance.destroy(); + } + } + + void activate() { + currentContext.set(new HashMap<>()); + currentCommandExecution.set(new CommandExecution()); + } + + void deactivate() { + Map, ContextualInstance> ctx = currentContext.get(); + if (ctx == null) { + return; + } + for (ContextualInstance instance : ctx.values()) { + try { + instance.destroy(); + } catch (Exception e) { + System.err.println("Unable to destroy instance" + instance.get() + " for bean: " + + instance.getContextual()); + } + } + ctx.clear(); + currentContext.remove(); + currentCommandExecution.remove(); + } + + CommandExecution getCurrentCommandExecution() { + return currentCommandExecution.get(); + } + + static final class ContextualInstance { + private final T value; + private final CreationalContext creationalContext; + private final Contextual contextual; + + ContextualInstance(T instance, CreationalContext creationalContext, Contextual contextual) { + this.value = instance; + this.creationalContext = creationalContext; + this.contextual = contextual; + } + + T get() { + return value; + } + + Contextual getContextual() { + return contextual; + } + + void destroy() { + contextual.destroy(value, creationalContext); + } + } +} diff --git a/impl/src/main/java/org/jboss/cdi/tck/tests/build/compatible/extensions/customNormalScope/CommandContextController.java b/impl/src/main/java/org/jboss/cdi/tck/tests/build/compatible/extensions/customNormalScope/CommandContextController.java new file mode 100644 index 000000000..c713fc0c4 --- /dev/null +++ b/impl/src/main/java/org/jboss/cdi/tck/tests/build/compatible/extensions/customNormalScope/CommandContextController.java @@ -0,0 +1,49 @@ +package org.jboss.cdi.tck.tests.build.compatible.extensions.customNormalScope; + +import jakarta.enterprise.context.ContextNotActiveException; +import jakarta.enterprise.inject.spi.BeanContainer; + +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * Allows manual activation and deactivation of the {@linkplain CommandScoped command} context. + * The {@code activate()} method returns {@code true} if the command context was not + * active on the current thread at the moment of the call and hence was activated by the call. + * When the command context was active on the current thread when {@code activate()} is called, + * {@code false} is returned and the operation is otherwise a noop. + *

+ * When {@code activate()} returns {@code true}, the caller is supposed to call + * {@code deactivate()} later on. Calling {@code deactivate()} when the command context + * is not active leads to {@code ContextNotActiveException}. Calling {@code deactivate()} + * when the command context is active but was not activated by this controller is a noop. + */ +public final class CommandContextController { + private final CommandContext context; + + private final BeanContainer beanContainer; + + private final AtomicBoolean activated = new AtomicBoolean(false); + + CommandContextController(CommandContext context, BeanContainer beanContainer) { + this.context = context; + this.beanContainer = beanContainer; + } + + public boolean activate() { + try { + beanContainer.getContext(CommandScoped.class); + return false; + } catch (ContextNotActiveException e) { + context.activate(); + activated.set(true); + return true; + } + } + + public void deactivate() throws ContextNotActiveException { + beanContainer.getContext(CommandScoped.class); + if (activated.compareAndSet(true, false)) { + context.deactivate(); + } + } +} diff --git a/impl/src/main/java/org/jboss/cdi/tck/tests/build/compatible/extensions/customNormalScope/CommandContextControllerCreator.java b/impl/src/main/java/org/jboss/cdi/tck/tests/build/compatible/extensions/customNormalScope/CommandContextControllerCreator.java new file mode 100644 index 000000000..578a24ac1 --- /dev/null +++ b/impl/src/main/java/org/jboss/cdi/tck/tests/build/compatible/extensions/customNormalScope/CommandContextControllerCreator.java @@ -0,0 +1,15 @@ +package org.jboss.cdi.tck.tests.build.compatible.extensions.customNormalScope; + +import jakarta.enterprise.inject.Instance; +import jakarta.enterprise.inject.build.compatible.spi.Parameters; +import jakarta.enterprise.inject.build.compatible.spi.SyntheticBeanCreator; +import jakarta.enterprise.inject.spi.BeanContainer; + +public class CommandContextControllerCreator implements SyntheticBeanCreator { + @Override + public CommandContextController create(Instance lookup, Parameters params) { + BeanContainer beanContainer = lookup.select(BeanContainer.class).get(); + CommandContext ctx = (CommandContext) beanContainer.getContexts(CommandScoped.class).iterator().next(); + return new CommandContextController(ctx, beanContainer); + } +} diff --git a/impl/src/main/java/org/jboss/cdi/tck/tests/build/compatible/extensions/customNormalScope/CommandExecution.java b/impl/src/main/java/org/jboss/cdi/tck/tests/build/compatible/extensions/customNormalScope/CommandExecution.java new file mode 100644 index 000000000..887f4816a --- /dev/null +++ b/impl/src/main/java/org/jboss/cdi/tck/tests/build/compatible/extensions/customNormalScope/CommandExecution.java @@ -0,0 +1,24 @@ +package org.jboss.cdi.tck.tests.build.compatible.extensions.customNormalScope; + +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +public class CommandExecution { + private final Date startedAt; + + private final Map data; + + CommandExecution() { + this.startedAt = new Date(); + this.data = new HashMap<>(); + } + + Date getStartedAt() { + return startedAt; + } + + Map getData() { + return data; + } +} diff --git a/impl/src/main/java/org/jboss/cdi/tck/tests/build/compatible/extensions/customNormalScope/CommandExecutionCreator.java b/impl/src/main/java/org/jboss/cdi/tck/tests/build/compatible/extensions/customNormalScope/CommandExecutionCreator.java new file mode 100644 index 000000000..13e91ed5f --- /dev/null +++ b/impl/src/main/java/org/jboss/cdi/tck/tests/build/compatible/extensions/customNormalScope/CommandExecutionCreator.java @@ -0,0 +1,14 @@ +package org.jboss.cdi.tck.tests.build.compatible.extensions.customNormalScope; + +import jakarta.enterprise.inject.Instance; +import jakarta.enterprise.inject.build.compatible.spi.Parameters; +import jakarta.enterprise.inject.build.compatible.spi.SyntheticBeanCreator; +import jakarta.enterprise.inject.spi.BeanContainer; + +public class CommandExecutionCreator implements SyntheticBeanCreator { + @Override + public CommandExecution create(Instance lookup, Parameters params) { + CommandContext ctx = (CommandContext) lookup.select(BeanContainer.class).get().getContext(CommandScoped.class); + return ctx.getCurrentCommandExecution(); + } +} diff --git a/impl/src/main/java/org/jboss/cdi/tck/tests/build/compatible/extensions/customNormalScope/CommandExecutor.java b/impl/src/main/java/org/jboss/cdi/tck/tests/build/compatible/extensions/customNormalScope/CommandExecutor.java new file mode 100644 index 000000000..f614a9666 --- /dev/null +++ b/impl/src/main/java/org/jboss/cdi/tck/tests/build/compatible/extensions/customNormalScope/CommandExecutor.java @@ -0,0 +1,28 @@ +package org.jboss.cdi.tck.tests.build.compatible.extensions.customNormalScope; + +import jakarta.enterprise.context.Dependent; +import jakarta.inject.Inject; + +/** + * Executes a {@link Command Command} in the command scope. That is, the command context + * is activated before calling {@code Command.execute()} and deactivated when + * {@code Command.execute()} returns (or throws). + */ +@Dependent +public class CommandExecutor { + private final CommandContextController control; + + @Inject + CommandExecutor(CommandContextController control) { + this.control = control; + } + + public void execute(Command command) { + try { + control.activate(); + command.execute(); + } finally { + control.deactivate(); + } + } +} diff --git a/impl/src/main/java/org/jboss/cdi/tck/tests/build/compatible/extensions/customNormalScope/CommandScoped.java b/impl/src/main/java/org/jboss/cdi/tck/tests/build/compatible/extensions/customNormalScope/CommandScoped.java new file mode 100644 index 000000000..db0a69582 --- /dev/null +++ b/impl/src/main/java/org/jboss/cdi/tck/tests/build/compatible/extensions/customNormalScope/CommandScoped.java @@ -0,0 +1,29 @@ +package org.jboss.cdi.tck.tests.build.compatible.extensions.customNormalScope; + +import jakarta.enterprise.context.NormalScope; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Specifies that a bean belongs to the command normal scope. + *

+ * A dependent-scoped bean of type {@link CommandContextController CommandContextController} + * is provided that may be used to manually activate and deactivate the command context. + *

+ * A dependent-scoped bean of type {@link CommandExecutor CommandExecutor} is provided that + * may be used to execute a {@link Command Command} implementation, activating and deactivating + * the command scope automatically. + *

+ * A command-scoped bean of type {@link CommandExecution CommandExecution} is provided that contains + * certain details about the command execution and allows exchanging data between beans in the same command scope. + */ +@NormalScope +@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD}) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +public @interface CommandScoped { +} diff --git a/impl/src/main/java/org/jboss/cdi/tck/tests/build/compatible/extensions/customNormalScope/CustomNormalScopeExtension.java b/impl/src/main/java/org/jboss/cdi/tck/tests/build/compatible/extensions/customNormalScope/CustomNormalScopeExtension.java new file mode 100644 index 000000000..20b859fc1 --- /dev/null +++ b/impl/src/main/java/org/jboss/cdi/tck/tests/build/compatible/extensions/customNormalScope/CustomNormalScopeExtension.java @@ -0,0 +1,29 @@ +package org.jboss.cdi.tck.tests.build.compatible.extensions.customNormalScope; + +import jakarta.enterprise.context.Dependent; +import jakarta.enterprise.inject.build.compatible.spi.BuildCompatibleExtension; +import jakarta.enterprise.inject.build.compatible.spi.Discovery; +import jakarta.enterprise.inject.build.compatible.spi.MetaAnnotations; +import jakarta.enterprise.inject.build.compatible.spi.ScannedClasses; +import jakarta.enterprise.inject.build.compatible.spi.Synthesis; +import jakarta.enterprise.inject.build.compatible.spi.SyntheticComponents; + +public class CustomNormalScopeExtension implements BuildCompatibleExtension { + @Discovery + public void discovery(MetaAnnotations meta, ScannedClasses scan) { + meta.addContext(CommandScoped.class, CommandContext.class); + } + + @Synthesis + public void synthesis(SyntheticComponents syn) { + syn.addBean(CommandContextController.class) + .type(CommandContextController.class) + .scope(Dependent.class) + .createWith(CommandContextControllerCreator.class); + + syn.addBean(CommandExecution.class) + .type(CommandExecution.class) + .scope(CommandScoped.class) + .createWith(CommandExecutionCreator.class); + } +} diff --git a/impl/src/main/java/org/jboss/cdi/tck/tests/build/compatible/extensions/customNormalScope/CustomNormalScopeTest.java b/impl/src/main/java/org/jboss/cdi/tck/tests/build/compatible/extensions/customNormalScope/CustomNormalScopeTest.java new file mode 100644 index 000000000..dc0356506 --- /dev/null +++ b/impl/src/main/java/org/jboss/cdi/tck/tests/build/compatible/extensions/customNormalScope/CustomNormalScopeTest.java @@ -0,0 +1,51 @@ +package org.jboss.cdi.tck.tests.build.compatible.extensions.customNormalScope; + +import org.jboss.arquillian.container.test.api.Deployment; +import org.jboss.cdi.tck.AbstractTest; +import org.jboss.cdi.tck.cdi.Sections; +import org.jboss.cdi.tck.shrinkwrap.WebArchiveBuilder; +import org.jboss.shrinkwrap.api.spec.WebArchive; +import org.jboss.test.audit.annotations.SpecAssertion; +import org.testng.annotations.Test; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertTrue; + +// this test is basically a stripped down version of https://github.com/weld/command-context-example +public class CustomNormalScopeTest extends AbstractTest { + @Deployment + public static WebArchive createTestArchive() { + return new WebArchiveBuilder() + .withTestClassPackage(CustomNormalScopeTest.class) + .withBuildCompatibleExtension(CustomNormalScopeExtension.class) + .build(); + } + + @Test + @SpecAssertion(section = Sections.DISCOVERY_PHASE, id = "a", note = "Register custom normal scope") + public void commandContextController() { + CommandContextController control = getContextualReference(CommandContextController.class); + boolean activated = control.activate(); + assertTrue(activated); + try { + assertEquals(getContextualReference(IdService.class).get(), getContextualReference(IdService.class).get()); + } finally { + control.deactivate(); + } + } + + @Test + @SpecAssertion(section = Sections.DISCOVERY_PHASE, id = "a", note = "Register custom normal scope") + public void commandExecutor() { + CommandExecutor executor = getContextualReference(CommandExecutor.class); + executor.execute(() -> { + CommandExecution execution = getContextualReference(CommandExecution.class); + IdService idService = getContextualReference(IdService.class); + + getContextualReference(MyService.class).process(); + assertEquals(execution.getData().get("id"), idService.get()); + assertNotNull(execution.getStartedAt()); + }); + } +} diff --git a/impl/src/main/java/org/jboss/cdi/tck/tests/build/compatible/extensions/customNormalScope/IdService.java b/impl/src/main/java/org/jboss/cdi/tck/tests/build/compatible/extensions/customNormalScope/IdService.java new file mode 100644 index 000000000..dd0ac9e56 --- /dev/null +++ b/impl/src/main/java/org/jboss/cdi/tck/tests/build/compatible/extensions/customNormalScope/IdService.java @@ -0,0 +1,12 @@ +package org.jboss.cdi.tck.tests.build.compatible.extensions.customNormalScope; + +import java.util.UUID; + +@CommandScoped +public class IdService { + private final String id = UUID.randomUUID().toString(); + + public String get() { + return id; + } +} diff --git a/impl/src/main/java/org/jboss/cdi/tck/tests/build/compatible/extensions/customNormalScope/MyService.java b/impl/src/main/java/org/jboss/cdi/tck/tests/build/compatible/extensions/customNormalScope/MyService.java new file mode 100644 index 000000000..e2416d717 --- /dev/null +++ b/impl/src/main/java/org/jboss/cdi/tck/tests/build/compatible/extensions/customNormalScope/MyService.java @@ -0,0 +1,17 @@ +package org.jboss.cdi.tck.tests.build.compatible.extensions.customNormalScope; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; + +@ApplicationScoped +public class MyService { + @Inject + CommandExecution execution; + + @Inject + IdService id; + + public void process() { + execution.getData().put("id", id.get()); + } +} diff --git a/impl/src/main/java/org/jboss/cdi/tck/tests/build/compatible/extensions/customPseudoScope/ApplicationScopedBean.java b/impl/src/main/java/org/jboss/cdi/tck/tests/build/compatible/extensions/customPseudoScope/ApplicationScopedBean.java new file mode 100644 index 000000000..469e9ff98 --- /dev/null +++ b/impl/src/main/java/org/jboss/cdi/tck/tests/build/compatible/extensions/customPseudoScope/ApplicationScopedBean.java @@ -0,0 +1,14 @@ +package org.jboss.cdi.tck.tests.build.compatible.extensions.customPseudoScope; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; + +@ApplicationScoped +public class ApplicationScopedBean { + @Inject + PrototypeBean prototype; + + public String getPrototypeId() { + return prototype.getId(); + } +} diff --git a/impl/src/main/java/org/jboss/cdi/tck/tests/build/compatible/extensions/customPseudoScope/CustomPseudoScopeExtension.java b/impl/src/main/java/org/jboss/cdi/tck/tests/build/compatible/extensions/customPseudoScope/CustomPseudoScopeExtension.java new file mode 100644 index 000000000..ccf8495f5 --- /dev/null +++ b/impl/src/main/java/org/jboss/cdi/tck/tests/build/compatible/extensions/customPseudoScope/CustomPseudoScopeExtension.java @@ -0,0 +1,12 @@ +package org.jboss.cdi.tck.tests.build.compatible.extensions.customPseudoScope; + +import jakarta.enterprise.inject.build.compatible.spi.BuildCompatibleExtension; +import jakarta.enterprise.inject.build.compatible.spi.Discovery; +import jakarta.enterprise.inject.build.compatible.spi.MetaAnnotations; + +public class CustomPseudoScopeExtension implements BuildCompatibleExtension { + @Discovery + public void discovery(MetaAnnotations meta) { + meta.addContext(Prototype.class, PrototypeContext.class); + } +} diff --git a/impl/src/main/java/org/jboss/cdi/tck/tests/build/compatible/extensions/customPseudoScope/CustomPseudoScopeTest.java b/impl/src/main/java/org/jboss/cdi/tck/tests/build/compatible/extensions/customPseudoScope/CustomPseudoScopeTest.java new file mode 100644 index 000000000..f648b95db --- /dev/null +++ b/impl/src/main/java/org/jboss/cdi/tck/tests/build/compatible/extensions/customPseudoScope/CustomPseudoScopeTest.java @@ -0,0 +1,47 @@ +package org.jboss.cdi.tck.tests.build.compatible.extensions.customPseudoScope; + +import org.jboss.arquillian.container.test.api.Deployment; +import org.jboss.cdi.tck.AbstractTest; +import org.jboss.cdi.tck.cdi.Sections; +import org.jboss.cdi.tck.shrinkwrap.WebArchiveBuilder; +import org.jboss.shrinkwrap.api.spec.WebArchive; +import org.jboss.test.audit.annotations.SpecAssertion; +import org.jboss.test.audit.annotations.SpecVersion; +import org.testng.annotations.Test; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNotEquals; + +@SpecVersion(spec = "cdi", version = "4.0") +public class CustomPseudoScopeTest extends AbstractTest { + @Deployment + public static WebArchive createTestArchive() { + return new WebArchiveBuilder() + .withTestClassPackage(CustomPseudoScopeTest.class) + .withBuildCompatibleExtension(CustomPseudoScopeExtension.class) + .build(); + } + + @Test + @SpecAssertion(section = Sections.DISCOVERY_PHASE, id = "a", note = "Register custom pseudo-scope") + public void test() { + PrototypeBean prototypeBean = getContextualReference(PrototypeBean.class); + + assertNotEquals(getContextualReference(PrototypeBean.class).getId(), prototypeBean.getId()); + + ApplicationScopedBean applicationScopedBean = getContextualReference(ApplicationScopedBean.class); + assertEquals(getContextualReference(ApplicationScopedBean.class).getPrototypeId(), + applicationScopedBean.getPrototypeId()); + assertNotEquals(prototypeBean.getId(), applicationScopedBean.getPrototypeId()); + + RequestScopedBean requestScopedBean = getContextualReference(RequestScopedBean.class); + assertEquals(getContextualReference(RequestScopedBean.class).getPrototypeId(), + requestScopedBean.getPrototypeId()); + assertNotEquals(prototypeBean.getId(), requestScopedBean.getPrototypeId()); + + DependentBean dependentBean = getContextualReference(DependentBean.class); + assertNotEquals(getContextualReference(DependentBean.class).getPrototypeId(), + dependentBean.getPrototypeId()); + assertNotEquals(prototypeBean.getId(), dependentBean.getPrototypeId()); + } +} diff --git a/impl/src/main/java/org/jboss/cdi/tck/tests/build/compatible/extensions/customPseudoScope/DependentBean.java b/impl/src/main/java/org/jboss/cdi/tck/tests/build/compatible/extensions/customPseudoScope/DependentBean.java new file mode 100644 index 000000000..869f80888 --- /dev/null +++ b/impl/src/main/java/org/jboss/cdi/tck/tests/build/compatible/extensions/customPseudoScope/DependentBean.java @@ -0,0 +1,14 @@ +package org.jboss.cdi.tck.tests.build.compatible.extensions.customPseudoScope; + +import jakarta.enterprise.context.Dependent; +import jakarta.inject.Inject; + +@Dependent +public class DependentBean { + @Inject + PrototypeBean prototype; + + public String getPrototypeId() { + return prototype.getId(); + } +} diff --git a/impl/src/main/java/org/jboss/cdi/tck/tests/build/compatible/extensions/customPseudoScope/Prototype.java b/impl/src/main/java/org/jboss/cdi/tck/tests/build/compatible/extensions/customPseudoScope/Prototype.java new file mode 100644 index 000000000..05d4a6027 --- /dev/null +++ b/impl/src/main/java/org/jboss/cdi/tck/tests/build/compatible/extensions/customPseudoScope/Prototype.java @@ -0,0 +1,36 @@ +package org.jboss.cdi.tck.tests.build.compatible.extensions.customPseudoScope; + +import jakarta.enterprise.context.spi.Context; +import jakarta.enterprise.context.spi.Contextual; +import jakarta.enterprise.context.spi.CreationalContext; +import jakarta.inject.Scope; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Specifies that a bean belongs to the prototype pseudo-scope. + *

+ * When a bean is declared to have the {@code @Prototype} scope: + *

    + *
  • Each injection point or dynamic lookup receives a new instance; instances are never shared.
  • + *
  • Lifecycle of instances is not managed by the CDI container.
  • + *
+ *

+ * Every invocation of the {@link Context#get(Contextual, CreationalContext)} operation on the + * context object for the {@code @Prototype} scope returns a new instance of given bean. + *

+ * Every invocation of the {@link Context#get(Contextual)} operation on the context object for the + * {@code @Prototype} scope returns a {@code null} value. + *

+ * The {@code @Prototype} scope is always active. + */ +@Scope +@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD}) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +public @interface Prototype { +} diff --git a/impl/src/main/java/org/jboss/cdi/tck/tests/build/compatible/extensions/customPseudoScope/PrototypeBean.java b/impl/src/main/java/org/jboss/cdi/tck/tests/build/compatible/extensions/customPseudoScope/PrototypeBean.java new file mode 100644 index 000000000..447982c31 --- /dev/null +++ b/impl/src/main/java/org/jboss/cdi/tck/tests/build/compatible/extensions/customPseudoScope/PrototypeBean.java @@ -0,0 +1,12 @@ +package org.jboss.cdi.tck.tests.build.compatible.extensions.customPseudoScope; + +import java.util.UUID; + +@PrototypeScoped // @Prototype itself is not a bean defining annotation, as it is a pseudo-scope +public class PrototypeBean { + private final String id = UUID.randomUUID().toString(); + + public String getId() { + return id; + } +} diff --git a/impl/src/main/java/org/jboss/cdi/tck/tests/build/compatible/extensions/customPseudoScope/PrototypeContext.java b/impl/src/main/java/org/jboss/cdi/tck/tests/build/compatible/extensions/customPseudoScope/PrototypeContext.java new file mode 100644 index 000000000..ff67ae5f2 --- /dev/null +++ b/impl/src/main/java/org/jboss/cdi/tck/tests/build/compatible/extensions/customPseudoScope/PrototypeContext.java @@ -0,0 +1,28 @@ +package org.jboss.cdi.tck.tests.build.compatible.extensions.customPseudoScope; + +import jakarta.enterprise.context.spi.AlterableContext; +import jakarta.enterprise.context.spi.Contextual; +import jakarta.enterprise.context.spi.CreationalContext; + +import java.lang.annotation.Annotation; + +public class PrototypeContext implements AlterableContext { + public Class getScope() { + return Prototype.class; + } + + public T get(Contextual contextual, CreationalContext creationalContext) { + return creationalContext != null ? contextual.create(creationalContext) : null; + } + + public T get(Contextual contextual) { + return null; + } + + public boolean isActive() { + return true; + } + + public void destroy(Contextual contextual) { + } +} diff --git a/impl/src/main/java/org/jboss/cdi/tck/tests/build/compatible/extensions/customPseudoScope/PrototypeScoped.java b/impl/src/main/java/org/jboss/cdi/tck/tests/build/compatible/extensions/customPseudoScope/PrototypeScoped.java new file mode 100644 index 000000000..8f9e4725b --- /dev/null +++ b/impl/src/main/java/org/jboss/cdi/tck/tests/build/compatible/extensions/customPseudoScope/PrototypeScoped.java @@ -0,0 +1,15 @@ +package org.jboss.cdi.tck.tests.build.compatible.extensions.customPseudoScope; + +import jakarta.enterprise.inject.Stereotype; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Prototype +@Stereotype +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface PrototypeScoped { +} diff --git a/impl/src/main/java/org/jboss/cdi/tck/tests/build/compatible/extensions/customPseudoScope/RequestScopedBean.java b/impl/src/main/java/org/jboss/cdi/tck/tests/build/compatible/extensions/customPseudoScope/RequestScopedBean.java new file mode 100644 index 000000000..4310e9391 --- /dev/null +++ b/impl/src/main/java/org/jboss/cdi/tck/tests/build/compatible/extensions/customPseudoScope/RequestScopedBean.java @@ -0,0 +1,14 @@ +package org.jboss.cdi.tck.tests.build.compatible.extensions.customPseudoScope; + +import jakarta.enterprise.context.RequestScoped; +import jakarta.inject.Inject; + +@RequestScoped +public class RequestScopedBean { + @Inject + PrototypeBean prototype; + + public String getPrototypeId() { + return prototype.getId(); + } +}