Skip to content

Commit

Permalink
Fix broken MongoClientBuildItem support
Browse files Browse the repository at this point in the history
Fixes: #7378
  • Loading branch information
geoand committed Feb 24, 2020
1 parent 19fc4cc commit 15e2f32
Show file tree
Hide file tree
Showing 8 changed files with 216 additions and 52 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package io.quarkus.mongodb.deployment;

import io.quarkus.builder.item.MultiBuildItem;
import io.quarkus.mongodb.runtime.MongoClientName;

/**
* Represents the values of the {@link MongoClientName}
*/
final class MongoClientNameBuildItem extends MultiBuildItem {

private final String name;

public MongoClientNameBuildItem(String name) {
this.name = name;
}

public String getName() {
return name;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

Expand Down Expand Up @@ -37,8 +37,10 @@
import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.deployment.annotations.Record;
import io.quarkus.deployment.annotations.Weak;
import io.quarkus.deployment.builditem.ApplicationArchivesBuildItem;
import io.quarkus.deployment.builditem.CombinedIndexBuildItem;
import io.quarkus.deployment.builditem.ConfigurationBuildItem;
import io.quarkus.deployment.builditem.ExtensionSslNativeSupportBuildItem;
import io.quarkus.deployment.builditem.FeatureBuildItem;
import io.quarkus.deployment.builditem.SslNativeConfigBuildItem;
Expand Down Expand Up @@ -73,23 +75,6 @@ UnremovableBeanBuildItem markBeansAsUnremovable() {
return new UnremovableBeanBuildItem(new UnremovableBeanBuildItem.BeanTypeExclusion(UNREMOVABLE_BEAN));
}

@Record(RUNTIME_INIT)
@BuildStep
void configureRuntimeProperties(MongoClientRecorder recorder,
CodecProviderBuildItem codecProvider,
BsonDiscriminatorBuildItem bsonDiscriminator,
MongodbConfig config,
List<MongoConnectionPoolListenerBuildItem> connectionPoolListenerProvider) {
List<ConnectionPoolListener> poolListenerList = connectionPoolListenerProvider.stream()
.map(MongoConnectionPoolListenerBuildItem::getConnectionPoolListener)
.collect(Collectors.toList());

recorder.configureRuntimeProperties(codecProvider.getCodecProviderClassNames(),
bsonDiscriminator.getBsonDiscriminatorClassNames(),
config,
poolListenerList);
}

@BuildStep
CodecProviderBuildItem collectCodecProviders(CombinedIndexBuildItem indexBuildItem) {
Collection<ClassInfo> codecProviderClasses = indexBuildItem.getIndex()
Expand Down Expand Up @@ -120,6 +105,20 @@ List<ReflectiveClassBuildItem> addCodecsAndDiscriminatorsToNative(CodecProviderB
.collect(Collectors.toList());
}

@BuildStep
public void mongoClientNames(ApplicationArchivesBuildItem applicationArchivesBuildItem,
BuildProducer<MongoClientNameBuildItem> mongoClientName) {
Set<String> values = new HashSet<>();
IndexView indexView = applicationArchivesBuildItem.getRootArchive().getIndex();
Collection<AnnotationInstance> mongoClientAnnotations = indexView.getAnnotations(MONGOCLIENT_ANNOTATION);
for (AnnotationInstance annotation : mongoClientAnnotations) {
values.add(annotation.value().asString());
}
for (String value : values) {
mongoClientName.produce(new MongoClientNameBuildItem(value));
}
}

/**
* Create a producer bean managing the lifecycle of the MongoClient.
* <p>
Expand Down Expand Up @@ -166,31 +165,26 @@ List<ReflectiveClassBuildItem> addCodecsAndDiscriminatorsToNative(CodecProviderB
* }
* </pre>
*/
private void createMongoClientProducerBean(ApplicationArchivesBuildItem applicationArchivesBuildItem,
private void createMongoClientProducerBean(List<MongoClientNameBuildItem> mongoClientNames,
BuildProducer<GeneratedBeanBuildItem> generatedBean,
String mongoClientProducerClassName) {
String mongoClientProducerClassName, boolean makeUnremovable) {

Set<String> mongoClientNames = new HashSet<>();
IndexView indexView = applicationArchivesBuildItem.getRootArchive().getIndex();
Collection<AnnotationInstance> mongoClientAnnotations = indexView.getAnnotations(MONGOCLIENT_ANNOTATION);
for (AnnotationInstance annotation : mongoClientAnnotations) {
String mongoClientName = annotation.value().asString();
mongoClientNames.add(mongoClientName);
}
ClassOutput classOutput = new GeneratedBeanGizmoAdaptor(generatedBean);

try (ClassCreator classCreator = ClassCreator.builder().classOutput(classOutput)
.className(mongoClientProducerClassName)
.superClass(AbstractMongoClientProducer.class)
.build()) {
classCreator.addAnnotation(ApplicationScoped.class);
classCreator.addAnnotation(Unremovable.class);

try (MethodCreator defaultMongoClientMethodCreator = classCreator.getMethodCreator("createDefaultMongoClient",
MongoClient.class)) {
defaultMongoClientMethodCreator.addAnnotation(ApplicationScoped.class);
defaultMongoClientMethodCreator.addAnnotation(Produces.class);
defaultMongoClientMethodCreator.addAnnotation(Default.class);
if (makeUnremovable) {
defaultMongoClientMethodCreator.addAnnotation(Unremovable.class);
}

ResultHandle mongoClientConfig = defaultMongoClientMethodCreator.invokeVirtualMethod(
MethodDescriptor.ofMethod(AbstractMongoClientProducer.class, "getDefaultMongoClientConfig",
Expand All @@ -208,7 +202,7 @@ private void createMongoClientProducerBean(ApplicationArchivesBuildItem applicat
mongoClientConfig, defaultMongoClientNameRH));
}

// Default Legacy reactive client.
// Default Legacy reactive client - this is never made unremovable since it's not part of MongoClientBuildItem
try (MethodCreator defaultReactiveMongoClientMethodCreator = classCreator.getMethodCreator(
"createDefaultLegacyReactiveMongoClient",
ReactiveMongoClient.class)) {
Expand Down Expand Up @@ -239,6 +233,9 @@ private void createMongoClientProducerBean(ApplicationArchivesBuildItem applicat
defaultReactiveMongoClientMethodCreator.addAnnotation(ApplicationScoped.class);
defaultReactiveMongoClientMethodCreator.addAnnotation(Produces.class);
defaultReactiveMongoClientMethodCreator.addAnnotation(Default.class);
if (makeUnremovable) {
defaultReactiveMongoClientMethodCreator.addAnnotation(Unremovable.class);
}

ResultHandle mongoReactiveClientConfig = defaultReactiveMongoClientMethodCreator.invokeVirtualMethod(
MethodDescriptor.ofMethod(AbstractMongoClientProducer.class, "getDefaultMongoClientConfig",
Expand All @@ -256,7 +253,8 @@ private void createMongoClientProducerBean(ApplicationArchivesBuildItem applicat
mongoReactiveClientConfig, defaultReactiveMongoClientNameRH));
}

for (String namedMongoClientName : mongoClientNames) {
for (MongoClientNameBuildItem bi : mongoClientNames) {
String namedMongoClientName = bi.getName();
try (MethodCreator namedMongoClientMethodCreator = classCreator.getMethodCreator(
"createNamedMongoClient_" + HashUtil.sha1(namedMongoClientName),
MongoClient.class)) {
Expand All @@ -268,6 +266,9 @@ private void createMongoClientProducerBean(ApplicationArchivesBuildItem applicat
.addAnnotation(AnnotationInstance.create(MONGOCLIENT_ANNOTATION, null,
new AnnotationValue[] {
AnnotationValue.createStringValue("value", namedMongoClientName) }));
if (makeUnremovable) {
namedMongoClientMethodCreator.addAnnotation(Unremovable.class);
}

ResultHandle namedMongoClientNameRH = namedMongoClientMethodCreator.load(namedMongoClientName);

Expand All @@ -285,7 +286,7 @@ private void createMongoClientProducerBean(ApplicationArchivesBuildItem applicat
namedMongoClientConfig, namedMongoClientNameRH));
}

// Legacy reactive client
// Legacy reactive client - this is never made unremovable since it's not part of MongoClientBuildItem
try (MethodCreator namedReactiveMongoClientMethodCreator = classCreator.getMethodCreator(
"createNamedLegacyReactiveMongoClient_" + HashUtil.sha1(namedMongoClientName),
ReactiveMongoClient.class)) {
Expand Down Expand Up @@ -325,11 +326,15 @@ private void createMongoClientProducerBean(ApplicationArchivesBuildItem applicat
namedReactiveMongoClientMethodCreator.addAnnotation(Produces.class);
namedReactiveMongoClientMethodCreator.addAnnotation(AnnotationInstance.create(DotNames.NAMED, null,
new AnnotationValue[] {
AnnotationValue.createStringValue("value", namedMongoClientName + "reactive") }));
AnnotationValue.createStringValue("value",
namedMongoClientName + MongoClientRecorder.REACTIVE_CLIENT_NAME_SUFFIX) }));
namedReactiveMongoClientMethodCreator
.addAnnotation(AnnotationInstance.create(MONGOCLIENT_ANNOTATION, null,
new AnnotationValue[] {
AnnotationValue.createStringValue("value", namedMongoClientName) }));
if (makeUnremovable) {
namedReactiveMongoClientMethodCreator.addAnnotation(Unremovable.class);
}

ResultHandle namedReactiveMongoClientNameRH = namedReactiveMongoClientMethodCreator
.load(namedMongoClientName);
Expand All @@ -355,18 +360,20 @@ private void createMongoClientProducerBean(ApplicationArchivesBuildItem applicat
@Record(STATIC_INIT)
@BuildStep
BeanContainerListenerBuildItem build(
ApplicationArchivesBuildItem applicationArchivesBuildItem,
List<MongoClientNameBuildItem> mongoClientNames,
RecorderContext recorderContext,
MongoClientRecorder recorder,
BuildProducer<FeatureBuildItem> feature,
Optional<MongoUnremovableClientsBuildItem> mongoUnremovableClientsBuildItem,
SslNativeConfigBuildItem sslNativeConfig, BuildProducer<ExtensionSslNativeSupportBuildItem> sslNativeSupport,
BuildProducer<GeneratedBeanBuildItem> generatedBean) throws Exception {

feature.produce(new FeatureBuildItem(FeatureBuildItem.MONGODB_CLIENT));
sslNativeSupport.produce(new ExtensionSslNativeSupportBuildItem(FeatureBuildItem.MONGODB_CLIENT));

String mongoClientProducerClassName = getMongoClientProducerClassName();
createMongoClientProducerBean(applicationArchivesBuildItem, generatedBean, mongoClientProducerClassName);
createMongoClientProducerBean(mongoClientNames, generatedBean, mongoClientProducerClassName,
mongoUnremovableClientsBuildItem.isPresent());

return new BeanContainerListenerBuildItem(recorder.addMongoClient(
(Class<? extends AbstractMongoClientProducer>) recorderContext.classProxy(mongoClientProducerClassName),
Expand All @@ -375,21 +382,54 @@ BeanContainerListenerBuildItem build(

@Record(RUNTIME_INIT)
@BuildStep
void build(MongoClientRecorder recorder, BuildProducer<MongoClientBuildItem> mongoClients, MongodbConfig config) {
if (config.mongoClientConfigs != null && !config.mongoClientConfigs.isEmpty()) {
for (Map.Entry<String, MongoClientConfig> namedDataSourceEntry : config.mongoClientConfigs.entrySet()) {
String name = namedDataSourceEntry.getKey();
mongoClients
.produce(new MongoClientBuildItem(recorder.getClient(name), recorder.getReactiveClient(name), name));
}
void configureRuntimePropertiesAndBuildClients(MongoClientRecorder recorder,
CodecProviderBuildItem codecProvider, BsonDiscriminatorBuildItem bsonDiscriminator,
List<MongoConnectionPoolListenerBuildItem> connectionPoolListenerProvider,
List<MongoClientNameBuildItem> mongoClientNames,
MongodbConfig mongodbConfig, ConfigurationBuildItem config,
BuildProducer<MongoConnectionNameBuildItem> mongoConnections) {

List<ConnectionPoolListener> poolListenerList = connectionPoolListenerProvider.stream()
.map(MongoConnectionPoolListenerBuildItem::getConnectionPoolListener)
.collect(Collectors.toList());

recorder.configureRuntimeProperties(codecProvider.getCodecProviderClassNames(),
bsonDiscriminator.getBsonDiscriminatorClassNames(),
mongodbConfig,
poolListenerList);

mongoConnections.produce(new MongoConnectionNameBuildItem(MongoClientRecorder.DEFAULT_MONGOCLIENT_NAME));
for (MongoClientNameBuildItem bi : mongoClientNames) {
mongoConnections.produce(new MongoConnectionNameBuildItem(bi.getName()));
}
if (config.defaultMongoClientConfig != null
&& (config.defaultMongoClientConfig.connectionString.isPresent()
|| !config.defaultMongoClientConfig.hosts.isEmpty())) {
mongoClients.produce(new MongoClientBuildItem(recorder.getClient(MongoClientRecorder.DEFAULT_MONGOCLIENT_NAME),
recorder.getReactiveClient(MongoClientRecorder.DEFAULT_MONGOCLIENT_NAME),
MongoClientRecorder.DEFAULT_MONGOCLIENT_NAME));
}

/**
* We only create the bytecode that returns Mongo clients when MongoClientBuildItem is used
* This is an optimization in order to avoid having to make all mongo client beans unremovable
* by default.
* When the build consumes MongoClientBuildItem, then we need to make the all clients unremovable
* by default, because they are not referenced by CDI injection points
*/
@BuildStep
@Record(value = RUNTIME_INIT, optional = true)
List<MongoClientBuildItem> mongoClients(MongoClientRecorder recorder, List<MongoConnectionNameBuildItem> mongoConnections) {
List<MongoClientBuildItem> result = new ArrayList<>(mongoConnections.size());
for (MongoConnectionNameBuildItem mongoConnection : mongoConnections) {
String name = mongoConnection.getName();
result.add(new MongoClientBuildItem(recorder.getClient(name), recorder.getReactiveClient(name), name));
}
return result;
}

/**
* When MongoClientBuildItem is actually consumed by the build, then we need to make all the mongo beans unremovable
* because they can be potentially used by the consumers
*/
@BuildStep
@Weak
MongoUnremovableClientsBuildItem unremovable(@SuppressWarnings("unused") BuildProducer<MongoClientBuildItem> producer) {
return new MongoUnremovableClientsBuildItem();
}

private String getMongoClientProducerClassName() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package io.quarkus.mongodb.deployment;

import io.quarkus.builder.item.MultiBuildItem;

/**
* Holds a Mongo connection name
*/
final class MongoConnectionNameBuildItem extends MultiBuildItem {

private final String name;

public MongoConnectionNameBuildItem(String name) {
this.name = name;
}

public String getName() {
return name;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package io.quarkus.mongodb.deployment;

import io.quarkus.builder.item.SimpleBuildItem;

/**
* If generated, all the Mongo clients need to be unremovable
*/
final class MongoUnremovableClientsBuildItem extends SimpleBuildItem {

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

import static org.assertj.core.api.Assertions.assertThat;

import java.util.List;
import java.util.function.Consumer;

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 com.mongodb.client.MongoClient;

import io.quarkus.arc.Arc;
import io.quarkus.builder.BuildChainBuilder;
import io.quarkus.deployment.builditem.FeatureBuildItem;
import io.quarkus.mongodb.deployment.MongoClientBuildItem;
import io.quarkus.mongodb.reactive.ReactiveMongoClient;
import io.quarkus.test.QuarkusUnitTest;

public class MongoClientBuildItemConsumerTest {

@RegisterExtension
static QuarkusUnitTest runner = new QuarkusUnitTest()
.setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class).addClasses(MongoTestBase.class))
.withConfigurationResource("default-mongoclient.properties")
.addBuildChainCustomizer(buildCustomizer());

@Test
public void testContainerHasBeans() {
assertThat(Arc.container().instance(MongoClient.class).get()).isNotNull();
assertThat(Arc.container().instance(ReactiveMongoClient.class).get()).isNotNull();
// this one hasn't been made un-removeable because it's not used in MongoClientBuildItem
assertThat(Arc.container().instance(io.quarkus.mongodb.ReactiveMongoClient.class).get()).isNull();
}

protected static Consumer<BuildChainBuilder> buildCustomizer() {
return new Consumer<BuildChainBuilder>() {
// This represents the extension.
@Override
public void accept(BuildChainBuilder builder) {
builder.addBuildStep(context -> {
List<MongoClientBuildItem> mongoClientBuildItems = context.consumeMulti(MongoClientBuildItem.class);
context.produce(new FeatureBuildItem("dummy"));
}).consumes(MongoClientBuildItem.class)
.produces(FeatureBuildItem.class)
.build();
}
};
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.quarkus.mongodb;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;

Expand All @@ -18,7 +19,9 @@

import com.mongodb.client.MongoClient;

import io.quarkus.arc.Arc;
import io.quarkus.mongodb.metrics.ConnectionPoolGauge;
import io.quarkus.mongodb.reactive.ReactiveMongoClient;
import io.quarkus.test.QuarkusUnitTest;

public class MongoMetricsTest extends MongoTestBase {
Expand Down Expand Up @@ -56,6 +59,11 @@ void testMetricsInitialization() {
client.close();
assertEquals(0L, getGaugeValueOrNull("mongodb.connection-pool.size", getTags()));
assertEquals(0L, getGaugeValueOrNull("mongodb.connection-pool.checked-out-count", getTags()));

// doing this here instead of in another method in order to avoid messing with the initialization stats
assertThat(Arc.container().instance(MongoClient.class).get()).isNotNull();
assertThat(Arc.container().instance(ReactiveMongoClient.class).get()).isNull();
assertThat(Arc.container().instance(io.quarkus.mongodb.ReactiveMongoClient.class).get()).isNull();
}

private Long getGaugeValueOrNull(String metricName, Tag[] tags) {
Expand Down
Loading

0 comments on commit 15e2f32

Please sign in to comment.