Allow user-provided CDI extensions to veto JAX-RS classes (#2429)

* Allow user-provided CDI extensions to veto JAX-RS classes so that they are not included as part of an application during discovery. Changed JaxRsCdiExtension to use a different event to process classes. Updated the bookstore-mp test to verify the veto process and also modernized its tests.

Signed-off-by: Santiago Pericasgeertsen <santiago.pericasgeertsen@oracle.com>

* Fixed checkstyle issues.

Signed-off-by: Santiago Pericasgeertsen <santiago.pericasgeertsen@oracle.com>
This commit is contained in:
Santiago Pericasgeertsen
2020-10-08 16:00:11 -04:00
committed by GitHub
parent 2a992422f9
commit b1ddbe8828
7 changed files with 154 additions and 65 deletions

View File

@@ -34,8 +34,7 @@ import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.context.Initialized;
import javax.enterprise.event.Observes;
import javax.enterprise.inject.spi.Extension;
import javax.enterprise.inject.spi.ProcessAnnotatedType;
import javax.enterprise.inject.spi.WithAnnotations;
import javax.enterprise.inject.spi.ProcessManagedBean;
import javax.ws.rs.Path;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Application;
@@ -64,30 +63,34 @@ public class JaxRsCdiExtension implements Extension {
private final Set<Class<?>> providers = new HashSet<>();
private final AtomicBoolean setInStone = new AtomicBoolean(false);
private void collectApplications(@Observes ProcessAnnotatedType<? extends Application> applicationType) {
applications.add(applicationType.getAnnotatedType().getJavaClass());
private void collectApplications(@Observes ProcessManagedBean<? extends Application> processManagedBean) {
applications.add(processManagedBean.getAnnotatedBeanClass().getJavaClass());
}
private void collectResourceClasses(@Observes @WithAnnotations(Path.class) ProcessAnnotatedType<?> resourceType) {
Class<?> resourceClass = resourceType.getAnnotatedType().getJavaClass();
if (resourceClass.isInterface()) {
// we are only interested in classes - interface is most likely a REST client API
return;
private void collectResourceClasses(@Observes ProcessManagedBean<?> processManagedBean) {
if (processManagedBean.getAnnotated().isAnnotationPresent(Path.class)) {
Class<?> resourceClass = processManagedBean.getAnnotatedBeanClass().getJavaClass();
if (resourceClass.isInterface()) {
// we are only interested in classes - interface is most likely a REST client API
return;
}
LOGGER.finest(() -> "Discovered resource class " + resourceClass.getName());
resources.add(resourceClass);
}
LOGGER.finest(() -> "Discovered resource class " + resourceClass.getName());
resources.add(resourceClass);
}
private void collectProviderClasses(@Observes @WithAnnotations(Provider.class) ProcessAnnotatedType<?> providerType) {
Class<?> providerClass = providerType.getAnnotatedType().getJavaClass();
if (providerClass.isInterface()) {
// we are only interested in classes
LOGGER.finest(() -> "Discovered @Provider interface " + providerClass
.getName() + ", ignored as we only support classes");
return;
private void collectProviderClasses(@Observes ProcessManagedBean<?> processManagedBean) {
if (processManagedBean.getAnnotated().isAnnotationPresent(Provider.class)) {
Class<?> providerClass = processManagedBean.getAnnotatedBeanClass().getJavaClass();
if (providerClass.isInterface()) {
// we are only interested in classes
LOGGER.finest(() -> "Discovered @Provider interface " + providerClass
.getName() + ", ignored as we only support classes");
return;
}
LOGGER.finest(() -> "Discovered @Provider class " + providerClass.getName());
providers.add(providerClass);
}
LOGGER.finest(() -> "Discovered @Provider class " + providerClass.getName());
providers.add(providerClass);
}
// once application scoped starts, we do not allow modification of applications

View File

@@ -70,6 +70,11 @@
<artifactId>junit-jupiter-api</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.helidon.microprofile.tests</groupId>
<artifactId>helidon-microprofile-tests-junit5</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>

View File

@@ -0,0 +1,37 @@
/*
* Copyright (c) 2020 Oracle and/or its affiliates.
*
* 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.helidon.tests.apps.bookstore.mp;
import javax.enterprise.event.Observes;
import javax.enterprise.inject.spi.Extension;
import javax.enterprise.inject.spi.ProcessAnnotatedType;
import javax.enterprise.inject.spi.WithAnnotations;
import javax.ws.rs.Path;
/**
* A CDI extension to dynamically veto the resource class {@link VetoedResource}.
* After this class is vetoed, it shall not be part of the application.
*/
public class VetoCdiExtension implements Extension {
private void vetoResourceClass(@Observes @WithAnnotations(Path.class) ProcessAnnotatedType<?> resourceType) {
Class<?> resourceClass = resourceType.getAnnotatedType().getJavaClass();
if (resourceClass == VetoedResource.class) {
resourceType.veto();
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2018, 2019 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2020 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,18 +16,24 @@
package io.helidon.tests.apps.bookstore.mp;
import java.util.Set;
import javax.enterprise.context.RequestScoped;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.enterprise.context.ApplicationScoped;
import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;
/**
* This resource class will be dynamically vetoed by {@link VetoCdiExtension} and
* should not be part of the application after startup.
*/
@Path("/vetoed")
@RequestScoped
public class VetoedResource {
@ApplicationScoped
@ApplicationPath("/")
public class BookApplication extends Application {
@Override
public Set<Class<?>> getClasses() {
return Set.of(BookResource.class);
@GET
@Produces(MediaType.TEXT_PLAIN)
public Response get() {
return Response.ok().build();
}
}

View File

@@ -0,0 +1,17 @@
#
# Copyright (c) 2020 Oracle and/or its affiliates. All rights reserved.
#
# 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.
#
io.helidon.tests.apps.bookstore.mp.VetoCdiExtension

View File

@@ -16,50 +16,40 @@
package io.helidon.tests.apps.bookstore.mp;
import io.helidon.tests.apps.bookstore.common.Book;
import javax.enterprise.inject.se.SeContainer;
import javax.enterprise.inject.spi.CDI;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.inject.Inject;
import javax.ws.rs.client.Entity;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.Response;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.stream.Collectors;
import io.helidon.microprofile.server.Server;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import io.helidon.microprofile.tests.junit5.HelidonTest;
import io.helidon.tests.apps.bookstore.common.Book;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
@HelidonTest
class BookResourceTest {
private static Server server;
private static Client client = ClientBuilder.newClient();
@BeforeAll
public static void startTheServer() {
server = Main.startServer();
}
@Inject
private WebTarget webTarget;
@Test
void testBooks() {
assertBookStoreSize(0);
Response res = client.target(getConnectionString("/books"))
Response res = webTarget.path("/books")
.request()
.post(Entity.json(getBookAsJson()));
assertEquals(Response.Status.OK.getStatusCode(), res.getStatus());
assertBookStoreSize(1);
res = client.target(getConnectionString("/books/123456"))
res = webTarget.path("/books/123456")
.request()
.get();
assertEquals(Response.Status.OK.getStatusCode(), res.getStatus());
@@ -67,14 +57,14 @@ class BookResourceTest {
assertBookStoreSize(1);
res = client.target(getConnectionString("/books/123456"))
res = webTarget.path("/books/123456")
.request()
.put(Entity.json(getBookAsJson()));
assertEquals(Response.Status.OK.getStatusCode(), res.getStatus());
assertBookStoreSize(1);
res = client.target(getConnectionString("/books/123456"))
res = webTarget.path("/books/123456")
.request()
.delete();
assertEquals(Response.Status.OK.getStatusCode(), res.getStatus());
@@ -82,16 +72,6 @@ class BookResourceTest {
assertBookStoreSize(0);
}
@AfterAll
static void destroyClass() {
CDI<Object> current = CDI.current();
((SeContainer) current).close();
}
private String getConnectionString(String path) {
return "http://localhost:" + server.port() + path;
}
private String getBookAsJson() {
InputStream is = getClass().getClassLoader().getResourceAsStream("book.json");
if (is != null) {
@@ -102,8 +82,7 @@ class BookResourceTest {
}
private void assertBookStoreSize(int size) {
Book[] jsonArray = client
.target(getConnectionString("/books"))
Book[] jsonArray = webTarget.path("/books")
.request()
.get(Book[].class);
assertEquals(size, jsonArray.length);

View File

@@ -0,0 +1,42 @@
/*
* Copyright (c) 2020 Oracle and/or its affiliates.
*
* 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.helidon.tests.apps.bookstore.mp;
import javax.inject.Inject;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.Response;
import io.helidon.microprofile.tests.junit5.HelidonTest;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
/**
* Test that the resource class {@link VetoedResource} is not part
* of the application after it has been vetoed by {@link VetoCdiExtension}.
*/
@HelidonTest
class VetoedResourceTest {
@Inject
private WebTarget webTarget;
@Test
void testVetoed() {
Response res = webTarget.path("/vetoed").request().get();
assertEquals(Response.Status.NOT_FOUND.getStatusCode(), res.getStatus());
}
}