Skip to content

Commit

Permalink
Merge pull request quarkusio#1440 from mkouba/issue-703-session-context
Browse files Browse the repository at this point in the history
Basic custom CDI contexts support and HttpSessionContext
  • Loading branch information
stuartwdouglas authored Mar 12, 2019
2 parents 67109dc + b720802 commit 6e4b16d
Show file tree
Hide file tree
Showing 47 changed files with 1,159 additions and 300 deletions.
41 changes: 35 additions & 6 deletions docs/src/main/asciidoc/cdi-reference.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,8 @@ public class CounterBean {
** Client proxies
** Injection point metadata footnote:[`InjectionPoint.getMember()` is currently not supported.]
* Scopes and contexts
** `@Dependent`, `@ApplicationScoped`, `@Singleton` and `@RequestScoped`
** `@Dependent`, `@ApplicationScoped`, `@Singleton`, `@RequestScoped` and `@SessionScoped`
** Custom scopes and contexts
* Interceptors
** Business method interceptors: `@AroundInvoke`
** Interceptors for lifecycle event callbacks: `@PostConstruct`, `@PreDestroy`, `@AroundConstruct`
Expand All @@ -106,8 +107,7 @@ public class CounterBean {
[[limitations]]
== Limitations

* `@SessionScoped` and `@ConversationScoped` are not supported
* Custom scopes and contexts are not supported
* `@ConversationScoped` is not supported
* Decorators are not supported
* Portable Extensions are not supported
* `BeanManager` - only the following methods are implemented: `getBeans()`, `createCreationalContext()`, `getReference()`, `resolve()`, `getContext()`, `getEvent()` and `createInstance()`
Expand Down Expand Up @@ -175,11 +175,24 @@ NOTE: A bean registration that is a result of an `AdditionalBeanBuildItem` is re

=== Synthetic Beans

Sometimes it's useful to register a synthetic bean, i.e. a bean that doesn't need to have a corresponding java class.
Sometimes it is very useful to register a synthetic bean, i.e. a bean that doesn't need to have a corresponding java class.
In CDI this could be achieved using `AfterBeanDiscovery.addBean()` methods.
In {project-name} we produce a `BeanRegistrarBuildItem` and leverage the `io.quarkus.arc.processor.BeanConfigurator` API to build a synthetic bean definition.

// TODO add example
[source,java]
----
@BuildStep
BeanRegistrarBuildItem syntheticBean() {
return new BeanRegistrarBuildItem(new BeanRegistrar() {
@Override
public void register(RegistrationContext registrationContext) {
registrationContext.configure(String.class).types(String.class).qualifiers(new MyQualifierLiteral()).creator(mc -> mc.returnValue(mc.load("foo"))).done();
}
}));
}
----


NOTE: The output of a `BeanConfigurator` is recorded as bytecode. Therefore there are some limitations in how a synthetic bean instance is created. See also `BeanConfigurator.creator()` methods.

Expand Down Expand Up @@ -229,6 +242,22 @@ BeanDeploymentValidatorBuildItem beanDeploymentValidator() {

NOTE: See also `io.quarkus.arc.processor.BuildExtension.Key` to discover the available metadata.

=== Custom Contexts

An extension can register a custom `InjectableContext` implementation by means of a `ContextRegistrarBuildItem`:

[source,java]
----
@BuildStep
ContextRegistrarBuildItem customContext() {
return new ContextRegistrarBuildItem(new ContextRegistrar() {
public void register(RegistrationContext registrationContext) {
registrationContext.configure(CustomScoped.class).normal().contextClass(MyCustomContext.class).done();
}
});
}
----

[[remove_unused_beans]]
== Removing Unused Beans

Expand All @@ -243,6 +272,6 @@ An unused bean:
* does not have a name,
* does not declare an observer,
* does not declare any producer which is eligible for injection to any injection point,
* is not directly eligible for injection into any `javax.enterprise.inject.Instance` injection point
* is not directly eligible for injection into any `javax.enterprise.inject.Instance` or `javax.inject.Provider` injection point

The extensions can eliminate possible false positives by producing `UnremovableBeanBuildItem`.
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,9 @@ public class ArcAnnotationProcessor {
@Inject
List<BeanRegistrarBuildItem> beanRegistrars;

@Inject
List<ContextRegistrarBuildItem> contextRegistrars;

@Inject
List<BeanDeploymentValidatorBuildItem> beanDeploymentValidators;

Expand Down Expand Up @@ -218,6 +221,9 @@ public void writeResource(Resource resource) throws IOException {
for (BeanRegistrarBuildItem item : beanRegistrars) {
builder.addBeanRegistrar(item.getBeanRegistrar());
}
for (ContextRegistrarBuildItem item : contextRegistrars) {
builder.addContextRegistrar(item.getContextRegistrar());
}
for (BeanDeploymentValidatorBuildItem item : beanDeploymentValidators) {
builder.addBeanDeploymentValidator(item.getBeanDeploymentValidator());
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* Copyright 2019 Red Hat, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package io.quarkus.arc.deployment;

import org.jboss.builder.item.MultiBuildItem;

import io.quarkus.arc.processor.BeanRegistrar;
import io.quarkus.arc.processor.ContextRegistrar;

public final class ContextRegistrarBuildItem extends MultiBuildItem {

private final ContextRegistrar contextRegistrar;

public ContextRegistrarBuildItem(ContextRegistrar contextRegistrar) {
this.contextRegistrar = contextRegistrar;
}

public ContextRegistrar getContextRegistrar() {
return contextRegistrar;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,8 @@
import io.quarkus.arc.processor.BeanDeploymentValidator;
import io.quarkus.arc.processor.BeanDeploymentValidator.ValidationContext;
import io.quarkus.arc.processor.BeanInfo;
import io.quarkus.arc.processor.BuiltinScope;
import io.quarkus.arc.processor.DotNames;
import io.quarkus.arc.processor.ScopeInfo;
import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.deployment.annotations.Record;
Expand Down Expand Up @@ -282,7 +282,7 @@ private String generateInvoker(BeanInfo bean, MethodInfo method, ClassOutput cla
beanInstanceHandle, invoke.getMethodParam(0));
}
// handle.destroy() - destroy dependent instance afterwards
if (bean.getScope() == ScopeInfo.DEPENDENT) {
if (BuiltinScope.DEPENDENT.is(bean.getScope())) {
invoke.invokeInterfaceMethod(MethodDescriptor.ofMethod(InstanceHandle.class, "destroy", void.class),
instanceHandle);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
import io.quarkus.arc.deployment.BeanRegistrarBuildItem;
import io.quarkus.arc.processor.BeanConfigurator;
import io.quarkus.arc.processor.BeanRegistrar;
import io.quarkus.arc.processor.BuiltinScope;
import io.quarkus.arc.processor.ScopeInfo;
import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.annotations.BuildStep;
Expand Down Expand Up @@ -191,7 +192,7 @@ public void register(RegistrationContext registrationContext) {
// The spec is not clear whether we should add superinterfaces too - let's keep aligned with SmallRye for now
configurator.addType(entry.getKey());
// We use @Singleton here as we do not need another proxy
configurator.scope(ScopeInfo.SINGLETON);
configurator.scope(BuiltinScope.SINGLETON.getInfo());
configurator.addQualifier(REST_CLIENT);
configurator.creator(m -> {
// return new RestClientBase(proxyType).create();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@

import io.quarkus.arc.deployment.AnnotationsTransformerBuildItem;
import io.quarkus.arc.processor.AnnotationsTransformer;
import io.quarkus.arc.processor.BuiltinScope;
import io.quarkus.arc.processor.DotNames;
import io.quarkus.arc.processor.ScopeInfo;
import io.quarkus.arc.processor.Transformation;
Expand Down Expand Up @@ -69,9 +70,9 @@ public class SpringDIProcessor {

private static final DotName VALUE_ANNOTATION = DotName.createSimple("org.springframework.beans.factory.annotation.Value");

private static final DotName CDI_SINGLETON_ANNOTATION = ScopeInfo.SINGLETON.getDotName();
private static final DotName CDI_DEPENDENT_ANNOTATION = ScopeInfo.DEPENDENT.getDotName();
private static final DotName CDI_APP_SCOPED_ANNOTATION = ScopeInfo.APPLICATION.getDotName();
private static final DotName CDI_SINGLETON_ANNOTATION = BuiltinScope.SINGLETON.getInfo().getDotName();
private static final DotName CDI_DEPENDENT_ANNOTATION = BuiltinScope.DEPENDENT.getInfo().getDotName();
private static final DotName CDI_APP_SCOPED_ANNOTATION = BuiltinScope.APPLICATION.getInfo().getDotName();
private static final DotName CDI_NAMED_ANNOTATION = DotNames.NAMED;
private static final DotName CDI_INJECT_ANNOTATION = DotNames.INJECT;
private static final DotName CDI_PRODUCES_ANNOTATION = DotNames.PRODUCES;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* Copyright 2019 Red Hat, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package io.quarkus.undertow.deployment;

import org.jboss.builder.item.MultiBuildItem;

public final class ListenerBuildItem extends MultiBuildItem {

private final String listenerClass;

public ListenerBuildItem(String listenerClass) {
this.listenerClass = listenerClass;
}

public String getListenerClass() {
return listenerClass;
}

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

import javax.annotation.security.DeclareRoles;
import javax.annotation.security.RunAs;
import javax.enterprise.context.SessionScoped;
import javax.inject.Inject;
import javax.servlet.annotation.MultipartConfig;
import javax.servlet.annotation.ServletSecurity;
Expand Down Expand Up @@ -90,6 +91,8 @@

import io.quarkus.arc.deployment.AdditionalBeanBuildItem;
import io.quarkus.arc.deployment.BeanContainerBuildItem;
import io.quarkus.arc.deployment.ContextRegistrarBuildItem;
import io.quarkus.arc.processor.ContextRegistrar;
import io.quarkus.deployment.ApplicationArchive;
import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.annotations.BuildStep;
Expand All @@ -109,6 +112,7 @@
import io.quarkus.runtime.RuntimeValue;
import io.quarkus.runtime.annotations.ConfigItem;
import io.quarkus.undertow.runtime.HttpConfig;
import io.quarkus.undertow.runtime.HttpSessionContext;
import io.quarkus.undertow.runtime.ServletProducer;
import io.quarkus.undertow.runtime.ServletSecurityInfoProxy;
import io.quarkus.undertow.runtime.ServletSecurityInfoSubstitution;
Expand Down Expand Up @@ -157,16 +161,24 @@ public ServiceStartBuildItem boot(UndertowDeploymentTemplate template,
}

@BuildStep
AdditionalBeanBuildItem httpProducers() {
return new AdditionalBeanBuildItem(ServletProducer.class);
void integrateCdi(BuildProducer<AdditionalBeanBuildItem> additionalBeans,
BuildProducer<ContextRegistrarBuildItem> contextRegistrars,
BuildProducer<ListenerBuildItem> listeners) {
additionalBeans.produce(new AdditionalBeanBuildItem(ServletProducer.class));
contextRegistrars.produce(new ContextRegistrarBuildItem(new ContextRegistrar() {
@Override
public void register(RegistrationContext registrationContext) {
registrationContext.configure(SessionScoped.class).normal().contextClass(HttpSessionContext.class).done();
}
}));
listeners.produce(new ListenerBuildItem(HttpSessionContext.class.getName()));
}

@BuildStep
SubstrateConfigBuildItem config() {
return SubstrateConfigBuildItem.builder()
.addRuntimeInitializedClass("io.undertow.server.protocol.ajp.AjpServerResponseConduit")
.addRuntimeInitializedClass("io.undertow.server.protocol.ajp.AjpServerRequestConduit")

.build();
}

Expand Down Expand Up @@ -219,6 +231,7 @@ WebMetadataBuildItem createWebMetadata(ApplicationArchivesBuildItem applicationA
public ServletDeploymentManagerBuildItem build(ApplicationArchivesBuildItem applicationArchivesBuildItem,
List<ServletBuildItem> servlets,
List<FilterBuildItem> filters,
List<ListenerBuildItem> listeners,
List<ServletInitParamBuildItem> initParams,
List<ServletContextAttributeBuildItem> contextParams,
UndertowDeploymentTemplate template, RecorderContext context,
Expand Down Expand Up @@ -416,6 +429,10 @@ public void accept(Path path) {
for (ServletExtensionBuildItem i : extensions) {
template.addServletExtension(deployment, i.getValue());
}
for (ListenerBuildItem i : listeners) {
reflectiveClasses.accept(new ReflectiveClassBuildItem(false, false, i.getListenerClass()));
template.registerListener(deployment, context.classProxy(i.getListenerClass()), bc.getValue());
}
return new ServletDeploymentManagerBuildItem(template.bootServletContainer(deployment, bc.getValue()));

}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package io.quarkus.undertow.test.sessioncontext;

import java.util.concurrent.atomic.AtomicLong;

import javax.annotation.PostConstruct;
import javax.enterprise.context.SessionScoped;

@SessionScoped
class Foo {

AtomicLong counter;

@PostConstruct
void init() {
counter = new AtomicLong();
}

long incrementAndGet() {
return counter.incrementAndGet();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* Copyright 2018 Red Hat, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package io.quarkus.undertow.test.sessioncontext;

import static io.restassured.RestAssured.given;
import static io.restassured.RestAssured.when;
import static org.hamcrest.Matchers.is;
import static org.junit.jupiter.api.Assertions.assertNotEquals;

import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.quarkus.test.QuarkusUnitTest;
import io.restassured.response.Response;

public class SessionContextTestCase {

@RegisterExtension
static QuarkusUnitTest runner = new QuarkusUnitTest()
.setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class).addClasses(TestServlet.class, Foo.class));

@Test
public void testServlet() {
Response response = when().get("/foo");
String sessionId = response.sessionId();
response.then().statusCode(200).body(is("count=1"));
given().sessionId(sessionId).when().get("/foo").then().statusCode(200).body(is("count=2"));
// Destroy session
when().get("/foo?destroy=true").then().statusCode(200);
response = when().get("/foo");
assertNotEquals(sessionId, response.sessionId());
response.then().statusCode(200).body(is("count=1"));
}

}
Loading

0 comments on commit 6e4b16d

Please sign in to comment.