Skip to content

Commit

Permalink
Add DataMapping for creating bean from JsonObject
Browse files Browse the repository at this point in the history
  • Loading branch information
mcruzdev committed Jul 3, 2024
1 parent d8bb371 commit a84021d
Show file tree
Hide file tree
Showing 11 changed files with 247 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,31 +7,48 @@
import java.nio.file.Path;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.jboss.jandex.AnnotationInstance;
import org.jboss.jandex.AnnotationTarget;
import org.jboss.jandex.Index;
import org.jboss.logging.Logger;

import io.quarkiverse.roq.data.deployment.converters.JsonObjectConverter;
import io.quarkiverse.roq.data.deployment.items.RoqDataJsonBuildItem;
import io.quarkiverse.roq.data.deployment.items.RoqDataMappingBuildItem;
import io.quarkiverse.roq.data.runtime.annotations.DataMapping;
import io.quarkiverse.roq.deployment.items.RoqProjectBuildItem;
import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.deployment.builditem.ApplicationIndexBuildItem;
import io.quarkus.deployment.builditem.HotDeploymentWatchedFileBuildItem;

public class ReadRoqDataProcessor {

private static final Set<String> SUPPORTED_EXTENSIONS = Set.of(".json", ".yaml", ".yml");

private static final Logger LOG = Logger.getLogger(ReadRoqDataProcessor.class);
RoqDataConfig roqDataConfig;

@BuildStep
void scanDataFiles(RoqProjectBuildItem roqProject, RoqDataConfig config, BuildProducer<RoqDataJsonBuildItem> dataProducer,
BuildProducer<HotDeploymentWatchedFileBuildItem> watchedFilesProducer) {
BuildProducer<HotDeploymentWatchedFileBuildItem> watchedFilesProducer,
ApplicationIndexBuildItem indexBuildItem, BuildProducer<RoqDataMappingBuildItem> dataMappings) {

if (roqProject.isActive()) {

Index jandex = indexBuildItem.getIndex();
Collection<AnnotationInstance> annotations = jandex.getAnnotations(DataMapping.class);

try {
Collection<RoqDataJsonBuildItem> items = scanDataFiles(roqProject, watchedFilesProducer, config);
Map<String, RoqDataJsonBuildItem> map = scanDataFiles(roqProject, watchedFilesProducer, config);

for (RoqDataJsonBuildItem item : items) {
for (RoqDataJsonBuildItem item : map.values()) {
dataProducer.produce(item);
}

Expand All @@ -42,7 +59,33 @@ void scanDataFiles(RoqProjectBuildItem roqProject, RoqDataConfig config, BuildPr

}

public Collection<RoqDataJsonBuildItem> scanDataFiles(RoqProjectBuildItem roqProject,
@BuildStep
void scanDataMappings(ApplicationIndexBuildItem indexBuildItem,
BuildProducer<RoqDataMappingBuildItem> roqMappings, List<RoqDataJsonBuildItem> roqDataJsonBuildItems,
RoqDataConfig config) {
Index jandex = indexBuildItem.getIndex();
Collection<AnnotationInstance> annotations = jandex.getAnnotations(DataMapping.class);

Map<String, Object> dataMap = roqDataJsonBuildItems.stream()
.collect(Collectors.toMap(RoqDataJsonBuildItem::getName, RoqDataJsonBuildItem::getData));

for (AnnotationInstance annotation : annotations) {
AnnotationTarget target = annotation.target();
String name = annotation.value().asString();

if (dataMap.containsKey(name)) {
Object jsonObject = dataMap.get(name);
String className = target.asClass().name().toString();
roqMappings.produce(new RoqDataMappingBuildItem(name, className, jsonObject));
} else if (config.enforceBean()) {
throw new IllegalArgumentException("Data file not found for data mapping: %s.".formatted(name));
} else {
LOG.warn("Data file not found for data mapping: " + name);
}
}
}

public Map<String, RoqDataJsonBuildItem> scanDataFiles(RoqProjectBuildItem roqProject,
BuildProducer<HotDeploymentWatchedFileBuildItem> watchedFilesProducer, RoqDataConfig config)
throws IOException {

Expand All @@ -59,7 +102,7 @@ public Collection<RoqDataJsonBuildItem> scanDataFiles(RoqProjectBuildItem roqPro
}
});

return items.values();
return items;
}

private static Consumer<Path> addRoqDataJsonBuildItem(BuildProducer<HotDeploymentWatchedFileBuildItem> watchedFilesProducer,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import java.util.Objects;

import io.quarkiverse.roq.data.runtime.annotations.DataMapping;
import io.quarkus.runtime.annotations.ConfigPhase;
import io.quarkus.runtime.annotations.ConfigRoot;
import io.smallrye.config.ConfigMapping;
Expand All @@ -19,10 +20,17 @@ public interface RoqDataConfig {
@WithDefault(DEFAULT_LOCATION)
String dir();

/**
* Whether to enforce the use of a bean for each data file.
* <br>
* With this option enabled, when a record is annotated with {@link DataMapping}, a bean will be created and populated
* with the data from the file.
*/
@WithDefault("false")
boolean enforceBean();

static boolean isEqual(RoqDataConfig q1, RoqDataConfig q2) {
if (!Objects.equals(q1.dir(), q2.dir())) {
return false;
}
return true;
return Objects.equals(q1.dir(), q2.dir()) && Objects.equals(q1.enforceBean(), q2.enforceBean());
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,16 @@
import java.util.List;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.inject.Default;
import jakarta.inject.Named;
import jakarta.inject.Singleton;

import org.jboss.jandex.DotName;
import org.jboss.logging.Logger;

import io.quarkiverse.roq.data.deployment.items.RoqDataJsonBuildItem;
import io.quarkiverse.roq.data.deployment.items.RoqDataMappingBuildItem;
import io.quarkiverse.roq.data.runtime.RoqDataRecorder;
import io.quarkus.arc.deployment.SyntheticBeanBuildItem;
import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.annotations.BuildStep;
Expand All @@ -31,7 +36,7 @@ FeatureBuildItem feature() {
@Record(ExecutionTime.RUNTIME_INIT)
void generateInjectable(BuildProducer<SyntheticBeanBuildItem> beansProducer,
List<RoqDataJsonBuildItem> roqDataJsonBuildItems,
io.quarkiverse.roq.data.runtime.RoqDataRecorder recorder) {
RoqDataRecorder recorder) {

for (RoqDataJsonBuildItem roqData : roqDataJsonBuildItems) {
LOGGER.info("Creating synthetic bean with identifier " + roqData.getName());
Expand All @@ -54,4 +59,23 @@ void generateInjectable(BuildProducer<SyntheticBeanBuildItem> beansProducer,
}
}

@BuildStep
@Record(ExecutionTime.RUNTIME_INIT)
void generateDataMappings(RoqDataRecorder roqDataRecorder, List<RoqDataMappingBuildItem> roqDataMappings,
BuildProducer<SyntheticBeanBuildItem> beansProducer) throws ClassNotFoundException {
for (RoqDataMappingBuildItem roqDataMapping : roqDataMappings) {

JsonObject jsonObject = JsonObject.mapFrom(roqDataMapping.getJsonObject());

Object object = jsonObject.mapTo(Class.forName(roqDataMapping.getClassName()));

beansProducer.produce(SyntheticBeanBuildItem.configure(DotName.createSimple(roqDataMapping.getClassName()))
.scope(Singleton.class)
.addQualifier().annotation(Default.class).done()
.setRuntimeInit()
.runtimeValue(roqDataRecorder.createRoqDataJson(object))
.done());
}
}

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.quarkiverse.roq.data.deployment;
package io.quarkiverse.roq.data.deployment.converters;

import io.quarkiverse.roq.data.deployment.DataConverter;
import io.vertx.core.json.Json;

public class JsonConverter implements DataConverter {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
package io.quarkiverse.roq.data.deployment;
package io.quarkiverse.roq.data.deployment.converters;

import io.quarkiverse.roq.data.deployment.DataConverter;

public class JsonObjectConverter {

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package io.quarkiverse.roq.data.deployment;
package io.quarkiverse.roq.data.deployment.converters;

import java.io.IOException;
import java.io.UncheckedIOException;
Expand All @@ -9,6 +9,7 @@
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;

import io.quarkiverse.roq.data.deployment.DataConverter;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package io.quarkiverse.roq.data.deployment.items;

import io.quarkus.builder.item.MultiBuildItem;

public final class RoqDataMappingBuildItem extends MultiBuildItem {

private final String name;
private final String className;
private final Object jsonObject;

public RoqDataMappingBuildItem(String name, String className, Object jsonObject) {
this.name = name;
this.className = className;
this.jsonObject = jsonObject;
}

public String getName() {
return name;
}

public String getClassName() {
return className;
}

public Object getJsonObject() {
return this.jsonObject;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package io.quarkiverse.roq.data.test;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.inject.Named;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;

import org.hamcrest.Matchers;
import org.jboss.shrinkwrap.api.asset.StringAsset;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.quarkiverse.roq.data.test.util.Foo;
import io.quarkus.test.QuarkusUnitTest;
import io.restassured.RestAssured;
import io.vertx.core.json.JsonObject;

public class RoqDataBindingEnforceBeanTest {

@RegisterExtension
static final QuarkusUnitTest quarkusUnitTest = new QuarkusUnitTest()
.withApplicationRoot((jar) -> jar
.addClass(Foo.class)
.add(new StringAsset("quarkus.roq.site-dir=src/test/site\nquarkus.roq.data.enforce-bean=true"),
"application.properties"));

@Test
public void foo() {
RestAssured.given()
.get("/bar")
.then()
.statusCode(200)
.body(Matchers.equalTo("Super Heroes from Yaml"));
}

@ApplicationScoped
@Path("/bar")
public static class PersonResource {

@Inject
@Named("bar")
JsonObject bar;

@GET
public String getFoo() {
return bar.getString("name");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package io.quarkiverse.roq.data.test;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;

import org.hamcrest.Matchers;
import org.jboss.shrinkwrap.api.asset.StringAsset;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.quarkiverse.roq.data.test.util.Foo;
import io.quarkus.test.QuarkusUnitTest;
import io.restassured.RestAssured;

public class RoqDataBindingTest {

@RegisterExtension
static final QuarkusUnitTest quarkusUnitTest = new QuarkusUnitTest()
.withApplicationRoot((jar) -> jar
.addClass(Foo.class)
.add(new StringAsset("quarkus.roq.site-dir=src/test/site"),
"application.properties"));

@Test
public void foo() {
RestAssured.given()
.get("/foo")
.then()
.statusCode(200)
.body(Matchers.equalTo("Super Heroes from Json"));
}

@ApplicationScoped
@Path("/foo")
public static class PersonResource {

@Inject
Foo foo;

@GET
public String getFoo() {
return foo.toString();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package io.quarkiverse.roq.data.test.util;

import io.quarkiverse.roq.data.runtime.annotations.DataMapping;

@DataMapping("foo")
public record Foo(String name) {
@Override
public String toString() {
// Original is Foo[name=Super Heroes from Json]
return this.name;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package io.quarkiverse.roq.data.runtime.annotations;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Annotation used to indicate that a class is a data mapping.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE })
@Documented
public @interface DataMapping {
String value();
}

0 comments on commit a84021d

Please sign in to comment.