Skip to content

Commit

Permalink
Allow JSON serialization customization in ORM
Browse files Browse the repository at this point in the history
- add a property to pass a custom FormatMapper for JSON formatting
- use a Quarkus-configured ObjectMapper if Jackson is present and user did not specify a format mapper
  • Loading branch information
marko-bekhta authored and Sanne committed Jul 31, 2023
1 parent d00556f commit add9361
Show file tree
Hide file tree
Showing 14 changed files with 297 additions and 15 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,12 @@ public boolean isAnyPropertySet() {
*/
@ConfigGroup
public static class HibernateOrmConfigPersistenceUnitMapping {
/**
* JSON mapping configuration.
*/
@ConfigItem
public Json json;

/**
* Timezone configuration.
*/
Expand All @@ -352,6 +358,18 @@ public static class HibernateOrmConfigPersistenceUnitMapping {
@ConfigItem
public Id id;

@ConfigGroup
public static class Json {
/**
* Class name of a custom
* https://docs.jboss.org/hibernate/stable/orm/javadocs/org/hibernate/type/format/FormatMapper.html[`FormatMapper`]
* implementation.
* This mapper will be used for JSON serialization and deserialization.
*/
@ConfigItem
public Optional<String> mapperFormatter;
}

@ConfigGroup
public static class Timezone {
/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,7 @@ public void configurationDescriptorBuilding(
Capabilities capabilities,
BuildProducer<SystemPropertyBuildItem> systemProperties,
BuildProducer<NativeImageResourceBuildItem> nativeImageResources,
BuildProducer<ReflectiveClassBuildItem> reflectiveClasses,
BuildProducer<HotDeploymentWatchedFileBuildItem> hotDeploymentWatchedFiles,
BuildProducer<PersistenceUnitDescriptorBuildItem> persistenceUnitDescriptors,
List<DatabaseKindDialectBuildItem> dbKindMetadataBuildItems) {
Expand Down Expand Up @@ -325,14 +326,14 @@ public void configurationDescriptorBuilding(
hibernateOrmConfig.database.ormCompatibilityVersion, Collections.emptyMap()),
null,
jpaModel.getXmlMappings(persistenceXmlDescriptorBuildItem.getDescriptor().getName()),
false, true));
false, true, capabilities.isPresent(Capability.JACKSON)));
}

if (impliedPU.shouldGenerateImpliedBlockingPersistenceUnit()) {
handleHibernateORMWithNoPersistenceXml(hibernateOrmConfig, index, persistenceXmlDescriptors,
jdbcDataSources, applicationArchivesBuildItem, launchMode.getLaunchMode(), jpaModel, capabilities,
systemProperties, nativeImageResources, hotDeploymentWatchedFiles, persistenceUnitDescriptors,
dbKindMetadataBuildItems);
systemProperties, nativeImageResources, reflectiveClasses, hotDeploymentWatchedFiles,
persistenceUnitDescriptors, dbKindMetadataBuildItems);
}
}

Expand Down Expand Up @@ -809,6 +810,7 @@ private void handleHibernateORMWithNoPersistenceXml(
Capabilities capabilities,
BuildProducer<SystemPropertyBuildItem> systemProperties,
BuildProducer<NativeImageResourceBuildItem> nativeImageResources,
BuildProducer<ReflectiveClassBuildItem> reflectiveClasses,
BuildProducer<HotDeploymentWatchedFileBuildItem> hotDeploymentWatchedFiles,
BuildProducer<PersistenceUnitDescriptorBuildItem> persistenceUnitDescriptors,
List<DatabaseKindDialectBuildItem> dbKindMetadataBuildItems) {
Expand Down Expand Up @@ -858,8 +860,8 @@ private void handleHibernateORMWithNoPersistenceXml(
modelClassesAndPackagesForDefaultPersistenceUnit,
jpaModel.getXmlMappings(PersistenceUnitUtil.DEFAULT_PERSISTENCE_UNIT_NAME),
jdbcDataSources, applicationArchivesBuildItem, launchMode, capabilities,
systemProperties, nativeImageResources, hotDeploymentWatchedFiles, persistenceUnitDescriptors,
storageEngineCollector, dbKindMetadataBuildItems);
systemProperties, nativeImageResources, reflectiveClasses, hotDeploymentWatchedFiles,
persistenceUnitDescriptors, storageEngineCollector, dbKindMetadataBuildItems);
} else if (!modelClassesAndPackagesForDefaultPersistenceUnit.isEmpty()
&& (!hibernateOrmConfig.defaultPersistenceUnit.datasource.isPresent()
|| DataSourceUtil.isDefault(hibernateOrmConfig.defaultPersistenceUnit.datasource.get()))
Expand All @@ -881,8 +883,8 @@ private void handleHibernateORMWithNoPersistenceXml(
Collections.emptySet()),
jpaModel.getXmlMappings(persistenceUnitEntry.getKey()),
jdbcDataSources, applicationArchivesBuildItem, launchMode, capabilities,
systemProperties, nativeImageResources, hotDeploymentWatchedFiles, persistenceUnitDescriptors,
storageEngineCollector, dbKindMetadataBuildItems);
systemProperties, nativeImageResources, reflectiveClasses, hotDeploymentWatchedFiles,
persistenceUnitDescriptors, storageEngineCollector, dbKindMetadataBuildItems);
}

if (storageEngineCollector.size() > 1) {
Expand All @@ -903,6 +905,7 @@ private static void producePersistenceUnitDescriptorFromConfig(
Capabilities capabilities,
BuildProducer<SystemPropertyBuildItem> systemProperties,
BuildProducer<NativeImageResourceBuildItem> nativeImageResources,
BuildProducer<ReflectiveClassBuildItem> reflectiveClasses,
BuildProducer<HotDeploymentWatchedFileBuildItem> hotDeploymentWatchedFiles,
BuildProducer<PersistenceUnitDescriptorBuildItem> persistenceUnitDescriptors,
Set<String> storageEngineCollector,
Expand Down Expand Up @@ -960,7 +963,11 @@ private static void producePersistenceUnitDescriptorFromConfig(
descriptor.getProperties().setProperty(AvailableSettings.PREFERRED_POOLED_OPTIMIZER,
persistenceUnitConfig.mapping.id.optimizer.idOptimizerDefault
.orElse(HibernateOrmConfigPersistenceUnit.IdOptimizerType.POOLED_LO).configName);

if (persistenceUnitConfig.mapping.json.mapperFormatter.isPresent()) {
String mapper = persistenceUnitConfig.mapping.json.mapperFormatter.get();
reflectiveClasses.produce(ReflectiveClassBuildItem.builder(mapper).build());
descriptor.getProperties().setProperty(AvailableSettings.JSON_FORMAT_MAPPER, mapper);
}
//charset
descriptor.getProperties().setProperty(AvailableSettings.HBM2DDL_CHARSET_NAME,
persistenceUnitConfig.database.charset.name());
Expand Down Expand Up @@ -1117,7 +1124,7 @@ private static void producePersistenceUnitDescriptorFromConfig(
persistenceUnitConfig.unsupportedProperties),
persistenceUnitConfig.multitenantSchemaDatasource.orElse(null),
xmlMappings,
false, false));
false, false, capabilities.isPresent(Capability.JACKSON)));
}

private static void collectDialectConfig(String persistenceUnitName,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,19 +30,21 @@ public final class PersistenceUnitDescriptorBuildItem extends MultiBuildItem {
private final List<RecordableXmlMapping> xmlMappings;
private final boolean isReactive;
private final boolean fromPersistenceXml;
private final boolean jacksonPresent;

public PersistenceUnitDescriptorBuildItem(ParsedPersistenceXmlDescriptor descriptor, String configurationName,
RecordedConfig config,
String multiTenancySchemaDataSource,
List<RecordableXmlMapping> xmlMappings,
boolean isReactive, boolean fromPersistenceXml) {
boolean isReactive, boolean fromPersistenceXml, boolean jacksonPresent) {
this.descriptor = descriptor;
this.configurationName = configurationName;
this.config = config;
this.multiTenancySchemaDataSource = multiTenancySchemaDataSource;
this.xmlMappings = xmlMappings;
this.isReactive = isReactive;
this.fromPersistenceXml = fromPersistenceXml;
this.jacksonPresent = jacksonPresent;
}

public Collection<String> getManagedClassNames() {
Expand Down Expand Up @@ -80,6 +82,6 @@ public boolean isFromPersistenceXml() {
public QuarkusPersistenceUnitDefinition asOutputPersistenceUnitDefinition(
List<HibernateOrmIntegrationStaticDescriptor> integrationStaticDescriptors) {
return new QuarkusPersistenceUnitDefinition(descriptor, configurationName, config,
xmlMappings, isReactive, fromPersistenceXml, integrationStaticDescriptors);
xmlMappings, isReactive, fromPersistenceXml, jacksonPresent, integrationStaticDescriptors);
}
}
5 changes: 5 additions & 0 deletions extensions/hibernate-orm/runtime/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,11 @@
<groupId>io.quarkus</groupId>
<artifactId>quarkus-caffeine</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-jackson</artifactId>
<optional>true</optional>
</dependency>
</dependencies>

<build>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@
import io.quarkus.hibernate.orm.runtime.BuildTimeSettings;
import io.quarkus.hibernate.orm.runtime.IntegrationSettings;
import io.quarkus.hibernate.orm.runtime.boot.xml.RecordableXmlMapping;
import io.quarkus.hibernate.orm.runtime.customized.QuarkusJsonFormatMapper;
import io.quarkus.hibernate.orm.runtime.integration.HibernateOrmIntegrationStaticDescriptor;
import io.quarkus.hibernate.orm.runtime.integration.HibernateOrmIntegrationStaticInitListener;
import io.quarkus.hibernate.orm.runtime.migration.MultiTenancyStrategy;
Expand Down Expand Up @@ -399,6 +400,12 @@ private MergedSettings mergeSettings(QuarkusPersistenceUnitDefinition puDefiniti
}
}

// If there's no user provided mapper but we have Jackson available,
// we want to add a "default" Quarkus mapper that delegates to the Quarkus-configured ObjectMapper:
if (!cfg.containsKey(AvailableSettings.JSON_FORMAT_MAPPER) && puDefinition.isJacksonPresent()) {
cfg.put(AvailableSettings.JSON_FORMAT_MAPPER, new QuarkusJsonFormatMapper());
}

return mergedSettings;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,13 @@ public final class QuarkusPersistenceUnitDefinition {
private final List<RecordableXmlMapping> xmlMappings;
private final boolean isReactive;
private final boolean fromPersistenceXml;
private final boolean jacksonPresent;
private final List<HibernateOrmIntegrationStaticDescriptor> integrationStaticDescriptors;

public QuarkusPersistenceUnitDefinition(PersistenceUnitDescriptor persistenceUnitDescriptor,
String configurationName, RecordedConfig config,
List<RecordableXmlMapping> xmlMappings,
boolean isReactive, boolean fromPersistenceXml,
boolean isReactive, boolean fromPersistenceXml, boolean jacksonPresent,
List<HibernateOrmIntegrationStaticDescriptor> integrationStaticDescriptors) {
Objects.requireNonNull(persistenceUnitDescriptor);
Objects.requireNonNull(config);
Expand All @@ -36,6 +37,7 @@ public QuarkusPersistenceUnitDefinition(PersistenceUnitDescriptor persistenceUni
this.xmlMappings = xmlMappings;
this.isReactive = isReactive;
this.fromPersistenceXml = fromPersistenceXml;
this.jacksonPresent = jacksonPresent;
this.integrationStaticDescriptors = integrationStaticDescriptors;
}

Expand All @@ -45,6 +47,7 @@ public QuarkusPersistenceUnitDefinition(RuntimePersistenceUnitDescriptor actualH
List<RecordableXmlMapping> xmlMappings,
boolean reactive,
boolean fromPersistenceXml,
boolean jacksonPresent,
List<HibernateOrmIntegrationStaticDescriptor> integrationStaticDescriptors) {
Objects.requireNonNull(actualHibernateDescriptor);
Objects.requireNonNull(config);
Expand All @@ -53,6 +56,7 @@ public QuarkusPersistenceUnitDefinition(RuntimePersistenceUnitDescriptor actualH
this.xmlMappings = xmlMappings;
this.isReactive = reactive;
this.fromPersistenceXml = fromPersistenceXml;
this.jacksonPresent = jacksonPresent;
this.integrationStaticDescriptors = integrationStaticDescriptors;
}

Expand Down Expand Up @@ -81,6 +85,10 @@ public boolean isFromPersistenceXml() {
return fromPersistenceXml;
}

public boolean isJacksonPresent() {
return jacksonPresent;
}

public List<HibernateOrmIntegrationStaticDescriptor> getIntegrationStaticDescriptors() {
return integrationStaticDescriptors;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package io.quarkus.hibernate.orm.runtime.customized;

import org.hibernate.type.descriptor.WrapperOptions;
import org.hibernate.type.descriptor.java.JavaType;
import org.hibernate.type.format.FormatMapper;
import org.hibernate.type.format.jackson.JacksonJsonFormatMapper;

import com.fasterxml.jackson.databind.ObjectMapper;

import io.quarkus.arc.Arc;

public class QuarkusJsonFormatMapper implements FormatMapper {
private final FormatMapper delegate = new JacksonJsonFormatMapper(Arc.container().instance(ObjectMapper.class).get());

public <T> T fromString(CharSequence charSequence, JavaType<T> javaType, WrapperOptions wrapperOptions) {
return delegate.fromString(charSequence, javaType, wrapperOptions);
}

public <T> String toString(T value, JavaType<T> javaType, WrapperOptions wrapperOptions) {
return delegate.toString(value, javaType, wrapperOptions);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
import java.util.Set;
import java.util.stream.Collectors;

import io.quarkus.deployment.Capabilities;
import io.quarkus.deployment.Capability;
import jakarta.persistence.SharedCacheMode;
import jakarta.persistence.spi.PersistenceUnitTransactionType;

Expand Down Expand Up @@ -130,6 +132,7 @@ public void buildReactivePersistenceUnit(
ApplicationArchivesBuildItem applicationArchivesBuildItem,
LaunchModeBuildItem launchMode,
JpaModelBuildItem jpaModel,
Capabilities capabilities,
BuildProducer<SystemPropertyBuildItem> systemProperties,
BuildProducer<NativeImageResourceBuildItem> nativeImageResources,
BuildProducer<HotDeploymentWatchedFileBuildItem> hotDeploymentWatchedFiles,
Expand Down Expand Up @@ -187,7 +190,7 @@ public void buildReactivePersistenceUnit(
persistenceUnitConfig.unsupportedProperties),
null,
jpaModel.getXmlMappings(reactivePU.getName()),
true, false));
true, false, capabilities.isPresent(Capability.JACKSON)));
}
}

Expand Down
17 changes: 17 additions & 0 deletions integration-tests/jpa-postgresql/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@
<groupId>io.quarkus</groupId>
<artifactId>quarkus-jdbc-postgresql</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-jackson</artifactId>
</dependency>

<!-- test dependencies -->
<dependency>
Expand Down Expand Up @@ -89,6 +93,19 @@
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-jackson-deployment</artifactId>
<version>${project.version}</version>
<type>pom</type>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>

<build>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package io.quarkus.it.jpa.postgresql;

import java.time.LocalDate;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;

import org.hibernate.annotations.JdbcTypeCode;
import org.hibernate.type.SqlTypes;

import com.fasterxml.jackson.annotation.JsonProperty;

import io.quarkus.runtime.annotations.RegisterForReflection;

@Entity
public class EntityWithJson {
@Id
@GeneratedValue
Long id;

@JdbcTypeCode(SqlTypes.JSON)
ToBeJsonWithDateTime json;

public EntityWithJson() {
}

public EntityWithJson(ToBeJsonWithDateTime json) {
this.json = json;
}

@Override
public String toString() {
return "EntityWithJson{" +
"id=" + id +
", json=" + json +
'}';
}

@RegisterForReflection
public static class ToBeJsonWithDateTime {
@JsonProperty
LocalDate date;

public ToBeJsonWithDateTime() {
}

public ToBeJsonWithDateTime(LocalDate date) {
this.date = date;
}

@Override
public String toString() {
return "ToBeJsonWithDateTime{" +
"date=" + date +
'}';
}
}
}
Loading

0 comments on commit add9361

Please sign in to comment.