Skip to content

Commit

Permalink
Merge pull request quarkiverse#46 from ia3andy/use-configured-jackson…
Browse files Browse the repository at this point in the history
…-with-new-typed-mapper

Use Roq Jackson and with new specific mappers
  • Loading branch information
ia3andy authored Jul 31, 2024
2 parents bc4acd5 + cbac246 commit 9b0b133
Show file tree
Hide file tree
Showing 13 changed files with 205 additions and 135 deletions.
4 changes: 2 additions & 2 deletions common/deployment/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@
<artifactId>quarkus-arc-deployment</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-jackson-deployment</artifactId>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-yaml</artifactId>
</dependency>
<dependency>
<groupId>io.quarkiverse.roq</groupId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package io.quarkiverse.roq.deployment;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.Optional;

import org.jboss.logging.Logger;

import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.json.JsonMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLMapper;

import io.quarkiverse.roq.deployment.config.RoqJacksonConfig;
import io.quarkiverse.roq.deployment.items.RoqJacksonBuildItem;
import io.quarkus.arc.impl.Reflections;
import io.quarkus.deployment.annotations.BuildStep;

public class RoqJacksonProcessor {

private static final Logger LOG = Logger.getLogger(RoqJacksonProcessor.class);

@BuildStep
RoqJacksonBuildItem findProject(RoqJacksonConfig jacksonConfig) {
YAMLMapper.Builder yamlMapperBuilder = YAMLMapper.builder();
JsonMapper.Builder jsonMapperBuilder = JsonMapper.builder();
if (!jacksonConfig.failOnUnknownProperties()) {
// this feature is enabled by default, so we disable it
yamlMapperBuilder.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
jsonMapperBuilder.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
}
if (!jacksonConfig.failOnEmptyBeans()) {
// this feature is enabled by default, so we disable it
yamlMapperBuilder.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
jsonMapperBuilder.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
}

if (jacksonConfig.acceptCaseInsensitiveEnums()) {
yamlMapperBuilder.enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS);
jsonMapperBuilder.enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS);
}
final Optional<PropertyNamingStrategy> propertyNamingStrategy = determinePropertyNamingStrategyClassName(jacksonConfig);
if (propertyNamingStrategy.isPresent()) {
jsonMapperBuilder.propertyNamingStrategy(propertyNamingStrategy.get());
yamlMapperBuilder.propertyNamingStrategy(propertyNamingStrategy.get());
}
return new RoqJacksonBuildItem(jsonMapperBuilder.build(), yamlMapperBuilder.build());
}

private Optional<PropertyNamingStrategy> determinePropertyNamingStrategyClassName(RoqJacksonConfig jacksonConfig) {
if (jacksonConfig.propertyNamingStrategy().isEmpty()) {
return Optional.empty();
}
var propertyNamingStrategy = jacksonConfig.propertyNamingStrategy().get();
Field field;

try {
// let's first try and see if the value is a constant defined in PropertyNamingStrategies
field = Reflections.findField(PropertyNamingStrategies.class, propertyNamingStrategy);
} catch (Exception e) {
// the provided value does not correspond to any of the defined constants, so let's see if it's actually a class name
try {
var clazz = Thread.currentThread().getContextClassLoader().loadClass(propertyNamingStrategy);
if (PropertyNamingStrategy.class.isAssignableFrom(clazz)) {
return Optional.of((PropertyNamingStrategy) clazz.getConstructor().newInstance());
}
throw new RuntimeException(invalidPropertyNameStrategyValueMessage(propertyNamingStrategy));
} catch (ClassNotFoundException | NoSuchMethodException | InstantiationException | IllegalAccessException
| InvocationTargetException ex) {
throw new RuntimeException(invalidPropertyNameStrategyValueMessage(propertyNamingStrategy));
}
}

try {
// we have a matching field, so let's see if the type is correct
final Object value = field.get(null);
Class<?> clazz = value.getClass();
if (PropertyNamingStrategy.class.isAssignableFrom(clazz)) {
return Optional.of((PropertyNamingStrategy) value);
}
throw new RuntimeException(invalidPropertyNameStrategyValueMessage(propertyNamingStrategy));
} catch (IllegalAccessException e) {
// shouldn't ever happen
throw new RuntimeException(invalidPropertyNameStrategyValueMessage(propertyNamingStrategy));
}
}

private static String invalidPropertyNameStrategyValueMessage(String propertyNamingStrategy) {
return "Unable to determine the property naming strategy for value '" + propertyNamingStrategy
+ "'. Make sure that the value is either a fully qualified class name of a subclass of '"
+ PropertyNamingStrategy.class.getName()
+ "' or one of the constants defined in '" + PropertyNamingStrategies.class.getName() + "'.";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,7 @@

import org.jboss.logging.Logger;

import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;

import io.quarkiverse.roq.deployment.config.RoqConfig;
import io.quarkiverse.roq.deployment.config.RoqJacksonConfig;
import io.quarkiverse.roq.deployment.items.RoqObjectMapperBuildItem;
import io.quarkiverse.roq.deployment.items.RoqProjectBuildItem;
import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.deployment.pkg.builditem.CurateOutcomeBuildItem;
Expand Down Expand Up @@ -44,23 +37,6 @@ RoqProjectBuildItem findProject(RoqConfig config, OutputTargetBuildItem outputTa
return roqProject;
}

@BuildStep
RoqObjectMapperBuildItem findProject(RoqJacksonConfig jacksonConfig) {
ObjectMapper objectMapper = new ObjectMapper();
if (!jacksonConfig.failOnUnknownProperties()) {
// this feature is enabled by default, so we disable it
objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
}
if (!jacksonConfig.failOnEmptyBeans()) {
// this feature is enabled by default, so we disable it
objectMapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
}
if (jacksonConfig.acceptCaseInsensitiveEnums()) {
objectMapper.enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS);
}
return new RoqObjectMapperBuildItem(objectMapper);
}

/**
* Resolves the project directories based on the provided configuration and output target.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package io.quarkiverse.roq.deployment.config;

import java.util.Optional;

import io.quarkus.runtime.annotations.ConfigPhase;
import io.quarkus.runtime.annotations.ConfigRoot;
import io.smallrye.config.ConfigMapping;
Expand All @@ -14,7 +16,7 @@ public interface RoqJacksonConfig {
* <p>
* You can still override it locally with {@code @JsonIgnoreProperties(ignoreUnknown = false)}.
*/
@WithDefault("false")
@WithDefault("true")
boolean failOnUnknownProperties();

/**
Expand All @@ -30,4 +32,15 @@ public interface RoqJacksonConfig {
@WithDefault("false")
boolean acceptCaseInsensitiveEnums();

/**
* Defines how names of JSON properties ("external names") are derived
* from names of POJO methods and fields ("internal names").
* The value can be one of the one of the constants in {@link com.fasterxml.jackson.databind.PropertyNamingStrategies},
* so for example, {@code LOWER_CAMEL_CASE} or {@code UPPER_CAMEL_CASE}.
*
* The value can also be a fully qualified class name of a {@link com.fasterxml.jackson.databind.PropertyNamingStrategy}
* subclass.
*/
Optional<String> propertyNamingStrategy();

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package io.quarkiverse.roq.deployment.items;

import com.fasterxml.jackson.databind.json.JsonMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLMapper;

import io.quarkus.builder.item.SimpleBuildItem;

public final class RoqJacksonBuildItem extends SimpleBuildItem {

private final JsonMapper jsonMapper;
private final YAMLMapper yamlMapper;

public RoqJacksonBuildItem(JsonMapper jsonMapper, YAMLMapper yamlMapper) {
this.jsonMapper = jsonMapper;
this.yamlMapper = yamlMapper;
}

public JsonMapper getJsonMapper() {
return jsonMapper;
}

public YAMLMapper getYamlMapper() {
return yamlMapper;
}
}

This file was deleted.

12 changes: 0 additions & 12 deletions roq-data/deployment/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,6 @@
<groupId>io.quarkus</groupId>
<artifactId>quarkus-arc-deployment</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-jackson-deployment</artifactId>
</dependency>
<dependency>
<groupId>io.quarkiverse.roq</groupId>
<artifactId>quarkus-roq-common-deployment</artifactId>
Expand All @@ -33,14 +29,6 @@
<artifactId>quarkus-roq-data</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-yaml</artifactId>
</dependency>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-core</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,12 @@
import org.jboss.jandex.*;
import org.jboss.logging.Logger;

import io.quarkiverse.roq.data.deployment.converters.JsonObjectConverter;
import io.quarkiverse.roq.data.deployment.converters.DataConverterFinder;
import io.quarkiverse.roq.data.deployment.items.DataMappingBuildItem;
import io.quarkiverse.roq.data.deployment.items.RoqDataBuildItem;
import io.quarkiverse.roq.data.deployment.items.RoqDataJsonBuildItem;
import io.quarkiverse.roq.data.runtime.annotations.DataMapping;
import io.quarkiverse.roq.deployment.items.RoqJacksonBuildItem;
import io.quarkiverse.roq.deployment.items.RoqProjectBuildItem;
import io.quarkus.arc.deployment.BeanArchiveIndexBuildItem;
import io.quarkus.deployment.annotations.BuildProducer;
Expand All @@ -34,11 +35,15 @@ public class RoqDataReaderProcessor {
RoqDataConfig roqDataConfig;

@BuildStep
void scanDataFiles(RoqProjectBuildItem roqProject, RoqDataConfig config, BuildProducer<RoqDataBuildItem> dataProducer,
void scanDataFiles(RoqProjectBuildItem roqProject,
RoqDataConfig config,
RoqJacksonBuildItem jackson,
BuildProducer<RoqDataBuildItem> dataProducer,
BuildProducer<HotDeploymentWatchedFileBuildItem> watchedFilesProducer) {
if (roqProject.isActive()) {
DataConverterFinder converter = new DataConverterFinder(jackson.getJsonMapper(), jackson.getYamlMapper());
try {
Collection<RoqDataBuildItem> items = scanDataFiles(roqProject, watchedFilesProducer, config);
Collection<RoqDataBuildItem> items = scanDataFiles(roqProject, converter, watchedFilesProducer, config);

for (RoqDataBuildItem item : items) {
dataProducer.produce(item);
Expand Down Expand Up @@ -153,7 +158,9 @@ private List<String> collectConfigErrors(Set<String> annotations, Set<String> da
}

public Collection<RoqDataBuildItem> scanDataFiles(RoqProjectBuildItem roqProject,
BuildProducer<HotDeploymentWatchedFileBuildItem> watchedFilesProducer, RoqDataConfig config)
DataConverterFinder converter,
BuildProducer<HotDeploymentWatchedFileBuildItem> watchedFilesProducer,
RoqDataConfig config)
throws IOException {

Map<String, RoqDataBuildItem> items = new HashMap<>();
Expand All @@ -162,7 +169,7 @@ public Collection<RoqDataBuildItem> scanDataFiles(RoqProjectBuildItem roqProject
if (Files.isDirectory(path)) {
try (Stream<Path> pathStream = Files.find(path, Integer.MAX_VALUE,
(p, a) -> Files.isRegularFile(p) && isExtensionSupported(p))) {
pathStream.forEach(addRoqDataBuildItem(watchedFilesProducer, path, items));
pathStream.forEach(addRoqDataBuildItem(converter, watchedFilesProducer, path, items));
} catch (IOException e) {
throw new RuntimeException("Error while scanning data files on location %s".formatted(path.toString()), e);
}
Expand All @@ -172,8 +179,11 @@ public Collection<RoqDataBuildItem> scanDataFiles(RoqProjectBuildItem roqProject
return items.values();
}

private static Consumer<Path> addRoqDataBuildItem(BuildProducer<HotDeploymentWatchedFileBuildItem> watchedFilesProducer,
Path rootDir, Map<String, RoqDataBuildItem> items) {
private static Consumer<Path> addRoqDataBuildItem(
DataConverterFinder converter,
BuildProducer<HotDeploymentWatchedFileBuildItem> watchedFilesProducer,
Path rootDir,
Map<String, RoqDataBuildItem> items) {
return file -> {
var name = rootDir.relativize(file).toString().replaceAll("\\..*", "").replaceAll("/", "_");
if (items.containsKey(name)) {
Expand All @@ -184,14 +194,14 @@ private static Consumer<Path> addRoqDataBuildItem(BuildProducer<HotDeploymentWat
// We don't need to watch file out of the local filesystem
watchedFilesProducer.produce(new HotDeploymentWatchedFileBuildItem(file.toAbsolutePath().toString(), true));
}
JsonObjectConverter.Extensions converter = JsonObjectConverter.findExtensionConverter(filename);
DataConverter dataConverter = converter.fromFileName(filename);

if (converter != null) {
if (dataConverter != null) {
try {
items.put(name, new RoqDataBuildItem(name, Files.readAllBytes(file), converter.converter()));
items.put(name, new RoqDataBuildItem(name, Files.readAllBytes(file), dataConverter));
} catch (IOException e) {
throw new UncheckedIOException("Error while decoding using %s converter: %s "
.formatted(filename, converter.getExtension()), e);
.formatted(filename, converter.getClass()), e);
}
}
};
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package io.quarkiverse.roq.data.deployment.converters;

import java.util.Map;

import com.fasterxml.jackson.databind.json.JsonMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLMapper;

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

public final class DataConverterFinder {

private final Map<String, DataConverter> converterByExtension;

public DataConverterFinder(JsonMapper jsonMapper, YAMLMapper yamlMapper) {
DataConverter jsonConverter = new JsonConverter(jsonMapper);
DataConverter yamlConverter = new YamlConverter(yamlMapper);
this.converterByExtension = Map.of(
"yaml", yamlConverter,
"yml", yamlConverter,
"json", jsonConverter);
}

public DataConverter fromFileName(String fileName) {
if (!fileName.contains(".")) {
return null;
}
final String extension = fileName.substring(fileName.lastIndexOf(".") + 1);
return converterByExtension.get(extension);
}

}
Loading

0 comments on commit 9b0b133

Please sign in to comment.