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

feature(bigtable): added bigtable client creation #508

Closed
wants to merge 7 commits into from
Closed
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
26 changes: 26 additions & 0 deletions bigtable/api/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>quarkus-google-cloud-bigtable-parent</artifactId>
<groupId>io.quarkiverse.googlecloudservices</groupId>
<version>2.6.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>quarkus-google-cloud-bigtable-api</artifactId>
<name>Quarkus - Google Cloud Services - Bigtable - API</name>

<dependencies>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-arc</artifactId>
</dependency>
<dependency>
<groupId>jakarta.enterprise</groupId>
<artifactId>jakarta.enterprise.cdi-api</artifactId>
</dependency>
</dependencies>

</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package io.quarkiverse.googlecloudservice.bigtable.api;

import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import jakarta.enterprise.util.AnnotationLiteral;
import jakarta.inject.Qualifier;

/**
* Qualifies an injected Bigtable client.
*/
@Qualifier
@Target({ FIELD, PARAMETER })
@Retention(RUNTIME)
public @interface BigtableClient {

/**
* Constant value for {@link #value()} indicating that the annotated element's name should be used as-is.
*/
String ELEMENT_NAME = "<<element name>>";

/**
* The name is used to configure the Bigtable client, e.g. the instance, project, etc.
*
* @return the client name
*/
String value() default ELEMENT_NAME;

final class Literal extends AnnotationLiteral<BigtableClient> implements BigtableClient {

private static final long serialVersionUID = 1L;

private final String value;

/**
* Creates a new instance of {@link Literal}.
*
* @param value the name of the Bigtable service, must not be {@code null}, must not be {@code blank}
* @return the literal instance.
*/
public static Literal of(String value) {
return new Literal(value);
}

private Literal(String value) {
this.value = value;
}

/**
* @return the service name.
*/
public String value() {
return value;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package io.quarkiverse.googlecloudservices.bigtable.deployment;

import org.jboss.jandex.AnnotationInstance;
import org.jboss.jandex.DotName;

import com.google.cloud.bigtable.data.v2.BigtableDataClient;

import io.quarkiverse.googlecloudservice.bigtable.api.BigtableClient;
import io.quarkiverse.googlecloudservices.bigtable.runtime.BigtableClients;
import io.quarkus.gizmo.MethodDescriptor;

public class BigTableDotNames {

public static final DotName BIGTABLE_CLIENT = DotName.createSimple(BigtableClient.class.getName());
public static final DotName DATA_CLIENT = DotName.createSimple(BigtableDataClient.class.getName());
public static final MethodDescriptor CREATE_CLIENT_METHOD = MethodDescriptor.ofMethod(BigtableClients.class, "createClient",
BigtableDataClient.class, String.class);

static boolean isBigtableClient(AnnotationInstance instance) {
return instance.name().equals(BIGTABLE_CLIENT);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package io.quarkiverse.googlecloudservices.bigtable.deployment;

import java.util.HashSet;
import java.util.Objects;
import java.util.Set;

import org.jboss.jandex.DotName;

import io.quarkus.builder.item.MultiBuildItem;

public final class BigtableBuildItem extends MultiBuildItem {

private final String clientName;
private final Set<ClientInfo> clients;

public BigtableBuildItem(String name) {
this.clientName = name;
this.clients = new HashSet<>();
}

public Set<ClientInfo> getClients() {
return clients;
}

public void addClient(ClientInfo client) {
clients.add(client);
clients.add(new ClientInfo(BigTableDotNames.DATA_CLIENT));
}

public String getClientName() {
return clientName;
}

public static final class ClientInfo {

public final DotName className;
public final DotName implName;

public ClientInfo(DotName className) {
this(className, null);
}

public ClientInfo(DotName className, DotName implName) {
this.className = className;
this.implName = implName;
}

@Override
public int hashCode() {
return Objects.hash(className);
}

@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
ClientInfo other = (ClientInfo) obj;
return Objects.equals(className, other.className);
}

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
package io.quarkiverse.googlecloudservices.bigtable.deployment;

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

import jakarta.enterprise.inject.spi.DeploymentException;
import jakarta.inject.Singleton;

import org.jboss.jandex.AnnotationInstance;
import org.jboss.jandex.AnnotationTarget;
import org.jboss.jandex.AnnotationValue;
import org.jboss.jandex.MethodParameterInfo;
import org.jboss.jandex.Type;
import org.jboss.logging.Logger;

import com.google.cloud.bigtable.data.v2.BigtableDataClient;

import io.quarkiverse.googlecloudservice.bigtable.api.BigtableClient;
import io.quarkiverse.googlecloudservices.bigtable.runtime.BigtableConfigProvider;
import io.quarkus.arc.deployment.AdditionalBeanBuildItem;
import io.quarkus.arc.deployment.BeanDiscoveryFinishedBuildItem;
import io.quarkus.arc.deployment.InjectionPointTransformerBuildItem;
import io.quarkus.arc.deployment.SyntheticBeanBuildItem;
import io.quarkus.arc.processor.Annotations;
import io.quarkus.arc.processor.InjectionPointInfo;
import io.quarkus.arc.processor.InjectionPointsTransformer;
import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.deployment.builditem.CombinedIndexBuildItem;
import io.quarkus.deployment.builditem.FeatureBuildItem;
import io.quarkus.gizmo.MethodCreator;
import io.quarkus.gizmo.ResultHandle;

public class BigtableClientProcessor {

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

private static final String FEATURE = "google-cloud-bigtable-client";

@BuildStep
public void registerBeans(BuildProducer<AdditionalBeanBuildItem> beans) {
beans.produce(new AdditionalBeanBuildItem(BigtableClient.class));
beans.produce(AdditionalBeanBuildItem.builder().setUnremovable().addBeanClasses(BigtableConfigProvider.class).build());
}

@BuildStep
public void discoverInjectedBeans(BeanDiscoveryFinishedBuildItem beanDiscovery,
BuildProducer<BigtableBuildItem> clients,
BuildProducer<FeatureBuildItem> features,
CombinedIndexBuildItem index) {
Map<String, BigtableBuildItem> items = new HashMap<>();
for (InjectionPointInfo injectionPoint : beanDiscovery.getInjectionPoints()) {
AnnotationInstance clientAnnotation = injectionPoint.getRequiredQualifier(BigTableDotNames.BIGTABLE_CLIENT);
if (clientAnnotation == null) {
continue;
}
String clientName;
AnnotationValue clientNameValue = clientAnnotation.value();
if (clientNameValue == null || clientNameValue.asString().equals(BigtableClient.ELEMENT_NAME)) {
// Determine the service name from the annotated element
if (clientAnnotation.target().kind() == AnnotationTarget.Kind.FIELD) {
clientName = clientAnnotation.target().asField().name();
} else if (clientAnnotation.target().kind() == AnnotationTarget.Kind.METHOD_PARAMETER) {
MethodParameterInfo param = clientAnnotation.target().asMethodParameter();
clientName = param.method().parameterName(param.position());
if (clientName == null) {
throw new DeploymentException("Unable to determine the client name from the parameter at position "
+ param.position()
+ " in method "
+ param.method().declaringClass().name() + "#" + param.method().name()
+
"() - compile the class with debug info enabled (-g) or parameter names recorded (-parameters), or use BigtableClient#value() to specify the service name");
}
} else {
// This should never happen because @BigtableClient has @Target({ FIELD, PARAMETER })
throw new IllegalStateException(clientAnnotation + " may not be declared at " + clientAnnotation.target());
}
} else {
clientName = clientNameValue.asString();
}
if (clientName.trim().isEmpty()) {
throw new DeploymentException(
"Invalid @BigtableClient `" + injectionPoint.getTargetInfo() + "` - client name cannot be empty");
}
BigtableBuildItem item;
if (items.containsKey(clientName)) {
item = items.get(clientName);
} else {
item = new BigtableBuildItem(clientName);
items.put(clientName, item);
}
Type injectionType = injectionPoint.getRequiredType();
if (injectionType.name().equals(BigTableDotNames.DATA_CLIENT)) {
item.addClient(new BigtableBuildItem.ClientInfo(BigTableDotNames.DATA_CLIENT));
}
}
if (!items.isEmpty()) {
for (BigtableBuildItem item : items.values()) {
clients.produce(item);
LOGGER.debugf("Detected client associated with the '%s' configuration prefix", item.getClientName());
}
features.produce(new FeatureBuildItem(FEATURE));
}
}

@BuildStep
public void generateBigtableClientProducers(List<BigtableBuildItem> clients,
BuildProducer<SyntheticBeanBuildItem> syntheticBeans) {
for (BigtableBuildItem client : clients) {
for (BigtableBuildItem.ClientInfo clientInfo : client.getClients()) {
String clientName = client.getClientName();
SyntheticBeanBuildItem.ExtendedBeanConfigurator configurator = SyntheticBeanBuildItem
.configure(clientInfo.className)
.addQualifier().annotation(BigTableDotNames.BIGTABLE_CLIENT).addValue("value", clientName).done()
.scope(Singleton.class)
.unremovable()
.forceApplicationClass()
.addType(BigtableDataClient.class)
.creator(new Consumer<MethodCreator>() {
@Override
public void accept(MethodCreator mc) {
BigtableClientProcessor.this.generateClientProducer(mc, clientName);
}
});
syntheticBeans.produce(configurator.done());
}
}
}

@BuildStep
InjectionPointTransformerBuildItem transformInjectionPoints() {
return new InjectionPointTransformerBuildItem(new InjectionPointsTransformer() {

@Override
public void transform(TransformationContext ctx) {
AnnotationInstance clientAnnotation = Annotations.find(ctx.getQualifiers(), BigTableDotNames.BIGTABLE_CLIENT);
if (clientAnnotation != null && clientAnnotation.value() == null) {
String clientName = null;
if (ctx.getTarget().kind() == AnnotationTarget.Kind.FIELD) {
clientName = clientAnnotation.target().asField().name();
} else if (ctx.getTarget().kind() == AnnotationTarget.Kind.METHOD_PARAMETER) {
MethodParameterInfo param = clientAnnotation.target().asMethodParameter();
// We don't need to check if parameter names are recorded - that's validated elsewhere
clientName = param.method().parameterName(param.position());
}
if (clientName != null) {
ctx.transform().remove(BigTableDotNames::isBigtableClient)
.add(BigTableDotNames.BIGTABLE_CLIENT, AnnotationValue.createStringValue("value", clientName))
.done();
}
}
}

@Override
public boolean appliesTo(Type requiredType) {
return true;
}

});
}

private void generateClientProducer(MethodCreator mc, String clientName) {
ResultHandle name = mc.load(clientName);
ResultHandle result = mc.invokeStaticMethod(BigTableDotNames.CREATE_CLIENT_METHOD, name);
mc.returnValue(result);
mc.close();
}

}
3 changes: 2 additions & 1 deletion bigtable/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,10 @@
<packaging>pom</packaging>

<modules>
<module>api</module>
<module>runtime</module>
<module>deployment</module>
</modules>


</project>
</project>
6 changes: 5 additions & 1 deletion bigtable/runtime/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@
<description>Use Google Cloud Bigtable key/value database service</description>

<dependencies>
<dependency>
<groupId>io.quarkiverse.googlecloudservices</groupId>
<artifactId>quarkus-google-cloud-bigtable-api</artifactId>
</dependency>
<dependency>
<groupId>io.quarkiverse.googlecloudservices</groupId>
<artifactId>quarkus-google-cloud-common</artifactId>
Expand Down Expand Up @@ -92,4 +96,4 @@
</plugins>
</build>

</project>
</project>
Loading
Loading